Skip to content

Commit ed069fa

Browse files
committed
enable combined locking with exclusive bit
2 parents 9bb4db8 + ccd35ab commit ed069fa

26 files changed

+782
-733
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 272 additions & 311 deletions
Large diffs are not rendered by default.

cachelib/allocator/CacheAllocator.h

Lines changed: 64 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,7 +1389,7 @@ class CacheAllocator : public CacheBase {
13891389
double slabsApproxFreePercentage(TierId tid) const;
13901390

13911391
// wrapper around Item's refcount and active handle tracking
1392-
FOLLY_ALWAYS_INLINE void incRef(Item& it);
1392+
FOLLY_ALWAYS_INLINE bool incRef(Item& it);
13931393
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef(Item& it);
13941394

13951395
// drops the refcount and if needed, frees the allocation back to the memory
@@ -1440,6 +1440,12 @@ class CacheAllocator : public CacheBase {
14401440
bool nascent = false,
14411441
const Item* toRecycle = nullptr);
14421442

1443+
// Must be called by the thread which called markExclusive and
1444+
// succeeded. After this call, the item is unlinked from Access and
1445+
// MM Containers. The item is no longer marked as exclusive and it's
1446+
// ref count is 0 - it's available for recycling.
1447+
void unlinkItemExclusive(Item& it);
1448+
14431449
// acquires an handle on the item. returns an empty handle if it is null.
14441450
// @param it pointer to an item
14451451
// @return WriteHandle return a handle to this item
@@ -1550,17 +1556,17 @@ class CacheAllocator : public CacheBase {
15501556
// @return handle to the parent item if the validations pass
15511557
// otherwise, an empty Handle is returned.
15521558
//
1553-
ReadHandle validateAndGetParentHandleForChainedMoveLocked(
1559+
WriteHandle validateAndGetParentHandleForChainedMoveLocked(
15541560
const ChainedItem& item, const Key& parentKey);
15551561

15561562
// Given an existing item, allocate a new one for the
15571563
// existing one to later be moved into.
15581564
//
1559-
// @param oldItem the item we want to allocate a new item for
1565+
// @param item reference to the item we want to allocate a new item for
15601566
//
15611567
// @return handle to the newly allocated item
15621568
//
1563-
WriteHandle allocateNewItemForOldItem(const Item& oldItem);
1569+
WriteHandle allocateNewItemForOldItem(const Item& item);
15641570

15651571
// internal helper that grabs a refcounted handle to the item. This does
15661572
// not record the access to reflect in the mmContainer.
@@ -1614,18 +1620,17 @@ class CacheAllocator : public CacheBase {
16141620
// @param oldItem Reference to the item being moved
16151621
// @param newItemHdl Reference to the handle of the new item being moved into
16161622
//
1617-
// @return the handle to the oldItem if the move was completed
1618-
// and the oldItem can be recycled.
1619-
// Otherwise an empty handle is returned.
1623+
// @return true If the move was completed, and the containers were updated
1624+
// successfully.
16201625
template <typename P>
1621-
WriteHandle moveRegularItemWithSync(Item& oldItem, WriteHandle& newItemHdl, P&& predicate);
1626+
bool moveRegularItemWithSync(Item& oldItem, WriteHandle& newItemHdl, P&& predicate);
16221627

16231628
// Moves a regular item to a different slab. This should only be used during
16241629
// slab release after the item's exclusive bit has been set. The user supplied
16251630
// callback is responsible for copying the contents and fixing the semantics
16261631
// of chained item.
16271632
//
1628-
// @param oldItem Reference to the item being moved
1633+
// @param oldItem item being moved
16291634
// @param newItemHdl Reference to the handle of the new item being moved into
16301635
//
16311636
// @return true If the move was completed, and the containers were updated
@@ -1787,7 +1792,7 @@ class CacheAllocator : public CacheBase {
17871792
//
17881793
// @return valid handle to the item. This will be the last
17891794
// handle to the item. On failure an empty handle.
1790-
WriteHandle tryEvictToNextMemoryTier(TierId tid, PoolId pid, Item& item, bool fromBgThread);
1795+
bool tryEvictToNextMemoryTier(TierId tid, PoolId pid, Item& item, bool fromBgThread);
17911796

17921797
bool tryPromoteToNextMemoryTier(TierId tid, PoolId pid, Item& item, bool fromBgThread);
17931798

@@ -1799,7 +1804,7 @@ class CacheAllocator : public CacheBase {
17991804
//
18001805
// @return valid handle to the item. This will be the last
18011806
// handle to the item. On failure an empty handle.
1802-
WriteHandle tryEvictToNextMemoryTier(Item& item, bool fromBgThread);
1807+
bool tryEvictToNextMemoryTier(Item& item, bool fromBgThread);
18031808

18041809
size_t memoryTierSize(TierId tid) const;
18051810

@@ -1878,22 +1883,23 @@ class CacheAllocator : public CacheBase {
18781883

18791884
// @return true when successfully marked as moving,
18801885
// fasle when this item has already been freed
1881-
bool markExclusiveForSlabRelease(const SlabReleaseContext& ctx,
1882-
void* alloc,
1883-
util::Throttler& throttler);
1886+
bool markMovingForSlabRelease(const SlabReleaseContext& ctx,
1887+
void* alloc,
1888+
util::Throttler& throttler);
18841889

18851890
// "Move" (by copying) the content in this item to another memory
18861891
// location by invoking the move callback.
18871892
//
18881893
//
18891894
// @param ctx slab release context
1890-
// @param item old item to be moved elsewhere
1895+
// @param oldItem old item to be moved elsewhere
1896+
// @param handle handle to the item or to it's parent (if chained)
18911897
// @param throttler slow this function down as not to take too much cpu
18921898
//
18931899
// @return true if the item has been moved
18941900
// false if we have exhausted moving attempts
18951901
bool moveForSlabRelease(const SlabReleaseContext& ctx,
1896-
Item& item,
1902+
Item& oldItem,
18971903
util::Throttler& throttler);
18981904

18991905
// "Move" (by copying) the content in this item to another memory
@@ -1929,6 +1935,8 @@ class CacheAllocator : public CacheBase {
19291935
// handle on failure. caller can retry.
19301936
WriteHandle evictChainedItemForSlabRelease(ChainedItem& item);
19311937

1938+
typename NvmCacheT::PutToken createPutToken(Item& item);
1939+
19321940
// Helper function to remove a item if predicates is true.
19331941
//
19341942
// @return last handle to the item on success. empty handle on failure.
@@ -1966,8 +1974,10 @@ class CacheAllocator : public CacheBase {
19661974
candidates.reserve(batch);
19671975

19681976
size_t tries = 0;
1969-
mmContainer.withEvictionIterator([&tries, &candidates, &batch, this](auto &&itr){
1970-
while (candidates.size() < batch && (config_.maxEvictionPromotionHotness == 0 || tries < config_.maxEvictionPromotionHotness) && itr) {
1977+
mmContainer.withEvictionIterator([&tries, &candidates, &batch, &mmContainer, this](auto &&itr) {
1978+
while (candidates.size() < batch &&
1979+
(config_.maxEvictionPromotionHotness == 0 || tries < config_.maxEvictionPromotionHotness) &&
1980+
itr) {
19711981
tries++;
19721982
Item* candidate = itr.get();
19731983
XDCHECK(candidate);
@@ -1976,7 +1986,8 @@ class CacheAllocator : public CacheBase {
19761986
throw std::runtime_error("Not supported for chained items");
19771987
}
19781988

1979-
if (candidate->getRefCount() == 0 && candidate->markExclusive()) {
1989+
if (candidate->markExclusive()) {
1990+
mmContainer.remove(itr);
19801991
candidates.push_back(candidate);
19811992
}
19821993

@@ -1985,37 +1996,29 @@ class CacheAllocator : public CacheBase {
19851996
});
19861997

19871998
for (Item *candidate : candidates) {
1988-
{
1989-
auto toReleaseHandle =
1990-
evictNormalItem(*candidate,
1991-
true /* skipIfTokenInvalid */, true /* from BG thread */);
1992-
// destroy toReleseHandle. The item won't be release to allocator
1993-
// since we marked it as exclusive.
1999+
auto evictedToNext = tryEvictToNextMemoryTier(*candidate, true /* from BgThread */);
2000+
XDCHECK(evictedToNext);
2001+
if (evictedToNext) {
2002+
auto ref = candidate->unmarkExclusive();
2003+
XDCHECK(ref == 0u);
2004+
evictions++;
2005+
} else {
2006+
unlinkItemExclusive(*candidate);
19942007
}
1995-
auto ref = candidate->unmarkExclusive();
1996-
1997-
if (ref == 0u) {
1998-
if (candidate->hasChainedItem()) {
1999-
(*stats_.chainedItemEvictions)[pid][cid].inc();
2000-
} else {
2001-
(*stats_.regularItemEvictions)[pid][cid].inc();
2002-
}
2003-
2004-
evictions++;
2005-
// it's safe to recycle the item here as there are no more
2006-
// references and the item could not been marked as moving
2007-
// by other thread since it's detached from MMContainer.
2008-
auto res = releaseBackToAllocator(*candidate, RemoveContext::kEviction,
2009-
/* isNascent */ false);
2010-
XDCHECK(res == ReleaseRes::kReleased);
2008+
XDCHECK(!candidate->isExclusive() && !candidate->isMoving());
20112009

2010+
if (candidate->hasChainedItem()) {
2011+
(*stats_.chainedItemEvictions)[pid][cid].inc();
20122012
} else {
2013-
if (candidate->hasChainedItem()) {
2014-
stats_.evictFailParentAC.inc();
2015-
} else {
2016-
stats_.evictFailAC.inc();
2017-
}
2013+
(*stats_.regularItemEvictions)[pid][cid].inc();
20182014
}
2015+
2016+
// it's safe to recycle the item here as there are no more
2017+
// references and the item could not been marked as moving
2018+
// by other thread since it's detached from MMContainer.
2019+
auto res = releaseBackToAllocator(*candidate, RemoveContext::kEviction,
2020+
/* isNascent */ false);
2021+
XDCHECK(res == ReleaseRes::kReleased);
20192022
}
20202023
return evictions;
20212024
}
@@ -2028,7 +2031,7 @@ class CacheAllocator : public CacheBase {
20282031

20292032
size_t tries = 0;
20302033

2031-
mmContainer.withPromotionIterator([&tries, &candidates, &batch, this](auto &&itr){
2034+
mmContainer.withPromotionIterator([&tries, &candidates, &batch, &mmContainer, this](auto &&itr){
20322035
while (candidates.size() < batch && (config_.maxEvictionPromotionHotness == 0 || tries < config_.maxEvictionPromotionHotness) && itr) {
20332036
tries++;
20342037
Item* candidate = itr.get();
@@ -2038,10 +2041,10 @@ class CacheAllocator : public CacheBase {
20382041
throw std::runtime_error("Not supported for chained items");
20392042
}
20402043

2041-
20422044
// TODO: only allow it for read-only items?
20432045
// or implement mvcc
2044-
if (!candidate->isExpired() && candidate->markExclusive()) {
2046+
if (candidate->markExclusive()) {
2047+
mmContainer.remove(itr);
20452048
candidates.push_back(candidate);
20462049
}
20472050

@@ -2051,16 +2054,18 @@ class CacheAllocator : public CacheBase {
20512054

20522055
for (Item *candidate : candidates) {
20532056
auto promoted = tryPromoteToNextMemoryTier(*candidate, true);
2054-
auto ref = candidate->unmarkExclusive();
2055-
if (promoted)
2057+
if (promoted) {
20562058
promotions++;
2057-
2058-
if (ref == 0u) {
2059-
// stats_.promotionMoveSuccess.inc();
2060-
auto res = releaseBackToAllocator(*candidate, RemoveContext::kEviction,
2061-
/* isNascent */ false);
2062-
XDCHECK(res == ReleaseRes::kReleased);
20632059
}
2060+
unlinkItemExclusive(*candidate);
2061+
XDCHECK(!candidate->isExclusive() && !candidate->isMoving());
2062+
2063+
// it's safe to recycle the item here as there are no more
2064+
// references and the item could not been marked as moving
2065+
// by other thread since it's detached from MMContainer.
2066+
auto res = releaseBackToAllocator(*candidate, RemoveContext::kEviction,
2067+
/* isNascent */ false);
2068+
XDCHECK(res == ReleaseRes::kReleased);
20642069
}
20652070

20662071
return promotions;
@@ -2173,18 +2178,14 @@ class CacheAllocator : public CacheBase {
21732178
std::optional<bool> saveNvmCache();
21742179
void saveRamCache();
21752180

2176-
static bool itemExclusivePredicate(const Item& item) {
2177-
return item.getRefCount() == 0;
2181+
static bool itemSlabMovePredicate(const Item& item) {
2182+
return item.isMoving() && item.getRefCount() == 0;
21782183
}
21792184

21802185
static bool itemExpiryPredicate(const Item& item) {
21812186
return item.getRefCount() == 1 && item.isExpired();
21822187
}
21832188

2184-
static bool parentEvictForSlabReleasePredicate(const Item& item) {
2185-
return item.getRefCount() == 1 && !item.isExclusive();
2186-
}
2187-
21882189
std::unique_ptr<Deserializer> createDeserializer();
21892190

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

cachelib/allocator/CacheItem-inl.h

Lines changed: 22 additions & 2 deletions
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

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -314,12 +314,17 @@ class CACHELIB_PACKED_ATTR CacheItem {
314314
*/
315315
RefcountWithFlags::Value getRefCountAndFlagsRaw() const noexcept;
316316

317-
FOLLY_ALWAYS_INLINE void incRef() {
318-
if (LIKELY(ref_.incRef())) {
319-
return;
317+
// Increments item's ref count
318+
//
319+
// @return true on success, failure if item is marked as exclusive
320+
// @throw exception::RefcountOverflow on ref count overflow
321+
FOLLY_ALWAYS_INLINE bool incRef() {
322+
try {
323+
return ref_.incRef();
324+
} catch (exception::RefcountOverflow& e) {
325+
throw exception::RefcountOverflow(
326+
folly::sformat("{} item: {}", e.what(), toString()));
320327
}
321-
throw exception::RefcountOverflow(
322-
folly::sformat("Refcount maxed out. item: {}", toString()));
323328
}
324329

325330
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef() {
@@ -353,23 +358,43 @@ class CACHELIB_PACKED_ATTR CacheItem {
353358

354359
/**
355360
* The following two functions corresond to whether or not an item is
356-
* currently in the process of being moved. This happens during a slab
357-
* rebalance, eviction or resize operation.
361+
* currently in the process of being evicted.
358362
*
359-
* An item can only be marked exclusive when `isInMMContainer` returns true.
363+
* An item can only be marked exclusive when `isInMMContainer` returns true
364+
* and item is not already exclusive nor moving and the ref count is 0.
360365
* This operation is atomic.
361366
*
362-
* User can also query if an item "isOnlyExclusive". This returns true only
363-
* if the refcount is 0 and only the exclusive bit is set.
364-
*
365-
* Unmarking exclusive does not depend on `isInMMContainer`.
367+
* Unmarking exclusive does not depend on `isInMMContainer`
366368
* Unmarking exclusive will also return the refcount at the moment of
367369
* unmarking.
368370
*/
369371
bool markExclusive() noexcept;
370372
RefcountWithFlags::Value unmarkExclusive() noexcept;
371373
bool isExclusive() const noexcept;
372-
bool isOnlyExclusive() const noexcept;
374+
375+
/**
376+
* The following functions correspond to whether or not an item is
377+
* currently in the processed of being moved. When moving, ref count
378+
* is always >= 1.
379+
*
380+
* An item can only be marked moving when `isInMMContainer` returns true
381+
* and item is not already exclusive nor moving.
382+
*
383+
* User can also query if an item "isOnlyMoving". This returns true only
384+
* if the refcount is one and only the exclusive bit is set.
385+
*
386+
* Unmarking moving does not depend on `isInMMContainer`
387+
* Unmarking moving will also return the refcount at the moment of
388+
* unmarking.
389+
*/
390+
bool markMoving();
391+
RefcountWithFlags::Value unmarkMoving() noexcept;
392+
bool isMoving() const noexcept;
393+
bool isOnlyMoving() const noexcept;
394+
395+
/** This function attempts to mark item as exclusive.
396+
* Can only be called on the item that is moving.*/
397+
bool markExclusiveWhenMoving();
373398

374399
/**
375400
* Item cannot be marked both chained allocation and

0 commit comments

Comments
 (0)