@@ -1241,83 +1241,142 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
12411241}
12421242
12431243template <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-
27912730template <typename CacheTrait>
27922731typename CacheAllocator<CacheTrait>::WriteHandle
27932732CacheAllocator<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