Skip to content

Commit ad479a9

Browse files
igchorvinser52
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 ad10921 commit ad479a9

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
@@ -987,7 +987,8 @@ CacheAllocator<CacheTrait>::acquire(Item* it) {
987987

988988
SCOPE_FAIL { stats_.numRefcountOverflow.inc(); };
989989

990-
auto failIfMoving = getNumTiers() > 1;
990+
// TODO: do not block incRef for child items to avoid deadlock
991+
auto failIfMoving = getNumTiers() > 1 && !it->isChainedItem();
991992
auto incRes = incRef(*it, failIfMoving);
992993
if (LIKELY(incRes == RefcountWithFlags::incResult::incOk)) {
993994
return WriteHandle{it, *this};
@@ -3051,7 +3052,8 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
30513052
// a regular item or chained item is synchronized with any potential
30523053
// user-side mutation.
30533054
std::unique_ptr<SyncObj> syncObj;
3054-
if (config_.movingSync) {
3055+
if (config_.movingSync && getNumTiers() == 1) {
3056+
// TODO: use moving-bit synchronization for single tier as well
30553057
if (!oldItem.isChainedItem()) {
30563058
syncObj = config_.movingSync(oldItem.getKey());
30573059
} else {
@@ -3149,47 +3151,51 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
31493151
Item* evicted;
31503152
if (item.isChainedItem()) {
31513153
auto& expectedParent = item.asChainedItem().getParentItem(compressor_);
3152-
const std::string parentKey = expectedParent.getKey().str();
3153-
auto l = chainedItemLocks_.lockExclusive(parentKey);
3154-
3155-
// check if the child is still in mmContainer and the expected parent is
3156-
// valid under the chained item lock.
3157-
if (expectedParent.getKey() != parentKey || !item.isInMMContainer() ||
3158-
item.isOnlyMoving() ||
3159-
&expectedParent != &item.asChainedItem().getParentItem(compressor_) ||
3160-
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
3161-
continue;
3162-
}
31633154

3164-
// search if the child is present in the chain
3165-
{
3166-
auto parentHandle = findInternal(parentKey);
3167-
if (!parentHandle || parentHandle != &expectedParent) {
3155+
if (getNumTiers() == 1) {
3156+
// TODO: unify this with multi-tier implementation
3157+
// right now, taking a chained item lock here would lead to deadlock
3158+
const std::string parentKey = expectedParent.getKey().str();
3159+
auto l = chainedItemLocks_.lockExclusive(parentKey);
3160+
3161+
// check if the child is still in mmContainer and the expected parent is
3162+
// valid under the chained item lock.
3163+
if (expectedParent.getKey() != parentKey || !item.isInMMContainer() ||
3164+
item.isOnlyMoving() ||
3165+
&expectedParent != &item.asChainedItem().getParentItem(compressor_) ||
3166+
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
31683167
continue;
31693168
}
31703169

3171-
ChainedItem* head = nullptr;
3172-
{ // scope for the handle
3173-
auto headHandle = findChainedItem(expectedParent);
3174-
head = headHandle ? &headHandle->asChainedItem() : nullptr;
3175-
}
3170+
// search if the child is present in the chain
3171+
{
3172+
auto parentHandle = findInternal(parentKey);
3173+
if (!parentHandle || parentHandle != &expectedParent) {
3174+
continue;
3175+
}
31763176

3177-
bool found = false;
3178-
while (head) {
3179-
if (head == &item) {
3180-
found = true;
3181-
break;
3177+
ChainedItem* head = nullptr;
3178+
{ // scope for the handle
3179+
auto headHandle = findChainedItem(expectedParent);
3180+
head = headHandle ? &headHandle->asChainedItem() : nullptr;
31823181
}
3183-
head = head->getNext(compressor_);
3184-
}
31853182

3186-
if (!found) {
3187-
continue;
3183+
bool found = false;
3184+
while (head) {
3185+
if (head == &item) {
3186+
found = true;
3187+
break;
3188+
}
3189+
head = head->getNext(compressor_);
3190+
}
3191+
3192+
if (!found) {
3193+
continue;
3194+
}
31883195
}
31893196
}
31903197

31913198
evicted = &expectedParent;
3192-
31933199
token = createPutToken(*evicted);
31943200
if (evicted->markForEviction()) {
31953201
// unmark the child so it will be freed
@@ -3200,6 +3206,9 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
32003206
// no other reader can be added to the waiters list
32013207
wakeUpWaiters(*evicted, {});
32023208
} else {
3209+
// TODO: potential deadlock with markUseful for parent item
3210+
// for now, we do not block any reader on child items but this
3211+
// should probably be fixed
32033212
continue;
32043213
}
32053214
} else {
@@ -3231,7 +3240,17 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
32313240
XDCHECK(evicted->getRefCount() == 0);
32323241
const auto res =
32333242
releaseBackToAllocator(*evicted, RemoveContext::kEviction, false);
3234-
XDCHECK(res == ReleaseRes::kReleased);
3243+
3244+
if (getNumTiers() == 1) {
3245+
XDCHECK(res == ReleaseRes::kReleased);
3246+
} else {
3247+
const bool isAlreadyFreed =
3248+
!markMovingForSlabRelease(ctx, &item, throttler);
3249+
if (!isAlreadyFreed) {
3250+
continue;
3251+
}
3252+
}
3253+
32353254
return;
32363255
}
32373256
}
@@ -3279,11 +3298,15 @@ bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
32793298
bool itemFreed = true;
32803299
bool markedMoving = false;
32813300
TierId tid = getTierId(alloc);
3282-
const auto fn = [&markedMoving, &itemFreed](void* memory) {
3301+
auto numTiers = getNumTiers();
3302+
const auto fn = [&markedMoving, &itemFreed, numTiers](void* memory) {
32833303
// Since this callback is executed, the item is not yet freed
32843304
itemFreed = false;
32853305
Item* item = static_cast<Item*>(memory);
3286-
if (item->markMoving(false)) {
3306+
// TODO: for chained items, moving bit is only used to avoid
3307+
// freeing the item prematurely
3308+
auto failIfRefNotZero = numTiers > 1 && !item->isChainedItem();
3309+
if (item->markMoving(failIfRefNotZero)) {
32873310
markedMoving = true;
32883311
}
32893312
};

0 commit comments

Comments
 (0)