Skip to content

Commit 8e33d00

Browse files
committed
Block readers when item is moving
This simplifies moving code a lot. We don't need to worry about any failure other than allocation failure since item is protected from any readers (and by extension, removes).
1 parent eaf1f35 commit 8e33d00

File tree

8 files changed

+159
-175
lines changed

8 files changed

+159
-175
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 123 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,8 @@ void CacheAllocator<CacheTrait>::addChainedItem(WriteHandle& parent,
596596
// Increment refcount since this chained item is now owned by the parent
597597
// Parent will decrement the refcount upon release. Since this is an
598598
// internal refcount, we dont include it in active handle tracking.
599-
child->incRef();
599+
auto ret = child->incRef(true);
600+
XDCHECK(ret == RefcountWithFlags::incResult::incOk);
600601
XDCHECK_EQ(2u, child->getRefCount());
601602

602603
invalidateNvm(*parent);
@@ -842,7 +843,8 @@ CacheAllocator<CacheTrait>::replaceChainedItemLocked(Item& oldItem,
842843
// Since this is an internal refcount, we dont include it in active handle
843844
// tracking.
844845

845-
newItemHdl->incRef();
846+
auto ret = newItemHdl->incRef(true);
847+
XDCHECK(ret == RefcountWithFlags::incResult::incOk);
846848
return oldItemHdl;
847849
}
848850

@@ -1008,12 +1010,14 @@ CacheAllocator<CacheTrait>::releaseBackToAllocator(Item& it,
10081010
}
10091011

10101012
template <typename CacheTrait>
1011-
bool CacheAllocator<CacheTrait>::incRef(Item& it) {
1012-
if (it.incRef()) {
1013+
RefcountWithFlags::incResult CacheAllocator<CacheTrait>::incRef(Item& it, bool failIfMoving) {
1014+
auto ret = it.incRef(failIfMoving);
1015+
1016+
if (ret == RefcountWithFlags::incResult::incOk) {
10131017
++handleCount_.tlStats();
1014-
return true;
10151018
}
1016-
return false;
1019+
1020+
return ret;
10171021
}
10181022

10191023
template <typename CacheTrait>
@@ -1033,11 +1037,19 @@ CacheAllocator<CacheTrait>::acquire(Item* it) {
10331037

10341038
SCOPE_FAIL { stats_.numRefcountOverflow.inc(); };
10351039

1036-
if (LIKELY(incRef(*it))) {
1040+
auto failIfMoving = true; // TODO: only for multi-tier
1041+
auto incRes = incRef(*it, failIfMoving);
1042+
if (LIKELY(incRes == RefcountWithFlags::incResult::incOk)) {
10371043
return WriteHandle{it, *this};
1038-
} else {
1044+
} else if (incRes == RefcountWithFlags::incResult::incFailedExclusive){
10391045
// item is being evicted
10401046
return WriteHandle{};
1047+
} else {
1048+
// item is being moved - wait for completion
1049+
WriteHandle hdl{*this};
1050+
auto waitContext = hdl.getItemWaitContext();
1051+
addWaitContextForMovingItem(it->getKey(), waitContext);
1052+
return hdl;
10411053
}
10421054
}
10431055

@@ -1248,38 +1260,53 @@ CacheAllocator<CacheTrait>::insertOrReplace(const WriteHandle& handle) {
12481260
*
12491261
* The thread, which moves Item, allocates new Item in the tier we are moving to
12501262
* and calls moveRegularItemWithSync() method. This method does the following:
1251-
* 1. Create MoveCtx and put it to the movesMap.
1252-
* 2. Update the access container with the new item from the tier we are
1253-
* moving to. This Item has kIncomplete flag set.
1254-
* 3. Copy data from the old Item to the new one.
1255-
* 4. Unset the kIncomplete flag and Notify MoveCtx
1263+
* 1. Update the access container with the new item from the tier we are
1264+
* moving to. This Item has moving flag set.
1265+
* 2. Copy data from the old Item to the new one.
12561266
*
12571267
* Concurrent threads which are getting handle to the same key:
1258-
* 1. When a handle is created it checks if the kIncomplete flag is set
1268+
* 1. When a handle is created it checks if the moving flag is set
12591269
* 2. If so, Handle implementation creates waitContext and adds it to the
12601270
* MoveCtx by calling addWaitContextForMovingItem() method.
12611271
* 3. Wait until the moving thread will complete its job.
12621272
*/
12631273
template <typename CacheTrait>
1264-
bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
1274+
void CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
12651275
folly::StringPiece key, std::shared_ptr<WaitContext<ReadHandle>> waiter) {
12661276
auto shard = getShardForKey(key);
12671277
auto& movesMap = getMoveMapForShard(shard);
1268-
auto lock = getMoveLockForShard(shard);
1269-
auto it = movesMap.find(key);
1270-
if (it == movesMap.end()) {
1271-
return false;
1278+
{
1279+
auto lock = getMoveLockForShard(shard);
1280+
auto ret = movesMap.try_emplace(key, std::make_unique<MoveCtx>());
1281+
ret.first->second->addWaiter(std::move(waiter));
12721282
}
1273-
auto ctx = it->second.get();
1274-
ctx->addWaiter(std::move(waiter));
1275-
return true;
12761283
}
12771284

12781285
template <typename CacheTrait>
1279-
template <typename P>
1286+
size_t CacheAllocator<CacheTrait>::wakeUpWaiters(folly::StringPiece key,
1287+
WriteHandle&& handle) {
1288+
std::unique_ptr<MoveCtx> ctx;
1289+
auto shard = getShardForKey(key);
1290+
auto& movesMap = getMoveMapForShard(shard);
1291+
{
1292+
auto lock = getMoveLockForShard(shard);
1293+
movesMap.eraseInto(key, [&](auto &&key, auto &&value) {
1294+
ctx = std::move(value);
1295+
});
1296+
}
1297+
1298+
if (ctx) {
1299+
ctx->setItemHandle(std::move(handle));
1300+
return ctx->numWaiters();
1301+
}
1302+
1303+
return 0;
1304+
}
1305+
1306+
template <typename CacheTrait>
12801307
bool CacheAllocator<CacheTrait>::moveRegularItemWithSync(
1281-
Item& oldItem, WriteHandle& newItemHdl, P&& predicate) {
1282-
XDCHECK(oldItem.isExclusive());
1308+
Item& oldItem, WriteHandle& newItemHdl) {
1309+
XDCHECK(oldItem.isMoving());
12831310
// TODO: should we introduce new latency tracker. E.g. evictRegularLatency_
12841311
// ??? util::LatencyTracker tracker{stats_.evictRegularLatency_};
12851312

@@ -1297,50 +1324,20 @@ bool CacheAllocator<CacheTrait>::moveRegularItemWithSync(
12971324
newItemHdl->markNvmClean();
12981325
}
12991326

1300-
folly::StringPiece key(oldItem.getKey());
1301-
auto shard = getShardForKey(key);
1302-
auto& movesMap = getMoveMapForShard(shard);
1303-
MoveCtx* ctx(nullptr);
1304-
{
1305-
auto lock = getMoveLockForShard(shard);
1306-
auto res = movesMap.try_emplace(key, std::make_unique<MoveCtx>());
1307-
if (!res.second) {
1308-
return {};
1309-
}
1310-
ctx = res.first->second.get();
1311-
}
1327+
// mark new item as moving to block readers until the data is copied
1328+
// (moveCb is called)
1329+
auto marked = newItemHdl->markMoving(true);
1330+
XDCHECK(marked);
13121331

1313-
auto resHdl = WriteHandle{};
1314-
auto guard = folly::makeGuard([key, this, ctx, shard, &resHdl]() {
1315-
auto& movesMap = getMoveMapForShard(shard);
1316-
if (resHdl)
1317-
resHdl->unmarkIncomplete();
1318-
auto lock = getMoveLockForShard(shard);
1319-
ctx->setItemHandle(std::move(resHdl));
1320-
movesMap.erase(key);
1321-
});
1332+
auto predicate = [&](const Item& item){
1333+
// we rely on moving flag being set (it should block all readers)
1334+
XDCHECK(item.getRefCount() == 0);
1335+
return true;
1336+
};
13221337

1323-
// TODO: Possibly we can use markMoving() instead. But today
1324-
// moveOnSlabRelease logic assume that we mark as moving old Item
1325-
// and than do copy and replace old Item with the new one in access
1326-
// container. Furthermore, Item can be marked as Moving only
1327-
// if it is linked to MM container. In our case we mark the new Item
1328-
// and update access container before the new Item is ready (content is
1329-
// copied).
1330-
newItemHdl->markIncomplete();
1331-
1332-
// Inside the access container's lock, this checks if the old item is
1333-
// accessible and its refcount is zero. If the item is not accessible,
1334-
// there is no point to replace it since it had already been removed
1335-
// or in the process of being removed. If the item is in cache but the
1336-
// refcount is non-zero, it means user could be attempting to remove
1337-
// this item through an API such as remove(ItemHandle). In this case,
1338-
// it is unsafe to replace the old item with a new one, so we should
1339-
// also abort.
1340-
if (!accessContainer_->replaceIf(oldItem, *newItemHdl,
1341-
predicate)) {
1342-
return false;
1343-
}
1338+
auto replaced = accessContainer_->replaceIf(oldItem, *newItemHdl,
1339+
predicate);
1340+
XDCHECK(replaced);
13441341

13451342
if (config_.moveCb) {
13461343
// Execute the move callback. We cannot make any guarantees about the
@@ -1356,20 +1353,12 @@ bool CacheAllocator<CacheTrait>::moveRegularItemWithSync(
13561353
oldItem.getSize());
13571354
}
13581355

1359-
// Inside the MM container's lock, this checks if the old item exists to
1360-
// make sure that no other thread removed it, and only then replaces it.
1356+
// Adding the item to mmContainer has to succeed since no one can remove the item
13611357
auto& newContainer = getMMContainer(*newItemHdl);
1362-
if (!newContainer.add(*newItemHdl)) {
1363-
return false;
1364-
}
1365-
1366-
// Replacing into the MM container was successful, but someone could have
1367-
// called insertOrReplace() or remove() before or after the
1368-
// replaceInMMContainer() operation, which would invalidate newItemHdl.
1369-
if (!newItemHdl->isAccessible()) {
1370-
removeFromMMContainer(*newItemHdl);
1371-
return false;
1372-
}
1358+
auto mmContainerAdded = newContainer.add(*newItemHdl);
1359+
XDCHECK(mmContainerAdded);
1360+
1361+
XDCHECK(newItemHdl->isAccessible());
13731362

13741363
// no one can add or remove chained items at this point
13751364
if (oldItem.hasChainedItem()) {
@@ -1390,7 +1379,7 @@ bool CacheAllocator<CacheTrait>::moveRegularItemWithSync(
13901379
XDCHECK(newItemHdl->hasChainedItem());
13911380
}
13921381
newItemHdl.unmarkNascent();
1393-
resHdl = std::move(newItemHdl); // guard will assign it to ctx under lock
1382+
newItemHdl->unmarkMoving();
13941383
return true;
13951384
}
13961385

@@ -1592,12 +1581,10 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
15921581
? &toRecycle_->asChainedItem().getParentItem(compressor_)
15931582
: toRecycle_;
15941583

1595-
token = createPutToken(*candidate_);
1596-
15971584
if (shouldWriteToNvmCache(*candidate_) && !token.isValid()) {
15981585
stats_.evictFailConcurrentFill.inc();
1599-
} else if (candidate_->markExclusive()) {
1600-
XDCHECK(candidate_->isExclusive());
1586+
} else if (candidate_->markMoving(true)) {
1587+
XDCHECK(candidate_->isMoving());
16011588
// markExclusive to make sure no other thead is evicting the item
16021589
// nor holding a handle to that item
16031590

@@ -1635,16 +1622,30 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
16351622

16361623
auto evictedToNext = tryEvictToNextMemoryTier(*candidate, false /* from BgThread */);
16371624
if (!evictedToNext) {
1625+
token = createPutToken(*candidate);
1626+
1627+
// tryEvictToNextMemoryTier should only fail if allocation of the new item fails
1628+
// in that case, it should be still possible to mark item as exclusive.
1629+
auto ret = candidate->markExclusiveWhenMoving();
1630+
XDCHECK(ret);
1631+
1632+
// wake up any readers that wait for the move to complete
1633+
// it's safe to do now, as we have the item marked exclusive and
1634+
// no other reader can be added to the waiters list
1635+
wakeUpWaiters(candidate->getKey(), WriteHandle{});
1636+
16381637
unlinkItemExclusive(*candidate);
1638+
1639+
if (token.isValid() && shouldWriteToNvmCacheExclusive(*candidate)) {
1640+
nvmCache_->put(*candidate, std::move(token));
1641+
}
16391642
} else {
1640-
auto ref = candidate->unmarkExclusive();
1641-
XDCHECK(ref == 0u);
1643+
XDCHECK(!evictedToNext->isExclusive() && !evictedToNext->isMoving());
1644+
XDCHECK(!candidate->isExclusive() && !candidate->isMoving());
1645+
wakeUpWaiters(candidate->getKey(), std::move(evictedToNext));
16421646
}
1643-
XDCHECK(!candidate->isExclusive() && !candidate->isMoving());
16441647

1645-
if (token.isValid() && shouldWriteToNvmCacheExclusive(*candidate)) {
1646-
nvmCache_->put(*candidate, std::move(token));
1647-
}
1648+
XDCHECK(!candidate->isExclusive() && !candidate->isMoving());
16481649

16491650
// recycle the item. it's safe to do so, even if toReleaseHandle was
16501651
// NULL. If `ref` == 0 then it means that we are the last holder of
@@ -1721,15 +1722,16 @@ bool CacheAllocator<CacheTrait>::shouldWriteToNvmCacheExclusive(
17211722
}
17221723

17231724
template <typename CacheTrait>
1724-
bool CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
1725+
typename CacheAllocator<CacheTrait>::WriteHandle
1726+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
17251727
TierId tid, PoolId pid, Item& item, bool fromBgThread) {
1726-
if(item.isChainedItem()) return false; // TODO: We do not support ChainedItem yet
1728+
XDCHECK(item.isMoving());
1729+
XDCHECK(item.getRefCount() == 0);
1730+
if(item.isChainedItem()) return {}; // TODO: We do not support ChainedItem yet
17271731
if(item.isExpired()) {
1728-
XDCHECK(item.isExclusive());
1729-
XDCHECK(item.getRefCount() == 0);
1730-
17311732
accessContainer_->remove(item);
1732-
return true;
1733+
item.unmarkMoving();
1734+
return acquire(&item); // TODO: wakeUpWaiters with null handle?
17331735
}
17341736

17351737
TierId nextTier = tid; // TODO - calculate this based on some admission policy
@@ -1744,25 +1746,31 @@ bool CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
17441746

17451747
if (newItemHdl) {
17461748
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1747-
auto predicate = [&](const Item& item){
1748-
return item.getRefCount() == 0;
1749-
};
1750-
return moveRegularItemWithSync(item, newItemHdl, predicate);
1749+
if (moveRegularItemWithSync(item, newItemHdl)) {
1750+
item.unmarkMoving();
1751+
return newItemHdl;
1752+
} else {
1753+
return WriteHandle{};
1754+
}
1755+
} else {
1756+
return WriteHandle{};
17511757
}
17521758
}
17531759

17541760
return {};
17551761
}
17561762

17571763
template <typename CacheTrait>
1758-
bool CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item& item, bool fromBgThread) {
1764+
typename CacheAllocator<CacheTrait>::WriteHandle
1765+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item& item, bool fromBgThread) {
17591766
auto tid = getTierId(item);
17601767
auto pid = allocator_[tid]->getAllocInfo(item.getMemory()).poolId;
17611768
return tryEvictToNextMemoryTier(tid, pid, item, fromBgThread);
17621769
}
17631770

17641771
template <typename CacheTrait>
1765-
bool CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(
1772+
typename CacheAllocator<CacheTrait>::WriteHandle
1773+
CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(
17661774
TierId tid, PoolId pid, Item& item, bool fromBgThread) {
17671775
TierId nextTier = tid;
17681776
while (nextTier > 0) { // try to evict down to the next memory tiers
@@ -1779,18 +1787,23 @@ bool CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(
17791787

17801788
if (newItemHdl) {
17811789
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1782-
auto predicate = [&](const Item& item){
1783-
return item.getRefCount() == 0 || config_.numDuplicateElements > 0;
1784-
};
1785-
return moveRegularItemWithSync(item, newItemHdl, predicate);
1790+
if (moveRegularItemWithSync(item, newItemHdl)) {
1791+
item.unmarkMoving();
1792+
return newItemHdl;
1793+
} else {
1794+
return WriteHandle{};
1795+
}
1796+
} else {
1797+
return WriteHandle{};
17861798
}
17871799
}
17881800

1789-
return false;
1801+
return {};
17901802
}
17911803

17921804
template <typename CacheTrait>
1793-
bool CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(Item& item, bool fromBgThread) {
1805+
typename CacheAllocator<CacheTrait>::WriteHandle
1806+
CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(Item& item, bool fromBgThread) {
17941807
auto tid = getTierId(item);
17951808
auto pid = allocator_[tid]->getAllocInfo(item.getMemory()).poolId;
17961809
return tryPromoteToNextMemoryTier(tid, pid, item, fromBgThread);
@@ -3318,7 +3331,7 @@ bool CacheAllocator<CacheTrait>::markMovingForSlabRelease(
33183331
// Since this callback is executed, the item is not yet freed
33193332
itemFreed = false;
33203333
Item* item = static_cast<Item*>(memory);
3321-
if (item->markMoving()) {
3334+
if (item->markMoving(false)) {
33223335
markedMoving = true;
33233336
}
33243337
};

0 commit comments

Comments
 (0)