Skip to content

Commit c5e6f2a

Browse files
committed
Synchronize with SlabRelease before eviction
attempts. Mark the item as exclusive before trying to remove from AC or MMContainer.
1 parent 2144b96 commit c5e6f2a

File tree

5 files changed

+188
-42
lines changed

5 files changed

+188
-42
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 148 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,55 +1221,44 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12211221
// Keep searching for a candidate until we were able to evict it
12221222
// or until the search limit has been exhausted
12231223
unsigned int searchTries = 0;
1224+
auto itr = mmContainer.getEvictionIterator();
12241225
while ((config_.evictionSearchTries == 0 ||
1225-
config_.evictionSearchTries > searchTries)) {
1226+
config_.evictionSearchTries > searchTries) &&
1227+
itr) {
12261228
++searchTries;
12271229
(*stats_.evictionAttempts)[pid][cid].inc();
12281230

1229-
Item* toRecycle = nullptr;
1230-
Item* candidate = nullptr;
1231-
1232-
mmContainer.withEvictionIterator(
1233-
[this, &candidate, &toRecycle, &searchTries](auto&& itr) {
1234-
while ((config_.evictionSearchTries == 0 ||
1235-
config_.evictionSearchTries > searchTries) &&
1236-
itr) {
1237-
++searchTries;
1238-
1239-
auto* toRecycle_ = itr.get();
1240-
auto* candidate_ =
1241-
toRecycle_->isChainedItem()
1242-
? &toRecycle_->asChainedItem().getParentItem(compressor_)
1243-
: toRecycle_;
1244-
1245-
// make sure no other thead is evicting the item
1246-
if (candidate_->getRefCount() == 0 && candidate_->markExclusive()) {
1247-
toRecycle = toRecycle_;
1248-
candidate = candidate_;
1249-
return;
1250-
}
1251-
1252-
++itr;
1253-
}
1254-
});
1255-
1256-
if (!toRecycle)
1257-
continue;
1231+
Item* toRecycle = itr.get();
1232+
1233+
Item* candidate =
1234+
toRecycle->isChainedItem()
1235+
? &toRecycle->asChainedItem().getParentItem(compressor_)
1236+
: toRecycle;
12581237

1259-
XDCHECK(toRecycle);
1260-
XDCHECK(candidate);
1238+
// make sure no other thead is evicting the item
1239+
if (candidate->getRefCount() != 0 || !candidate->markExclusive()) {
1240+
++itr;
1241+
continue;
1242+
}
12611243

12621244
// for chained items, the ownership of the parent can change. We try to
12631245
// evict what we think as parent and see if the eviction of parent
12641246
// recycles the child we intend to.
12651247
{
1266-
auto toReleaseHandle = evictNormalItem(*candidate);
1248+
auto toReleaseHandle =
1249+
itr->isChainedItem()
1250+
? advanceIteratorAndTryEvictChainedItem(itr)
1251+
: advanceIteratorAndTryEvictRegularItem(mmContainer, itr);
12671252
// destroy toReleseHandle. The item won't be release to allocator
12681253
// since we marked it as exclusive.
12691254
}
1270-
const auto ref = candidate->unmarkExclusive();
12711255

1256+
const auto ref = candidate->unmarkExclusive();
12721257
if (ref == 0u) {
1258+
// Invalidate iterator since later on we may use this mmContainer
1259+
// again, which cannot be done unless we drop this iterator
1260+
itr.destroy();
1261+
12731262
// recycle the item. it's safe to do so, even if toReleaseHandle was
12741263
// NULL. If `ref` == 0 then it means that we are the last holder of
12751264
// that item.
@@ -1299,6 +1288,8 @@ CacheAllocator<CacheTrait>::findEviction(PoolId pid, ClassId cid) {
12991288
stats_.evictFailAC.inc();
13001289
}
13011290
}
1291+
1292+
itr.resetToBegin();
13021293
}
13031294
return nullptr;
13041295
}
@@ -2589,7 +2580,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
25892580
auto owningHandle =
25902581
item.isChainedItem()
25912582
? evictChainedItemForSlabRelease(item.asChainedItem())
2592-
: evictNormalItem(item);
2583+
: evictNormalItemForSlabRelease(item);
25932584

25942585
// we managed to evict the corresponding owner of the item and have the
25952586
// last handle for the owner.
@@ -2644,7 +2635,128 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
26442635

26452636
template <typename CacheTrait>
26462637
typename CacheAllocator<CacheTrait>::WriteHandle
2647-
CacheAllocator<CacheTrait>::evictNormalItem(Item& item) {
2638+
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
2639+
MMContainer& mmContainer, EvictionIterator& itr) {
2640+
// we should flush this to nvmcache if it is not already present in nvmcache
2641+
// and the item is not expired.
2642+
Item& item = *itr;
2643+
const bool evictToNvmCache = shouldWriteToNvmCache(item);
2644+
2645+
auto token = evictToNvmCache ? nvmCache_->createPutToken(item.getKey())
2646+
: typename NvmCacheT::PutToken{};
2647+
2648+
// record the in-flight eviciton. If not, we move on to next item to avoid
2649+
// stalling eviction.
2650+
if (evictToNvmCache && !token.isValid()) {
2651+
++itr;
2652+
stats_.evictFailConcurrentFill.inc();
2653+
return WriteHandle{};
2654+
}
2655+
2656+
// If there are other accessors, we should abort. Acquire a handle here since
2657+
// if we remove the item from both access containers and mm containers
2658+
// below, we will need a handle to ensure proper cleanup in case we end up
2659+
// not evicting this item
2660+
auto evictHandle = accessContainer_->removeIf(item, &itemExclusivePredicate);
2661+
if (!evictHandle) {
2662+
++itr;
2663+
stats_.evictFailAC.inc();
2664+
return evictHandle;
2665+
}
2666+
2667+
mmContainer.remove(itr);
2668+
XDCHECK_EQ(reinterpret_cast<uintptr_t>(evictHandle.get()),
2669+
reinterpret_cast<uintptr_t>(&item));
2670+
XDCHECK(!evictHandle->isInMMContainer());
2671+
XDCHECK(!evictHandle->isAccessible());
2672+
2673+
// Invalidate iterator since later on if we are not evicting this
2674+
// item, we may need to rely on the handle we created above to ensure
2675+
// proper cleanup if the item's raw refcount has dropped to 0.
2676+
// And since this item may be a parent item that has some child items
2677+
// in this very same mmContainer, we need to make sure we drop this
2678+
// exclusive iterator so we can gain access to it when we're cleaning
2679+
// up the child items
2680+
itr.destroy();
2681+
2682+
// Ensure that there are no accessors after removing from the access
2683+
// container
2684+
XDCHECK(evictHandle->getRefCount() == 1);
2685+
2686+
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(item)) {
2687+
XDCHECK(token.isValid());
2688+
nvmCache_->put(evictHandle, std::move(token));
2689+
}
2690+
return evictHandle;
2691+
}
2692+
2693+
template <typename CacheTrait>
2694+
typename CacheAllocator<CacheTrait>::WriteHandle
2695+
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
2696+
EvictionIterator& itr) {
2697+
XDCHECK(itr->isChainedItem());
2698+
2699+
ChainedItem* candidate = &itr->asChainedItem();
2700+
++itr;
2701+
2702+
// The parent could change at any point through transferChain. However, if
2703+
// that happens, we would realize that the releaseBackToAllocator return
2704+
// kNotRecycled and we would try another chained item, leading to transient
2705+
// failure.
2706+
auto& parent = candidate->getParentItem(compressor_);
2707+
2708+
const bool evictToNvmCache = shouldWriteToNvmCache(parent);
2709+
2710+
auto token = evictToNvmCache ? nvmCache_->createPutToken(parent.getKey())
2711+
: typename NvmCacheT::PutToken{};
2712+
2713+
// if token is invalid, return. iterator is already advanced.
2714+
if (evictToNvmCache && !token.isValid()) {
2715+
++itr;
2716+
stats_.evictFailConcurrentFill.inc();
2717+
return WriteHandle{};
2718+
}
2719+
2720+
// check if the parent exists in the hashtable and refcount is drained.
2721+
auto parentHandle =
2722+
accessContainer_->removeIf(parent, &itemExclusivePredicate);
2723+
if (!parentHandle) {
2724+
stats_.evictFailParentAC.inc();
2725+
return parentHandle;
2726+
}
2727+
2728+
// Invalidate iterator since later on we may use the mmContainer
2729+
// associated with this iterator which cannot be done unless we
2730+
// drop this iterator
2731+
//
2732+
// This must be done once we know the parent is not nullptr.
2733+
// Since we can very well be the last holder of this parent item,
2734+
// which may have a chained item that is linked in this MM container.
2735+
itr.destroy();
2736+
2737+
// Ensure we have the correct parent and we're the only user of the
2738+
// parent, then free it from access container. Otherwise, we abort
2739+
XDCHECK_EQ(reinterpret_cast<uintptr_t>(&parent),
2740+
reinterpret_cast<uintptr_t>(parentHandle.get()));
2741+
XDCHECK_EQ(1u, parent.getRefCount());
2742+
2743+
removeFromMMContainer(*parentHandle);
2744+
2745+
XDCHECK(!parent.isInMMContainer());
2746+
XDCHECK(!parent.isAccessible());
2747+
2748+
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(*parentHandle)) {
2749+
XDCHECK(token.isValid());
2750+
XDCHECK(parentHandle->hasChainedItem());
2751+
nvmCache_->put(parentHandle, std::move(token));
2752+
}
2753+
2754+
return parentHandle;
2755+
}
2756+
2757+
template <typename CacheTrait>
2758+
typename CacheAllocator<CacheTrait>::WriteHandle
2759+
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
26482760
XDCHECK(item.isExclusive());
26492761

26502762
if (item.isOnlyExclusive()) {
@@ -2841,7 +2953,6 @@ bool CacheAllocator<CacheTrait>::markExclusiveForSlabRelease(
28412953
itemFreed = true;
28422954

28432955
if (shutDownInProgress_) {
2844-
XDCHECK(!static_cast<Item*>(alloc)->isExclusive());
28452956
allocator_->abortSlabRelease(ctx);
28462957
throw exception::SlabReleaseAborted(
28472958
folly::sformat("Slab Release aborted while still trying to mark"

cachelib/allocator/CacheAllocator.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1661,6 +1661,25 @@ class CacheAllocator : public CacheBase {
16611661

16621662
using EvictionIterator = typename MMContainer::Iterator;
16631663

1664+
// Advance the current iterator and try to evict a regular item
1665+
//
1666+
// @param mmContainer the container to look for evictions.
1667+
// @param itr iterator holding the item
1668+
//
1669+
// @return valid handle to regular item on success. This will be the last
1670+
// handle to the item. On failure an empty handle.
1671+
WriteHandle advanceIteratorAndTryEvictRegularItem(MMContainer& mmContainer,
1672+
EvictionIterator& itr);
1673+
1674+
// Advance the current iterator and try to evict a chained item
1675+
// Iterator may also be reset during the course of this function
1676+
//
1677+
// @param itr iterator holding the item
1678+
//
1679+
// @return valid handle to the parent item on success. This will be the last
1680+
// handle to the item
1681+
WriteHandle advanceIteratorAndTryEvictChainedItem(EvictionIterator& itr);
1682+
16641683
// Deserializer CacheAllocatorMetadata and verify the version
16651684
//
16661685
// @param deserializer Deserializer object
@@ -1778,7 +1797,7 @@ class CacheAllocator : public CacheBase {
17781797
//
17791798
// @return last handle for corresponding to item on success. empty handle on
17801799
// failure. caller can retry if needed.
1781-
WriteHandle evictNormalItem(Item& item);
1800+
WriteHandle evictNormalItemForSlabRelease(Item& item);
17821801

17831802
// Helper function to evict a child item for slab release
17841803
// As a side effect, the parent item is also evicted

cachelib/allocator/CacheItem.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,9 @@ class CACHELIB_PACKED_ATTR CacheItem {
353353
* User can also query if an item "isOnlyExclusive". This returns true only
354354
* if the refcount is 0 and only the exclusive bit is set.
355355
*
356-
* Unmarking exclusive does not depend on `isInMMContainer`
356+
* Unmarking exclusive does not depend on `isInMMContainer`.
357+
* Unmarking exclusive will also return the refcount at the moment of
358+
* unmarking.
357359
*/
358360
bool markExclusive() noexcept;
359361
RefcountWithFlags::Value unmarkExclusive() noexcept;

cachelib/allocator/MM2Q-inl.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,18 @@ MM2Q::Container<T, HookPtr>::getEvictionIterator() const noexcept {
250250
// arbitrary amount of time outside a lambda-friendly piece of code (eg. they
251251
// can return the iterator from functions, pass it to functions, etc)
252252
//
253-
// to get advantage of combining, use withEvictionIterator
253+
// it would be theoretically possible to refactor this interface into
254+
// something like the following to allow combining
255+
//
256+
// mm2q.withEvictionIterator([&](auto iterator) {
257+
// // user code
258+
// });
259+
//
260+
// at the time of writing it is unclear if the gains from combining are
261+
// reasonable justification for the codemod required to achieve combinability
262+
// as we don't expect this critical section to be the hotspot in user code.
263+
// This is however subject to change at some time in the future as and when
264+
// this assertion becomes false.
254265
LockHolder l(*lruMutex_);
255266
return Iterator{std::move(l), lru_.rbegin()};
256267
}

cachelib/allocator/Refcount.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
256256
* User can also query if an item "isOnlyExclusive". This returns true only
257257
* if the refcount is 0 and only the exclusive bit is set.
258258
*
259-
* Unmarking exclusive does not depend on `isInMMContainer`
259+
* Unmarking exclusive does not depend on `isInMMContainer`.
260+
* Unmarking exclusive will also return the refcount at the moment of
261+
* unmarking.
260262
*/
261263
bool markExclusive() noexcept {
262264
Value bitMask = getAdminRef<kExclusive>();
@@ -298,7 +300,8 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
298300
bool isOnlyExclusive() const noexcept {
299301
// An item is only exclusive when its refcount is zero and only the
300302
// exclusive bit among all the control bits is set. This indicates an item
301-
// is already on its way out of cache and does not need to be moved.
303+
// is exclusive to the current thread. No other thread is allowed to
304+
// do anything with it.
302305
auto ref = getRefWithAccessAndAdmin();
303306
bool anyOtherBitSet = ref & ~getAdminRef<kExclusive>();
304307
if (anyOtherBitSet) {

0 commit comments

Comments
 (0)