Skip to content

Commit 233be02

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 752971c commit 233be02

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

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

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

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

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

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

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

31903197
evicted = &expectedParent;
3191-
31923198
token = createPutToken(*evicted);
31933199
if (evicted->markForEviction()) {
31943200
// unmark the child so it will be freed
@@ -3199,6 +3205,9 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
31993205
// no other reader can be added to the waiters list
32003206
wakeUpWaiters(*evicted, {});
32013207
} else {
3208+
// TODO: potential deadlock with markUseful for parent item
3209+
// for now, we do not block any reader on child items but this
3210+
// should probably be fixed
32023211
continue;
32033212
}
32043213
} else {
@@ -3230,7 +3239,17 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
32303239
XDCHECK(evicted->getRefCount() == 0);
32313240
const auto res =
32323241
releaseBackToAllocator(*evicted, RemoveContext::kEviction, false);
3233-
XDCHECK(res == ReleaseRes::kReleased);
3242+
3243+
if (getNumTiers() == 1) {
3244+
XDCHECK(res == ReleaseRes::kReleased);
3245+
} else {
3246+
const bool isAlreadyFreed =
3247+
!markMovingForSlabRelease(ctx, &item, throttler);
3248+
if (!isAlreadyFreed) {
3249+
continue;
3250+
}
3251+
}
3252+
32343253
return;
32353254
}
32363255
}
@@ -3278,11 +3297,15 @@ bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
32783297
bool itemFreed = true;
32793298
bool markedMoving = false;
32803299
TierId tid = getTierId(alloc);
3281-
const auto fn = [&markedMoving, &itemFreed](void* memory) {
3300+
auto numTiers = getNumTiers();
3301+
const auto fn = [&markedMoving, &itemFreed, numTiers](void* memory) {
32823302
// Since this callback is executed, the item is not yet freed
32833303
itemFreed = false;
32843304
Item* item = static_cast<Item*>(memory);
3285-
if (item->markMoving(false)) {
3305+
// TODO: for chained items, moving bit is only used to avoid
3306+
// freeing the item prematurely
3307+
auto failIfRefNotZero = numTiers > 1 && !item->isChainedItem();
3308+
if (item->markMoving(failIfRefNotZero)) {
32863309
markedMoving = true;
32873310
}
32883311
};

0 commit comments

Comments
 (0)