Skip to content

Commit ae7442a

Browse files
haowu14facebook-github-bot
authored andcommitted
Shorten critical section in findEviction
Summary: Reattempting D45071460 after backout D46566152. Details in the task. Reviewed By: therealgymmy Differential Revision: D46599875 fbshipit-source-id: c9394cd40bcc8ce99e2ea393f23cec8b900ebae3
1 parent 6db16bd commit ae7442a

File tree

9 files changed

+179
-234
lines changed

9 files changed

+179
-234
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 127 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,83 +1241,142 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12411241
}
12421242

12431243
template <typename CacheTrait>
1244-
typename CacheAllocator<CacheTrait>::Item*
1245-
CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
1244+
void CacheAllocator<CacheTrait>::unlinkItemForEviction(Item& it) {
1245+
XDCHECK(it.isMarkedForEviction());
1246+
XDCHECK_EQ(0u, it.getRefCount());
1247+
accessContainer_->remove(it);
1248+
removeFromMMContainer(it);
1249+
1250+
// Since we managed to mark the item for eviction we must be the only
1251+
// owner of the item.
1252+
const auto ref = it.unmarkForEviction();
1253+
XDCHECK_EQ(0u, ref);
1254+
}
1255+
1256+
template <typename CacheTrait>
1257+
std::pair<typename CacheAllocator<CacheTrait>::Item*,
1258+
typename CacheAllocator<CacheTrait>::Item*>
1259+
CacheAllocator<CacheTrait>::getNextCandidate(PoolId pid,
1260+
ClassId cid,
1261+
unsigned int& searchTries) {
1262+
typename NvmCacheT::PutToken token;
1263+
Item* toRecycle = nullptr;
1264+
Item* candidate = nullptr;
12461265
auto& mmContainer = getMMContainer(pid, cid);
12471266

1248-
// Keep searching for a candidate until we were able to evict it
1249-
// or until the search limit has been exhausted
1250-
unsigned int searchTries = 0;
1251-
auto itr = mmContainer.getEvictionIterator();
1252-
while ((config_.evictionSearchTries == 0 ||
1253-
config_.evictionSearchTries > searchTries) &&
1254-
itr) {
1255-
++searchTries;
1256-
(*stats_.evictionAttempts)[pid][cid].inc();
1257-
1258-
Item* toRecycle = itr.get();
1259-
1260-
Item* candidate =
1261-
toRecycle->isChainedItem()
1262-
? &toRecycle->asChainedItem().getParentItem(compressor_)
1263-
: toRecycle;
1264-
1265-
// make sure no other thead is evicting the item
1266-
if (candidate->getRefCount() != 0 || !candidate->markMoving()) {
1267-
++itr;
1268-
continue;
1267+
mmContainer.withEvictionIterator([this, pid, cid, &candidate, &toRecycle,
1268+
&searchTries, &mmContainer,
1269+
&token](auto&& itr) {
1270+
if (!itr) {
1271+
++searchTries;
1272+
(*stats_.evictionAttempts)[pid][cid].inc();
1273+
return;
12691274
}
12701275

1271-
// for chained items, the ownership of the parent can change. We try to
1272-
// evict what we think as parent and see if the eviction of parent
1273-
// recycles the child we intend to.
1274-
bool evictionSuccessful = false;
1275-
{
1276-
auto toReleaseHandle =
1277-
itr->isChainedItem()
1278-
? advanceIteratorAndTryEvictChainedItem(itr)
1279-
: advanceIteratorAndTryEvictRegularItem(mmContainer, itr);
1280-
evictionSuccessful = toReleaseHandle != nullptr;
1281-
// destroy toReleaseHandle. The item won't be released to allocator
1282-
// since we marked for eviction.
1283-
}
1284-
1285-
const auto ref = candidate->unmarkMoving();
1286-
if (ref == 0u) {
1287-
// Invalidate iterator since later on we may use this mmContainer
1288-
// again, which cannot be done unless we drop this iterator
1289-
itr.destroy();
1290-
1291-
// recycle the item. it's safe to do so, even if toReleaseHandle was
1292-
// NULL. If `ref` == 0 then it means that we are the last holder of
1293-
// that item.
1294-
if (candidate->hasChainedItem()) {
1295-
(*stats_.chainedItemEvictions)[pid][cid].inc();
1296-
} else {
1297-
(*stats_.regularItemEvictions)[pid][cid].inc();
1276+
while ((config_.evictionSearchTries == 0 ||
1277+
config_.evictionSearchTries > searchTries) &&
1278+
itr) {
1279+
++searchTries;
1280+
(*stats_.evictionAttempts)[pid][cid].inc();
1281+
1282+
auto* toRecycle_ = itr.get();
1283+
auto* candidate_ =
1284+
toRecycle_->isChainedItem()
1285+
? &toRecycle_->asChainedItem().getParentItem(compressor_)
1286+
: toRecycle_;
1287+
1288+
const bool evictToNvmCache = shouldWriteToNvmCache(*candidate_);
1289+
auto putToken = evictToNvmCache
1290+
? nvmCache_->createPutToken(candidate_->getKey())
1291+
: typename NvmCacheT::PutToken{};
1292+
1293+
if (evictToNvmCache && !putToken.isValid()) {
1294+
stats_.evictFailConcurrentFill.inc();
1295+
++itr;
1296+
continue;
12981297
}
12991298

1300-
if (auto eventTracker = getEventTracker()) {
1301-
eventTracker->record(AllocatorApiEvent::DRAM_EVICT, candidate->getKey(),
1302-
AllocatorApiResult::EVICTED, candidate->getSize(),
1303-
candidate->getConfiguredTTL().count());
1299+
auto markedForEviction = candidate_->markForEviction();
1300+
if (!markedForEviction) {
1301+
if (candidate_->hasChainedItem()) {
1302+
stats_.evictFailParentAC.inc();
1303+
} else {
1304+
stats_.evictFailAC.inc();
1305+
}
1306+
++itr;
1307+
continue;
13041308
}
13051309

1306-
// check if by releasing the item we intend to, we actually
1307-
// recycle the candidate.
1308-
if (ReleaseRes::kRecycled ==
1309-
releaseBackToAllocator(*candidate, RemoveContext::kEviction,
1310-
/* isNascent */ false, toRecycle)) {
1311-
return toRecycle;
1310+
XDCHECK(candidate_->isMarkedForEviction());
1311+
// markForEviction to make sure no other thead is evicting the item
1312+
// nor holding a handle to that item
1313+
toRecycle = toRecycle_;
1314+
candidate = candidate_;
1315+
token = std::move(putToken);
1316+
1317+
// Check if parent changed for chained items - if yes, we cannot
1318+
// remove the child from the mmContainer as we will not be evicting
1319+
// it. We could abort right here, but we need to cleanup in case
1320+
// unmarkForEviction() returns 0 - so just go through normal path.
1321+
if (!toRecycle_->isChainedItem() ||
1322+
&toRecycle->asChainedItem().getParentItem(compressor_) == candidate) {
1323+
mmContainer.remove(itr);
13121324
}
1325+
return;
1326+
}
1327+
});
1328+
1329+
if (!toRecycle) {
1330+
return {candidate, toRecycle};
1331+
}
1332+
1333+
XDCHECK(toRecycle);
1334+
XDCHECK(candidate);
1335+
1336+
unlinkItemForEviction(*candidate);
1337+
1338+
if (token.isValid() && shouldWriteToNvmCacheExclusive(*candidate)) {
1339+
nvmCache_->put(*candidate, std::move(token));
1340+
}
1341+
return {candidate, toRecycle};
1342+
}
1343+
1344+
template <typename CacheTrait>
1345+
typename CacheAllocator<CacheTrait>::Item*
1346+
CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
1347+
// Keep searching for a candidate until we were able to evict it
1348+
// or until the search limit has been exhausted
1349+
unsigned int searchTries = 0;
1350+
while (config_.evictionSearchTries == 0 ||
1351+
config_.evictionSearchTries > searchTries) {
1352+
auto [candidate, toRecycle] = getNextCandidate(pid, cid, searchTries);
1353+
1354+
// Reached the end of the eviction queue but doulen't find a candidate,
1355+
// start again.
1356+
if (!toRecycle) {
1357+
continue;
1358+
}
1359+
// recycle the item. it's safe to do so, even if toReleaseHandle was
1360+
// NULL. If `ref` == 0 then it means that we are the last holder of
1361+
// that item.
1362+
if (candidate->hasChainedItem()) {
1363+
(*stats_.chainedItemEvictions)[pid][cid].inc();
13131364
} else {
1314-
XDCHECK(!evictionSuccessful);
1365+
(*stats_.regularItemEvictions)[pid][cid].inc();
13151366
}
13161367

1317-
// If we destroyed the itr to possibly evict and failed, we restart
1318-
// from the beginning again
1319-
if (!itr) {
1320-
itr.resetToBegin();
1368+
if (auto eventTracker = getEventTracker()) {
1369+
eventTracker->record(AllocatorApiEvent::DRAM_EVICT, candidate->getKey(),
1370+
AllocatorApiResult::EVICTED, candidate->getSize(),
1371+
candidate->getConfiguredTTL().count());
1372+
}
1373+
1374+
// check if by releasing the item we intend to, we actually
1375+
// recycle the candidate.
1376+
auto ret = releaseBackToAllocator(*candidate, RemoveContext::kEviction,
1377+
/* isNascent */ false, toRecycle);
1378+
if (ret == ReleaseRes::kRecycled) {
1379+
return toRecycle;
13211380
}
13221381
}
13231382
return nullptr;
@@ -1461,7 +1520,7 @@ bool CacheAllocator<CacheTrait>::pushToNvmCacheFromRamForTesting(
14611520

14621521
if (handle && nvmCache_ && shouldWriteToNvmCache(*handle) &&
14631522
shouldWriteToNvmCacheExclusive(*handle)) {
1464-
nvmCache_->put(handle, nvmCache_->createPutToken(handle->getKey()));
1523+
nvmCache_->put(*handle, nvmCache_->createPutToken(handle->getKey()));
14651524
return true;
14661525
}
14671526
return false;
@@ -2668,126 +2727,6 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26682727
}
26692728
}
26702729

2671-
template <typename CacheTrait>
2672-
typename CacheAllocator<CacheTrait>::WriteHandle
2673-
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
2674-
MMContainer& mmContainer, EvictionIterator& itr) {
2675-
// we should flush this to nvmcache if it is not already present in nvmcache
2676-
// and the item is not expired.
2677-
Item& item = *itr;
2678-
const bool evictToNvmCache = shouldWriteToNvmCache(item);
2679-
2680-
auto token = evictToNvmCache ? nvmCache_->createPutToken(item.getKey())
2681-
: typename NvmCacheT::PutToken{};
2682-
2683-
// record the in-flight eviciton. If not, we move on to next item to avoid
2684-
// stalling eviction.
2685-
if (evictToNvmCache && !token.isValid()) {
2686-
++itr;
2687-
stats_.evictFailConcurrentFill.inc();
2688-
return WriteHandle{};
2689-
}
2690-
2691-
// If there are other accessors, we should abort. Acquire a handle here since
2692-
// if we remove the item from both access containers and mm containers
2693-
// below, we will need a handle to ensure proper cleanup in case we end up
2694-
// not evicting this item
2695-
auto evictHandle = accessContainer_->removeIf(item, &itemExclusivePredicate);
2696-
if (!evictHandle) {
2697-
++itr;
2698-
stats_.evictFailAC.inc();
2699-
return evictHandle;
2700-
}
2701-
2702-
mmContainer.remove(itr);
2703-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(evictHandle.get()),
2704-
reinterpret_cast<uintptr_t>(&item));
2705-
XDCHECK(!evictHandle->isInMMContainer());
2706-
XDCHECK(!evictHandle->isAccessible());
2707-
2708-
// Invalidate iterator since later on if we are not evicting this
2709-
// item, we may need to rely on the handle we created above to ensure
2710-
// proper cleanup if the item's raw refcount has dropped to 0.
2711-
// And since this item may be a parent item that has some child items
2712-
// in this very same mmContainer, we need to make sure we drop this
2713-
// exclusive iterator so we can gain access to it when we're cleaning
2714-
// up the child items
2715-
itr.destroy();
2716-
2717-
// Ensure that there are no accessors after removing from the access
2718-
// container
2719-
XDCHECK(evictHandle->getRefCount() == 1);
2720-
2721-
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(item)) {
2722-
XDCHECK(token.isValid());
2723-
nvmCache_->put(evictHandle, std::move(token));
2724-
}
2725-
return evictHandle;
2726-
}
2727-
2728-
template <typename CacheTrait>
2729-
typename CacheAllocator<CacheTrait>::WriteHandle
2730-
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
2731-
EvictionIterator& itr) {
2732-
XDCHECK(itr->isChainedItem());
2733-
2734-
ChainedItem* candidate = &itr->asChainedItem();
2735-
++itr;
2736-
2737-
// The parent could change at any point through transferChain. However, if
2738-
// that happens, we would realize that the releaseBackToAllocator return
2739-
// kNotRecycled and we would try another chained item, leading to transient
2740-
// failure.
2741-
auto& parent = candidate->getParentItem(compressor_);
2742-
2743-
const bool evictToNvmCache = shouldWriteToNvmCache(parent);
2744-
2745-
auto token = evictToNvmCache ? nvmCache_->createPutToken(parent.getKey())
2746-
: typename NvmCacheT::PutToken{};
2747-
2748-
// if token is invalid, return. iterator is already advanced.
2749-
if (evictToNvmCache && !token.isValid()) {
2750-
stats_.evictFailConcurrentFill.inc();
2751-
return WriteHandle{};
2752-
}
2753-
2754-
// check if the parent exists in the hashtable and refcount is drained.
2755-
auto parentHandle =
2756-
accessContainer_->removeIf(parent, &itemExclusivePredicate);
2757-
if (!parentHandle) {
2758-
stats_.evictFailParentAC.inc();
2759-
return parentHandle;
2760-
}
2761-
2762-
// Invalidate iterator since later on we may use the mmContainer
2763-
// associated with this iterator which cannot be done unless we
2764-
// drop this iterator
2765-
//
2766-
// This must be done once we know the parent is not nullptr.
2767-
// Since we can very well be the last holder of this parent item,
2768-
// which may have a chained item that is linked in this MM container.
2769-
itr.destroy();
2770-
2771-
// Ensure we have the correct parent and we're the only user of the
2772-
// parent, then free it from access container. Otherwise, we abort
2773-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(&parent),
2774-
reinterpret_cast<uintptr_t>(parentHandle.get()));
2775-
XDCHECK_EQ(1u, parent.getRefCount());
2776-
2777-
removeFromMMContainer(*parentHandle);
2778-
2779-
XDCHECK(!parent.isInMMContainer());
2780-
XDCHECK(!parent.isAccessible());
2781-
2782-
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(*parentHandle)) {
2783-
XDCHECK(token.isValid());
2784-
XDCHECK(parentHandle->hasChainedItem());
2785-
nvmCache_->put(parentHandle, std::move(token));
2786-
}
2787-
2788-
return parentHandle;
2789-
}
2790-
27912730
template <typename CacheTrait>
27922731
typename CacheAllocator<CacheTrait>::WriteHandle
27932732
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
@@ -2820,7 +2759,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
28202759
// now that we are the only handle and we actually removed something from
28212760
// the RAM cache, we enqueue it to nvmcache.
28222761
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(item)) {
2823-
nvmCache_->put(handle, std::move(token));
2762+
nvmCache_->put(*handle, std::move(token));
28242763
}
28252764

28262765
return handle;
@@ -2921,7 +2860,7 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
29212860
// the RAM cache, we enqueue it to nvmcache.
29222861
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(*parentHandle)) {
29232862
DCHECK(parentHandle->hasChainedItem());
2924-
nvmCache_->put(parentHandle, std::move(token));
2863+
nvmCache_->put(*parentHandle, std::move(token));
29252864
}
29262865

29272866
return parentHandle;

0 commit comments

Comments
 (0)