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