// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. // This source code is licensed under both the GPLv2 (found in the // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). // // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. #include "cache/cache_reservation_manager.h" #include #include #include #include "cache/cache_entry_roles.h" #include "rocksdb/cache.h" #include "rocksdb/slice.h" #include "test_util/testharness.h" #include "util/coding.h" namespace ROCKSDB_NAMESPACE { class CacheReservationManagerTest : public ::testing::Test { protected: static constexpr std::size_t kSizeDummyEntry = CacheReservationManagerImpl::GetDummyEntrySize(); static constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry; static constexpr int kNumShardBits = 0; // 2^0 shard static constexpr std::size_t kMetaDataChargeOverhead = 10000; std::shared_ptr cache = NewLRUCache(kCacheCapacity, kNumShardBits); std::shared_ptr test_cache_rev_mng; CacheReservationManagerTest() { test_cache_rev_mng = std::make_shared>( cache); } }; TEST_F(CacheReservationManagerTest, GenerateCacheKey) { std::size_t new_mem_used = 1 * kSizeDummyEntry; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); ASSERT_OK(s); ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry); ASSERT_LT(cache->GetPinnedUsage(), 1 * kSizeDummyEntry + kMetaDataChargeOverhead); // Next unique Cache key CacheKey ckey = CacheKey::CreateUniqueForCacheLifetime(cache.get()); // Get to the underlying values uint64_t* ckey_data = reinterpret_cast(&ckey); // Back it up to the one used by CRM (using CacheKey implementation details) ckey_data[1]--; // Specific key (subject to implementation details) EXPECT_EQ(ckey_data[0], 0); EXPECT_EQ(ckey_data[1], 2); Cache::Handle* handle = cache->Lookup(ckey.AsSlice()); EXPECT_NE(handle, nullptr) << "Failed to generate the cache key for the dummy entry correctly"; // Clean up the returned handle from Lookup() to prevent memory leak cache->Release(handle); } TEST_F(CacheReservationManagerTest, KeepCacheReservationTheSame) { std::size_t new_mem_used = 1 * kSizeDummyEntry; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); ASSERT_OK(s); ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 1 * kSizeDummyEntry); ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used); std::size_t initial_pinned_usage = cache->GetPinnedUsage(); ASSERT_GE(initial_pinned_usage, 1 * kSizeDummyEntry); ASSERT_LT(initial_pinned_usage, 1 * kSizeDummyEntry + kMetaDataChargeOverhead); s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to keep cache reservation the same when new_mem_used equals " "to current cache reservation"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 1 * kSizeDummyEntry) << "Failed to bookkeep correctly when new_mem_used equals to current " "cache reservation"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly when new_mem_used " "equals to current cache reservation"; EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage) << "Failed to keep underlying dummy entries the same when new_mem_used " "equals to current cache reservation"; } TEST_F(CacheReservationManagerTest, IncreaseCacheReservationByMultiplesOfDummyEntrySize) { std::size_t new_mem_used = 2 * kSizeDummyEntry; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to increase cache reservation correctly"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 2 * kSizeDummyEntry) << "Failed to bookkeep cache reservation increase correctly"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry) << "Failed to increase underlying dummy entries in cache correctly"; EXPECT_LT(cache->GetPinnedUsage(), 2 * kSizeDummyEntry + kMetaDataChargeOverhead) << "Failed to increase underlying dummy entries in cache correctly"; } TEST_F(CacheReservationManagerTest, IncreaseCacheReservationNotByMultiplesOfDummyEntrySize) { std::size_t new_mem_used = 2 * kSizeDummyEntry + kSizeDummyEntry / 2; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to increase cache reservation correctly"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 3 * kSizeDummyEntry) << "Failed to bookkeep cache reservation increase correctly"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 3 * kSizeDummyEntry) << "Failed to increase underlying dummy entries in cache correctly"; EXPECT_LT(cache->GetPinnedUsage(), 3 * kSizeDummyEntry + kMetaDataChargeOverhead) << "Failed to increase underlying dummy entries in cache correctly"; } TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest, IncreaseCacheReservationOnFullCache) { ; constexpr std::size_t kSizeDummyEntry = CacheReservationManagerImpl::GetDummyEntrySize(); constexpr std::size_t kSmallCacheCapacity = 4 * kSizeDummyEntry; constexpr std::size_t kBigCacheCapacity = 4096 * kSizeDummyEntry; constexpr std::size_t kMetaDataChargeOverhead = 10000; LRUCacheOptions lo; lo.capacity = kSmallCacheCapacity; lo.num_shard_bits = 0; // 2^0 shard lo.strict_capacity_limit = true; std::shared_ptr cache = NewLRUCache(lo); std::shared_ptr test_cache_rev_mng = std::make_shared>( cache); std::size_t new_mem_used = kSmallCacheCapacity + 1; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_TRUE(s.IsMemoryLimit()) << "Failed to return status to indicate failure of dummy entry insertion " "during cache reservation on full cache"; EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(), 1 * kSizeDummyEntry) << "Failed to bookkeep correctly before cache resevation failure happens " "due to full cache"; EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), kSmallCacheCapacity) << "Failed to bookkeep correctly (i.e, bookkeep only successful dummy " "entry insertions) when encountering cache resevation failure due to " "full cache"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry) << "Failed to insert underlying dummy entries correctly when " "encountering cache resevation failure due to full cache"; EXPECT_LE(cache->GetPinnedUsage(), kSmallCacheCapacity) << "Failed to insert underlying dummy entries correctly when " "encountering cache resevation failure due to full cache"; new_mem_used = kSmallCacheCapacity / 2; // 2 dummy entries s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to decrease cache reservation after encountering cache " "reservation failure due to full cache"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 2 * kSizeDummyEntry) << "Failed to bookkeep cache reservation decrease correctly after " "encountering cache reservation due to full cache"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry) << "Failed to release underlying dummy entries correctly on cache " "reservation decrease after encountering cache resevation failure due " "to full cache"; EXPECT_LT(cache->GetPinnedUsage(), 2 * kSizeDummyEntry + kMetaDataChargeOverhead) << "Failed to release underlying dummy entries correctly on cache " "reservation decrease after encountering cache resevation failure due " "to full cache"; // Create cache full again for subsequent tests new_mem_used = kSmallCacheCapacity + 1; s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_TRUE(s.IsMemoryLimit()) << "Failed to return status to indicate failure of dummy entry insertion " "during cache reservation on full cache"; EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(), 1 * kSizeDummyEntry) << "Failed to bookkeep correctly before cache resevation failure happens " "due to full cache"; EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), kSmallCacheCapacity) << "Failed to bookkeep correctly (i.e, bookkeep only successful dummy " "entry insertions) when encountering cache resevation failure due to " "full cache"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry) << "Failed to insert underlying dummy entries correctly when " "encountering cache resevation failure due to full cache"; EXPECT_LE(cache->GetPinnedUsage(), kSmallCacheCapacity) << "Failed to insert underlying dummy entries correctly when " "encountering cache resevation failure due to full cache"; // Increase cache capacity so the previously failed insertion can fully // succeed cache->SetCapacity(kBigCacheCapacity); new_mem_used = kSmallCacheCapacity + 1; s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to increase cache reservation after increasing cache capacity " "and mitigating cache full error"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 5 * kSizeDummyEntry) << "Failed to bookkeep cache reservation increase correctly after " "increasing cache capacity and mitigating cache full error"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 5 * kSizeDummyEntry) << "Failed to insert underlying dummy entries correctly after increasing " "cache capacity and mitigating cache full error"; EXPECT_LT(cache->GetPinnedUsage(), 5 * kSizeDummyEntry + kMetaDataChargeOverhead) << "Failed to insert underlying dummy entries correctly after increasing " "cache capacity and mitigating cache full error"; } TEST_F(CacheReservationManagerTest, DecreaseCacheReservationByMultiplesOfDummyEntrySize) { std::size_t new_mem_used = 2 * kSizeDummyEntry; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); ASSERT_OK(s); ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 2 * kSizeDummyEntry); ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used); ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry); ASSERT_LT(cache->GetPinnedUsage(), 2 * kSizeDummyEntry + kMetaDataChargeOverhead); new_mem_used = 1 * kSizeDummyEntry; s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to decrease cache reservation correctly"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 1 * kSizeDummyEntry) << "Failed to bookkeep cache reservation decrease correctly"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry) << "Failed to decrease underlying dummy entries in cache correctly"; EXPECT_LT(cache->GetPinnedUsage(), 1 * kSizeDummyEntry + kMetaDataChargeOverhead) << "Failed to decrease underlying dummy entries in cache correctly"; } TEST_F(CacheReservationManagerTest, DecreaseCacheReservationNotByMultiplesOfDummyEntrySize) { std::size_t new_mem_used = 2 * kSizeDummyEntry; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); ASSERT_OK(s); ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 2 * kSizeDummyEntry); ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used); ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry); ASSERT_LT(cache->GetPinnedUsage(), 2 * kSizeDummyEntry + kMetaDataChargeOverhead); new_mem_used = kSizeDummyEntry / 2; s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to decrease cache reservation correctly"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 1 * kSizeDummyEntry) << "Failed to bookkeep cache reservation decrease correctly"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry) << "Failed to decrease underlying dummy entries in cache correctly"; EXPECT_LT(cache->GetPinnedUsage(), 1 * kSizeDummyEntry + kMetaDataChargeOverhead) << "Failed to decrease underlying dummy entries in cache correctly"; } TEST(CacheReservationManagerWithDelayedDecreaseTest, DecreaseCacheReservationWithDelayedDecrease) { constexpr std::size_t kSizeDummyEntry = CacheReservationManagerImpl::GetDummyEntrySize(); constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry; constexpr std::size_t kMetaDataChargeOverhead = 10000; LRUCacheOptions lo; lo.capacity = kCacheCapacity; lo.num_shard_bits = 0; std::shared_ptr cache = NewLRUCache(lo); std::shared_ptr test_cache_rev_mng = std::make_shared>( cache, true /* delayed_decrease */); std::size_t new_mem_used = 8 * kSizeDummyEntry; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); ASSERT_OK(s); ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 8 * kSizeDummyEntry); ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used); std::size_t initial_pinned_usage = cache->GetPinnedUsage(); ASSERT_GE(initial_pinned_usage, 8 * kSizeDummyEntry); ASSERT_LT(initial_pinned_usage, 8 * kSizeDummyEntry + kMetaDataChargeOverhead); new_mem_used = 6 * kSizeDummyEntry; s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to delay decreasing cache reservation"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 8 * kSizeDummyEntry) << "Failed to bookkeep correctly when delaying cache reservation " "decrease"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage) << "Failed to delay decreasing underlying dummy entries in cache"; new_mem_used = 7 * kSizeDummyEntry; s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to delay decreasing cache reservation"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 8 * kSizeDummyEntry) << "Failed to bookkeep correctly when delaying cache reservation " "decrease"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage) << "Failed to delay decreasing underlying dummy entries in cache"; new_mem_used = 6 * kSizeDummyEntry - 1; s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); EXPECT_OK(s) << "Failed to decrease cache reservation correctly when new_mem_used < " "GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode"; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), 6 * kSizeDummyEntry) << "Failed to bookkeep correctly when new_mem_used < " "GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode"; EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) << "Failed to bookkeep the used memory correctly"; EXPECT_GE(cache->GetPinnedUsage(), 6 * kSizeDummyEntry) << "Failed to decrease underlying dummy entries in cache when " "new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed " "decrease mode"; EXPECT_LT(cache->GetPinnedUsage(), 6 * kSizeDummyEntry + kMetaDataChargeOverhead) << "Failed to decrease underlying dummy entries in cache when " "new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed " "decrease mode"; } TEST(CacheReservationManagerDestructorTest, ReleaseRemainingDummyEntriesOnDestruction) { constexpr std::size_t kSizeDummyEntry = CacheReservationManagerImpl::GetDummyEntrySize(); constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry; constexpr std::size_t kMetaDataChargeOverhead = 10000; LRUCacheOptions lo; lo.capacity = kCacheCapacity; lo.num_shard_bits = 0; std::shared_ptr cache = NewLRUCache(lo); { std::shared_ptr test_cache_rev_mng = std::make_shared>( cache); std::size_t new_mem_used = 1 * kSizeDummyEntry; Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); ASSERT_OK(s); ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry); ASSERT_LT(cache->GetPinnedUsage(), 1 * kSizeDummyEntry + kMetaDataChargeOverhead); } EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry) << "Failed to release remaining underlying dummy entries in cache in " "CacheReservationManager's destructor"; } TEST(CacheReservationHandleTest, HandleTest) { constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024; constexpr std::size_t kSizeDummyEntry = 256 * 1024; constexpr std::size_t kMetaDataChargeOverhead = 10000; LRUCacheOptions lo; lo.capacity = kOneGigabyte; lo.num_shard_bits = 0; std::shared_ptr cache = NewLRUCache(lo); std::shared_ptr test_cache_rev_mng( std::make_shared>( cache)); std::size_t mem_used = 0; const std::size_t incremental_mem_used_handle_1 = 1 * kSizeDummyEntry; const std::size_t incremental_mem_used_handle_2 = 2 * kSizeDummyEntry; std::unique_ptr handle_1, handle_2; // To test consecutive CacheReservationManager::MakeCacheReservation works // correctly in terms of returning the handle as well as updating cache // reservation and the latest total memory used Status s = test_cache_rev_mng->MakeCacheReservation( incremental_mem_used_handle_1, &handle_1); mem_used = mem_used + incremental_mem_used_handle_1; ASSERT_OK(s); EXPECT_TRUE(handle_1 != nullptr); EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used); EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used); EXPECT_GE(cache->GetPinnedUsage(), mem_used); EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead); s = test_cache_rev_mng->MakeCacheReservation(incremental_mem_used_handle_2, &handle_2); mem_used = mem_used + incremental_mem_used_handle_2; ASSERT_OK(s); EXPECT_TRUE(handle_2 != nullptr); EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used); EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used); EXPECT_GE(cache->GetPinnedUsage(), mem_used); EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead); // To test // CacheReservationManager::CacheReservationHandle::~CacheReservationHandle() // works correctly in releasing the cache reserved for the handle handle_1.reset(); EXPECT_TRUE(handle_1 == nullptr); mem_used = mem_used - incremental_mem_used_handle_1; EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used); EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used); EXPECT_GE(cache->GetPinnedUsage(), mem_used); EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead); // To test the actual CacheReservationManager object won't be deallocated // as long as there remain handles pointing to it. // We strongly recommend deallocating CacheReservationManager object only // after all its handles are deallocated to keep things easy to reasonate test_cache_rev_mng.reset(); EXPECT_GE(cache->GetPinnedUsage(), mem_used); EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead); handle_2.reset(); // The CacheReservationManager object is now deallocated since all the handles // and its original pointer is gone mem_used = mem_used - incremental_mem_used_handle_2; EXPECT_EQ(mem_used, 0); EXPECT_EQ(cache->GetPinnedUsage(), mem_used); } } // namespace ROCKSDB_NAMESPACE int main(int argc, char** argv) { ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }