@@ -1241,83 +1241,142 @@ bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
1241
1241
}
1242
1242
1243
1243
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 ;
1246
1265
auto & mmContainer = getMMContainer (pid, cid);
1247
1266
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 ;
1269
1274
}
1270
1275
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 ;
1298
1297
}
1299
1298
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 ;
1304
1308
}
1305
1309
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);
1312
1324
}
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 ();
1313
1364
} else {
1314
- XDCHECK (!evictionSuccessful );
1365
+ (*stats_. regularItemEvictions )[pid][cid]. inc ( );
1315
1366
}
1316
1367
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;
1321
1380
}
1322
1381
}
1323
1382
return nullptr ;
@@ -1461,7 +1520,7 @@ bool CacheAllocator<CacheTrait>::pushToNvmCacheFromRamForTesting(
1461
1520
1462
1521
if (handle && nvmCache_ && shouldWriteToNvmCache (*handle) &&
1463
1522
shouldWriteToNvmCacheExclusive (*handle)) {
1464
- nvmCache_->put (handle, nvmCache_->createPutToken (handle->getKey ()));
1523
+ nvmCache_->put (* handle, nvmCache_->createPutToken (handle->getKey ()));
1465
1524
return true ;
1466
1525
}
1467
1526
return false ;
@@ -2668,126 +2727,6 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
2668
2727
}
2669
2728
}
2670
2729
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
-
2791
2730
template <typename CacheTrait>
2792
2731
typename CacheAllocator<CacheTrait>::WriteHandle
2793
2732
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
@@ -2820,7 +2759,7 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
2820
2759
// now that we are the only handle and we actually removed something from
2821
2760
// the RAM cache, we enqueue it to nvmcache.
2822
2761
if (evictToNvmCache && shouldWriteToNvmCacheExclusive (item)) {
2823
- nvmCache_->put (handle, std::move (token));
2762
+ nvmCache_->put (* handle, std::move (token));
2824
2763
}
2825
2764
2826
2765
return handle;
@@ -2921,7 +2860,7 @@ CacheAllocator<CacheTrait>::evictChainedItemForSlabRelease(ChainedItem& child) {
2921
2860
// the RAM cache, we enqueue it to nvmcache.
2922
2861
if (evictToNvmCache && shouldWriteToNvmCacheExclusive (*parentHandle)) {
2923
2862
DCHECK (parentHandle->hasChainedItem ());
2924
- nvmCache_->put (parentHandle, std::move (token));
2863
+ nvmCache_->put (* parentHandle, std::move (token));
2925
2864
}
2926
2865
2927
2866
return parentHandle;
0 commit comments