Skip to content

Commit e60eeed

Browse files
committed
Expose 'eviction' and 'moving' states for an item
markForEviction is used only in findEviction and evictForSlabRelease but not for item movement. moveForSlabRelease relies on markMoving(). Only allow to mark item for eviction if ref count is 0. This ensures that after item is marked, eviction cannot fail. This makes it possible to return NULL handle immediately from find if item is marked for eviction. markMoving() does have those restrictions and still allows readers to obtain a handle to a moving item. Also, add option to use combined locking for MMContainer iteration. Pass item ref to NavyCache::put
1 parent 94eaded commit e60eeed

27 files changed

+762
-876
lines changed

cachelib/allocator/CacheAllocator-inl.h

+251-399
Large diffs are not rendered by default.

cachelib/allocator/CacheAllocator.h

+20-47
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,7 @@ class CacheAllocator : public CacheBase {
13081308

13091309
private:
13101310
// wrapper around Item's refcount and active handle tracking
1311-
FOLLY_ALWAYS_INLINE void incRef(Item& it);
1311+
FOLLY_ALWAYS_INLINE bool incRef(Item& it);
13121312
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef(Item& it);
13131313

13141314
// drops the refcount and if needed, frees the allocation back to the memory
@@ -1359,6 +1359,12 @@ class CacheAllocator : public CacheBase {
13591359
bool nascent = false,
13601360
const Item* toRecycle = nullptr);
13611361

1362+
// Must be called by the thread which called markForEviction and
1363+
// succeeded. After this call, the item is unlinked from Access and
1364+
// MM Containers. The item is no longer marked as exclusive and it's
1365+
// ref count is 0 - it's available for recycling.
1366+
void unlinkItemForEviction(Item& it);
1367+
13621368
// acquires an handle on the item. returns an empty handle if it is null.
13631369
// @param it pointer to an item
13641370
// @return WriteHandle return a handle to this item
@@ -1448,17 +1454,17 @@ class CacheAllocator : public CacheBase {
14481454
// @return handle to the parent item if the validations pass
14491455
// otherwise, an empty Handle is returned.
14501456
//
1451-
ReadHandle validateAndGetParentHandleForChainedMoveLocked(
1457+
WriteHandle validateAndGetParentHandleForChainedMoveLocked(
14521458
const ChainedItem& item, const Key& parentKey);
14531459

14541460
// Given an existing item, allocate a new one for the
14551461
// existing one to later be moved into.
14561462
//
1457-
// @param oldItem the item we want to allocate a new item for
1463+
// @param item reference to the item we want to allocate a new item for
14581464
//
14591465
// @return handle to the newly allocated item
14601466
//
1461-
WriteHandle allocateNewItemForOldItem(const Item& oldItem);
1467+
WriteHandle allocateNewItemForOldItem(const Item& item);
14621468

14631469
// internal helper that grabs a refcounted handle to the item. This does
14641470
// not record the access to reflect in the mmContainer.
@@ -1512,7 +1518,7 @@ class CacheAllocator : public CacheBase {
15121518
// callback is responsible for copying the contents and fixing the semantics
15131519
// of chained item.
15141520
//
1515-
// @param oldItem Reference to the item being moved
1521+
// @param oldItem item being moved
15161522
// @param newItemHdl Reference to the handle of the new item being moved into
15171523
//
15181524
// @return true If the move was completed, and the containers were updated
@@ -1662,25 +1668,6 @@ class CacheAllocator : public CacheBase {
16621668

16631669
using EvictionIterator = typename MMContainer::Iterator;
16641670

1665-
// Advance the current iterator and try to evict a regular item
1666-
//
1667-
// @param mmContainer the container to look for evictions.
1668-
// @param itr iterator holding the item
1669-
//
1670-
// @return valid handle to regular item on success. This will be the last
1671-
// handle to the item. On failure an empty handle.
1672-
WriteHandle advanceIteratorAndTryEvictRegularItem(MMContainer& mmContainer,
1673-
EvictionIterator& itr);
1674-
1675-
// Advance the current iterator and try to evict a chained item
1676-
// Iterator may also be reset during the course of this function
1677-
//
1678-
// @param itr iterator holding the item
1679-
//
1680-
// @return valid handle to the parent item on success. This will be the last
1681-
// handle to the item
1682-
WriteHandle advanceIteratorAndTryEvictChainedItem(EvictionIterator& itr);
1683-
16841671
// Deserializer CacheAllocatorMetadata and verify the version
16851672
//
16861673
// @param deserializer Deserializer object
@@ -1756,22 +1743,23 @@ class CacheAllocator : public CacheBase {
17561743

17571744
// @return true when successfully marked as moving,
17581745
// fasle when this item has already been freed
1759-
bool markExclusiveForSlabRelease(const SlabReleaseContext& ctx,
1760-
void* alloc,
1761-
util::Throttler& throttler);
1746+
bool markMovingForSlabRelease(const SlabReleaseContext& ctx,
1747+
void* alloc,
1748+
util::Throttler& throttler);
17621749

17631750
// "Move" (by copying) the content in this item to another memory
17641751
// location by invoking the move callback.
17651752
//
17661753
//
17671754
// @param ctx slab release context
1768-
// @param item old item to be moved elsewhere
1755+
// @param oldItem old item to be moved elsewhere
1756+
// @param handle handle to the item or to it's parent (if chained)
17691757
// @param throttler slow this function down as not to take too much cpu
17701758
//
17711759
// @return true if the item has been moved
17721760
// false if we have exhausted moving attempts
17731761
bool moveForSlabRelease(const SlabReleaseContext& ctx,
1774-
Item& item,
1762+
Item& oldItem,
17751763
util::Throttler& throttler);
17761764

17771765
// "Move" (by copying) the content in this item to another memory
@@ -1794,18 +1782,7 @@ class CacheAllocator : public CacheBase {
17941782
Item& item,
17951783
util::Throttler& throttler);
17961784

1797-
// Helper function to evict a normal item for slab release
1798-
//
1799-
// @return last handle for corresponding to item on success. empty handle on
1800-
// failure. caller can retry if needed.
1801-
WriteHandle evictNormalItemForSlabRelease(Item& item);
1802-
1803-
// Helper function to evict a child item for slab release
1804-
// As a side effect, the parent item is also evicted
1805-
//
1806-
// @return last handle to the parent item of the child on success. empty
1807-
// handle on failure. caller can retry.
1808-
WriteHandle evictChainedItemForSlabRelease(ChainedItem& item);
1785+
typename NvmCacheT::PutToken createPutToken(Item& item);
18091786

18101787
// Helper function to remove a item if expired.
18111788
//
@@ -1927,18 +1904,14 @@ class CacheAllocator : public CacheBase {
19271904
std::optional<bool> saveNvmCache();
19281905
void saveRamCache();
19291906

1930-
static bool itemExclusivePredicate(const Item& item) {
1931-
return item.getRefCount() == 0;
1907+
static bool itemSlabMovePredicate(const Item& item) {
1908+
return item.isMoving() && item.getRefCount() == 0;
19321909
}
19331910

19341911
static bool itemExpiryPredicate(const Item& item) {
19351912
return item.getRefCount() == 1 && item.isExpired();
19361913
}
19371914

1938-
static bool parentEvictForSlabReleasePredicate(const Item& item) {
1939-
return item.getRefCount() == 1 && !item.isExclusive();
1940-
}
1941-
19421915
std::unique_ptr<Deserializer> createDeserializer();
19431916

19441917
// Execute func on each item. `func` can throw exception but must ensure

cachelib/allocator/CacheItem-inl.h

+37-15
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,14 @@ std::string CacheItem<CacheTrait>::toString() const {
148148
return folly::sformat(
149149
"item: "
150150
"memory={}:raw-ref={}:size={}:key={}:hex-key={}:"
151-
"isInMMContainer={}:isAccessible={}:isExclusive={}:references={}:ctime="
151+
"isInMMContainer={}:isAccessible={}:isMarkedForEviction={}:references={"
152+
"}:ctime="
152153
"{}:"
153154
"expTime={}:updateTime={}:isNvmClean={}:isNvmEvicted={}:hasChainedItem="
154155
"{}",
155156
this, getRefCountAndFlagsRaw(), getSize(),
156157
folly::humanify(getKey().str()), folly::hexlify(getKey()),
157-
isInMMContainer(), isAccessible(), isExclusive(), getRefCount(),
158+
isInMMContainer(), isAccessible(), isMarkedForEviction(), getRefCount(),
158159
getCreationTime(), getExpiryTime(), getLastAccessTime(), isNvmClean(),
159160
isNvmEvicted(), hasChainedItem());
160161
}
@@ -217,23 +218,43 @@ bool CacheItem<CacheTrait>::isInMMContainer() const noexcept {
217218
}
218219

219220
template <typename CacheTrait>
220-
bool CacheItem<CacheTrait>::markExclusive() noexcept {
221-
return ref_.markExclusive();
221+
bool CacheItem<CacheTrait>::markForEviction() noexcept {
222+
return ref_.markForEviction();
222223
}
223224

224225
template <typename CacheTrait>
225-
RefcountWithFlags::Value CacheItem<CacheTrait>::unmarkExclusive() noexcept {
226-
return ref_.unmarkExclusive();
226+
RefcountWithFlags::Value CacheItem<CacheTrait>::unmarkForEviction() noexcept {
227+
return ref_.unmarkForEviction();
227228
}
228229

229230
template <typename CacheTrait>
230-
bool CacheItem<CacheTrait>::isExclusive() const noexcept {
231-
return ref_.isExclusive();
231+
bool CacheItem<CacheTrait>::isMarkedForEviction() const noexcept {
232+
return ref_.isMarkedForEviction();
232233
}
233234

234235
template <typename CacheTrait>
235-
bool CacheItem<CacheTrait>::isOnlyExclusive() const noexcept {
236-
return ref_.isOnlyExclusive();
236+
bool CacheItem<CacheTrait>::markForEvictionWhenMoving() {
237+
return ref_.markForEvictionWhenMoving();
238+
}
239+
240+
template <typename CacheTrait>
241+
bool CacheItem<CacheTrait>::markMoving() {
242+
return ref_.markMoving();
243+
}
244+
245+
template <typename CacheTrait>
246+
RefcountWithFlags::Value CacheItem<CacheTrait>::unmarkMoving() noexcept {
247+
return ref_.unmarkMoving();
248+
}
249+
250+
template <typename CacheTrait>
251+
bool CacheItem<CacheTrait>::isMoving() const noexcept {
252+
return ref_.isMoving();
253+
}
254+
255+
template <typename CacheTrait>
256+
bool CacheItem<CacheTrait>::isOnlyMoving() const noexcept {
257+
return ref_.isOnlyMoving();
237258
}
238259

239260
template <typename CacheTrait>
@@ -335,7 +356,7 @@ bool CacheItem<CacheTrait>::updateExpiryTime(uint32_t expiryTimeSecs) noexcept {
335356
// check for moving to make sure we are not updating the expiry time while at
336357
// the same time re-allocating the item with the old state of the expiry time
337358
// in moveRegularItem(). See D6852328
338-
if (isExclusive() || !isInMMContainer() || isChainedItem()) {
359+
if (isMarkedForEviction() || !isInMMContainer() || isChainedItem()) {
339360
return false;
340361
}
341362
// attempt to atomically update the value of expiryTime
@@ -451,13 +472,14 @@ std::string CacheChainedItem<CacheTrait>::toString() const {
451472
return folly::sformat(
452473
"chained item: "
453474
"memory={}:raw-ref={}:size={}:parent-compressed-ptr={}:"
454-
"isInMMContainer={}:isAccessible={}:isExclusive={}:references={}:ctime={}"
475+
"isInMMContainer={}:isAccessible={}:isMarkedForEviction={}:references={}:"
476+
"ctime={}"
455477
":"
456478
"expTime={}:updateTime={}",
457479
this, Item::getRefCountAndFlagsRaw(), Item::getSize(), cPtr.getRaw(),
458-
Item::isInMMContainer(), Item::isAccessible(), Item::isExclusive(),
459-
Item::getRefCount(), Item::getCreationTime(), Item::getExpiryTime(),
460-
Item::getLastAccessTime());
480+
Item::isInMMContainer(), Item::isAccessible(),
481+
Item::isMarkedForEviction(), Item::getRefCount(), Item::getCreationTime(),
482+
Item::getExpiryTime(), Item::getLastAccessTime());
461483
}
462484

463485
template <typename CacheTrait>

cachelib/allocator/CacheItem.h

+41-16
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,17 @@ class CACHELIB_PACKED_ATTR CacheItem {
305305
*/
306306
RefcountWithFlags::Value getRefCountAndFlagsRaw() const noexcept;
307307

308-
FOLLY_ALWAYS_INLINE void incRef() {
309-
if (LIKELY(ref_.incRef())) {
310-
return;
308+
// Increments item's ref count
309+
//
310+
// @return true on success, failure if item is marked as exclusive
311+
// @throw exception::RefcountOverflow on ref count overflow
312+
FOLLY_ALWAYS_INLINE bool incRef() {
313+
try {
314+
return ref_.incRef();
315+
} catch (exception::RefcountOverflow& e) {
316+
throw exception::RefcountOverflow(
317+
folly::sformat("{} item: {}", e.what(), toString()));
311318
}
312-
throw exception::RefcountOverflow(
313-
folly::sformat("Refcount maxed out. item: {}", toString()));
314319
}
315320

316321
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef() {
@@ -344,23 +349,43 @@ class CACHELIB_PACKED_ATTR CacheItem {
344349

345350
/**
346351
* The following two functions corresond to whether or not an item is
347-
* currently in the process of being moved. This happens during a slab
348-
* rebalance, eviction or resize operation.
352+
* currently in the process of being evicted.
349353
*
350-
* An item can only be marked exclusive when `isInMMContainer` returns true.
354+
* An item can only be marked exclusive when `isInMMContainer` returns true
355+
* and item is not already exclusive nor moving and the ref count is 0.
351356
* This operation is atomic.
352357
*
353-
* User can also query if an item "isOnlyExclusive". This returns true only
354-
* if the refcount is 0 and only the exclusive bit is set.
355-
*
356-
* Unmarking exclusive does not depend on `isInMMContainer`.
358+
* Unmarking exclusive does not depend on `isInMMContainer`
357359
* Unmarking exclusive will also return the refcount at the moment of
358360
* unmarking.
359361
*/
360-
bool markExclusive() noexcept;
361-
RefcountWithFlags::Value unmarkExclusive() noexcept;
362-
bool isExclusive() const noexcept;
363-
bool isOnlyExclusive() const noexcept;
362+
bool markForEviction() noexcept;
363+
RefcountWithFlags::Value unmarkForEviction() noexcept;
364+
bool isMarkedForEviction() const noexcept;
365+
366+
/**
367+
* The following functions correspond to whether or not an item is
368+
* currently in the processed of being moved. When moving, ref count
369+
* is always >= 1.
370+
*
371+
* An item can only be marked moving when `isInMMContainer` returns true
372+
* and item is not already exclusive nor moving.
373+
*
374+
* User can also query if an item "isOnlyMoving". This returns true only
375+
* if the refcount is one and only the exclusive bit is set.
376+
*
377+
* Unmarking moving does not depend on `isInMMContainer`
378+
* Unmarking moving will also return the refcount at the moment of
379+
* unmarking.
380+
*/
381+
bool markMoving();
382+
RefcountWithFlags::Value unmarkMoving() noexcept;
383+
bool isMoving() const noexcept;
384+
bool isOnlyMoving() const noexcept;
385+
386+
/** This function attempts to mark item as exclusive.
387+
* Can only be called on the item that is moving.*/
388+
bool markForEvictionWhenMoving();
364389

365390
/**
366391
* Item cannot be marked both chained allocation and

cachelib/allocator/MM2Q-inl.h

+3-28
Original file line numberDiff line numberDiff line change
@@ -241,29 +241,9 @@ bool MM2Q::Container<T, HookPtr>::add(T& node) noexcept {
241241
}
242242

243243
template <typename T, MM2Q::Hook<T> T::*HookPtr>
244-
typename MM2Q::Container<T, HookPtr>::Iterator
245-
MM2Q::Container<T, HookPtr>::getEvictionIterator() const noexcept {
246-
// we cannot use combined critical sections with folly::DistributedMutex here
247-
// because the lock is held for the lifetime of the eviction iterator. In
248-
// other words, the abstraction of the iterator just does not lend itself well
249-
// to combinable critical sections as the user can hold the lock for an
250-
// arbitrary amount of time outside a lambda-friendly piece of code (eg. they
251-
// can return the iterator from functions, pass it to functions, etc)
252-
//
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.
265-
LockHolder l(*lruMutex_);
266-
return Iterator{std::move(l), lru_.rbegin()};
244+
template <typename F>
245+
void MM2Q::Container<T, HookPtr>::withEvictionIterator(F&& fun) {
246+
lruMutex_->lock_combine([this, &fun]() { fun(Iterator{lru_.rbegin()}); });
267247
}
268248

269249
template <typename T, MM2Q::Hook<T> T::*HookPtr>
@@ -460,10 +440,5 @@ void MM2Q::Container<T, HookPtr>::reconfigureLocked(const Time& currTime) {
460440
lruRefreshTime_.store(lruRefreshTime, std::memory_order_relaxed);
461441
}
462442

463-
// Iterator Context Implementation
464-
template <typename T, MM2Q::Hook<T> T::*HookPtr>
465-
MM2Q::Container<T, HookPtr>::Iterator::Iterator(
466-
LockHolder l, const typename LruList::Iterator& iter) noexcept
467-
: LruList::Iterator(iter), l_(std::move(l)) {}
468443
} // namespace cachelib
469444
} // namespace facebook

0 commit comments

Comments
 (0)