Skip to content

Commit 7aed493

Browse files
byrnedjigchor
andauthored
Combined locking with exclusive bit (#38)
- 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. - Block readers when item is moving This simplifies moving code a lot. We don't need to worry about any failure other than allocation failure since item is protected from any readers (and by extension, removes). Co-authored-by: Igor Chorążewicz <igor.chorazewicz@intel.com>
1 parent 621d0cf commit 7aed493

29 files changed

+939
-885
lines changed

cachelib/allocator/CacheAllocator-inl.h

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

cachelib/allocator/CacheAllocator.h

Lines changed: 93 additions & 67 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 RefcountWithFlags::incResult incRef(Item& it, bool failIfMoving);
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,16 @@ 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.
1620-
template <typename P>
1621-
WriteHandle moveRegularItemWithSync(Item& oldItem, WriteHandle& newItemHdl, P&& predicate);
1623+
// @return true If the move was completed, and the containers were updated
1624+
// successfully.
1625+
void moveRegularItemWithSync(Item& oldItem, WriteHandle& newItemHdl);
16221626

16231627
// Moves a regular item to a different slab. This should only be used during
16241628
// slab release after the item's exclusive bit has been set. The user supplied
16251629
// callback is responsible for copying the contents and fixing the semantics
16261630
// of chained item.
16271631
//
1628-
// @param oldItem Reference to the item being moved
1632+
// @param oldItem item being moved
16291633
// @param newItemHdl Reference to the handle of the new item being moved into
16301634
//
16311635
// @return true If the move was completed, and the containers were updated
@@ -1789,9 +1793,9 @@ class CacheAllocator : public CacheBase {
17891793
// handle to the item. On failure an empty handle.
17901794
WriteHandle tryEvictToNextMemoryTier(TierId tid, PoolId pid, Item& item, bool fromBgThread);
17911795

1792-
bool tryPromoteToNextMemoryTier(TierId tid, PoolId pid, Item& item, bool fromBgThread);
1796+
WriteHandle tryPromoteToNextMemoryTier(TierId tid, PoolId pid, Item& item, bool fromBgThread);
17931797

1794-
bool tryPromoteToNextMemoryTier(Item& item, bool fromBgThread);
1798+
WriteHandle tryPromoteToNextMemoryTier(Item& item, bool fromBgThread);
17951799

17961800
// Try to move the item down to the next memory tier
17971801
//
@@ -1878,22 +1882,23 @@ class CacheAllocator : public CacheBase {
18781882

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

18851889
// "Move" (by copying) the content in this item to another memory
18861890
// location by invoking the move callback.
18871891
//
18881892
//
18891893
// @param ctx slab release context
1890-
// @param item old item to be moved elsewhere
1894+
// @param oldItem old item to be moved elsewhere
1895+
// @param handle handle to the item or to it's parent (if chained)
18911896
// @param throttler slow this function down as not to take too much cpu
18921897
//
18931898
// @return true if the item has been moved
18941899
// false if we have exhausted moving attempts
18951900
bool moveForSlabRelease(const SlabReleaseContext& ctx,
1896-
Item& item,
1901+
Item& oldItem,
18971902
util::Throttler& throttler);
18981903

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

1937+
typename NvmCacheT::PutToken createPutToken(Item& item);
1938+
19321939
// Helper function to remove a item if predicates is true.
19331940
//
19341941
// @return last handle to the item on success. empty handle on failure.
@@ -1966,8 +1973,10 @@ class CacheAllocator : public CacheBase {
19661973
candidates.reserve(batch);
19671974

19681975
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) {
1976+
mmContainer.withEvictionIterator([&tries, &candidates, &batch, &mmContainer, this](auto &&itr) {
1977+
while (candidates.size() < batch &&
1978+
(config_.maxEvictionPromotionHotness == 0 || tries < config_.maxEvictionPromotionHotness) &&
1979+
itr) {
19711980
tries++;
19721981
Item* candidate = itr.get();
19731982
XDCHECK(candidate);
@@ -1976,7 +1985,8 @@ class CacheAllocator : public CacheBase {
19761985
throw std::runtime_error("Not supported for chained items");
19771986
}
19781987

1979-
if (candidate->getRefCount() == 0 && candidate->markExclusive()) {
1988+
if (candidate->markMoving(true)) {
1989+
mmContainer.remove(itr);
19801990
candidates.push_back(candidate);
19811991
}
19821992

@@ -1985,37 +1995,45 @@ class CacheAllocator : public CacheBase {
19851995
});
19861996

19871997
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.
1994-
}
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-
}
1998+
auto evictedToNext = tryEvictToNextMemoryTier(*candidate, true /* from BgThread */);
1999+
if (!evictedToNext) {
2000+
auto token = createPutToken(*candidate);
2001+
2002+
auto ret = candidate->markExclusiveWhenMoving();
2003+
XDCHECK(ret);
2004+
2005+
unlinkItemExclusive(*candidate);
2006+
// wake up any readers that wait for the move to complete
2007+
// it's safe to do now, as we have the item marked exclusive and
2008+
// no other reader can be added to the waiters list
2009+
wakeUpWaiters(candidate->getKey(), WriteHandle{});
2010+
2011+
if (token.isValid() && shouldWriteToNvmCacheExclusive(*candidate)) {
2012+
nvmCache_->put(*candidate, std::move(token));
2013+
}
2014+
} else {
2015+
evictions++;
2016+
XDCHECK(!evictedToNext->isExclusive() && !evictedToNext->isMoving());
2017+
XDCHECK(!candidate->isExclusive() && !candidate->isMoving());
2018+
XDCHECK(!candidate->isAccessible());
2019+
XDCHECK(candidate->getKey() == evictedToNext->getKey());
20032020

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);
2021+
wakeUpWaiters(candidate->getKey(), std::move(evictedToNext));
2022+
}
2023+
XDCHECK(!candidate->isExclusive() && !candidate->isMoving());
20112024

2025+
if (candidate->hasChainedItem()) {
2026+
(*stats_.chainedItemEvictions)[pid][cid].inc();
20122027
} else {
2013-
if (candidate->hasChainedItem()) {
2014-
stats_.evictFailParentAC.inc();
2015-
} else {
2016-
stats_.evictFailAC.inc();
2017-
}
2028+
(*stats_.regularItemEvictions)[pid][cid].inc();
20182029
}
2030+
2031+
// it's safe to recycle the item here as there are no more
2032+
// references and the item could not been marked as moving
2033+
// by other thread since it's detached from MMContainer.
2034+
auto res = releaseBackToAllocator(*candidate, RemoveContext::kEviction,
2035+
/* isNascent */ false);
2036+
XDCHECK(res == ReleaseRes::kReleased);
20192037
}
20202038
return evictions;
20212039
}
@@ -2028,7 +2046,7 @@ class CacheAllocator : public CacheBase {
20282046

20292047
size_t tries = 0;
20302048

2031-
mmContainer.withPromotionIterator([&tries, &candidates, &batch, this](auto &&itr){
2049+
mmContainer.withPromotionIterator([&tries, &candidates, &batch, &mmContainer, this](auto &&itr){
20322050
while (candidates.size() < batch && (config_.maxEvictionPromotionHotness == 0 || tries < config_.maxEvictionPromotionHotness) && itr) {
20332051
tries++;
20342052
Item* candidate = itr.get();
@@ -2038,10 +2056,9 @@ class CacheAllocator : public CacheBase {
20382056
throw std::runtime_error("Not supported for chained items");
20392057
}
20402058

2041-
20422059
// TODO: only allow it for read-only items?
20432060
// or implement mvcc
2044-
if (!candidate->isExpired() && candidate->markExclusive()) {
2061+
if (candidate->markMoving(true)) {
20452062
candidates.push_back(candidate);
20462063
}
20472064

@@ -2051,18 +2068,28 @@ class CacheAllocator : public CacheBase {
20512068

20522069
for (Item *candidate : candidates) {
20532070
auto promoted = tryPromoteToNextMemoryTier(*candidate, true);
2054-
auto ref = candidate->unmarkExclusive();
2055-
if (promoted)
2071+
if (promoted) {
20562072
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);
2073+
removeFromMMContainer(*candidate);
2074+
XDCHECK(!candidate->isExclusive() && !candidate->isMoving());
2075+
// it's safe to recycle the item here as there are no more
2076+
// references and the item could not been marked as moving
2077+
// by other thread since it's detached from MMContainer.
2078+
auto res = releaseBackToAllocator(*candidate, RemoveContext::kEviction,
2079+
/* isNascent */ false);
2080+
XDCHECK(res == ReleaseRes::kReleased);
2081+
} else {
2082+
//we failed to allocate a new item, this item is no longer moving
2083+
auto ref = candidate->unmarkMoving();
2084+
if (UNLIKELY(ref == 0)) {
2085+
const auto res =
2086+
releaseBackToAllocator(*candidate,
2087+
RemoveContext::kNormal, false);
2088+
XDCHECK(res == ReleaseRes::kReleased);
2089+
}
20632090
}
2091+
20642092
}
2065-
20662093
return promotions;
20672094
}
20682095

@@ -2173,18 +2200,14 @@ class CacheAllocator : public CacheBase {
21732200
std::optional<bool> saveNvmCache();
21742201
void saveRamCache();
21752202

2176-
static bool itemExclusivePredicate(const Item& item) {
2177-
return item.getRefCount() == 0;
2203+
static bool itemSlabMovePredicate(const Item& item) {
2204+
return item.isMoving() && item.getRefCount() == 0;
21782205
}
21792206

21802207
static bool itemExpiryPredicate(const Item& item) {
21812208
return item.getRefCount() == 1 && item.isExpired();
21822209
}
21832210

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

21902213
// Execute func on each item. `func` can throw exception but must ensure
@@ -2234,8 +2257,9 @@ class CacheAllocator : public CacheBase {
22342257
return memoryTierConfigs.size();
22352258
}
22362259

2237-
bool addWaitContextForMovingItem(
2238-
folly::StringPiece key, std::shared_ptr<WaitContext<ReadHandle>> waiter);
2260+
WriteHandle handleWithWaitContextForMovingItem(Item& item);
2261+
2262+
size_t wakeUpWaiters(folly::StringPiece key, WriteHandle&& handle);
22392263

22402264
class MoveCtx {
22412265
public:
@@ -2260,6 +2284,8 @@ class CacheAllocator : public CacheBase {
22602284
waiters.push_back(std::move(waiter));
22612285
}
22622286

2287+
size_t numWaiters() const { return waiters.size(); }
2288+
22632289
private:
22642290
// notify all pending waiters that are waiting for the fetch.
22652291
void wakeUpWaiters() {

cachelib/allocator/CacheItem-inl.h

Lines changed: 22 additions & 17 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(bool failIfRefNotZero) {
241+
return ref_.markMoving(failIfRefNotZero);
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>
@@ -266,21 +286,6 @@ bool CacheItem<CacheTrait>::isNvmEvicted() const noexcept {
266286
return ref_.isNvmEvicted();
267287
}
268288

269-
template <typename CacheTrait>
270-
void CacheItem<CacheTrait>::markIncomplete() noexcept {
271-
ref_.markIncomplete();
272-
}
273-
274-
template <typename CacheTrait>
275-
void CacheItem<CacheTrait>::unmarkIncomplete() noexcept {
276-
ref_.unmarkIncomplete();
277-
}
278-
279-
template <typename CacheTrait>
280-
bool CacheItem<CacheTrait>::isIncomplete() const noexcept {
281-
return ref_.isIncomplete();
282-
}
283-
284289
template <typename CacheTrait>
285290
void CacheItem<CacheTrait>::markIsChainedItem() noexcept {
286291
XDCHECK(!hasChainedItem());

0 commit comments

Comments
 (0)