@@ -1240,6 +1240,19 @@ 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 (it.getRefCount () == 0 );
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 (ref == 0u );
1254+ }
1255+
12431256template <typename CacheTrait>
12441257typename CacheAllocator<CacheTrait>::Item*
12451258CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
@@ -1248,76 +1261,102 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12481261 // Keep searching for a candidate until we were able to evict it
12491262 // or until the search limit has been exhausted
12501263 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 ;
1269- }
1270-
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 ();
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 ;
12981277 }
12991278
1300- if (auto eventTracker = getEventTracker ()) {
1301- eventTracker->record (AllocatorApiEvent::DRAM_EVICT, candidate->getKey (),
1302- AllocatorApiResult::EVICTED, candidate->getSize (),
1303- candidate->getConfiguredTTL ().count ());
1304- }
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+ if (evictToNvmCache)
1293+ token = nvmCache_->createPutToken (candidate_->getKey ());
1294+
1295+ if (evictToNvmCache && !token.isValid ()) {
1296+ stats_.evictFailConcurrentFill .inc ();
1297+ } else if (candidate_->markForEviction ()) {
1298+ XDCHECK (candidate_->isMarkedForEviction ());
1299+ // markForEviction to make sure no other thead is evicting the item
1300+ // nor holding a handle to that item
1301+ toRecycle = toRecycle_;
1302+ candidate = candidate_;
1303+
1304+ // Check if parent changed for chained items - if yes, we cannot
1305+ // remove the child from the mmContainer as we will not be evicting
1306+ // it. We could abort right here, but we need to cleanup in case
1307+ // unmarkForEviction() returns 0 - so just go through normal path.
1308+ if (!toRecycle_->isChainedItem () ||
1309+ &toRecycle->asChainedItem ().getParentItem (compressor_) ==
1310+ candidate)
1311+ mmContainer.remove (itr);
1312+ return ;
1313+ } else {
1314+ if (candidate_->hasChainedItem ()) {
1315+ stats_.evictFailParentAC .inc ();
1316+ } else {
1317+ stats_.evictFailAC .inc ();
1318+ }
1319+ }
13051320
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;
1321+ ++itr;
1322+ XDCHECK (toRecycle == nullptr );
1323+ XDCHECK (candidate == nullptr );
13121324 }
1325+ });
1326+
1327+ if (!toRecycle)
1328+ continue ;
1329+
1330+ XDCHECK (toRecycle);
1331+ XDCHECK (candidate);
1332+
1333+ unlinkItemForEviction (*candidate);
1334+
1335+ if (token.isValid () && shouldWriteToNvmCacheExclusive (*candidate)) {
1336+ nvmCache_->put (*candidate, std::move (token));
1337+ }
1338+
1339+ // recycle the item. it's safe to do so, even if toReleaseHandle was
1340+ // NULL. If `ref` == 0 then it means that we are the last holder of
1341+ // that item.
1342+ if (candidate->hasChainedItem ()) {
1343+ (*stats_.chainedItemEvictions )[pid][cid].inc ();
13131344 } else {
1314- XDCHECK (!evictionSuccessful);
1345+ (*stats_.regularItemEvictions )[pid][cid].inc ();
1346+ }
1347+
1348+ if (auto eventTracker = getEventTracker ()) {
1349+ eventTracker->record (AllocatorApiEvent::DRAM_EVICT, candidate->getKey (),
1350+ AllocatorApiResult::EVICTED, candidate->getSize (),
1351+ candidate->getConfiguredTTL ().count ());
13151352 }
13161353
1317- // If we destroyed the itr to possibly evict and failed, we restart
1318- // from the beginning again
1319- if (!itr) {
1320- itr.resetToBegin ();
1354+ // check if by releasing the item we intend to, we actually
1355+ // recycle the candidate.
1356+ auto ret = releaseBackToAllocator (*candidate, RemoveContext::kEviction ,
1357+ /* isNascent */ false , toRecycle);
1358+ if (ret == ReleaseRes::kRecycled ) {
1359+ return toRecycle;
13211360 }
13221361 }
13231362 return nullptr ;
@@ -2660,126 +2699,6 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26602699 }
26612700}
26622701
2663- template <typename CacheTrait>
2664- typename CacheAllocator<CacheTrait>::WriteHandle
2665- CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
2666- MMContainer& mmContainer, EvictionIterator& itr) {
2667- // we should flush this to nvmcache if it is not already present in nvmcache
2668- // and the item is not expired.
2669- Item& item = *itr;
2670- const bool evictToNvmCache = shouldWriteToNvmCache (item);
2671-
2672- auto token = evictToNvmCache ? nvmCache_->createPutToken (item.getKey ())
2673- : typename NvmCacheT::PutToken{};
2674-
2675- // record the in-flight eviciton. If not, we move on to next item to avoid
2676- // stalling eviction.
2677- if (evictToNvmCache && !token.isValid ()) {
2678- ++itr;
2679- stats_.evictFailConcurrentFill .inc ();
2680- return WriteHandle{};
2681- }
2682-
2683- // If there are other accessors, we should abort. Acquire a handle here since
2684- // if we remove the item from both access containers and mm containers
2685- // below, we will need a handle to ensure proper cleanup in case we end up
2686- // not evicting this item
2687- auto evictHandle = accessContainer_->removeIf (item, &itemExclusivePredicate);
2688- if (!evictHandle) {
2689- ++itr;
2690- stats_.evictFailAC .inc ();
2691- return evictHandle;
2692- }
2693-
2694- mmContainer.remove (itr);
2695- XDCHECK_EQ (reinterpret_cast <uintptr_t >(evictHandle.get ()),
2696- reinterpret_cast <uintptr_t >(&item));
2697- XDCHECK (!evictHandle->isInMMContainer ());
2698- XDCHECK (!evictHandle->isAccessible ());
2699-
2700- // Invalidate iterator since later on if we are not evicting this
2701- // item, we may need to rely on the handle we created above to ensure
2702- // proper cleanup if the item's raw refcount has dropped to 0.
2703- // And since this item may be a parent item that has some child items
2704- // in this very same mmContainer, we need to make sure we drop this
2705- // exclusive iterator so we can gain access to it when we're cleaning
2706- // up the child items
2707- itr.destroy ();
2708-
2709- // Ensure that there are no accessors after removing from the access
2710- // container
2711- XDCHECK (evictHandle->getRefCount () == 1 );
2712-
2713- if (evictToNvmCache && shouldWriteToNvmCacheExclusive (item)) {
2714- XDCHECK (token.isValid ());
2715- nvmCache_->put (*evictHandle, std::move (token));
2716- }
2717- return evictHandle;
2718- }
2719-
2720- template <typename CacheTrait>
2721- typename CacheAllocator<CacheTrait>::WriteHandle
2722- CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
2723- EvictionIterator& itr) {
2724- XDCHECK (itr->isChainedItem ());
2725-
2726- ChainedItem* candidate = &itr->asChainedItem ();
2727- ++itr;
2728-
2729- // The parent could change at any point through transferChain. However, if
2730- // that happens, we would realize that the releaseBackToAllocator return
2731- // kNotRecycled and we would try another chained item, leading to transient
2732- // failure.
2733- auto & parent = candidate->getParentItem (compressor_);
2734-
2735- const bool evictToNvmCache = shouldWriteToNvmCache (parent);
2736-
2737- auto token = evictToNvmCache ? nvmCache_->createPutToken (parent.getKey ())
2738- : typename NvmCacheT::PutToken{};
2739-
2740- // if token is invalid, return. iterator is already advanced.
2741- if (evictToNvmCache && !token.isValid ()) {
2742- stats_.evictFailConcurrentFill .inc ();
2743- return WriteHandle{};
2744- }
2745-
2746- // check if the parent exists in the hashtable and refcount is drained.
2747- auto parentHandle =
2748- accessContainer_->removeIf (parent, &itemExclusivePredicate);
2749- if (!parentHandle) {
2750- stats_.evictFailParentAC .inc ();
2751- return parentHandle;
2752- }
2753-
2754- // Invalidate iterator since later on we may use the mmContainer
2755- // associated with this iterator which cannot be done unless we
2756- // drop this iterator
2757- //
2758- // This must be done once we know the parent is not nullptr.
2759- // Since we can very well be the last holder of this parent item,
2760- // which may have a chained item that is linked in this MM container.
2761- itr.destroy ();
2762-
2763- // Ensure we have the correct parent and we're the only user of the
2764- // parent, then free it from access container. Otherwise, we abort
2765- XDCHECK_EQ (reinterpret_cast <uintptr_t >(&parent),
2766- reinterpret_cast <uintptr_t >(parentHandle.get ()));
2767- XDCHECK_EQ (1u , parent.getRefCount ());
2768-
2769- removeFromMMContainer (*parentHandle);
2770-
2771- XDCHECK (!parent.isInMMContainer ());
2772- XDCHECK (!parent.isAccessible ());
2773-
2774- if (evictToNvmCache && shouldWriteToNvmCacheExclusive (*parentHandle)) {
2775- XDCHECK (token.isValid ());
2776- XDCHECK (parentHandle->hasChainedItem ());
2777- nvmCache_->put (*parentHandle, std::move (token));
2778- }
2779-
2780- return parentHandle;
2781- }
2782-
27832702template <typename CacheTrait>
27842703typename CacheAllocator<CacheTrait>::WriteHandle
27852704CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
0 commit comments