@@ -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-
12561243template <typename CacheTrait>
12571244typename CacheAllocator<CacheTrait>::Item*
12581245CacheAllocator<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+
27172791template <typename CacheTrait>
27182792typename CacheAllocator<CacheTrait>::WriteHandle
27192793CacheAllocator<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;
0 commit comments