Skip to content

Commit 2ffdc5b

Browse files
committed
Use markExclusive only for eviction
(findEviction and evictForSlabRelease) but not for item movement. moveForSlabRelease relies on markMoving(). Only allow to mark item as exclusive 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 as exclusive. 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 93109ff commit 2ffdc5b

26 files changed

+706
-816
lines changed

cachelib/allocator/CacheAllocator-inl.h

+274-409
Large diffs are not rendered by default.

cachelib/allocator/CacheAllocator.h

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

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

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

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

14551461
// Given an existing item, allocate a new one for the
14561462
// existing one to later be moved into.
14571463
//
1458-
// @param oldItem the item we want to allocate a new item for
1464+
// @param item reference to the item we want to allocate a new item for
14591465
//
14601466
// @return handle to the newly allocated item
14611467
//
1462-
WriteHandle allocateNewItemForOldItem(const Item& oldItem);
1468+
WriteHandle allocateNewItemForOldItem(const Item& item);
14631469

14641470
// internal helper that grabs a refcounted handle to the item. This does
14651471
// not record the access to reflect in the mmContainer.
@@ -1513,7 +1519,7 @@ class CacheAllocator : public CacheBase {
15131519
// callback is responsible for copying the contents and fixing the semantics
15141520
// of chained item.
15151521
//
1516-
// @param oldItem Reference to the item being moved
1522+
// @param oldItem item being moved
15171523
// @param newItemHdl Reference to the handle of the new item being moved into
15181524
//
15191525
// @return true If the move was completed, and the containers were updated
@@ -1663,25 +1669,6 @@ class CacheAllocator : public CacheBase {
16631669

16641670
using EvictionIterator = typename MMContainer::Iterator;
16651671

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

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

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

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

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

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

1931-
static bool itemExclusivePredicate(const Item& item) {
1932-
return item.getRefCount() == 0;
1908+
static bool itemSlabMovePredicate(const Item& item) {
1909+
return item.isMoving() && item.getRefCount() == 1;
19331910
}
19341911

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

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

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

cachelib/allocator/CacheItem-inl.h

+22-2
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,28 @@ bool CacheItem<CacheTrait>::isExclusive() const noexcept {
232232
}
233233

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

239259
template <typename CacheTrait>

cachelib/allocator/CacheItem.h

+38-13
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
*/
360362
bool markExclusive() noexcept;
361363
RefcountWithFlags::Value unmarkExclusive() noexcept;
362364
bool isExclusive() const noexcept;
363-
bool isOnlyExclusive() 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 markExclusiveWhenMoving();
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

cachelib/allocator/MM2Q.h

+6-46
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class MM2Q {
6868
enum LruType { Warm, WarmTail, Hot, Cold, ColdTail, NumTypes };
6969

7070
// Config class for MM2Q
71+
// TODO: implement support for useCombinedLockForIterators
7172
struct Config {
7273
// Create from serialized config
7374
explicit Config(SerializationConfigType configState)
@@ -347,48 +348,7 @@ class MM2Q {
347348
Container(const Container&) = delete;
348349
Container& operator=(const Container&) = delete;
349350

350-
// context for iterating the MM container. At any given point of time,
351-
// there can be only one iterator active since we need to lock the LRU for
352-
// iteration. we can support multiple iterators at same time, by using a
353-
// shared ptr in the context for the lock holder in the future.
354-
class Iterator : public LruList::Iterator {
355-
public:
356-
// noncopyable but movable.
357-
Iterator(const Iterator&) = delete;
358-
Iterator& operator=(const Iterator&) = delete;
359-
360-
Iterator(Iterator&&) noexcept = default;
361-
362-
// 1. Invalidate this iterator
363-
// 2. Unlock
364-
void destroy() {
365-
LruList::Iterator::reset();
366-
if (l_.owns_lock()) {
367-
l_.unlock();
368-
}
369-
}
370-
371-
// Reset this iterator to the beginning
372-
void resetToBegin() {
373-
if (!l_.owns_lock()) {
374-
l_.lock();
375-
}
376-
LruList::Iterator::resetToBegin();
377-
}
378-
379-
private:
380-
// private because it's easy to misuse and cause deadlock for MM2Q
381-
Iterator& operator=(Iterator&&) noexcept = default;
382-
383-
// create an lru iterator with the lock being held.
384-
Iterator(LockHolder l, const typename LruList::Iterator& iter) noexcept;
385-
386-
// only the container can create iterators
387-
friend Container<T, HookPtr>;
388-
389-
// lock protecting the validity of the iterator
390-
LockHolder l_;
391-
};
351+
using Iterator = typename LruList::Iterator;
392352

393353
// records the information that the node was accessed. This could bump up
394354
// the node to the head of the lru depending on the time when the node was
@@ -442,10 +402,10 @@ class MM2Q {
442402
// source node already existed.
443403
bool replace(T& oldNode, T& newNode) noexcept;
444404

445-
// Obtain an iterator that start from the tail and can be used
446-
// to search for evictions. This iterator holds a lock to this
447-
// container and only one such iterator can exist at a time
448-
Iterator getEvictionIterator() const noexcept;
405+
// Execute provided function under container lock. Function gets
406+
// iterator passed as parameter.
407+
template <typename F>
408+
void withEvictionIterator(F&& f);
449409

450410
// get the current config as a copy
451411
Config getConfig() const;

0 commit comments

Comments
 (0)