Skip to content

Commit b6e7301

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 b6e7301

File tree

7 files changed

+445
-181
lines changed

7 files changed

+445
-181
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 48 additions & 39 deletions
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);
@@ -872,9 +873,12 @@ CacheAllocator<CacheTrait>::releaseBackToAllocator(Item& it,
872873
}
873874

874875
template <typename CacheTrait>
875-
void CacheAllocator<CacheTrait>::incRef(Item& it) {
876-
it.incRef();
877-
++handleCount_.tlStats();
876+
bool CacheAllocator<CacheTrait>::incRef(Item& it) {
877+
if (it.incRef()) {
878+
++handleCount_.tlStats();
879+
return true;
880+
}
881+
return false;
878882
}
879883

880884
template <typename CacheTrait>
@@ -894,7 +898,12 @@ CacheAllocator<CacheTrait>::acquire(Item* it) {
894898

895899
SCOPE_FAIL { stats_.numRefcountOverflow.inc(); };
896900

897-
incRef(*it);
901+
if (LIKELY(incRef(*it))) {
902+
return WriteHandle{it, *this};
903+
} else {
904+
// item is being evicted
905+
return WriteHandle{};
906+
}
898907
return WriteHandle{it, *this};
899908
}
900909

@@ -1170,7 +1179,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
11701179

11711180
// This item has been unlinked from its parent and we're the only
11721181
// owner of it, so we're done here
1173-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1182+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
11741183
return false;
11751184
}
11761185

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

12021211
// In case someone else had removed this chained item from its parent by now
12031212
// So we check again to see if the it has been unlinked from its parent
1204-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1213+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
12051214
return false;
12061215
}
12071216

@@ -1217,7 +1226,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12171226
// parent's chain and the MMContainer.
12181227
auto oldItemHandle =
12191228
replaceChainedItemLocked(oldItem, std::move(newItemHdl), *parentHandle);
1220-
XDCHECK(oldItemHandle->isExclusive());
1229+
XDCHECK(oldItemHandle->isMoving());
12211230
XDCHECK(!oldItemHandle->isInMMContainer());
12221231

12231232
return true;
@@ -1246,7 +1255,7 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12461255
: toRecycle;
12471256

12481257
// make sure no other thead is evicting the item
1249-
if (candidate->getRefCount() != 0 || !candidate->markExclusive()) {
1258+
if (candidate->getRefCount() != 0 || !candidate->markMoving()) {
12501259
++itr;
12511260
continue;
12521261
}
@@ -1261,11 +1270,11 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12611270
? advanceIteratorAndTryEvictChainedItem(itr)
12621271
: advanceIteratorAndTryEvictRegularItem(mmContainer, itr);
12631272
evictionSuccessful = toReleaseHandle != nullptr;
1264-
// destroy toReleseHandle. The item won't be released to allocator
1265-
// since we marked it as exclusive.
1273+
// destroy toReleaseHandle. The item won't be released to allocator
1274+
// since we marked for eviction.
12661275
}
12671276

1268-
const auto ref = candidate->unmarkExclusive();
1277+
const auto ref = candidate->unmarkMoving();
12691278
if (ref == 0u) {
12701279
// Invalidate iterator since later on we may use this mmContainer
12711280
// again, which cannot be done unless we drop this iterator
@@ -2352,7 +2361,7 @@ void CacheAllocator<CacheTrait>::releaseSlabImpl(
23522361
// Need to mark an item for release before proceeding
23532362
// If we can't mark as moving, it means the item is already freed
23542363
const bool isAlreadyFreed =
2355-
!markExclusiveForSlabRelease(releaseContext, alloc, throttler);
2364+
!markMovingForSlabRelease(releaseContext, alloc, throttler);
23562365
if (isAlreadyFreed) {
23572366
continue;
23582367
}
@@ -2397,8 +2406,8 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
23972406
stats_.numMoveAttempts.inc();
23982407

23992408
// Nothing to move and the key is likely also bogus for chained items.
2400-
if (oldItem.isOnlyExclusive()) {
2401-
oldItem.unmarkExclusive();
2409+
if (oldItem.isOnlyMoving()) {
2410+
oldItem.unmarkMoving();
24022411
const auto res =
24032412
releaseBackToAllocator(oldItem, RemoveContext::kNormal, false);
24042413
XDCHECK(res == ReleaseRes::kReleased);
@@ -2437,7 +2446,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
24372446
// that's identical to this one to replace it. Here we just need to wait
24382447
// until all users have dropped the item handles before we can proceed.
24392448
startTime = util::getCurrentTimeSec();
2440-
while (!oldItem.isOnlyExclusive()) {
2449+
while (!oldItem.isOnlyMoving()) {
24412450
throttleWith(throttler, [&] {
24422451
XLOGF(WARN,
24432452
"Spent {} seconds, slab release still waiting for refcount to "
@@ -2491,8 +2500,8 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
24912500
return {};
24922501
}
24932502

2494-
// Set up the destination for the move. Since oldChainedItem would have
2495-
// the exclusive bit set, it won't be picked for eviction.
2503+
// Set up the destination for the move. Since oldChainedItem would be
2504+
// marked as moving, it won't be picked for eviction.
24962505
auto newItemHdl =
24972506
allocateChainedItemInternal(parentHandle, oldChainedItem.getSize());
24982507
if (!newItemHdl) {
@@ -2544,7 +2553,7 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
25442553
// item is still valid.
25452554
const std::string parentKey =
25462555
oldItem.asChainedItem().getParentItem(compressor_).getKey().str();
2547-
if (oldItem.isOnlyExclusive()) {
2556+
if (oldItem.isOnlyMoving()) {
25482557
// If chained item no longer has a refcount, its parent is already
25492558
// being released, so we abort this try to moving.
25502559
return false;
@@ -2574,10 +2583,10 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
25742583
stats_.numEvictionAttempts.inc();
25752584

25762585
// 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
2586+
// nothing needs to be done. We simply need to call unmarkMoving and free
25782587
// the item.
2579-
if (item.isOnlyExclusive()) {
2580-
item.unmarkExclusive();
2588+
if (item.isOnlyMoving()) {
2589+
item.unmarkMoving();
25812590
const auto res =
25822591
releaseBackToAllocator(item, RemoveContext::kNormal, false);
25832592
XDCHECK(ReleaseRes::kReleased == res);
@@ -2608,7 +2617,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26082617
stats_.numEvictionSuccesses.inc();
26092618

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

26132622
// manually decrement the refcount to call releaseBackToAllocator
26142623
const auto ref = decRef(*owningHandle);
@@ -2620,7 +2629,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26202629
}
26212630

26222631
if (shutDownInProgress_) {
2623-
item.unmarkExclusive();
2632+
item.unmarkMoving();
26242633
allocator_->abortSlabRelease(ctx);
26252634
throw exception::SlabReleaseAborted(
26262635
folly::sformat("Slab Release aborted while trying to evict"
@@ -2766,9 +2775,9 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
27662775
template <typename CacheTrait>
27672776
typename CacheAllocator<CacheTrait>::WriteHandle
27682777
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
2769-
XDCHECK(item.isExclusive());
2778+
XDCHECK(item.isMoving());
27702779

2771-
if (item.isOnlyExclusive()) {
2780+
if (item.isOnlyMoving()) {
27722781
return WriteHandle{};
27732782
}
27742783

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

27812790
// We remove the item from both access and mm containers. It doesn't matter
27822791
// 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.
2792+
// be freed as long as it's marked for eviction.
27842793
auto handle = accessContainer_->removeIf(item, std::move(predicate));
27852794

27862795
if (!handle) {
@@ -2804,7 +2813,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
28042813
template <typename CacheTrait>
28052814
typename CacheAllocator<CacheTrait>::WriteHandle
28062815
CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
2807-
XDCHECK(child.isExclusive());
2816+
XDCHECK(child.isMoving());
28082817

28092818
// We have the child marked as moving, but dont know anything about the
28102819
// state of the parent. Unlike the case of regular eviction where we are
@@ -2826,7 +2835,7 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28262835
// check if the child is still in mmContainer and the expected parent is
28272836
// valid under the chained item lock.
28282837
if (expectedParent.getKey() != parentKey || !child.isInMMContainer() ||
2829-
child.isOnlyExclusive() ||
2838+
child.isOnlyMoving() ||
28302839
&expectedParent != &child.getParentItem(compressor_) ||
28312840
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
28322841
return {};
@@ -2881,14 +2890,14 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28812890

28822891
// In case someone else had removed this chained item from its parent by now
28832892
// So we check again to see if it has been unlinked from its parent
2884-
if (!child.isInMMContainer() || child.isOnlyExclusive()) {
2893+
if (!child.isInMMContainer() || child.isOnlyMoving()) {
28852894
return {};
28862895
}
28872896

28882897
// check after removing from the MMContainer that the parent is still not
28892898
// being marked as moving. If parent is moving, it will release the child
28902899
// item and we will wait for that.
2891-
if (parentHandle->isExclusive()) {
2900+
if (parentHandle->isMoving()) {
28922901
return {};
28932902
}
28942903

@@ -2921,7 +2930,7 @@ bool CacheAllocator<CacheTrait>::removeIfExpired(const ReadHandle& handle) {
29212930
}
29222931

29232932
template <typename CacheTrait>
2924-
bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
2933+
bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
29252934
const SlabReleaseContext& ctx, void* alloc, util::Throttler& throttler) {
29262935
// MemoryAllocator::processAllocForRelease will execute the callback
29272936
// if the item is not already free. So there are three outcomes here:
@@ -2940,7 +2949,7 @@ bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
29402949
// Since this callback is executed, the item is not yet freed
29412950
itemFreed = false;
29422951
Item* item = static_cast<Item*>(memory);
2943-
if (item->markExclusive()) {
2952+
if (item->markMoving()) {
29442953
markedMoving = true;
29452954
}
29462955
};

cachelib/allocator/CacheAllocator.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,7 @@ class CacheAllocator : public CacheBase {
13081308

13091309
private:
13101310
// wrapper around Item's refcount and active handle tracking
1311-
FOLLY_ALWAYS_INLINE void incRef(Item& it);
1311+
FOLLY_ALWAYS_INLINE bool incRef(Item& it);
13121312
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef(Item& it);
13131313

13141314
// drops the refcount and if needed, frees the allocation back to the memory
@@ -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();

0 commit comments

Comments
 (0)