Coverage Report

Created: 2024-09-08 07:17

/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