Skip to content

Commit a8af77d

Browse files
igchorbyrnedj
authored andcommitted
Do not block reader if a child item is moving
This would lead to deadlock (.e.g in forEachChainedItem) if the child is moving (e.g. marked by Slab Release thread). Instead treat moving bit only to prevent freeing the item and do all synchronization on parent.
1 parent 8bcd0cf commit a8af77d

File tree

1 file changed

+58
-35
lines changed

1 file changed

+58
-35
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,8 @@ CacheAllocator<CacheTrait>::acquire(Item* it) {
10371037

10381038
SCOPE_FAIL { stats_.numRefcountOverflow.inc(); };
10391039

1040-
auto failIfMoving = getNumTiers() > 1;
1040+
// TODO: do not block incRef for child items to avoid deadlock
1041+
auto failIfMoving = getNumTiers() > 1 && !it->isChainedItem();
10411042
auto incRes = incRef(*it, failIfMoving);
10421043
if (LIKELY(incRes == RefcountWithFlags::incResult::incOk)) {
10431044
return WriteHandle{it, *this};
@@ -3154,7 +3155,8 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
31543155
// a regular item or chained item is synchronized with any potential
31553156
// user-side mutation.
31563157
std::unique_ptr<SyncObj> syncObj;
3157-
if (config_.movingSync) {
3158+
if (config_.movingSync && getNumTiers() == 1) {
3159+
// TODO: use moving-bit synchronization for single tier as well
31583160
if (!oldItem.isChainedItem()) {
31593161
syncObj = config_.movingSync(oldItem.getKey());
31603162
} else {
@@ -3253,47 +3255,51 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
32533255
Item* evicted;
32543256
if (item.isChainedItem()) {
32553257
auto& expectedParent = item.asChainedItem().getParentItem(compressor_);
3256-
const std::string parentKey = expectedParent.getKey().str();
3257-
auto l = chainedItemLocks_.lockExclusive(parentKey);
3258-
3259-
// check if the child is still in mmContainer and the expected parent is
3260-
// valid under the chained item lock.
3261-
if (expectedParent.getKey() != parentKey || !item.isInMMContainer() ||
3262-
item.isOnlyMoving() ||
3263-
&expectedParent != &item.asChainedItem().getParentItem(compressor_) ||
3264-
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
3265-
continue;
3266-
}
32673258

3268-
// search if the child is present in the chain
3269-
{
3270-
auto parentHandle = findInternal(parentKey);
3271-
if (!parentHandle || parentHandle != &expectedParent) {
3259+
if (getNumTiers() == 1) {
3260+
// TODO: unify this with multi-tier implementation
3261+
// right now, taking a chained item lock here would lead to deadlock
3262+
const std::string parentKey = expectedParent.getKey().str();
3263+
auto l = chainedItemLocks_.lockExclusive(parentKey);
3264+
3265+
// check if the child is still in mmContainer and the expected parent is
3266+
// valid under the chained item lock.
3267+
if (expectedParent.getKey() != parentKey || !item.isInMMContainer() ||
3268+
item.isOnlyMoving() ||
3269+
&expectedParent != &item.asChainedItem().getParentItem(compressor_) ||
3270+
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
32723271
continue;
32733272
}
32743273

3275-
ChainedItem* head = nullptr;
3276-
{ // scope for the handle
3277-
auto headHandle = findChainedItem(expectedParent);
3278-
head = headHandle ? &headHandle->asChainedItem() : nullptr;
3279-
}
3274+
// search if the child is present in the chain
3275+
{
3276+
auto parentHandle = findInternal(parentKey);
3277+
if (!parentHandle || parentHandle != &expectedParent) {
3278+
continue;
3279+
}
32803280

3281-
bool found = false;
3282-
while (head) {
3283-
if (head == &item) {
3284-
found = true;
3285-
break;
3281+
ChainedItem* head = nullptr;
3282+
{ // scope for the handle
3283+
auto headHandle = findChainedItem(expectedParent);
3284+
head = headHandle ? &headHandle->asChainedItem() : nullptr;
32863285
}
3287-
head = head->getNext(compressor_);
3288-
}
32893286

3290-
if (!found) {
3291-
continue;
3287+
bool found = false;
3288+
while (head) {
3289+
if (head == &item) {
3290+
found = true;
3291+
break;
3292+
}
3293+
head = head->getNext(compressor_);
3294+
}
3295+
3296+
if (!found) {
3297+
continue;
3298+
}
32923299
}
32933300
}
32943301

32953302
evicted = &expectedParent;
3296-
32973303
token = createPutToken(*evicted);
32983304
if (evicted->markExclusive()) {
32993305
// unmark the child so it will be freed
@@ -3306,6 +3312,9 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
33063312
// no other reader can be added to the waiters list
33073313
wakeUpWaiters(*evicted, {});
33083314
} else {
3315+
// TODO: potential deadlock with markUseful for parent item
3316+
// for now, we do not block any reader on child items but this
3317+
// should probably be fixed
33093318
continue;
33103319
}
33113320
} else {
@@ -3340,7 +3349,17 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
33403349
XDCHECK(evicted->getRefCount() == 0);
33413350
const auto res =
33423351
releaseBackToAllocator(*evicted, RemoveContext::kEviction, false);
3343-
XDCHECK(res == ReleaseRes::kReleased);
3352+
3353+
if (getNumTiers() == 1) {
3354+
XDCHECK(res == ReleaseRes::kReleased);
3355+
} else {
3356+
const bool isAlreadyFreed =
3357+
!markMovingForSlabRelease(ctx, &item, throttler);
3358+
if (!isAlreadyFreed) {
3359+
continue;
3360+
}
3361+
}
3362+
33443363
return;
33453364
}
33463365
}
@@ -3388,11 +3407,15 @@ bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
33883407
bool itemFreed = true;
33893408
bool markedMoving = false;
33903409
TierId tid = getTierId(alloc);
3391-
const auto fn = [&markedMoving, &itemFreed](void* memory) {
3410+
auto numTiers = getNumTiers();
3411+
const auto fn = [&markedMoving, &itemFreed, numTiers](void* memory) {
33923412
// Since this callback is executed, the item is not yet freed
33933413
itemFreed = false;
33943414
Item* item = static_cast<Item*>(memory);
3395-
if (item->markMoving(false)) {
3415+
// TODO: for chained items, moving bit is only used to avoid
3416+
// freeing the item prematurely
3417+
auto failIfRefNotZero = numTiers > 1 && !item->isChainedItem();
3418+
if (item->markMoving(failIfRefNotZero)) {
33963419
markedMoving = true;
33973420
}
33983421
};

0 commit comments

Comments
 (0)