Skip to content

Commit ed9e088

Browse files
Tushar Pankajfacebook-github-bot
authored andcommitted
Back out "Shorten critical section in findEviction"
Summary: Original commit changeset: 4d44d7518253 Original Phabricator Diff: D45071460 Reviewed By: haowu14 Differential Revision: D46566152 fbshipit-source-id: d62121506527e71a88dbd48f7ec83b57971552a7
1 parent 39478cc commit ed9e088

File tree

9 files changed

+232
-149
lines changed

9 files changed

+232
-149
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 185 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,19 +1240,6 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12401240
return true;
12411241
}
12421242

1243-
template <typename CacheTrait>
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-
12561243
template <typename CacheTrait>
12571244
typename CacheAllocator<CacheTrait>::Item*
12581245
CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
@@ -1261,109 +1248,76 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12611248
// Keep searching for a candidate until we were able to evict it
12621249
// or until the search limit has been exhausted
12631250
unsigned int searchTries = 0;
1264-
while (config_.evictionSearchTries == 0 ||
1265-
config_.evictionSearchTries > searchTries) {
1266-
Item* toRecycle = nullptr;
1267-
Item* candidate = nullptr;
1268-
typename NvmCacheT::PutToken token;
1269-
1270-
mmContainer.withEvictionIterator([this, pid, cid, &candidate, &toRecycle,
1271-
&searchTries, &mmContainer,
1272-
&token](auto&& itr) {
1273-
if (!itr) {
1274-
++searchTries;
1275-
(*stats_.evictionAttempts)[pid][cid].inc();
1276-
return;
1277-
}
1278-
1279-
while ((config_.evictionSearchTries == 0 ||
1280-
config_.evictionSearchTries > searchTries) &&
1281-
itr) {
1282-
++searchTries;
1283-
(*stats_.evictionAttempts)[pid][cid].inc();
1284-
1285-
auto* toRecycle_ = itr.get();
1286-
auto* candidate_ =
1287-
toRecycle_->isChainedItem()
1288-
? &toRecycle_->asChainedItem().getParentItem(compressor_)
1289-
: toRecycle_;
1290-
1291-
const bool evictToNvmCache = shouldWriteToNvmCache(*candidate_);
1292-
auto putToken = evictToNvmCache
1293-
? nvmCache_->createPutToken(candidate_->getKey())
1294-
: typename NvmCacheT::PutToken{};
1295-
1296-
if (evictToNvmCache && !putToken.isValid()) {
1297-
stats_.evictFailConcurrentFill.inc();
1298-
++itr;
1299-
continue;
1300-
}
1301-
1302-
auto markedForEviction = candidate_->markForEviction();
1303-
if (!markedForEviction) {
1304-
if (candidate_->hasChainedItem()) {
1305-
stats_.evictFailParentAC.inc();
1306-
} else {
1307-
stats_.evictFailAC.inc();
1308-
}
1309-
++itr;
1310-
continue;
1311-
}
1312-
1313-
XDCHECK(candidate_->isMarkedForEviction());
1314-
// markForEviction to make sure no other thead is evicting the item
1315-
// nor holding a handle to that item
1316-
toRecycle = toRecycle_;
1317-
candidate = candidate_;
1318-
token = std::move(putToken);
1319-
1320-
// Check if parent changed for chained items - if yes, we cannot
1321-
// remove the child from the mmContainer as we will not be evicting
1322-
// it. We could abort right here, but we need to cleanup in case
1323-
// unmarkForEviction() returns 0 - so just go through normal path.
1324-
if (!toRecycle_->isChainedItem() ||
1325-
&toRecycle->asChainedItem().getParentItem(compressor_) ==
1326-
candidate) {
1327-
mmContainer.remove(itr);
1328-
}
1329-
return;
1330-
}
1331-
});
1332-
1333-
if (!toRecycle) {
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;
13341268
continue;
13351269
}
13361270

1337-
XDCHECK(toRecycle);
1338-
XDCHECK(candidate);
1339-
1340-
unlinkItemForEviction(*candidate);
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();
1298+
}
13411299

1342-
if (token.isValid() && shouldWriteToNvmCacheExclusive(*candidate)) {
1343-
nvmCache_->put(*candidate, std::move(token));
1344-
}
1300+
if (auto eventTracker = getEventTracker()) {
1301+
eventTracker->record(AllocatorApiEvent::DRAM_EVICT, candidate->getKey(),
1302+
AllocatorApiResult::EVICTED, candidate->getSize(),
1303+
candidate->getConfiguredTTL().count());
1304+
}
13451305

1346-
// recycle the item. it's safe to do so, even if toReleaseHandle was
1347-
// NULL. If `ref` == 0 then it means that we are the last holder of
1348-
// that item.
1349-
if (candidate->hasChainedItem()) {
1350-
(*stats_.chainedItemEvictions)[pid][cid].inc();
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;
1312+
}
13511313
} else {
1352-
(*stats_.regularItemEvictions)[pid][cid].inc();
1314+
XDCHECK(!evictionSuccessful);
13531315
}
13541316

1355-
if (auto eventTracker = getEventTracker()) {
1356-
eventTracker->record(AllocatorApiEvent::DRAM_EVICT, candidate->getKey(),
1357-
AllocatorApiResult::EVICTED, candidate->getSize(),
1358-
candidate->getConfiguredTTL().count());
1359-
}
1360-
1361-
// check if by releasing the item we intend to, we actually
1362-
// recycle the candidate.
1363-
auto ret = releaseBackToAllocator(*candidate, RemoveContext::kEviction,
1364-
/* isNascent */ false, toRecycle);
1365-
if (ret == ReleaseRes::kRecycled) {
1366-
return toRecycle;
1317+
// If we destroyed the itr to possibly evict and failed, we restart
1318+
// from the beginning again
1319+
if (!itr) {
1320+
itr.resetToBegin();
13671321
}
13681322
}
13691323
return nullptr;
@@ -1507,7 +1461,7 @@ bool CacheAllocator<CacheTrait>::pushToNvmCacheFromRamForTesting(
15071461

15081462
if (handle && nvmCache_ && shouldWriteToNvmCache(*handle) &&
15091463
shouldWriteToNvmCacheExclusive(*handle)) {
1510-
nvmCache_->put(*handle, nvmCache_->createPutToken(handle->getKey()));
1464+
nvmCache_->put(handle, nvmCache_->createPutToken(handle->getKey()));
15111465
return true;
15121466
}
15131467
return false;
@@ -2714,6 +2668,126 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
27142668
}
27152669
}
27162670

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+
27172791
template <typename CacheTrait>
27182792
typename CacheAllocator<CacheTrait>::WriteHandle
27192793
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
@@ -2746,7 +2820,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
27462820
// now that we are the only handle and we actually removed something from
27472821
// the RAM cache, we enqueue it to nvmcache.
27482822
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(item)) {
2749-
nvmCache_->put(*handle, std::move(token));
2823+
nvmCache_->put(handle, std::move(token));
27502824
}
27512825

27522826
return handle;
@@ -2847,7 +2921,7 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
28472921
// the RAM cache, we enqueue it to nvmcache.
28482922
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(*parentHandle)) {
28492923
DCHECK(parentHandle->hasChainedItem());
2850-
nvmCache_->put(*parentHandle, std::move(token));
2924+
nvmCache_->put(parentHandle, std::move(token));
28512925
}
28522926

28532927
return parentHandle;

cachelib/allocator/CacheAllocator.h

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,12 +1664,6 @@ class CacheAllocator : public CacheBase {
16641664
bool removeFromNvm = true,
16651665
bool recordApiEvent = true);
16661666

1667-
// Must be called by the thread which called markForEviction and
1668-
// succeeded. After this call, the item is unlinked from Access and
1669-
// MM Containers. The item is no longer marked as exclusive and it's
1670-
// ref count is 0 - it's available for recycling.
1671-
void unlinkItemForEviction(Item& it);
1672-
16731667
// Implementation to find a suitable eviction from the container. The
16741668
// two parameters together identify a single container.
16751669
//
@@ -1680,6 +1674,25 @@ class CacheAllocator : public CacheBase {
16801674

16811675
using EvictionIterator = typename MMContainer::LockedIterator;
16821676

1677+
// Advance the current iterator and try to evict a regular item
1678+
//
1679+
// @param mmContainer the container to look for evictions.
1680+
// @param itr iterator holding the item
1681+
//
1682+
// @return valid handle to regular item on success. This will be the last
1683+
// handle to the item. On failure an empty handle.
1684+
WriteHandle advanceIteratorAndTryEvictRegularItem(MMContainer& mmContainer,
1685+
EvictionIterator& itr);
1686+
1687+
// Advance the current iterator and try to evict a chained item
1688+
// Iterator may also be reset during the course of this function
1689+
//
1690+
// @param itr iterator holding the item
1691+
//
1692+
// @return valid handle to the parent item on success. This will be the last
1693+
// handle to the item
1694+
WriteHandle advanceIteratorAndTryEvictChainedItem(EvictionIterator& itr);
1695+
16831696
// Deserializer CacheAllocatorMetadata and verify the version
16841697
//
16851698
// @param deserializer Deserializer object

0 commit comments

Comments
 (0)