From 4490b93d71c1b32e4b7aa40c328fe190f05fc0d6 Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Mon, 21 Sep 2026 21:16:53 +0000 Subject: [PATCH 1/5] Add cache size policy to reuse shorter cache entries --- velox/common/caching/AsyncDataCache.cpp | 30 +++++++++++++-- velox/common/caching/AsyncDataCache.h | 37 +++++++++++++++++-- .../caching/tests/AsyncDataCacheTest.cpp | 20 +++++++++- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/velox/common/caching/AsyncDataCache.cpp b/velox/common/caching/AsyncDataCache.cpp index c02b6f95928..74bf407ace3 100644 --- a/velox/common/caching/AsyncDataCache.cpp +++ b/velox/common/caching/AsyncDataCache.cpp @@ -251,7 +251,8 @@ std::unique_ptr CacheShard::getFreeEntryLocked() { std::optional CacheShard::lookupLocked( RawFileCacheKey key, uint64_t size, - folly::SemiFuture* wait) { + folly::SemiFuture* wait, + CacheEntrySizePolicy sizePolicy) { ++eventCounter_; auto it = entryMap_.find(key); if (it == entryMap_.end()) { @@ -266,7 +267,8 @@ std::optional CacheShard::lookupLocked( return CachePin{}; } // size=0 (from find()) always passes since entry size is non-negative. - if (foundEntry->size() < size) { + if (sizePolicy == CacheEntrySizePolicy::kRequireAtLeast && + foundEntry->size() < size) { // This can happen if different load quanta apply to access via different // connectors. This is not an error but still worth logging. VELOX_CACHE_LOG_EVERY_MS(WARNING, 1'000) @@ -297,10 +299,20 @@ CachePin CacheShard::findOrCreate( uint64_t size, bool contiguous, folly::SemiFuture* wait) { + return findOrCreate( + key, size, contiguous, wait, CacheEntrySizePolicy::kRequireAtLeast); +} + +CachePin CacheShard::findOrCreate( + RawFileCacheKey key, + uint64_t size, + bool contiguous, + folly::SemiFuture* wait, + CacheEntrySizePolicy sizePolicy) { AsyncDataCacheEntry* entryToInit = nullptr; { std::lock_guard l(mutex_); - auto result = lookupLocked(key, size, wait); + auto result = lookupLocked(key, size, wait, sizePolicy); if (result.has_value()) { return std::move(result.value()); } @@ -875,8 +887,18 @@ CachePin AsyncDataCache::findOrCreate( uint64_t size, bool contiguous, folly::SemiFuture* wait) { + return findOrCreate( + key, size, contiguous, wait, CacheEntrySizePolicy::kRequireAtLeast); +} + +CachePin AsyncDataCache::findOrCreate( + RawFileCacheKey key, + uint64_t size, + bool contiguous, + folly::SemiFuture* wait, + CacheEntrySizePolicy sizePolicy) { const int shard = std::hash()(key) & shardMask_; - return shards_[shard]->findOrCreate(key, size, contiguous, wait); + return shards_[shard]->findOrCreate(key, size, contiguous, wait, sizePolicy); } std::optional AsyncDataCache::find( diff --git a/velox/common/caching/AsyncDataCache.h b/velox/common/caching/AsyncDataCache.h index 40d9a4b4ed1..007bc8a52b1 100644 --- a/velox/common/caching/AsyncDataCache.h +++ b/velox/common/caching/AsyncDataCache.h @@ -120,6 +120,13 @@ struct RawFileCacheKey { return offset == other.offset && fileNum == other.fileNum; } }; + +/// Controls whether an entry that begins at the requested key but is shorter +/// than the requested size is stale or is a reusable prefix. +enum class CacheEntrySizePolicy : uint8_t { + kRequireAtLeast, + kAllowSmaller, +}; } // namespace facebook::velox::cache namespace std { @@ -625,13 +632,22 @@ class CacheShard { : cache_(cache), maxWriteRatio_(maxWriteRatio) {} /// See AsyncDataCache::findOrCreate. If 'contiguous' is true, the - /// entry's data is allocated as a single contiguous region. + /// entry's data is allocated as a single contiguous region. With + /// kAllowSmaller, an existing shared entry that starts at 'key' but is + /// shorter than 'size' is returned as a valid prefix. CachePin findOrCreate( RawFileCacheKey key, uint64_t size, bool contiguous = false, folly::SemiFuture* readyFuture = nullptr); + CachePin findOrCreate( + RawFileCacheKey key, + uint64_t size, + bool contiguous, + folly::SemiFuture* readyFuture, + CacheEntrySizePolicy sizePolicy); + /// Finds a cache entry for 'key'. Returns a shared-mode pin if the entry /// exists and is not exclusive. Returns an empty pin (inside optional) if /// the entry is exclusive; if 'waitFuture' is not nullptr it is set to a @@ -731,7 +747,8 @@ class CacheShard { std::optional lookupLocked( RawFileCacheKey key, uint64_t size, - folly::SemiFuture* waitFuture); + folly::SemiFuture* waitFuture, + CacheEntrySizePolicy sizePolicy = CacheEntrySizePolicy::kRequireAtLeast); void tryAddFreeEntry(std::unique_ptr&& entry); @@ -893,6 +910,9 @@ class AsyncDataCache : public memory::Cache { /// nullptr and the pin is exclusive on some other pin, this is set to /// a future that is realized when the pin is no longer exclusive. /// When the future is realized, the caller may retry findOrCreate(). + /// With kAllowSmaller, an existing shared entry that starts at 'key' but is + /// shorter than 'size' is returned as a valid prefix. The + /// caller is responsible for requesting the remaining suffix separately. /// runtime error with code kNoCacheSpace if there is no space to /// create the new entry after evicting any unpinned content. CachePin findOrCreate( @@ -901,6 +921,13 @@ class AsyncDataCache : public memory::Cache { bool contiguous = false, folly::SemiFuture* waitFuture = nullptr); + CachePin findOrCreate( + RawFileCacheKey key, + uint64_t size, + bool contiguous, + folly::SemiFuture* waitFuture, + CacheEntrySizePolicy sizePolicy); + /// Finds a cache entry for 'key'. Returns a shared-mode pin if the entry /// exists and is not exclusive. Returns an empty pin (inside optional) if /// the entry is exclusive; if 'waitFuture' is not nullptr it is set to a @@ -978,9 +1005,11 @@ class AsyncDataCache : public memory::Cache { const std::vector& keys, const SizeFunc& sizeFunc, const ProcessPin& processPin, - bool contiguous = false) { + bool contiguous = false, + CacheEntrySizePolicy sizePolicy = CacheEntrySizePolicy::kRequireAtLeast) { for (size_t i = 0; i < keys.size(); ++i) { - auto pin = findOrCreate(keys[i], sizeFunc(i), contiguous); + auto pin = + findOrCreate(keys[i], sizeFunc(i), contiguous, nullptr, sizePolicy); if (pin.empty() || pin.checkedEntry()->isShared()) { continue; } diff --git a/velox/common/caching/tests/AsyncDataCacheTest.cpp b/velox/common/caching/tests/AsyncDataCacheTest.cpp index 7df81482038..cd0610476cb 100644 --- a/velox/common/caching/tests/AsyncDataCacheTest.cpp +++ b/velox/common/caching/tests/AsyncDataCacheTest.cpp @@ -1291,6 +1291,24 @@ TEST_P(AsyncDataCacheTest, staleEntry) { ASSERT_EQ(stats.numEntries, 1); ASSERT_EQ(stats.numHit, 1); + // A caller that can compose adjacent entries may consume this entry as a + // valid prefix instead of invalidating and reloading it. + auto prefixPin = cache_->findOrCreate( + key, + 2 * size, + /*contiguous=*/false, + &wait, + CacheEntrySizePolicy::kAllowSmaller); + ASSERT_FALSE(prefixPin.empty()); + ASSERT_TRUE(wait.isReady()); + ASSERT_FALSE(prefixPin.entry()->isExclusive()); + ASSERT_EQ(prefixPin.entry()->size(), size); + prefixPin.clear(); + stats = cache_->refreshStats(); + ASSERT_EQ(stats.numStales, 0); + ASSERT_EQ(stats.numEntries, 1); + ASSERT_EQ(stats.numHit, 2); + // Stale cache access with large cache size. auto stalePin = cache_->findOrCreate(key, 2 * size, /*contiguous=*/false, &wait); @@ -1301,7 +1319,7 @@ TEST_P(AsyncDataCacheTest, staleEntry) { stats = cache_->refreshStats(); ASSERT_EQ(stats.numStales, 1); ASSERT_EQ(stats.numEntries, 1); - ASSERT_EQ(stats.numHit, 1); + ASSERT_EQ(stats.numHit, 2); } TEST_P(AsyncDataCacheTest, shrinkCache) { From 7fbccd925452b7271787dee5249efbb41fd6f28a Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Mon, 21 Sep 2026 21:28:45 +0000 Subject: [PATCH 2/5] Improve comments --- velox/common/caching/AsyncDataCache.h | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/velox/common/caching/AsyncDataCache.h b/velox/common/caching/AsyncDataCache.h index 007bc8a52b1..441810cd19a 100644 --- a/velox/common/caching/AsyncDataCache.h +++ b/velox/common/caching/AsyncDataCache.h @@ -632,15 +632,16 @@ class CacheShard { : cache_(cache), maxWriteRatio_(maxWriteRatio) {} /// See AsyncDataCache::findOrCreate. If 'contiguous' is true, the - /// entry's data is allocated as a single contiguous region. With - /// kAllowSmaller, an existing shared entry that starts at 'key' but is - /// shorter than 'size' is returned as a valid prefix. + /// entry's data is allocated as a single contiguous region. CachePin findOrCreate( RawFileCacheKey key, uint64_t size, bool contiguous = false, folly::SemiFuture* readyFuture = nullptr); + /// Same as above, with 'sizePolicy' deciding how an existing entry at 'key' + /// that is shorter than 'size' is treated. See + /// AsyncDataCache::findOrCreate for details. CachePin findOrCreate( RawFileCacheKey key, uint64_t size, @@ -910,9 +911,6 @@ class AsyncDataCache : public memory::Cache { /// nullptr and the pin is exclusive on some other pin, this is set to /// a future that is realized when the pin is no longer exclusive. /// When the future is realized, the caller may retry findOrCreate(). - /// With kAllowSmaller, an existing shared entry that starts at 'key' but is - /// shorter than 'size' is returned as a valid prefix. The - /// caller is responsible for requesting the remaining suffix separately. /// runtime error with code kNoCacheSpace if there is no space to /// create the new entry after evicting any unpinned content. CachePin findOrCreate( @@ -921,6 +919,15 @@ class AsyncDataCache : public memory::Cache { bool contiguous = false, folly::SemiFuture* waitFuture = nullptr); + /// Same as above, with 'sizePolicy' deciding how an existing shared entry + /// at 'key' that is shorter than 'size' is treated. With kRequireAtLeast + /// (the behavior of the overload above) the entry is stale: it is evicted + /// and a new exclusive entry of 'size' is created for the caller to fill. + /// With kAllowSmaller the shorter entry is returned as a valid prefix and + /// counted as a hit. The cache does not track the uncovered suffix. The + /// caller must read the returned entry's size(), and request + /// [key.offset + size(), key.offset + 'size') under a separate key, which + /// may in turn hit, miss, or return another shorter prefix. CachePin findOrCreate( RawFileCacheKey key, uint64_t size, From 6d6ed96b22bef13937d8b8c12db0a8290cdb4413 Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Tue, 22 Sep 2026 01:49:38 +0000 Subject: [PATCH 3/5] Simplify design a bit Co-authored-by: Kyle Hubert --- velox/common/caching/AsyncDataCache.cpp | 18 -------- velox/common/caching/AsyncDataCache.h | 46 +++++++------------ .../caching/tests/AsyncDataCacheTest.cpp | 5 +- 3 files changed, 20 insertions(+), 49 deletions(-) diff --git a/velox/common/caching/AsyncDataCache.cpp b/velox/common/caching/AsyncDataCache.cpp index 74bf407ace3..6b9a63229d8 100644 --- a/velox/common/caching/AsyncDataCache.cpp +++ b/velox/common/caching/AsyncDataCache.cpp @@ -294,15 +294,6 @@ std::optional CacheShard::lookupLocked( return pin; } -CachePin CacheShard::findOrCreate( - RawFileCacheKey key, - uint64_t size, - bool contiguous, - folly::SemiFuture* wait) { - return findOrCreate( - key, size, contiguous, wait, CacheEntrySizePolicy::kRequireAtLeast); -} - CachePin CacheShard::findOrCreate( RawFileCacheKey key, uint64_t size, @@ -882,15 +873,6 @@ void CacheShard::shutdown() { freeEntries_.clear(); } -CachePin AsyncDataCache::findOrCreate( - RawFileCacheKey key, - uint64_t size, - bool contiguous, - folly::SemiFuture* wait) { - return findOrCreate( - key, size, contiguous, wait, CacheEntrySizePolicy::kRequireAtLeast); -} - CachePin AsyncDataCache::findOrCreate( RawFileCacheKey key, uint64_t size, diff --git a/velox/common/caching/AsyncDataCache.h b/velox/common/caching/AsyncDataCache.h index 441810cd19a..9bd8c59da94 100644 --- a/velox/common/caching/AsyncDataCache.h +++ b/velox/common/caching/AsyncDataCache.h @@ -632,22 +632,15 @@ class CacheShard { : cache_(cache), maxWriteRatio_(maxWriteRatio) {} /// See AsyncDataCache::findOrCreate. If 'contiguous' is true, the - /// entry's data is allocated as a single contiguous region. + /// entry's data is allocated as a single contiguous region. 'sizePolicy' + /// decides how an existing entry at 'key' that is shorter than 'size' is + /// treated. See AsyncDataCache::findOrCreate for details. CachePin findOrCreate( RawFileCacheKey key, uint64_t size, bool contiguous = false, - folly::SemiFuture* readyFuture = nullptr); - - /// Same as above, with 'sizePolicy' deciding how an existing entry at 'key' - /// that is shorter than 'size' is treated. See - /// AsyncDataCache::findOrCreate for details. - CachePin findOrCreate( - RawFileCacheKey key, - uint64_t size, - bool contiguous, - folly::SemiFuture* readyFuture, - CacheEntrySizePolicy sizePolicy); + folly::SemiFuture* readyFuture = nullptr, + CacheEntrySizePolicy sizePolicy = CacheEntrySizePolicy::kRequireAtLeast); /// Finds a cache entry for 'key'. Returns a shared-mode pin if the entry /// exists and is not exclusive. Returns an empty pin (inside optional) if @@ -913,27 +906,22 @@ class AsyncDataCache : public memory::Cache { /// When the future is realized, the caller may retry findOrCreate(). /// runtime error with code kNoCacheSpace if there is no space to /// create the new entry after evicting any unpinned content. + /// + /// 'sizePolicy' decides how an existing shared entry at 'key' that is + /// shorter than 'size' is treated. With kRequireAtLeast (the default) the + /// entry is stale: it is evicted and a new exclusive entry of 'size' is + /// created for the caller to fill. With kAllowSmaller the shorter entry is + /// returned as a valid prefix and counted as a hit. The cache does not + /// track the uncovered suffix. The caller must read the returned entry's + /// size(), and request [key.offset + size(), key.offset + 'size') under a + /// separate key, which may in turn hit, miss, or return another shorter + /// prefix. CachePin findOrCreate( RawFileCacheKey key, uint64_t size, bool contiguous = false, - folly::SemiFuture* waitFuture = nullptr); - - /// Same as above, with 'sizePolicy' deciding how an existing shared entry - /// at 'key' that is shorter than 'size' is treated. With kRequireAtLeast - /// (the behavior of the overload above) the entry is stale: it is evicted - /// and a new exclusive entry of 'size' is created for the caller to fill. - /// With kAllowSmaller the shorter entry is returned as a valid prefix and - /// counted as a hit. The cache does not track the uncovered suffix. The - /// caller must read the returned entry's size(), and request - /// [key.offset + size(), key.offset + 'size') under a separate key, which - /// may in turn hit, miss, or return another shorter prefix. - CachePin findOrCreate( - RawFileCacheKey key, - uint64_t size, - bool contiguous, - folly::SemiFuture* waitFuture, - CacheEntrySizePolicy sizePolicy); + folly::SemiFuture* waitFuture = nullptr, + CacheEntrySizePolicy sizePolicy = CacheEntrySizePolicy::kRequireAtLeast); /// Finds a cache entry for 'key'. Returns a shared-mode pin if the entry /// exists and is not exclusive. Returns an empty pin (inside optional) if diff --git a/velox/common/caching/tests/AsyncDataCacheTest.cpp b/velox/common/caching/tests/AsyncDataCacheTest.cpp index cd0610476cb..2d9f3715967 100644 --- a/velox/common/caching/tests/AsyncDataCacheTest.cpp +++ b/velox/common/caching/tests/AsyncDataCacheTest.cpp @@ -1291,8 +1291,9 @@ TEST_P(AsyncDataCacheTest, staleEntry) { ASSERT_EQ(stats.numEntries, 1); ASSERT_EQ(stats.numHit, 1); - // A caller that can compose adjacent entries may consume this entry as a - // valid prefix instead of invalidating and reloading it. + // A caller that can assemble one read from several consecutive cache entries + // may consume this entry as a valid prefix instead of invalidating and + // reloading it. auto prefixPin = cache_->findOrCreate( key, 2 * size, From d688f6806fac5ac7026532a88329340bee748f3a Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Wed, 14 Oct 2026 03:20:30 +0000 Subject: [PATCH 4/5] Get stats delta --- velox/common/caching/tests/AsyncDataCacheTest.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/velox/common/caching/tests/AsyncDataCacheTest.cpp b/velox/common/caching/tests/AsyncDataCacheTest.cpp index 2d9f3715967..b6f29458b6a 100644 --- a/velox/common/caching/tests/AsyncDataCacheTest.cpp +++ b/velox/common/caching/tests/AsyncDataCacheTest.cpp @@ -1294,6 +1294,7 @@ TEST_P(AsyncDataCacheTest, staleEntry) { // A caller that can assemble one read from several consecutive cache entries // may consume this entry as a valid prefix instead of invalidating and // reloading it. + const auto beforePrefix = cache_->refreshStats(); auto prefixPin = cache_->findOrCreate( key, 2 * size, @@ -1306,11 +1307,13 @@ TEST_P(AsyncDataCacheTest, staleEntry) { ASSERT_EQ(prefixPin.entry()->size(), size); prefixPin.clear(); stats = cache_->refreshStats(); - ASSERT_EQ(stats.numStales, 0); ASSERT_EQ(stats.numEntries, 1); - ASSERT_EQ(stats.numHit, 2); + auto delta = stats - beforePrefix; + ASSERT_EQ(delta.numStales, 0); + ASSERT_EQ(delta.numHit, 1); // Stale cache access with large cache size. + const auto beforeStale = cache_->refreshStats(); auto stalePin = cache_->findOrCreate(key, 2 * size, /*contiguous=*/false, &wait); ASSERT_FALSE(stalePin.empty()); @@ -1318,9 +1321,10 @@ TEST_P(AsyncDataCacheTest, staleEntry) { ASSERT_TRUE(stalePin.entry()->isExclusive()); stalePin.entry()->setExclusiveToShared(); stats = cache_->refreshStats(); - ASSERT_EQ(stats.numStales, 1); ASSERT_EQ(stats.numEntries, 1); - ASSERT_EQ(stats.numHit, 2); + delta = stats - beforeStale; + ASSERT_EQ(delta.numStales, 1); + ASSERT_EQ(delta.numHit, 0); } TEST_P(AsyncDataCacheTest, shrinkCache) { From cc6cf78bc8f20fa3b7e36e3428f6119b8bab2e26 Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Mon, 19 Oct 2026 19:22:12 +0000 Subject: [PATCH 5/5] Use a separate unit test for this PR. --- .../caching/tests/AsyncDataCacheTest.cpp | 82 ++++++++++++++++--- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/velox/common/caching/tests/AsyncDataCacheTest.cpp b/velox/common/caching/tests/AsyncDataCacheTest.cpp index b6f29458b6a..849cf1bc7d7 100644 --- a/velox/common/caching/tests/AsyncDataCacheTest.cpp +++ b/velox/common/caching/tests/AsyncDataCacheTest.cpp @@ -1291,10 +1291,47 @@ TEST_P(AsyncDataCacheTest, staleEntry) { ASSERT_EQ(stats.numEntries, 1); ASSERT_EQ(stats.numHit, 1); - // A caller that can assemble one read from several consecutive cache entries - // may consume this entry as a valid prefix instead of invalidating and - // reloading it. - const auto beforePrefix = cache_->refreshStats(); + // Stale cache access with large cache size. + auto stalePin = + cache_->findOrCreate(key, 2 * size, /*contiguous=*/false, &wait); + ASSERT_FALSE(stalePin.empty()); + ASSERT_TRUE(wait.isReady()); + ASSERT_TRUE(stalePin.entry()->isExclusive()); + stalePin.entry()->setExclusiveToShared(); + stats = cache_->refreshStats(); + ASSERT_EQ(stats.numStales, 1); + ASSERT_EQ(stats.numEntries, 1); + ASSERT_EQ(stats.numHit, 1); +} + +TEST_P(AsyncDataCacheTest, cacheEntrySizePolicy) { + constexpr uint64_t kRamBytes = 1UL << 30; + initializeCache(kRamBytes, 0, 0); + StringIdLease file(fileIds(), std::string_view("cacheEntrySizePolicy")); + const uint64_t size = 200; + folly::SemiFuture wait(false); + RawFileCacheKey key{file.id(), 1000}; + + // A miss creates an exclusive entry of the requested size. + auto before = cache_->refreshStats(); + auto pin = cache_->findOrCreate( + key, + size, + /*contiguous=*/false, + &wait, + CacheEntrySizePolicy::kAllowSmaller); + ASSERT_FALSE(pin.empty()); + ASSERT_TRUE(wait.isReady()); + ASSERT_TRUE(pin.entry()->isExclusive()); + ASSERT_EQ(pin.entry()->size(), size); + pin.entry()->setExclusiveToShared(); + pin.clear(); + auto delta = cache_->refreshStats() - before; + ASSERT_EQ(delta.numHit, 0); + ASSERT_EQ(delta.numStales, 0); + + // kAllowSmaller returns a shorter entry as a shared prefix and counts a hit. + before = cache_->refreshStats(); auto prefixPin = cache_->findOrCreate( key, 2 * size, @@ -1306,25 +1343,46 @@ TEST_P(AsyncDataCacheTest, staleEntry) { ASSERT_FALSE(prefixPin.entry()->isExclusive()); ASSERT_EQ(prefixPin.entry()->size(), size); prefixPin.clear(); - stats = cache_->refreshStats(); + auto stats = cache_->refreshStats(); ASSERT_EQ(stats.numEntries, 1); - auto delta = stats - beforePrefix; + delta = stats - before; + ASSERT_EQ(delta.numHit, 1); ASSERT_EQ(delta.numStales, 0); + + // kAllowSmaller with a request that fits the entry is an ordinary hit. + before = cache_->refreshStats(); + auto exactPin = cache_->findOrCreate( + key, + size, + /*contiguous=*/false, + &wait, + CacheEntrySizePolicy::kAllowSmaller); + ASSERT_FALSE(exactPin.empty()); + ASSERT_FALSE(exactPin.entry()->isExclusive()); + ASSERT_EQ(exactPin.entry()->size(), size); + exactPin.clear(); + delta = cache_->refreshStats() - before; ASSERT_EQ(delta.numHit, 1); + ASSERT_EQ(delta.numStales, 0); - // Stale cache access with large cache size. - const auto beforeStale = cache_->refreshStats(); - auto stalePin = - cache_->findOrCreate(key, 2 * size, /*contiguous=*/false, &wait); + // kRequireAtLeast treats the same shorter entry as stale and replaces it. + before = cache_->refreshStats(); + auto stalePin = cache_->findOrCreate( + key, + 2 * size, + /*contiguous=*/false, + &wait, + CacheEntrySizePolicy::kRequireAtLeast); ASSERT_FALSE(stalePin.empty()); ASSERT_TRUE(wait.isReady()); ASSERT_TRUE(stalePin.entry()->isExclusive()); + ASSERT_EQ(stalePin.entry()->size(), 2 * size); stalePin.entry()->setExclusiveToShared(); stats = cache_->refreshStats(); ASSERT_EQ(stats.numEntries, 1); - delta = stats - beforeStale; - ASSERT_EQ(delta.numStales, 1); + delta = stats - before; ASSERT_EQ(delta.numHit, 0); + ASSERT_EQ(delta.numStales, 1); } TEST_P(AsyncDataCacheTest, shrinkCache) {