1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #pragma once 18 19 #include <functional> 20 #include <memory> 21 #include <mutex> 22 #include <optional> 23 #include <type_traits> 24 #include <unordered_set> 25 #include <utility> 26 #include <vector> 27 28 #include <android-base/logging.h> 29 30 #include "common/libs/utils/contains.h" 31 32 namespace cuttlefish { 33 34 /** 35 * Generic allocator that can provide RAII-aware resource reservations. 36 * 37 * See go/cf-resource-allocator-utils for more details. 38 */ 39 template <typename T> 40 class UniqueResourceAllocator { 41 template <typename U> 42 using RemoveCvref = 43 typename std::remove_cv_t<typename std::remove_reference_t<U>>; 44 45 public: 46 /* 47 * Returning the inner resource to the pool at destruction time 48 * 49 * The pool must live longer than the resources. Use this like you use 50 * std::unique_ptr. 51 */ 52 class Reservation { 53 friend class UniqueResourceAllocator; 54 friend class ReservationSet; 55 56 public: 57 Reservation(const Reservation&) = delete; Reservation(Reservation && src)58 Reservation(Reservation&& src) 59 : resource_pool_(src.resource_pool_), resource_(src.resource_) { 60 src.resource_pool_ = nullptr; 61 } 62 Reservation& operator=(const Reservation&) = delete; 63 Reservation& operator=(Reservation&& src) = delete; 64 65 bool operator==(const Reservation& src) const { 66 return (resource_ == src.resource_ && 67 resource_pool_ == src.resource_pool_); 68 } 69 ~Reservation()70 ~Reservation() { 71 if (resource_pool_) { 72 resource_pool_->Reclaim(*resource_); 73 } 74 } Get()75 const T& Get() const { return *resource_; } 76 77 private: Reservation(UniqueResourceAllocator & resource_pool,const T & resource)78 Reservation(UniqueResourceAllocator& resource_pool, const T& resource) 79 : resource_pool_(std::addressof(resource_pool)), 80 resource_(std::addressof(resource)) {} 81 /* 82 * Once this Reservation is std::move-ed out to other object, 83 * resource_pool_ should be invalidated, and resource_ shouldn't 84 * be tried to be returned to the invalid resource_pool_ 85 */ 86 UniqueResourceAllocator* resource_pool_; 87 const T* resource_; 88 }; 89 90 struct ReservationHash { operatorReservationHash91 std::size_t operator()(const Reservation& resource_wrapper) const { 92 return std::hash<const T*>()(std::addressof(resource_wrapper.Get())); 93 } 94 }; 95 using ReservationSet = std::unordered_set<Reservation, ReservationHash>; 96 /* 97 * Creates the singleton object. 98 * 99 * Call this function once during the entire program's life 100 */ Create(const std::vector<T> & pool)101 static UniqueResourceAllocator& Create(const std::vector<T>& pool) { 102 static UniqueResourceAllocator singleton_allocator(pool); 103 return singleton_allocator; 104 } 105 New(const std::vector<T> & pool)106 static std::unique_ptr<UniqueResourceAllocator> New( 107 const std::vector<T>& pool) { 108 UniqueResourceAllocator* new_allocator = new UniqueResourceAllocator(pool); 109 return std::unique_ptr<UniqueResourceAllocator>(new_allocator); 110 } 111 112 // Adds the elements from new pool that did not belong to and have not 113 // belonged to the current pool of the allocator. returns the leftover ExpandPool(std::vector<T> another_pool)114 std::vector<T> ExpandPool(std::vector<T> another_pool) { 115 std::lock_guard lock(mutex_); 116 std::vector<T> not_selected; 117 for (auto& new_item : another_pool) { 118 if (Contains(available_resources_, new_item) || 119 Contains(allocated_resources_, new_item)) { 120 not_selected.emplace_back(std::move(new_item)); 121 continue; 122 } 123 available_resources_.insert(std::move(new_item)); 124 } 125 return not_selected; 126 } 127 ExpandPool(T && t)128 std::vector<T> ExpandPool(T&& t) { 129 std::vector<T> pool_to_add; 130 pool_to_add.emplace_back(std::move(t)); 131 return ExpandPool(std::move(pool_to_add)); 132 } 133 ExpandPool(const T & t)134 std::vector<T> ExpandPool(const T& t) { 135 std::vector<T> pool_to_add; 136 pool_to_add.emplace_back(t); 137 return ExpandPool(std::move(pool_to_add)); 138 } 139 UniqueItem()140 std::optional<Reservation> UniqueItem() { 141 std::lock_guard<std::mutex> lock(mutex_); 142 auto itr = available_resources_.begin(); 143 if (itr == available_resources_.end()) { 144 return std::nullopt; 145 } 146 Reservation r(*this, *(RemoveFromPool(itr))); 147 return {std::move(r)}; 148 } 149 150 // gives n unique integers from the pool, and then remove them from the pool UniqueItems(const int n)151 std::optional<ReservationSet> UniqueItems(const int n) { 152 std::lock_guard<std::mutex> lock(mutex_); 153 if (n <= 0 || available_resources_.size() < n) { 154 return std::nullopt; 155 } 156 ReservationSet result; 157 for (int i = 0; i < n; i++) { 158 auto itr = available_resources_.begin(); 159 result.insert(Reservation{*this, *(RemoveFromPool(itr))}); 160 } 161 return {std::move(result)}; 162 } 163 164 template <typename V = T> 165 std::enable_if_t<std::is_integral<V>::value, std::optional<ReservationSet>> UniqueConsecutiveItems(const std::size_t n)166 UniqueConsecutiveItems(const std::size_t n) { 167 static_assert(std::is_same<T, V>::value); 168 std::lock_guard<std::mutex> lock(mutex_); 169 if (n <= 0 || available_resources_.size() < n) { 170 return std::nullopt; 171 } 172 173 for (const auto& available_resource : available_resources_) { 174 auto start_inclusive = available_resource; 175 auto resources_opt = 176 TakeRangeInternal(start_inclusive, start_inclusive + n); 177 if (!resources_opt) { 178 continue; 179 } 180 return resources_opt; 181 } 182 return std::nullopt; 183 } 184 185 // takes t if available 186 // returns false if not available or not in the pool at all Take(const T & t)187 std::optional<Reservation> Take(const T& t) { 188 std::lock_guard<std::mutex> lock(mutex_); 189 auto itr = available_resources_.find(t); 190 if (itr == available_resources_.end()) { 191 return std::nullopt; 192 } 193 Reservation resource{*this, *(RemoveFromPool(itr))}; 194 return resource; 195 } 196 197 template <typename Container> TakeAll(const Container & ts)198 std::optional<ReservationSet> TakeAll(const Container& ts) { 199 std::lock_guard<std::mutex> lock(mutex_); 200 for (const auto& t : ts) { 201 if (!Contains(available_resources_, t)) { 202 return std::nullopt; 203 } 204 } 205 ReservationSet resources; 206 for (const auto& t : ts) { 207 auto itr = available_resources_.find(t); 208 resources.insert(Reservation{*this, *(RemoveFromPool(itr))}); 209 } 210 return resources; 211 } 212 213 /* 214 * If the range is available, returns the resources from the pool 215 * 216 * Otherwise, makes no change in the internal data structure but 217 * returns false. 218 */ 219 template <typename V = T> 220 std::enable_if_t<std::is_integral<V>::value, std::optional<ReservationSet>> TakeRange(const T & start_inclusive,const T & end_exclusive)221 TakeRange(const T& start_inclusive, const T& end_exclusive) { 222 static_assert(std::is_same<T, V>::value); 223 std::lock_guard<std::mutex> lock(mutex_); 224 return TakeRangeInternal(start_inclusive, end_exclusive); 225 } 226 227 private: 228 template <typename Container> UniqueResourceAllocator(const Container & items)229 UniqueResourceAllocator(const Container& items) 230 : available_resources_{items.cbegin(), items.cend()} {} 231 232 bool operator==(const UniqueResourceAllocator& other) const { 233 return std::addressof(*this) == std::addressof(other); 234 } 235 236 // only called by the destructor of Reservation 237 // harder to use Result as this is called by destructors only Reclaim(const T & t)238 void Reclaim(const T& t) { 239 std::lock_guard<std::mutex> lock(mutex_); 240 auto itr = allocated_resources_.find(t); 241 if (itr == allocated_resources_.end()) { 242 if (!Contains(available_resources_, t)) { 243 LOG(ERROR) << "The resource " << t << " does not belong to this pool"; 244 return; 245 } 246 // already reclaimed. 247 return; 248 } 249 T tmp = std::move(*itr); 250 allocated_resources_.erase(itr); 251 available_resources_.insert(std::move(tmp)); 252 } 253 254 /* 255 * If the range is available, returns the resources from the pool 256 * 257 * Otherwise, makes no change in the internal data structure but 258 * returns false. 259 */ 260 template <typename V = T> 261 std::enable_if_t<std::is_integral<V>::value, std::optional<ReservationSet>> TakeRangeInternal(const T & start_inclusive,const T & end_exclusive)262 TakeRangeInternal(const T& start_inclusive, const T& end_exclusive) { 263 static_assert(std::is_same<T, V>::value); 264 for (auto cursor = start_inclusive; cursor < end_exclusive; cursor++) { 265 if (!Contains(available_resources_, cursor)) { 266 return std::nullopt; 267 } 268 } 269 ReservationSet resources; 270 for (auto cursor = start_inclusive; cursor < end_exclusive; cursor++) { 271 auto itr = available_resources_.find(cursor); 272 resources.insert(Reservation{*this, *(RemoveFromPool(itr))}); 273 } 274 return resources; 275 } 276 277 /* 278 * Moves *itr from available_resources_ to allocated_resources_, and returns 279 * the pointer of the object in the allocated_resources_. The pointer is never 280 * nullptr as it is std::addressof(an object in the unordered_set buffer). 281 * 282 * The itr must belong to available_resources_. 283 */ RemoveFromPool(const typename std::unordered_set<T>::iterator itr)284 const T* RemoveFromPool(const typename std::unordered_set<T>::iterator itr) { 285 T tmp = std::move(*itr); 286 available_resources_.erase(itr); 287 const auto [new_itr, _] = allocated_resources_.insert(std::move(tmp)); 288 return std::addressof(*new_itr); 289 } 290 std::unordered_set<T> available_resources_; 291 std::unordered_set<T> allocated_resources_; 292 std::mutex mutex_; 293 }; 294 295 } // namespace cuttlefish 296