/src/rocksdb/cache/cache_reservation_manager.h
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
2 | | // This source code is licensed under both the GPLv2 (found in the |
3 | | // COPYING file in the root directory) and Apache 2.0 License |
4 | | // (found in the LICENSE.Apache file in the root directory). |
5 | | // |
6 | | // Copyright (c) 2011 The LevelDB Authors. All rights reserved. |
7 | | // Use of this source code is governed by a BSD-style license that can be |
8 | | // found in the LICENSE file. See the AUTHORS file for names of contributors. |
9 | | |
10 | | #pragma once |
11 | | |
12 | | #include <atomic> |
13 | | #include <cstddef> |
14 | | #include <cstdint> |
15 | | #include <memory> |
16 | | #include <mutex> |
17 | | #include <vector> |
18 | | |
19 | | #include "cache/cache_entry_roles.h" |
20 | | #include "cache/cache_key.h" |
21 | | #include "cache/typed_cache.h" |
22 | | #include "rocksdb/slice.h" |
23 | | #include "rocksdb/status.h" |
24 | | #include "util/coding.h" |
25 | | |
26 | | namespace ROCKSDB_NAMESPACE { |
27 | | // CacheReservationManager is an interface for reserving cache space for the |
28 | | // memory used |
29 | | class CacheReservationManager { |
30 | | public: |
31 | | // CacheReservationHandle is for managing the lifetime of a cache reservation |
32 | | // for an incremental amount of memory used (i.e, incremental_memory_used) |
33 | | class CacheReservationHandle { |
34 | | public: |
35 | 0 | virtual ~CacheReservationHandle() {} |
36 | | }; |
37 | 5.44k | virtual ~CacheReservationManager() {} |
38 | | virtual Status UpdateCacheReservation(std::size_t new_memory_used) = 0; |
39 | | // TODO(hx235): replace the usage of |
40 | | // `UpdateCacheReservation(memory_used_delta, increase)` with |
41 | | // `UpdateCacheReservation(new_memory_used)` so that we only have one |
42 | | // `UpdateCacheReservation` function |
43 | | virtual Status UpdateCacheReservation(std::size_t memory_used_delta, |
44 | | bool increase) = 0; |
45 | | virtual Status MakeCacheReservation( |
46 | | std::size_t incremental_memory_used, |
47 | | std::unique_ptr<CacheReservationManager::CacheReservationHandle> |
48 | | *handle) = 0; |
49 | | virtual std::size_t GetTotalReservedCacheSize() = 0; |
50 | | virtual std::size_t GetTotalMemoryUsed() = 0; |
51 | | }; |
52 | | |
53 | | // CacheReservationManagerImpl implements interface CacheReservationManager |
54 | | // for reserving cache space for the memory used by inserting/releasing dummy |
55 | | // entries in the cache. |
56 | | // |
57 | | // This class is NOT thread-safe, except that GetTotalReservedCacheSize() |
58 | | // can be called without external synchronization. |
59 | | template <CacheEntryRole R> |
60 | | class CacheReservationManagerImpl |
61 | | : public CacheReservationManager, |
62 | | public std::enable_shared_from_this<CacheReservationManagerImpl<R>> { |
63 | | public: |
64 | | class CacheReservationHandle |
65 | | : public CacheReservationManager::CacheReservationHandle { |
66 | | public: |
67 | | CacheReservationHandle( |
68 | | std::size_t incremental_memory_used, |
69 | | std::shared_ptr<CacheReservationManagerImpl> cache_res_mgr); |
70 | | ~CacheReservationHandle() override; |
71 | | |
72 | | private: |
73 | | std::size_t incremental_memory_used_; |
74 | | std::shared_ptr<CacheReservationManagerImpl> cache_res_mgr_; |
75 | | }; |
76 | | |
77 | | // Construct a CacheReservationManagerImpl |
78 | | // @param cache The cache where dummy entries are inserted and released for |
79 | | // reserving cache space |
80 | | // @param delayed_decrease If set true, then dummy entries won't be released |
81 | | // immediately when memory usage decreases. |
82 | | // Instead, it will be released when the memory usage |
83 | | // decreases to 3/4 of what we have reserved so far. |
84 | | // This is for saving some future dummy entry |
85 | | // insertion when memory usage increases are likely to |
86 | | // happen in the near future. |
87 | | // |
88 | | // REQUIRED: cache is not nullptr |
89 | | explicit CacheReservationManagerImpl(std::shared_ptr<Cache> cache, |
90 | | bool delayed_decrease = false); |
91 | | |
92 | | // no copy constructor, copy assignment, move constructor, move assignment |
93 | | CacheReservationManagerImpl(const CacheReservationManagerImpl &) = delete; |
94 | | CacheReservationManagerImpl &operator=(const CacheReservationManagerImpl &) = |
95 | | delete; |
96 | | CacheReservationManagerImpl(CacheReservationManagerImpl &&) = delete; |
97 | | CacheReservationManagerImpl &operator=(CacheReservationManagerImpl &&) = |
98 | | delete; |
99 | | |
100 | | ~CacheReservationManagerImpl() override; |
101 | | |
102 | | // One of the two ways of reserving/releasing cache space, |
103 | | // see MakeCacheReservation() for the other. |
104 | | // |
105 | | // Use ONLY one of these two ways to prevent unexpected behavior. |
106 | | // |
107 | | // Insert and release dummy entries in the cache to |
108 | | // match the size of total dummy entries with the least multiple of |
109 | | // kSizeDummyEntry greater than or equal to new_mem_used |
110 | | // |
111 | | // Insert dummy entries if new_memory_used > cache_allocated_size_; |
112 | | // |
113 | | // Release dummy entries if new_memory_used < cache_allocated_size_ |
114 | | // (and new_memory_used < cache_allocated_size_ * 3/4 |
115 | | // when delayed_decrease is set true); |
116 | | // |
117 | | // Keey dummy entries the same if (1) new_memory_used == cache_allocated_size_ |
118 | | // or (2) new_memory_used is in the interval of |
119 | | // [cache_allocated_size_ * 3/4, cache_allocated_size) when delayed_decrease |
120 | | // is set true. |
121 | | // |
122 | | // @param new_memory_used The number of bytes used by new memory |
123 | | // The most recent new_memoy_used passed in will be returned |
124 | | // in GetTotalMemoryUsed() even when the call return non-ok status. |
125 | | // |
126 | | // Since the class is NOT thread-safe, external synchronization on the |
127 | | // order of calling UpdateCacheReservation() is needed if you want |
128 | | // GetTotalMemoryUsed() indeed returns the latest memory used. |
129 | | // |
130 | | // @return On inserting dummy entries, it returns Status::OK() if all dummy |
131 | | // entry insertions succeed. |
132 | | // Otherwise, it returns the first non-ok status; |
133 | | // On releasing dummy entries, it always returns Status::OK(). |
134 | | // On keeping dummy entries the same, it always returns Status::OK(). |
135 | | Status UpdateCacheReservation(std::size_t new_memory_used) override; |
136 | | |
137 | | Status UpdateCacheReservation(std::size_t /* memory_used_delta */, |
138 | 0 | bool /* increase */) override { |
139 | 0 | return Status::NotSupported(); |
140 | 0 | } Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)9>::UpdateCacheReservation(unsigned long, bool) Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)7>::UpdateCacheReservation(unsigned long, bool) Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)8>::UpdateCacheReservation(unsigned long, bool) Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)13>::UpdateCacheReservation(unsigned long, bool) Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)6>::UpdateCacheReservation(unsigned long, bool) Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)10>::UpdateCacheReservation(unsigned long, bool) Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)12>::UpdateCacheReservation(unsigned long, bool) |
141 | | |
142 | | // One of the two ways of reserving cache space and releasing is done through |
143 | | // destruction of CacheReservationHandle. |
144 | | // See UpdateCacheReservation() for the other way. |
145 | | // |
146 | | // Use ONLY one of these two ways to prevent unexpected behavior. |
147 | | // |
148 | | // Insert dummy entries in the cache for the incremental memory usage |
149 | | // to match the size of total dummy entries with the least multiple of |
150 | | // kSizeDummyEntry greater than or equal to the total memory used. |
151 | | // |
152 | | // A CacheReservationHandle is returned as an output parameter. |
153 | | // The reserved dummy entries are automatically released on the destruction of |
154 | | // this handle, which achieves better RAII per cache reservation. |
155 | | // |
156 | | // WARNING: Deallocate all the handles of the CacheReservationManager object |
157 | | // before deallocating the object to prevent unexpected behavior. |
158 | | // |
159 | | // @param incremental_memory_used The number of bytes increased in memory |
160 | | // usage. |
161 | | // |
162 | | // Calling GetTotalMemoryUsed() afterward will return the total memory |
163 | | // increased by this number, even when calling MakeCacheReservation() |
164 | | // returns non-ok status. |
165 | | // |
166 | | // Since the class is NOT thread-safe, external synchronization in |
167 | | // calling MakeCacheReservation() is needed if you want |
168 | | // GetTotalMemoryUsed() indeed returns the latest memory used. |
169 | | // |
170 | | // @param handle An pointer to std::unique_ptr<CacheReservationHandle> that |
171 | | // manages the lifetime of the cache reservation represented by the |
172 | | // handle. |
173 | | // |
174 | | // @return It returns Status::OK() if all dummy |
175 | | // entry insertions succeed. |
176 | | // Otherwise, it returns the first non-ok status; |
177 | | // |
178 | | // REQUIRES: handle != nullptr |
179 | | Status MakeCacheReservation( |
180 | | std::size_t incremental_memory_used, |
181 | | std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle) |
182 | | override; |
183 | | |
184 | | // Return the size of the cache (which is a multiple of kSizeDummyEntry) |
185 | | // successfully reserved by calling UpdateCacheReservation(). |
186 | | // |
187 | | // When UpdateCacheReservation() returns non-ok status, |
188 | | // calling GetTotalReservedCacheSize() after that might return a slightly |
189 | | // smaller number than the actual reserved cache size due to |
190 | | // the returned number will always be a multiple of kSizeDummyEntry |
191 | | // and cache full might happen in the middle of inserting a dummy entry. |
192 | | std::size_t GetTotalReservedCacheSize() override; |
193 | | |
194 | | // Return the latest total memory used indicated by the most recent call of |
195 | | // UpdateCacheReservation(std::size_t new_memory_used); |
196 | | std::size_t GetTotalMemoryUsed() override; |
197 | | |
198 | 0 | static constexpr std::size_t GetDummyEntrySize() { return kSizeDummyEntry; } Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)9>::GetDummyEntrySize() Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)7>::GetDummyEntrySize() Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)8>::GetDummyEntrySize() Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)13>::GetDummyEntrySize() Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)6>::GetDummyEntrySize() Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)10>::GetDummyEntrySize() Unexecuted instantiation: rocksdb::CacheReservationManagerImpl<(rocksdb::CacheEntryRole)12>::GetDummyEntrySize() |
199 | | |
200 | | // For testing only - it is to help ensure the CacheItemHelperForRole<R> |
201 | | // accessed from CacheReservationManagerImpl and the one accessed from the |
202 | | // test are from the same translation units |
203 | | static const Cache::CacheItemHelper *TEST_GetCacheItemHelperForRole(); |
204 | | |
205 | | private: |
206 | | static constexpr std::size_t kSizeDummyEntry = 256 * 1024; |
207 | | |
208 | | Slice GetNextCacheKey(); |
209 | | |
210 | | Status ReleaseCacheReservation(std::size_t incremental_memory_used); |
211 | | Status IncreaseCacheReservation(std::size_t new_mem_used); |
212 | | Status DecreaseCacheReservation(std::size_t new_mem_used); |
213 | | |
214 | | using CacheInterface = PlaceholderSharedCacheInterface<R>; |
215 | | CacheInterface cache_; |
216 | | bool delayed_decrease_; |
217 | | std::atomic<std::size_t> cache_allocated_size_; |
218 | | std::size_t memory_used_; |
219 | | std::vector<Cache::Handle *> dummy_handles_; |
220 | | CacheKey cache_key_; |
221 | | }; |
222 | | |
223 | | class ConcurrentCacheReservationManager |
224 | | : public CacheReservationManager, |
225 | | public std::enable_shared_from_this<ConcurrentCacheReservationManager> { |
226 | | public: |
227 | | class CacheReservationHandle |
228 | | : public CacheReservationManager::CacheReservationHandle { |
229 | | public: |
230 | | CacheReservationHandle( |
231 | | std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr, |
232 | | std::unique_ptr<CacheReservationManager::CacheReservationHandle> |
233 | 0 | cache_res_handle) { |
234 | 0 | assert(cache_res_mgr && cache_res_handle); |
235 | 0 | cache_res_mgr_ = cache_res_mgr; |
236 | 0 | cache_res_handle_ = std::move(cache_res_handle); |
237 | 0 | } |
238 | | |
239 | 0 | ~CacheReservationHandle() override { |
240 | 0 | std::lock_guard<std::mutex> lock(cache_res_mgr_->cache_res_mgr_mu_); |
241 | 0 | cache_res_handle_.reset(); |
242 | 0 | } |
243 | | |
244 | | private: |
245 | | std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_; |
246 | | std::unique_ptr<CacheReservationManager::CacheReservationHandle> |
247 | | cache_res_handle_; |
248 | | }; |
249 | | |
250 | | explicit ConcurrentCacheReservationManager( |
251 | 0 | std::shared_ptr<CacheReservationManager> cache_res_mgr) { |
252 | 0 | cache_res_mgr_ = std::move(cache_res_mgr); |
253 | 0 | } |
254 | | ConcurrentCacheReservationManager(const ConcurrentCacheReservationManager &) = |
255 | | delete; |
256 | | ConcurrentCacheReservationManager &operator=( |
257 | | const ConcurrentCacheReservationManager &) = delete; |
258 | | ConcurrentCacheReservationManager(ConcurrentCacheReservationManager &&) = |
259 | | delete; |
260 | | ConcurrentCacheReservationManager &operator=( |
261 | | ConcurrentCacheReservationManager &&) = delete; |
262 | | |
263 | 0 | ~ConcurrentCacheReservationManager() override {} |
264 | | |
265 | 0 | inline Status UpdateCacheReservation(std::size_t new_memory_used) override { |
266 | 0 | std::lock_guard<std::mutex> lock(cache_res_mgr_mu_); |
267 | 0 | return cache_res_mgr_->UpdateCacheReservation(new_memory_used); |
268 | 0 | } |
269 | | |
270 | | inline Status UpdateCacheReservation(std::size_t memory_used_delta, |
271 | 0 | bool increase) override { |
272 | 0 | std::lock_guard<std::mutex> lock(cache_res_mgr_mu_); |
273 | 0 | std::size_t total_mem_used = cache_res_mgr_->GetTotalMemoryUsed(); |
274 | 0 | Status s; |
275 | 0 | if (!increase) { |
276 | 0 | s = cache_res_mgr_->UpdateCacheReservation( |
277 | 0 | (total_mem_used > memory_used_delta) |
278 | 0 | ? (total_mem_used - memory_used_delta) |
279 | 0 | : 0); |
280 | 0 | } else { |
281 | 0 | s = cache_res_mgr_->UpdateCacheReservation(total_mem_used + |
282 | 0 | memory_used_delta); |
283 | 0 | } |
284 | 0 | return s; |
285 | 0 | } |
286 | | |
287 | | inline Status MakeCacheReservation( |
288 | | std::size_t incremental_memory_used, |
289 | | std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle) |
290 | 0 | override { |
291 | 0 | std::unique_ptr<CacheReservationManager::CacheReservationHandle> |
292 | 0 | wrapped_handle; |
293 | 0 | Status s; |
294 | 0 | { |
295 | 0 | std::lock_guard<std::mutex> lock(cache_res_mgr_mu_); |
296 | 0 | s = cache_res_mgr_->MakeCacheReservation(incremental_memory_used, |
297 | 0 | &wrapped_handle); |
298 | 0 | } |
299 | 0 | (*handle).reset( |
300 | 0 | new ConcurrentCacheReservationManager::CacheReservationHandle( |
301 | 0 | std::enable_shared_from_this< |
302 | 0 | ConcurrentCacheReservationManager>::shared_from_this(), |
303 | 0 | std::move(wrapped_handle))); |
304 | 0 | return s; |
305 | 0 | } |
306 | 0 | inline std::size_t GetTotalReservedCacheSize() override { |
307 | 0 | return cache_res_mgr_->GetTotalReservedCacheSize(); |
308 | 0 | } |
309 | 0 | inline std::size_t GetTotalMemoryUsed() override { |
310 | 0 | std::lock_guard<std::mutex> lock(cache_res_mgr_mu_); |
311 | 0 | return cache_res_mgr_->GetTotalMemoryUsed(); |
312 | 0 | } |
313 | | |
314 | | private: |
315 | | std::mutex cache_res_mgr_mu_; |
316 | | std::shared_ptr<CacheReservationManager> cache_res_mgr_; |
317 | | }; |
318 | | } // namespace ROCKSDB_NAMESPACE |