Skip to content

Commit 0c10a77

Browse files
Prateek Goyalmeta-codesync[bot]
authored andcommitted
Report successful ObjectCache replacements in destructor data
Summary: ObjectCache reports explicit removal, TTL reaping, and insertOrReplace through the same kRemoved destructor context. Add removedBySuccessfulReplacement so consumers can suppress work only when insertOrReplace actually installed a newer object. The old cache item is marked after insertOrReplace succeeds while its returned handle prevents the destructor from running. The marker uses an available bit in the existing atomic item flags, so the persisted item layout does not change across warm rolls. Reviewed By: rlyerly Differential Revision: D113092104 fbshipit-source-id: 87b4df52fd39e80e59bc5f8b54475ad332380f2e
1 parent 5956fa4 commit 0c10a77

5 files changed

Lines changed: 91 additions & 5 deletions

File tree

cachelib/allocator/CacheAllocator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3856,6 +3856,7 @@ CacheAllocator<CacheTrait>::insertOrReplace(const WriteHandle& handle) {
38563856
// Remove from LRU as well if we do have a handle of old item
38573857
if (replaced) {
38583858
stats_.numInsertOrReplaceReplaced.inc();
3859+
replaced->markRemovedByReplacement();
38593860
removeFromMMContainer(*replaced);
38603861
} else {
38613862
stats_.numInsertOrReplaceInserted.inc();

cachelib/allocator/CacheItem.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ class CACHELIB_PACKED_ATTR CacheItem {
264264
void markNvmLargeItem() noexcept;
265265
bool isNvmLargeItem() const noexcept;
266266

267+
void markRemovedByReplacement() noexcept;
268+
bool isRemovedByReplacement() const noexcept;
269+
267270
/**
268271
* Function to set the timestamp for when to expire an item
269272
*
@@ -909,6 +912,16 @@ bool CacheItem<CacheTrait>::isNvmLargeItem() const noexcept {
909912
return ref_.isNvmLargeItem();
910913
}
911914

915+
template <typename CacheTrait>
916+
void CacheItem<CacheTrait>::markRemovedByReplacement() noexcept {
917+
ref_.markRemovedByReplacement();
918+
}
919+
920+
template <typename CacheTrait>
921+
bool CacheItem<CacheTrait>::isRemovedByReplacement() const noexcept {
922+
return ref_.isRemovedByReplacement();
923+
}
924+
912925
template <typename CacheTrait>
913926
void CacheItem<CacheTrait>::markIsChainedItem() noexcept {
914927
XDCHECK(!hasChainedItem());

cachelib/allocator/Refcount.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
122122
// Set during NVM promotion when the exact NVM buffer size is known.
123123
kNvmLargeItem,
124124

125+
// Item was removed by a successful replacement.
126+
kRemovedByReplacement,
127+
125128
// Unused. This is just to indciate the maximum number of flags
126129
kFlagMax,
127130
};
@@ -455,6 +458,13 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
455458
void markNvmLargeItem() noexcept { return setFlag<kNvmLargeItem>(); }
456459
bool isNvmLargeItem() const noexcept { return isFlagSet<kNvmLargeItem>(); }
457460

461+
void markRemovedByReplacement() noexcept {
462+
return setFlag<kRemovedByReplacement>();
463+
}
464+
bool isRemovedByReplacement() const noexcept {
465+
return isFlagSet<kRemovedByReplacement>();
466+
}
467+
458468
// Whether or not an item is completely drained of access
459469
// Refcount is 0 and the item is not linked, accessible, nor exclusive
460470
bool isDrained() const noexcept { return getRefWithAccessAndAdmin() == 0; }

cachelib/object_cache/ObjectCache.h

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ struct ObjectCacheItem {
5151
enum class ObjectCacheDestructorContext {
5252
// evicted from cache
5353
kEvicted,
54-
// removed by user calling remove()/insertOrReplace() or due to expired
54+
// removed by user calling remove()/insertOrReplace() or due to expired.
55+
// Use ObjectCacheDestructorData::removedBySuccessfulReplacement to
56+
// distinguish a successful insertOrReplace() from other removals.
5557
kRemoved,
5658
// unknown cases
5759
kUnknown,
@@ -63,13 +65,15 @@ struct ObjectCacheDestructorData {
6365
const KAllocation::Key& k,
6466
uint32_t expiryTime,
6567
uint32_t creationTime,
66-
uint32_t lastAccessTime)
68+
uint32_t lastAccessTime,
69+
bool removedBySuccessfulReplacement = false)
6770
: context(ctx),
6871
objectPtr(ptr),
6972
key(k),
7073
expiryTime(expiryTime),
7174
creationTime(creationTime),
72-
lastAccessTime(lastAccessTime) {}
75+
lastAccessTime(lastAccessTime),
76+
removedBySuccessfulReplacement(removedBySuccessfulReplacement) {}
7377

7478
// release the evicted/removed/expired object memory
7579
template <typename T>
@@ -94,6 +98,10 @@ struct ObjectCacheDestructorData {
9498

9599
// the last time this object was accessed
96100
uint32_t lastAccessTime;
101+
102+
// Whether the object was removed by an insertOrReplace() that successfully
103+
// installed its replacement. Always false for kEvicted/kUnknown.
104+
bool removedBySuccessfulReplacement;
97105
};
98106

99107
// Information about cache memory capacity calculated from configuration
@@ -792,6 +800,9 @@ void ObjectCache<AllocatorT>::init() {
792800
auto& item = data.item;
793801

794802
auto itemPtr = getAlignedItemPtr(item.getMemory());
803+
const bool removedBySuccessfulReplacement =
804+
ctx == ObjectCacheDestructorContext::kRemoved &&
805+
item.isRemovedByReplacement();
795806

796807
SCOPE_EXIT {
797808
if (config_.objectSizeTrackingEnabled) {
@@ -804,7 +815,8 @@ void ObjectCache<AllocatorT>::init() {
804815
// execute user defined item destructor
805816
config_.itemDestructor(ObjectCacheDestructorData(
806817
ctx, itemPtr->objectPtr, item.getKey(), item.getExpiryTime(),
807-
item.getCreationTime(), item.getLastAccessTime()));
818+
item.getCreationTime(), item.getLastAccessTime(),
819+
removedBySuccessfulReplacement));
808820
};
809821
});
810822
} else {
@@ -822,6 +834,9 @@ void ObjectCache<AllocatorT>::init() {
822834
auto& item = data.item;
823835

824836
auto itemPtr = getAlignedItemPtr(item.getMemory());
837+
const bool removedBySuccessfulReplacement =
838+
ctx == ObjectCacheDestructorContext::kRemoved &&
839+
item.isRemovedByReplacement();
825840

826841
SCOPE_EXIT {
827842
if (config_.objectSizeTrackingEnabled) {
@@ -834,7 +849,8 @@ void ObjectCache<AllocatorT>::init() {
834849
// execute user defined item destructor
835850
config_.removeCb(ObjectCacheDestructorData(
836851
ctx, itemPtr->objectPtr, item.getKey(), item.getExpiryTime(),
837-
item.getCreationTime(), item.getLastAccessTime()));
852+
item.getCreationTime(), item.getLastAccessTime(),
853+
removedBySuccessfulReplacement));
838854
};
839855
});
840856
}

cachelib/object_cache/tests/ObjectCacheTest.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <folly/Random.h>
2020
#include <gtest/gtest.h>
2121

22+
#include <atomic>
2223
#include <cstddef>
2324
#include <memory>
2425

@@ -407,6 +408,48 @@ class ObjectCacheTest : public ::testing::Test {
407408
ASSERT_EQ(nullptr, found2);
408409
}
409410

411+
void testSuccessfulReplacementFlag() {
412+
std::atomic<int> numReplaced{0};
413+
std::atomic<int> numNotReplaced{0};
414+
ObjectCacheConfig config;
415+
config.setCacheName("test")
416+
.setCacheCapacity(10'000)
417+
.setItemReaperInterval(std::chrono::seconds{1})
418+
.setItemDestructor([&](ObjectCacheDestructorData data) {
419+
EXPECT_EQ(data.context, ObjectCacheDestructorContext::kRemoved);
420+
if (data.removedBySuccessfulReplacement) {
421+
++numReplaced;
422+
} else {
423+
++numNotReplaced;
424+
}
425+
data.deleteObject<Foo>();
426+
});
427+
auto objcache = ObjectCache::create(config);
428+
429+
objcache->insertOrReplace("k", std::make_unique<Foo>(), 0 /*object size*/,
430+
1000 /*ttlSecs*/);
431+
auto heldOldObject = objcache->template find<Foo>("k");
432+
auto [status, newObject, replacedObject] = objcache->insertOrReplace(
433+
"k", std::make_unique<Foo>(), 0 /*object size*/, 1000 /*ttlSecs*/);
434+
ASSERT_EQ(ObjectCache::AllocStatus::kSuccess, status);
435+
436+
heldOldObject.reset();
437+
EXPECT_EQ(0, numReplaced.load());
438+
replacedObject.reset();
439+
EXPECT_EQ(1, numReplaced.load());
440+
EXPECT_EQ(0, numNotReplaced.load());
441+
442+
ASSERT_TRUE(objcache->remove("k"));
443+
newObject.reset();
444+
EXPECT_EQ(1, numReplaced.load());
445+
EXPECT_EQ(1, numNotReplaced.load());
446+
447+
objcache->insertOrReplace("e", std::make_unique<Foo>(), 0 /*object size*/,
448+
1 /*ttlSecs*/);
449+
ASSERT_EVENTUALLY_TRUE([&] { return numNotReplaced.load() == 2; });
450+
EXPECT_EQ(1, numReplaced.load());
451+
}
452+
410453
void testExpirationWithCustomizedReaper() {
411454
ObjectCacheConfig config;
412455
config.setCacheName("test")
@@ -2025,6 +2068,9 @@ TYPED_TEST(ObjectCacheTest, UserItemDestructor) {
20252068
this->testUserItemDestructor();
20262069
}
20272070
TYPED_TEST(ObjectCacheTest, Expiration) { this->testExpiration(); }
2071+
TYPED_TEST(ObjectCacheTest, SuccessfulReplacementFlag) {
2072+
this->testSuccessfulReplacementFlag();
2073+
}
20282074
TYPED_TEST(ObjectCacheTest, ExpirationWithCustomizedReaper) {
20292075
this->testExpirationWithCustomizedReaper();
20302076
}

0 commit comments

Comments
 (0)