Skip to content

Commit 470f563

Browse files
igchorbyrnedj
authored andcommitted
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 b791b77 commit 470f563

File tree

7 files changed

+434
-176
lines changed

7 files changed

+434
-176
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 36 additions & 35 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);
@@ -1179,7 +1180,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
11791180

11801181
// This item has been unlinked from its parent and we're the only
11811182
// owner of it, so we're done here
1182-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1183+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
11831184
return false;
11841185
}
11851186

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

12111212
// In case someone else had removed this chained item from its parent by now
12121213
// So we check again to see if the it has been unlinked from its parent
1213-
if (!oldItem.isInMMContainer() || oldItem.isOnlyExclusive()) {
1214+
if (!oldItem.isInMMContainer() || oldItem.isOnlyMoving()) {
12141215
return false;
12151216
}
12161217

@@ -1226,7 +1227,7 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12261227
// parent's chain and the MMContainer.
12271228
auto oldItemHandle =
12281229
replaceChainedItemLocked(oldItem, std::move(newItemHdl), *parentHandle);
1229-
XDCHECK(oldItemHandle->isExclusive());
1230+
XDCHECK(oldItemHandle->isMoving());
12301231
XDCHECK(!oldItemHandle->isInMMContainer());
12311232

12321233
return true;
@@ -1255,7 +1256,7 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12551256
: toRecycle;
12561257

12571258
// make sure no other thead is evicting the item
1258-
if (candidate->getRefCount() != 0 || !candidate->markExclusive()) {
1259+
if (candidate->getRefCount() != 0 || !candidate->markMoving()) {
12591260
++itr;
12601261
continue;
12611262
}
@@ -1270,11 +1271,11 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12701271
? advanceIteratorAndTryEvictChainedItem(itr)
12711272
: advanceIteratorAndTryEvictRegularItem(mmContainer, itr);
12721273
evictionSuccessful = toReleaseHandle != nullptr;
1273-
// destroy toReleseHandle. The item won't be released to allocator
1274-
// since we marked it as exclusive.
1274+
// destroy toReleaseHandle. The item won't be released to allocator
1275+
// since we marked for eviction.
12751276
}
12761277

1277-
const auto ref = candidate->unmarkExclusive();
1278+
const auto ref = candidate->unmarkMoving();
12781279
if (ref == 0u) {
12791280
// Invalidate iterator since later on we may use this mmContainer
12801281
// again, which cannot be done unless we drop this iterator
@@ -2361,7 +2362,7 @@ void CacheAllocator<CacheTrait>::releaseSlabImpl(
23612362
// Need to mark an item for release before proceeding
23622363
// If we can't mark as moving, it means the item is already freed
23632364
const bool isAlreadyFreed =
2364-
!markExclusiveForSlabRelease(releaseContext, alloc, throttler);
2365+
!markMovingForSlabRelease(releaseContext, alloc, throttler);
23652366
if (isAlreadyFreed) {
23662367
continue;
23672368
}
@@ -2406,8 +2407,8 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
24062407
stats_.numMoveAttempts.inc();
24072408

24082409
// Nothing to move and the key is likely also bogus for chained items.
2409-
if (oldItem.isOnlyExclusive()) {
2410-
oldItem.unmarkExclusive();
2410+
if (oldItem.isOnlyMoving()) {
2411+
oldItem.unmarkMoving();
24112412
const auto res =
24122413
releaseBackToAllocator(oldItem, RemoveContext::kNormal, false);
24132414
XDCHECK(res == ReleaseRes::kReleased);
@@ -2446,7 +2447,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
24462447
// that's identical to this one to replace it. Here we just need to wait
24472448
// until all users have dropped the item handles before we can proceed.
24482449
startTime = util::getCurrentTimeSec();
2449-
while (!oldItem.isOnlyExclusive()) {
2450+
while (!oldItem.isOnlyMoving()) {
24502451
throttleWith(throttler, [&] {
24512452
XLOGF(WARN,
24522453
"Spent {} seconds, slab release still waiting for refcount to "
@@ -2500,8 +2501,8 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
25002501
return {};
25012502
}
25022503

2503-
// Set up the destination for the move. Since oldChainedItem would have
2504-
// the exclusive bit set, it won't be picked for eviction.
2504+
// Set up the destination for the move. Since oldChainedItem would be
2505+
// marked as moving, it won't be picked for eviction.
25052506
auto newItemHdl =
25062507
allocateChainedItemInternal(parentHandle, oldChainedItem.getSize());
25072508
if (!newItemHdl) {
@@ -2553,7 +2554,7 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
25532554
// item is still valid.
25542555
const std::string parentKey =
25552556
oldItem.asChainedItem().getParentItem(compressor_).getKey().str();
2556-
if (oldItem.isOnlyExclusive()) {
2557+
if (oldItem.isOnlyMoving()) {
25572558
// If chained item no longer has a refcount, its parent is already
25582559
// being released, so we abort this try to moving.
25592560
return false;
@@ -2583,10 +2584,10 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
25832584
stats_.numEvictionAttempts.inc();
25842585

25852586
// 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
2587+
// nothing needs to be done. We simply need to call unmarkMoving and free
25872588
// the item.
2588-
if (item.isOnlyExclusive()) {
2589-
item.unmarkExclusive();
2589+
if (item.isOnlyMoving()) {
2590+
item.unmarkMoving();
25902591
const auto res =
25912592
releaseBackToAllocator(item, RemoveContext::kNormal, false);
25922593
XDCHECK(ReleaseRes::kReleased == res);
@@ -2617,7 +2618,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26172618
stats_.numEvictionSuccesses.inc();
26182619

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

26222623
// manually decrement the refcount to call releaseBackToAllocator
26232624
const auto ref = decRef(*owningHandle);
@@ -2629,7 +2630,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26292630
}
26302631

26312632
if (shutDownInProgress_) {
2632-
item.unmarkExclusive();
2633+
item.unmarkMoving();
26332634
allocator_->abortSlabRelease(ctx);
26342635
throw exception::SlabReleaseAborted(
26352636
folly::sformat("Slab Release aborted while trying to evict"
@@ -2775,9 +2776,9 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
27752776
template <typename CacheTrait>
27762777
typename CacheAllocator<CacheTrait>::WriteHandle
27772778
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
2778-
XDCHECK(item.isExclusive());
2779+
XDCHECK(item.isMoving());
27792780

2780-
if (item.isOnlyExclusive()) {
2781+
if (item.isOnlyMoving()) {
27812782
return WriteHandle{};
27822783
}
27832784

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

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

27952796
if (!handle) {
@@ -2813,7 +2814,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
28132814
template <typename CacheTrait>
28142815
typename CacheAllocator<CacheTrait>::WriteHandle
28152816
CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
2816-
XDCHECK(child.isExclusive());
2817+
XDCHECK(child.isMoving());
28172818

28182819
// We have the child marked as moving, but dont know anything about the
28192820
// state of the parent. Unlike the case of regular eviction where we are
@@ -2835,7 +2836,7 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28352836
// check if the child is still in mmContainer and the expected parent is
28362837
// valid under the chained item lock.
28372838
if (expectedParent.getKey() != parentKey || !child.isInMMContainer() ||
2838-
child.isOnlyExclusive() ||
2839+
child.isOnlyMoving() ||
28392840
&expectedParent != &child.getParentItem(compressor_) ||
28402841
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
28412842
return {};
@@ -2890,14 +2891,14 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28902891

28912892
// In case someone else had removed this chained item from its parent by now
28922893
// So we check again to see if it has been unlinked from its parent
2893-
if (!child.isInMMContainer() || child.isOnlyExclusive()) {
2894+
if (!child.isInMMContainer() || child.isOnlyMoving()) {
28942895
return {};
28952896
}
28962897

28972898
// check after removing from the MMContainer that the parent is still not
28982899
// being marked as moving. If parent is moving, it will release the child
28992900
// item and we will wait for that.
2900-
if (parentHandle->isExclusive()) {
2901+
if (parentHandle->isMoving()) {
29012902
return {};
29022903
}
29032904

@@ -2930,7 +2931,7 @@ bool CacheAllocator<CacheTrait>::removeIfExpired(const ReadHandle& handle) {
29302931
}
29312932

29322933
template <typename CacheTrait>
2933-
bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
2934+
bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
29342935
const SlabReleaseContext& ctx, void* alloc, util::Throttler& throttler) {
29352936
// MemoryAllocator::processAllocForRelease will execute the callback
29362937
// if the item is not already free. So there are three outcomes here:
@@ -2949,7 +2950,7 @@ bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
29492950
// Since this callback is executed, the item is not yet freed
29502951
itemFreed = false;
29512952
Item* item = static_cast<Item*>(memory);
2952-
if (item->markExclusive()) {
2953+
if (item->markMoving()) {
29532954
markedMoving = true;
29542955
}
29552956
};

cachelib/allocator/CacheAllocator.h

Lines changed: 4 additions & 4 deletions
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.
@@ -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();

cachelib/allocator/CacheItem-inl.h

Lines changed: 39 additions & 16 deletions
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,7 @@ 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() || isChainedItem()) {
339360
return false;
340361
}
341362
// attempt to atomically update the value of expiryTime
@@ -451,12 +472,14 @@ std::string CacheChainedItem<CacheTrait>::toString() const {
451472
return folly::sformat(
452473
"chained item: "
453474
"memory={}:raw-ref={}:size={}:parent-compressed-ptr={}:"
454-
"isInMMContainer={}:isAccessible={}:isExclusive={}:references={}:ctime={}"
475+
"isInMMContainer={}:isAccessible={}:isMarkedForEviction={}:"
476+
"isMoving={}:references={}:ctime={}"
455477
":"
456478
"expTime={}:updateTime={}",
457479
this, Item::getRefCountAndFlagsRaw(), Item::getSize(), cPtr.getRaw(),
458-
Item::isInMMContainer(), Item::isAccessible(), Item::isExclusive(),
459-
Item::getRefCount(), Item::getCreationTime(), Item::getExpiryTime(),
480+
Item::isInMMContainer(), Item::isAccessible(),
481+
Item::isMarkedForEviction(), Item::isMoving(), Item::getRefCount(),
482+
Item::getCreationTime(), Item::getExpiryTime(),
460483
Item::getLastAccessTime());
461484
}
462485

0 commit comments

Comments
 (0)