Skip to content

Commit 0bc9f66

Browse files
igchorfacebook-github-bot
authored andcommitted
Introduce 'markedForEviction' state for the Item. (#183)
Summary: 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. Pull Request resolved: #183 Test Plan: flatmemcache shadow result: It seems there is a slight latency regression in allocation (~1 micro second) https://fburl.com/ods/u3isn8b4 It's probably small enough to ignore. But we can come back if this starts showing up in large scale in production. Imported from GitHub, without a `Test Plan:` line. Reviewed By: therealgymmy Differential Revision: D42089974 Pulled By: haowu14 fbshipit-source-id: 90477faa9443080a7ddefa37a524965a04b6f084
1 parent 1e05a16 commit 0bc9f66

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
@@ -832,28 +832,29 @@ CacheAllocator<CacheTrait>::releaseBackToAllocator(Item& it,
832832

833833
removeFromMMContainer(*head);
834834

835-
// If this chained item is marked as exclusive, we will not free it.
836-
// We must capture the exclusive state before we do the decRef when
835+
// If this chained item is marked as moving, we will not free it.
836+
// We must capture the moving state before we do the decRef when
837837
// we know the item must still be valid
838-
const bool wasExclusive = head->isExclusive();
838+
const bool wasMoving = head->isMoving();
839+
XDCHECK(!head->isMarkedForEviction());
839840

840841
// Decref and check if we were the last reference. Now if the item
841-
// was marked exclusive, after decRef, it will be free to be released
842+
// was marked moving, after decRef, it will be free to be released
842843
// by slab release thread
843844
const auto childRef = head->decRef();
844845

845-
// If the item is already exclusive and we already decremented the
846+
// If the item is already moving and we already decremented the
846847
// refcount, we don't need to free this item. We'll let the slab
847848
// release thread take care of that
848-
if (!wasExclusive) {
849+
if (!wasMoving) {
849850
if (childRef != 0) {
850851
throw std::runtime_error(folly::sformat(
851852
"chained item refcount is not zero. We cannot proceed! "
852853
"Ref: {}, Chained Item: {}",
853854
childRef, head->toString()));
854855
}
855856

856-
// Item is not exclusive and refcount is 0, we can proceed to
857+
// Item is not moving and refcount is 0, we can proceed to
857858
// free it or recylce the memory
858859
if (head == toRecycle) {
859860
XDCHECK(ReleaseRes::kReleased != res);
@@ -881,9 +882,12 @@ CacheAllocator<CacheTrait>::releaseBackToAllocator(Item& it,
881882
}
882883

883884
template <typename CacheTrait>
884-
void CacheAllocator<CacheTrait>::incRef(Item& it) {
885-
it.incRef();
886-
++handleCount_.tlStats();
885+
bool CacheAllocator<CacheTrait>::incRef(Item& it) {
886+
if (it.incRef()) {
887+
++handleCount_.tlStats();
888+
return true;
889+
}
890+
return false;
887891
}
888892

889893
template <typename CacheTrait>
@@ -903,8 +907,12 @@ CacheAllocator<CacheTrait>::acquire(Item* it) {
903907

904908
SCOPE_FAIL { stats_.numRefcountOverflow.inc(); };
905909

906-
incRef(*it);
907-
return WriteHandle{it, *this};
910+
if (LIKELY(incRef(*it))) {
911+
return WriteHandle{it, *this};
912+
} else {
913+
// item is being evicted
914+
return WriteHandle{};
915+
}
908916
}
909917

910918
template <typename CacheTrait>
@@ -1179,7 +1187,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
11791187

11801188
// This item has been unlinked from its parent and we're the only
11811189
// owner of it, so we're done here
1182-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1190+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
11831191
return false;
11841192
}
11851193

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

12111219
// In case someone else had removed this chained item from its parent by now
12121220
// So we check again to see if the it has been unlinked from its parent
1213-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1221+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
12141222
return false;
12151223
}
12161224

@@ -1226,7 +1234,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12261234
// parent's chain and the MMContainer.
12271235
auto oldItemHandle =
12281236
replaceChainedItemLocked(oldItem, std::move(newItemHdl), *parentHandle);
1229-
XDCHECK(oldItemHandle->isExclusive());
1237+
XDCHECK(oldItemHandle->isMoving());
12301238
XDCHECK(!oldItemHandle->isInMMContainer());
12311239

12321240
return true;
@@ -1255,7 +1263,7 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12551263
: toRecycle;
12561264

12571265
// make sure no other thead is evicting the item
1258-
if (candidate->getRefCount() != 0 || !candidate->markExclusive()) {
1266+
if (candidate->getRefCount() != 0 || !candidate->markMoving()) {
12591267
++itr;
12601268
continue;
12611269
}
@@ -1270,11 +1278,11 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12701278
? advanceIteratorAndTryEvictChainedItem(itr)
12711279
: advanceIteratorAndTryEvictRegularItem(mmContainer, itr);
12721280
evictionSuccessful = toReleaseHandle != nullptr;
1273-
// destroy toReleseHandle. The item won't be released to allocator
1274-
// since we marked it as exclusive.
1281+
// destroy toReleaseHandle. The item won't be released to allocator
1282+
// since we marked for eviction.
12751283
}
12761284

1277-
const auto ref = candidate->unmarkExclusive();
1285+
const auto ref = candidate->unmarkMoving();
12781286
if (ref == 0u) {
12791287
// Invalidate iterator since later on we may use this mmContainer
12801288
// again, which cannot be done unless we drop this iterator
@@ -2361,7 +2369,7 @@ void CacheAllocator<CacheTrait>::releaseSlabImpl(
23612369
// Need to mark an item for release before proceeding
23622370
// If we can't mark as moving, it means the item is already freed
23632371
const bool isAlreadyFreed =
2364-
!markExclusiveForSlabRelease(releaseContext, alloc, throttler);
2372+
!markMovingForSlabRelease(releaseContext, alloc, throttler);
23652373
if (isAlreadyFreed) {
23662374
continue;
23672375
}
@@ -2406,8 +2414,8 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
24062414
stats_.numMoveAttempts.inc();
24072415

24082416
// Nothing to move and the key is likely also bogus for chained items.
2409-
if (oldItem.isOnlyExclusive()) {
2410-
oldItem.unmarkExclusive();
2417+
if (oldItem.isOnlyMoving()) {
2418+
oldItem.unmarkMoving();
24112419
const auto res =
24122420
releaseBackToAllocator(oldItem, RemoveContext::kNormal, false);
24132421
XDCHECK(res == ReleaseRes::kReleased);
@@ -2446,7 +2454,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
24462454
// that's identical to this one to replace it. Here we just need to wait
24472455
// until all users have dropped the item handles before we can proceed.
24482456
startTime = util::getCurrentTimeSec();
2449-
while (!oldItem.isOnlyExclusive()) {
2457+
while (!oldItem.isOnlyMoving()) {
24502458
throttleWith(throttler, [&] {
24512459
XLOGF(WARN,
24522460
"Spent {} seconds, slab release still waiting for refcount to "
@@ -2500,8 +2508,8 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
25002508
return {};
25012509
}
25022510

2503-
// Set up the destination for the move. Since oldChainedItem would have
2504-
// the exclusive bit set, it won't be picked for eviction.
2511+
// Set up the destination for the move. Since oldChainedItem would be
2512+
// marked as moving, it won't be picked for eviction.
25052513
auto newItemHdl =
25062514
allocateChainedItemInternal(parentHandle, oldChainedItem.getSize());
25072515
if (!newItemHdl) {
@@ -2553,7 +2561,7 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
25532561
// item is still valid.
25542562
const std::string parentKey =
25552563
oldItem.asChainedItem().getParentItem(compressor_).getKey().str();
2556-
if (oldItem.isOnlyExclusive()) {
2564+
if (oldItem.isOnlyMoving()) {
25572565
// If chained item no longer has a refcount, its parent is already
25582566
// being released, so we abort this try to moving.
25592567
return false;
@@ -2583,10 +2591,10 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
25832591
stats_.numEvictionAttempts.inc();
25842592

25852593
// if the item is already in a state where only the exclusive bit is set,
2586-
// nothing needs to be done. We simply need to unmark exclusive bit and free
2594+
// nothing needs to be done. We simply need to call unmarkMoving and free
25872595
// the item.
2588-
if (item.isOnlyExclusive()) {
2589-
item.unmarkExclusive();
2596+
if (item.isOnlyMoving()) {
2597+
item.unmarkMoving();
25902598
const auto res =
25912599
releaseBackToAllocator(item, RemoveContext::kNormal, false);
25922600
XDCHECK(ReleaseRes::kReleased == res);
@@ -2617,7 +2625,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26172625
stats_.numEvictionSuccesses.inc();
26182626

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

26222630
// manually decrement the refcount to call releaseBackToAllocator
26232631
const auto ref = decRef(*owningHandle);
@@ -2629,7 +2637,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26292637
}
26302638

26312639
if (shutDownInProgress_) {
2632-
item.unmarkExclusive();
2640+
item.unmarkMoving();
26332641
allocator_->abortSlabRelease(ctx);
26342642
throw exception::SlabReleaseAborted(
26352643
folly::sformat("Slab Release aborted while trying to evict"
@@ -2775,9 +2783,9 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
27752783
template <typename CacheTrait>
27762784
typename CacheAllocator<CacheTrait>::WriteHandle
27772785
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
2778-
XDCHECK(item.isExclusive());
2786+
XDCHECK(item.isMoving());
27792787

2780-
if (item.isOnlyExclusive()) {
2788+
if (item.isOnlyMoving()) {
27812789
return WriteHandle{};
27822790
}
27832791

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

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

27952803
if (!handle) {
@@ -2813,7 +2821,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
28132821
template <typename CacheTrait>
28142822
typename CacheAllocator<CacheTrait>::WriteHandle
28152823
CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
2816-
XDCHECK(child.isExclusive());
2824+
XDCHECK(child.isMoving());
28172825

28182826
// We have the child marked as moving, but dont know anything about the
28192827
// state of the parent. Unlike the case of regular eviction where we are
@@ -2835,7 +2843,7 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28352843
// check if the child is still in mmContainer and the expected parent is
28362844
// valid under the chained item lock.
28372845
if (expectedParent.getKey() != parentKey || !child.isInMMContainer() ||
2838-
child.isOnlyExclusive() ||
2846+
child.isOnlyMoving() ||
28392847
&expectedParent != &child.getParentItem(compressor_) ||
28402848
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
28412849
return {};
@@ -2890,14 +2898,14 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28902898

28912899
// In case someone else had removed this chained item from its parent by now
28922900
// So we check again to see if it has been unlinked from its parent
2893-
if (!child.isInMMContainer() || child.isOnlyExclusive()) {
2901+
if (!child.isInMMContainer() || child.isOnlyMoving()) {
28942902
return {};
28952903
}
28962904

28972905
// check after removing from the MMContainer that the parent is still not
28982906
// being marked as moving. If parent is moving, it will release the child
28992907
// item and we will wait for that.
2900-
if (parentHandle->isExclusive()) {
2908+
if (parentHandle->isMoving()) {
29012909
return {};
29022910
}
29032911

@@ -2930,7 +2938,7 @@ bool CacheAllocator<CacheTrait>::removeIfExpired(const ReadHandle& handle) {
29302938
}
29312939

29322940
template <typename CacheTrait>
2933-
bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
2941+
bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
29342942
const SlabReleaseContext& ctx, void* alloc, util::Throttler& throttler) {
29352943
// MemoryAllocator::processAllocForRelease will execute the callback
29362944
// if the item is not already free. So there are three outcomes here:
@@ -2949,7 +2957,7 @@ bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
29492957
// Since this callback is executed, the item is not yet freed
29502958
itemFreed = false;
29512959
Item* item = static_cast<Item*>(memory);
2952-
if (item->markExclusive()) {
2960+
if (item->markMoving()) {
29532961
markedMoving = true;
29542962
}
29552963
};

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.
@@ -1937,7 +1937,7 @@ class CacheAllocator : public CacheBase {
19371937
}
19381938

19391939
static bool parentEvictForSlabReleasePredicate(const Item& item) {
1940-
return item.getRefCount() == 1 && !item.isExclusive();
1940+
return item.getRefCount() == 1 && !item.isMoving();
19411941
}
19421942

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

0 commit comments

Comments
 (0)