Skip to content

Commit 88309d4

Browse files
committed
Introduce 'markedForEviction' state for the Item.
It is similar to 'moving' but requires ref count to be 0. An item which is marked for eviction causes all incRef() calls to that item to fail. This will be used to ensure that once item is selected for eviction, no one can interfere and prevent the eviction from suceeding. 'markedForEviction' relies on the same 'exlusive' bit as the 'moving' state. To distinguish between those two states, 'moving' add 1 to the refCount. This is hidden from the user, so getRefCount() will not return that extra ref.
1 parent 519f664 commit 88309d4

File tree

7 files changed

+432
-176
lines changed

7 files changed

+432
-176
lines changed

cachelib/allocator/CacheAllocator-inl.h

+36-35
Original file line numberDiff line numberDiff line change
@@ -823,28 +823,29 @@ CacheAllocator<CacheTrait>::releaseBackToAllocator(Item& it,
823823

824824
removeFromMMContainer(*head);
825825

826-
// If this chained item is marked as exclusive, we will not free it.
827-
// We must capture the exclusive state before we do the decRef when
826+
// If this chained item is marked as moving, we will not free it.
827+
// We must capture the moving state before we do the decRef when
828828
// we know the item must still be valid
829-
const bool wasExclusive = head->isExclusive();
829+
const bool wasMoving = head->isMoving();
830+
XDCHECK(!head->isMarkedForEviction());
830831

831832
// Decref and check if we were the last reference. Now if the item
832-
// was marked exclusive, after decRef, it will be free to be released
833+
// was marked moving, after decRef, it will be free to be released
833834
// by slab release thread
834835
const auto childRef = head->decRef();
835836

836-
// If the item is already exclusive and we already decremented the
837+
// If the item is already moving and we already decremented the
837838
// refcount, we don't need to free this item. We'll let the slab
838839
// release thread take care of that
839-
if (!wasExclusive) {
840+
if (!wasMoving) {
840841
if (childRef != 0) {
841842
throw std::runtime_error(folly::sformat(
842843
"chained item refcount is not zero. We cannot proceed! "
843844
"Ref: {}, Chained Item: {}",
844845
childRef, head->toString()));
845846
}
846847

847-
// Item is not exclusive and refcount is 0, we can proceed to
848+
// Item is not moving and refcount is 0, we can proceed to
848849
// free it or recylce the memory
849850
if (head == toRecycle) {
850851
XDCHECK(ReleaseRes::kReleased != res);
@@ -1170,7 +1171,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
11701171

11711172
// This item has been unlinked from its parent and we're the only
11721173
// owner of it, so we're done here
1173-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1174+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
11741175
return false;
11751176
}
11761177

@@ -1201,7 +1202,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12011202

12021203
// In case someone else had removed this chained item from its parent by now
12031204
// So we check again to see if the it has been unlinked from its parent
1204-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1205+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
12051206
return false;
12061207
}
12071208

@@ -1217,7 +1218,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12171218
// parent's chain and the MMContainer.
12181219
auto oldItemHandle =
12191220
replaceChainedItemLocked(oldItem, std::move(newItemHdl), *parentHandle);
1220-
XDCHECK(oldItemHandle->isExclusive());
1221+
XDCHECK(oldItemHandle->isMoving());
12211222
XDCHECK(!oldItemHandle->isInMMContainer());
12221223

12231224
return true;
@@ -1246,7 +1247,7 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12461247
: toRecycle;
12471248

12481249
// make sure no other thead is evicting the item
1249-
if (candidate->getRefCount() != 0 || !candidate->markExclusive()) {
1250+
if (candidate->getRefCount() != 0 || !candidate->markMoving()) {
12501251
++itr;
12511252
continue;
12521253
}
@@ -1261,11 +1262,11 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12611262
? advanceIteratorAndTryEvictChainedItem(itr)
12621263
: advanceIteratorAndTryEvictRegularItem(mmContainer, itr);
12631264
evictionSuccessful = toReleaseHandle != nullptr;
1264-
// destroy toReleseHandle. The item won't be released to allocator
1265-
// since we marked it as exclusive.
1265+
// destroy toReleaseHandle. The item won't be released to allocator
1266+
// since we marked for eviction.
12661267
}
12671268

1268-
const auto ref = candidate->unmarkExclusive();
1269+
const auto ref = candidate->unmarkMoving();
12691270
if (ref == 0u) {
12701271
// Invalidate iterator since later on we may use this mmContainer
12711272
// again, which cannot be done unless we drop this iterator
@@ -2352,7 +2353,7 @@ void CacheAllocator<CacheTrait>::releaseSlabImpl(
23522353
// Need to mark an item for release before proceeding
23532354
// If we can't mark as moving, it means the item is already freed
23542355
const bool isAlreadyFreed =
2355-
!markExclusiveForSlabRelease(releaseContext, alloc, throttler);
2356+
!markMovingForSlabRelease(releaseContext, alloc, throttler);
23562357
if (isAlreadyFreed) {
23572358
continue;
23582359
}
@@ -2397,8 +2398,8 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
23972398
stats_.numMoveAttempts.inc();
23982399

23992400
// Nothing to move and the key is likely also bogus for chained items.
2400-
if (oldItem.isOnlyExclusive()) {
2401-
oldItem.unmarkExclusive();
2401+
if (oldItem.isOnlyMoving()) {
2402+
oldItem.unmarkMoving();
24022403
const auto res =
24032404
releaseBackToAllocator(oldItem, RemoveContext::kNormal, false);
24042405
XDCHECK(res == ReleaseRes::kReleased);
@@ -2437,7 +2438,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
24372438
// that's identical to this one to replace it. Here we just need to wait
24382439
// until all users have dropped the item handles before we can proceed.
24392440
startTime = util::getCurrentTimeSec();
2440-
while (!oldItem.isOnlyExclusive()) {
2441+
while (!oldItem.isOnlyMoving()) {
24412442
throttleWith(throttler, [&] {
24422443
XLOGF(WARN,
24432444
"Spent {} seconds, slab release still waiting for refcount to "
@@ -2491,8 +2492,8 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
24912492
return {};
24922493
}
24932494

2494-
// Set up the destination for the move. Since oldChainedItem would have
2495-
// the exclusive bit set, it won't be picked for eviction.
2495+
// Set up the destination for the move. Since oldChainedItem would be
2496+
// marked as moving, it won't be picked for eviction.
24962497
auto newItemHdl =
24972498
allocateChainedItemInternal(parentHandle, oldChainedItem.getSize());
24982499
if (!newItemHdl) {
@@ -2544,7 +2545,7 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
25442545
// item is still valid.
25452546
const std::string parentKey =
25462547
oldItem.asChainedItem().getParentItem(compressor_).getKey().str();
2547-
if (oldItem.isOnlyExclusive()) {
2548+
if (oldItem.isOnlyMoving()) {
25482549
// If chained item no longer has a refcount, its parent is already
25492550
// being released, so we abort this try to moving.
25502551
return false;
@@ -2574,10 +2575,10 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
25742575
stats_.numEvictionAttempts.inc();
25752576

25762577
// if the item is already in a state where only the exclusive bit is set,
2577-
// nothing needs to be done. We simply need to unmark exclusive bit and free
2578+
// nothing needs to be done. We simply need to call unmarkMoving and free
25782579
// the item.
2579-
if (item.isOnlyExclusive()) {
2580-
item.unmarkExclusive();
2580+
if (item.isOnlyMoving()) {
2581+
item.unmarkMoving();
25812582
const auto res =
25822583
releaseBackToAllocator(item, RemoveContext::kNormal, false);
25832584
XDCHECK(ReleaseRes::kReleased == res);
@@ -2608,7 +2609,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26082609
stats_.numEvictionSuccesses.inc();
26092610

26102611
// we have the last handle. no longer need to hold on to the exclusive bit
2611-
item.unmarkExclusive();
2612+
item.unmarkMoving();
26122613

26132614
// manually decrement the refcount to call releaseBackToAllocator
26142615
const auto ref = decRef(*owningHandle);
@@ -2620,7 +2621,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26202621
}
26212622

26222623
if (shutDownInProgress_) {
2623-
item.unmarkExclusive();
2624+
item.unmarkMoving();
26242625
allocator_->abortSlabRelease(ctx);
26252626
throw exception::SlabReleaseAborted(
26262627
folly::sformat("Slab Release aborted while trying to evict"
@@ -2766,9 +2767,9 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
27662767
template <typename CacheTrait>
27672768
typename CacheAllocator<CacheTrait>::WriteHandle
27682769
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
2769-
XDCHECK(item.isExclusive());
2770+
XDCHECK(item.isMoving());
27702771

2771-
if (item.isOnlyExclusive()) {
2772+
if (item.isOnlyMoving()) {
27722773
return WriteHandle{};
27732774
}
27742775

@@ -2780,7 +2781,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
27802781

27812782
// We remove the item from both access and mm containers. It doesn't matter
27822783
// if someone else calls remove on the item at this moment, the item cannot
2783-
// be freed as long as we have the exclusive bit set.
2784+
// be freed as long as it's marked for eviction.
27842785
auto handle = accessContainer_->removeIf(item, std::move(predicate));
27852786

27862787
if (!handle) {
@@ -2804,7 +2805,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
28042805
template <typename CacheTrait>
28052806
typename CacheAllocator<CacheTrait>::WriteHandle
28062807
CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
2807-
XDCHECK(child.isExclusive());
2808+
XDCHECK(child.isMoving());
28082809

28092810
// We have the child marked as moving, but dont know anything about the
28102811
// state of the parent. Unlike the case of regular eviction where we are
@@ -2826,7 +2827,7 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28262827
// check if the child is still in mmContainer and the expected parent is
28272828
// valid under the chained item lock.
28282829
if (expectedParent.getKey() != parentKey || !child.isInMMContainer() ||
2829-
child.isOnlyExclusive() ||
2830+
child.isOnlyMoving() ||
28302831
&expectedParent != &child.getParentItem(compressor_) ||
28312832
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
28322833
return {};
@@ -2881,14 +2882,14 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28812882

28822883
// In case someone else had removed this chained item from its parent by now
28832884
// So we check again to see if it has been unlinked from its parent
2884-
if (!child.isInMMContainer() || child.isOnlyExclusive()) {
2885+
if (!child.isInMMContainer() || child.isOnlyMoving()) {
28852886
return {};
28862887
}
28872888

28882889
// check after removing from the MMContainer that the parent is still not
28892890
// being marked as moving. If parent is moving, it will release the child
28902891
// item and we will wait for that.
2891-
if (parentHandle->isExclusive()) {
2892+
if (parentHandle->isMoving()) {
28922893
return {};
28932894
}
28942895

@@ -2921,7 +2922,7 @@ bool CacheAllocator<CacheTrait>::removeIfExpired(const ReadHandle& handle) {
29212922
}
29222923

29232924
template <typename CacheTrait>
2924-
bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
2925+
bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
29252926
const SlabReleaseContext& ctx, void* alloc, util::Throttler& throttler) {
29262927
// MemoryAllocator::processAllocForRelease will execute the callback
29272928
// if the item is not already free. So there are three outcomes here:
@@ -2940,7 +2941,7 @@ bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
29402941
// Since this callback is executed, the item is not yet freed
29412942
itemFreed = false;
29422943
Item* item = static_cast<Item*>(memory);
2943-
if (item->markExclusive()) {
2944+
if (item->markMoving()) {
29442945
markedMoving = true;
29452946
}
29462947
};

cachelib/allocator/CacheAllocator.h

+4-4
Original file line numberDiff line numberDiff line change
@@ -1756,9 +1756,9 @@ class CacheAllocator : public CacheBase {
17561756

17571757
// @return true when successfully marked as moving,
17581758
// fasle when this item has already been freed
1759-
bool markExclusiveForSlabRelease(const SlabReleaseContext& ctx,
1760-
void* alloc,
1761-
util::Throttler& throttler);
1759+
bool markMovingForSlabRelease(const SlabReleaseContext& ctx,
1760+
void* alloc,
1761+
util::Throttler& throttler);
17621762

17631763
// "Move" (by copying) the content in this item to another memory
17641764
// location by invoking the move callback.
@@ -1936,7 +1936,7 @@ class CacheAllocator : public CacheBase {
19361936
}
19371937

19381938
static bool parentEvictForSlabReleasePredicate(const Item& item) {
1939-
return item.getRefCount() == 1 && !item.isExclusive();
1939+
return item.getRefCount() == 1 && !item.isMoving();
19401940
}
19411941

19421942
std::unique_ptr<Deserializer> createDeserializer();

cachelib/allocator/CacheItem-inl.h

+40-16
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,16 @@ std::string CacheItem<CacheTrait>::toString() const {
148148
return folly::sformat(
149149
"item: "
150150
"memory={}:raw-ref={}:size={}:key={}:hex-key={}:"
151-
"isInMMContainer={}:isAccessible={}:isExclusive={}:references={}:ctime="
151+
"isInMMContainer={}:isAccessible={}:isMarkedForEviction={}:"
152+
"isMoving={}:references={}:ctime="
152153
"{}:"
153154
"expTime={}:updateTime={}:isNvmClean={}:isNvmEvicted={}:hasChainedItem="
154155
"{}",
155156
this, getRefCountAndFlagsRaw(), getSize(),
156157
folly::humanify(getKey().str()), folly::hexlify(getKey()),
157-
isInMMContainer(), isAccessible(), isExclusive(), getRefCount(),
158-
getCreationTime(), getExpiryTime(), getLastAccessTime(), isNvmClean(),
159-
isNvmEvicted(), hasChainedItem());
158+
isInMMContainer(), isAccessible(), isMarkedForEviction(), isMoving(),
159+
getRefCount(), getCreationTime(), getExpiryTime(), getLastAccessTime(),
160+
isNvmClean(), isNvmEvicted(), hasChainedItem());
160161
}
161162
}
162163

@@ -217,23 +218,43 @@ bool CacheItem<CacheTrait>::isInMMContainer() const noexcept {
217218
}
218219

219220
template <typename CacheTrait>
220-
bool CacheItem<CacheTrait>::markExclusive() noexcept {
221-
return ref_.markExclusive();
221+
bool CacheItem<CacheTrait>::markForEviction() noexcept {
222+
return ref_.markForEviction();
222223
}
223224

224225
template <typename CacheTrait>
225-
RefcountWithFlags::Value CacheItem<CacheTrait>::unmarkExclusive() noexcept {
226-
return ref_.unmarkExclusive();
226+
RefcountWithFlags::Value CacheItem<CacheTrait>::unmarkForEviction() noexcept {
227+
return ref_.unmarkForEviction();
227228
}
228229

229230
template <typename CacheTrait>
230-
bool CacheItem<CacheTrait>::isExclusive() const noexcept {
231-
return ref_.isExclusive();
231+
bool CacheItem<CacheTrait>::isMarkedForEviction() const noexcept {
232+
return ref_.isMarkedForEviction();
232233
}
233234

234235
template <typename CacheTrait>
235-
bool CacheItem<CacheTrait>::isOnlyExclusive() const noexcept {
236-
return ref_.isOnlyExclusive();
236+
bool CacheItem<CacheTrait>::markForEvictionWhenMoving() {
237+
return ref_.markForEvictionWhenMoving();
238+
}
239+
240+
template <typename CacheTrait>
241+
bool CacheItem<CacheTrait>::markMoving() {
242+
return ref_.markMoving();
243+
}
244+
245+
template <typename CacheTrait>
246+
RefcountWithFlags::Value CacheItem<CacheTrait>::unmarkMoving() noexcept {
247+
return ref_.unmarkMoving();
248+
}
249+
250+
template <typename CacheTrait>
251+
bool CacheItem<CacheTrait>::isMoving() const noexcept {
252+
return ref_.isMoving();
253+
}
254+
255+
template <typename CacheTrait>
256+
bool CacheItem<CacheTrait>::isOnlyMoving() const noexcept {
257+
return ref_.isOnlyMoving();
237258
}
238259

239260
template <typename CacheTrait>
@@ -335,7 +356,8 @@ bool CacheItem<CacheTrait>::updateExpiryTime(uint32_t expiryTimeSecs) noexcept {
335356
// check for moving to make sure we are not updating the expiry time while at
336357
// the same time re-allocating the item with the old state of the expiry time
337358
// in moveRegularItem(). See D6852328
338-
if (isExclusive() || !isInMMContainer() || isChainedItem()) {
359+
if (isMoving() || isMarkedForEviction() || !isInMMContainer() ||
360+
isChainedItem()) {
339361
return false;
340362
}
341363
// attempt to atomically update the value of expiryTime
@@ -451,12 +473,14 @@ std::string CacheChainedItem<CacheTrait>::toString() const {
451473
return folly::sformat(
452474
"chained item: "
453475
"memory={}:raw-ref={}:size={}:parent-compressed-ptr={}:"
454-
"isInMMContainer={}:isAccessible={}:isExclusive={}:references={}:ctime={}"
476+
"isInMMContainer={}:isAccessible={}:isMarkedForEviction={}:"
477+
"isMoving={}:references={}:ctime={}"
455478
":"
456479
"expTime={}:updateTime={}",
457480
this, Item::getRefCountAndFlagsRaw(), Item::getSize(), cPtr.getRaw(),
458-
Item::isInMMContainer(), Item::isAccessible(), Item::isExclusive(),
459-
Item::getRefCount(), Item::getCreationTime(), Item::getExpiryTime(),
481+
Item::isInMMContainer(), Item::isAccessible(),
482+
Item::isMarkedForEviction(), Item::isMoving(), Item::getRefCount(),
483+
Item::getCreationTime(), Item::getExpiryTime(),
460484
Item::getLastAccessTime());
461485
}
462486

0 commit comments

Comments
 (0)