Skip to content

Commit b9b7db3

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 b9b7db3

File tree

7 files changed

+450
-182
lines changed

7 files changed

+450
-182
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 48 additions & 40 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,8 +898,12 @@ CacheAllocator<CacheTrait>::acquire(Item* it) {
894898

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

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

901909
template <typename CacheTrait>
@@ -1170,7 +1178,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
11701178

11711179
// This item has been unlinked from its parent and we're the only
11721180
// owner of it, so we're done here
1173-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1181+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
11741182
return false;
11751183
}
11761184

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

12021210
// In case someone else had removed this chained item from its parent by now
12031211
// So we check again to see if the it has been unlinked from its parent
1204-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1212+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
12051213
return false;
12061214
}
12071215

@@ -1217,7 +1225,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12171225
// parent's chain and the MMContainer.
12181226
auto oldItemHandle =
12191227
replaceChainedItemLocked(oldItem, std::move(newItemHdl), *parentHandle);
1220-
XDCHECK(oldItemHandle->isExclusive());
1228+
XDCHECK(oldItemHandle->isMoving());
12211229
XDCHECK(!oldItemHandle->isInMMContainer());
12221230

12231231
return true;
@@ -1246,7 +1254,7 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12461254
: toRecycle;
12471255

12481256
// make sure no other thead is evicting the item
1249-
if (candidate->getRefCount() != 0 || !candidate->markExclusive()) {
1257+
if (candidate->getRefCount() != 0 || !candidate->markMoving()) {
12501258
++itr;
12511259
continue;
12521260
}
@@ -1261,11 +1269,11 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12611269
? advanceIteratorAndTryEvictChainedItem(itr)
12621270
: advanceIteratorAndTryEvictRegularItem(mmContainer, itr);
12631271
evictionSuccessful = toReleaseHandle != nullptr;
1264-
// destroy toReleseHandle. The item won't be released to allocator
1265-
// since we marked it as exclusive.
1272+
// destroy toReleaseHandle. The item won't be released to allocator
1273+
// since we marked for eviction.
12661274
}
12671275

1268-
const auto ref = candidate->unmarkExclusive();
1276+
const auto ref = candidate->unmarkMoving();
12691277
if (ref == 0u) {
12701278
// Invalidate iterator since later on we may use this mmContainer
12711279
// again, which cannot be done unless we drop this iterator
@@ -2352,7 +2360,7 @@ void CacheAllocator<CacheTrait>::releaseSlabImpl(
23522360
// Need to mark an item for release before proceeding
23532361
// If we can't mark as moving, it means the item is already freed
23542362
const bool isAlreadyFreed =
2355-
!markExclusiveForSlabRelease(releaseContext, alloc, throttler);
2363+
!markMovingForSlabRelease(releaseContext, alloc, throttler);
23562364
if (isAlreadyFreed) {
23572365
continue;
23582366
}
@@ -2397,8 +2405,8 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
23972405
stats_.numMoveAttempts.inc();
23982406

23992407
// Nothing to move and the key is likely also bogus for chained items.
2400-
if (oldItem.isOnlyExclusive()) {
2401-
oldItem.unmarkExclusive();
2408+
if (oldItem.isOnlyMoving()) {
2409+
oldItem.unmarkMoving();
24022410
const auto res =
24032411
releaseBackToAllocator(oldItem, RemoveContext::kNormal, false);
24042412
XDCHECK(res == ReleaseRes::kReleased);
@@ -2437,7 +2445,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
24372445
// that's identical to this one to replace it. Here we just need to wait
24382446
// until all users have dropped the item handles before we can proceed.
24392447
startTime = util::getCurrentTimeSec();
2440-
while (!oldItem.isOnlyExclusive()) {
2448+
while (!oldItem.isOnlyMoving()) {
24412449
throttleWith(throttler, [&] {
24422450
XLOGF(WARN,
24432451
"Spent {} seconds, slab release still waiting for refcount to "
@@ -2491,8 +2499,8 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
24912499
return {};
24922500
}
24932501

2494-
// Set up the destination for the move. Since oldChainedItem would have
2495-
// the exclusive bit set, it won't be picked for eviction.
2502+
// Set up the destination for the move. Since oldChainedItem would be
2503+
// marked as moving, it won't be picked for eviction.
24962504
auto newItemHdl =
24972505
allocateChainedItemInternal(parentHandle, oldChainedItem.getSize());
24982506
if (!newItemHdl) {
@@ -2544,7 +2552,7 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
25442552
// item is still valid.
25452553
const std::string parentKey =
25462554
oldItem.asChainedItem().getParentItem(compressor_).getKey().str();
2547-
if (oldItem.isOnlyExclusive()) {
2555+
if (oldItem.isOnlyMoving()) {
25482556
// If chained item no longer has a refcount, its parent is already
25492557
// being released, so we abort this try to moving.
25502558
return false;
@@ -2574,10 +2582,10 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
25742582
stats_.numEvictionAttempts.inc();
25752583

25762584
// 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
2585+
// nothing needs to be done. We simply need to call unmarkMoving and free
25782586
// the item.
2579-
if (item.isOnlyExclusive()) {
2580-
item.unmarkExclusive();
2587+
if (item.isOnlyMoving()) {
2588+
item.unmarkMoving();
25812589
const auto res =
25822590
releaseBackToAllocator(item, RemoveContext::kNormal, false);
25832591
XDCHECK(ReleaseRes::kReleased == res);
@@ -2608,7 +2616,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26082616
stats_.numEvictionSuccesses.inc();
26092617

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

26132621
// manually decrement the refcount to call releaseBackToAllocator
26142622
const auto ref = decRef(*owningHandle);
@@ -2620,7 +2628,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26202628
}
26212629

26222630
if (shutDownInProgress_) {
2623-
item.unmarkExclusive();
2631+
item.unmarkMoving();
26242632
allocator_->abortSlabRelease(ctx);
26252633
throw exception::SlabReleaseAborted(
26262634
folly::sformat("Slab Release aborted while trying to evict"
@@ -2766,9 +2774,9 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
27662774
template <typename CacheTrait>
27672775
typename CacheAllocator<CacheTrait>::WriteHandle
27682776
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
2769-
XDCHECK(item.isExclusive());
2777+
XDCHECK(item.isMoving());
27702778

2771-
if (item.isOnlyExclusive()) {
2779+
if (item.isOnlyMoving()) {
27722780
return WriteHandle{};
27732781
}
27742782

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

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

27862794
if (!handle) {
@@ -2804,7 +2812,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
28042812
template <typename CacheTrait>
28052813
typename CacheAllocator<CacheTrait>::WriteHandle
28062814
CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
2807-
XDCHECK(child.isExclusive());
2815+
XDCHECK(child.isMoving());
28082816

28092817
// We have the child marked as moving, but dont know anything about the
28102818
// state of the parent. Unlike the case of regular eviction where we are
@@ -2826,7 +2834,7 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28262834
// check if the child is still in mmContainer and the expected parent is
28272835
// valid under the chained item lock.
28282836
if (expectedParent.getKey() != parentKey || !child.isInMMContainer() ||
2829-
child.isOnlyExclusive() ||
2837+
child.isOnlyMoving() ||
28302838
&expectedParent != &child.getParentItem(compressor_) ||
28312839
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
28322840
return {};
@@ -2881,14 +2889,14 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28812889

28822890
// In case someone else had removed this chained item from its parent by now
28832891
// So we check again to see if it has been unlinked from its parent
2884-
if (!child.isInMMContainer() || child.isOnlyExclusive()) {
2892+
if (!child.isInMMContainer() || child.isOnlyMoving()) {
28852893
return {};
28862894
}
28872895

28882896
// check after removing from the MMContainer that the parent is still not
28892897
// being marked as moving. If parent is moving, it will release the child
28902898
// item and we will wait for that.
2891-
if (parentHandle->isExclusive()) {
2899+
if (parentHandle->isMoving()) {
28922900
return {};
28932901
}
28942902

@@ -2921,7 +2929,7 @@ bool CacheAllocator<CacheTrait>::removeIfExpired(const ReadHandle& handle) {
29212929
}
29222930

29232931
template <typename CacheTrait>
2924-
bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
2932+
bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
29252933
const SlabReleaseContext& ctx, void* alloc, util::Throttler& throttler) {
29262934
// MemoryAllocator::processAllocForRelease will execute the callback
29272935
// if the item is not already free. So there are three outcomes here:
@@ -2940,7 +2948,7 @@ bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
29402948
// Since this callback is executed, the item is not yet freed
29412949
itemFreed = false;
29422950
Item* item = static_cast<Item*>(memory);
2943-
if (item->markExclusive()) {
2951+
if (item->markMoving()) {
29442952
markedMoving = true;
29452953
}
29462954
};

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)