Skip to content

Commit 1e40a00

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 e303104 commit 1e40a00

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};
@@ -3024,7 +3025,8 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
30243025
// a regular item or chained item is synchronized with any potential
30253026
// user-side mutation.
30263027
std::unique_ptr<SyncObj> syncObj;
3027-
if (config_.movingSync) {
3028+
if (config_.movingSync && getNumTiers() == 1) {
3029+
// TODO: use moving-bit synchronization for single tier as well
30283030
if (!oldItem.isChainedItem()) {
30293031
syncObj = config_.movingSync(oldItem.getKey());
30303032
} else {
@@ -3122,47 +3124,51 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
31223124
Item* evicted;
31233125
if (item.isChainedItem()) {
31243126
auto& expectedParent = item.asChainedItem().getParentItem(compressor_);
3125-
const std::string parentKey = expectedParent.getKey().str();
3126-
auto l = chainedItemLocks_.lockExclusive(parentKey);
3127-
3128-
// check if the child is still in mmContainer and the expected parent is
3129-
// valid under the chained item lock.
3130-
if (expectedParent.getKey() != parentKey || !item.isInMMContainer() ||
3131-
item.isOnlyMoving() ||
3132-
&expectedParent != &item.asChainedItem().getParentItem(compressor_) ||
3133-
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
3134-
continue;
3135-
}
31363127

3137-
// search if the child is present in the chain
3138-
{
3139-
auto parentHandle = findInternal(parentKey);
3140-
if (!parentHandle || parentHandle != &expectedParent) {
3128+
if (getNumTiers() == 1) {
3129+
// TODO: unify this with multi-tier implementation
3130+
// right now, taking a chained item lock here would lead to deadlock
3131+
const std::string parentKey = expectedParent.getKey().str();
3132+
auto l = chainedItemLocks_.lockExclusive(parentKey);
3133+
3134+
// check if the child is still in mmContainer and the expected parent is
3135+
// valid under the chained item lock.
3136+
if (expectedParent.getKey() != parentKey || !item.isInMMContainer() ||
3137+
item.isOnlyMoving() ||
3138+
&expectedParent != &item.asChainedItem().getParentItem(compressor_) ||
3139+
!expectedParent.isAccessible() || !expectedParent.hasChainedItem()) {
31413140
continue;
31423141
}
31433142

3144-
ChainedItem* head = nullptr;
3145-
{ // scope for the handle
3146-
auto headHandle = findChainedItem(expectedParent);
3147-
head = headHandle ? &headHandle->asChainedItem() : nullptr;
3148-
}
3143+
// search if the child is present in the chain
3144+
{
3145+
auto parentHandle = findInternal(parentKey);
3146+
if (!parentHandle || parentHandle != &expectedParent) {
3147+
continue;
3148+
}
31493149

3150-
bool found = false;
3151-
while (head) {
3152-
if (head == &item) {
3153-
found = true;
3154-
break;
3150+
ChainedItem* head = nullptr;
3151+
{ // scope for the handle
3152+
auto headHandle = findChainedItem(expectedParent);
3153+
head = headHandle ? &headHandle->asChainedItem() : nullptr;
31553154
}
3156-
head = head->getNext(compressor_);
3157-
}
31583155

3159-
if (!found) {
3160-
continue;
3156+
bool found = false;
3157+
while (head) {
3158+
if (head == &item) {
3159+
found = true;
3160+
break;
3161+
}
3162+
head = head->getNext(compressor_);
3163+
}
3164+
3165+
if (!found) {
3166+
continue;
3167+
}
31613168
}
31623169
}
31633170

31643171
evicted = &expectedParent;
3165-
31663172
token = createPutToken(*evicted);
31673173
if (evicted->markForEviction()) {
31683174
// unmark the child so it will be freed
@@ -3173,6 +3179,9 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
31733179
// no other reader can be added to the waiters list
31743180
wakeUpWaiters(*evicted, {});
31753181
} else {
3182+
// TODO: potential deadlock with markUseful for parent item
3183+
// for now, we do not block any reader on child items but this
3184+
// should probably be fixed
31763185
continue;
31773186
}
31783187
} else {
@@ -3204,7 +3213,17 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
32043213
XDCHECK(evicted->getRefCount() == 0);
32053214
const auto res =
32063215
releaseBackToAllocator(*evicted, RemoveContext::kEviction, false);
3207-
XDCHECK(res == ReleaseRes::kReleased);
3216+
3217+
if (getNumTiers() == 1) {
3218+
XDCHECK(res == ReleaseRes::kReleased);
3219+
} else {
3220+
const bool isAlreadyFreed =
3221+
!markMovingForSlabRelease(ctx, &item, throttler);
3222+
if (!isAlreadyFreed) {
3223+
continue;
3224+
}
3225+
}
3226+
32083227
return;
32093228
}
32103229
}
@@ -3252,11 +3271,15 @@ bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
32523271
bool itemFreed = true;
32533272
bool markedMoving = false;
32543273
TierId tid = getTierId(alloc);
3255-
const auto fn = [&markedMoving, &itemFreed](void* memory) {
3274+
auto numTiers = getNumTiers();
3275+
const auto fn = [&markedMoving, &itemFreed, numTiers](void* memory) {
32563276
// Since this callback is executed, the item is not yet freed
32573277
itemFreed = false;
32583278
Item* item = static_cast<Item*>(memory);
3259-
if (item->markMoving(false)) {
3279+
// TODO: for chained items, moving bit is only used to avoid
3280+
// freeing the item prematurely
3281+
auto failIfRefNotZero = numTiers > 1 && !item->isChainedItem();
3282+
if (item->markMoving(failIfRefNotZero)) {
32603283
markedMoving = true;
32613284
}
32623285
};

0 commit comments

Comments
 (0)