Skip to content

Commit 8ab8c75

Browse files
committed
CS Patch Part 2 for mulit-tier cachelib:
- transparent item movement - multi-tier combined locking with exclusive bit (#38) with refactored incRef to support returning the result of markMoving (fail if already moving or exclusvie bit is set) option. - add tests (updated for numa bindings - post combined locking) for transparent item movement
1 parent f0baeb1 commit 8ab8c75

14 files changed

+669
-106
lines changed

cachelib/allocator/CacheAllocator-inl.h

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

cachelib/allocator/CacheAllocator.h

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#include <folly/ScopeGuard.h>
2323
#include <folly/logging/xlog.h>
2424
#include <folly/synchronization/SanitizeThread.h>
25+
#include <folly/hash/Hash.h>
26+
#include <folly/container/F14Map.h>
2527
#include <gtest/gtest.h>
2628

2729
#include <chrono>
@@ -1319,7 +1321,7 @@ class CacheAllocator : public CacheBase {
13191321

13201322
private:
13211323
// wrapper around Item's refcount and active handle tracking
1322-
FOLLY_ALWAYS_INLINE bool incRef(Item& it);
1324+
FOLLY_ALWAYS_INLINE RefcountWithFlags::incResult incRef(Item& it, bool failIfMoving);
13231325
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef(Item& it);
13241326

13251327
// drops the refcount and if needed, frees the allocation back to the memory
@@ -1550,7 +1552,7 @@ class CacheAllocator : public CacheBase {
15501552
//
15511553
// @return true If the move was completed, and the containers were updated
15521554
// successfully.
1553-
bool moveRegularItemOnEviction(Item& oldItem, WriteHandle& newItemHdl);
1555+
void moveRegularItemWithSync(Item& oldItem, WriteHandle& newItemHdl);
15541556

15551557
// Moves a regular item to a different slab. This should only be used during
15561558
// slab release after the item's exclusive bit has been set. The user supplied
@@ -1637,6 +1639,10 @@ class CacheAllocator : public CacheBase {
16371639
// false if the item is not in MMContainer
16381640
bool removeFromMMContainer(Item& item);
16391641

1642+
using EvictionIterator = typename MMContainer::LockedIterator;
1643+
1644+
WriteHandle acquire(EvictionIterator& it) { return acquire(it.get()); }
1645+
16401646
// Replaces an item in the MMContainer with another item, at the same
16411647
// position.
16421648
//
@@ -1647,6 +1653,8 @@ class CacheAllocator : public CacheBase {
16471653
// destination item did not exist in the container, or if the
16481654
// source item already existed.
16491655
bool replaceInMMContainer(Item& oldItem, Item& newItem);
1656+
bool replaceInMMContainer(Item* oldItem, Item& newItem);
1657+
bool replaceInMMContainer(EvictionIterator& oldItemIt, Item& newItem);
16501658

16511659
// Replaces an item in the MMContainer with another item, at the same
16521660
// position. Or, if the two chained items belong to two different MM
@@ -1705,7 +1713,35 @@ class CacheAllocator : public CacheBase {
17051713
// @return An evicted item or nullptr if there is no suitable candidate.
17061714
Item* findEviction(TierId tid, PoolId pid, ClassId cid);
17071715

1708-
using EvictionIterator = typename MMContainer::LockedIterator;
1716+
// Try to move the item down to the next memory tier
1717+
//
1718+
// @param tid current tier ID of the item
1719+
// @param pid the pool ID the item belong to.
1720+
// @param item the item to evict
1721+
//
1722+
// @return valid handle to the item. This will be the last
1723+
// handle to the item. On failure an empty handle.
1724+
WriteHandle tryEvictToNextMemoryTier(TierId tid, PoolId pid, Item& item);
1725+
1726+
// Wakes up waiters if there are any
1727+
//
1728+
// @param item wakes waiters that are waiting on that item
1729+
// @param handle handle to pass to the waiters
1730+
void wakeUpWaiters(Item& item, WriteHandle handle);
1731+
1732+
// Unmarks item as moving and wakes up any waiters waiting on that item
1733+
//
1734+
// @param item wakes waiters that are waiting on that item
1735+
// @param handle handle to pass to the waiters
1736+
typename RefcountWithFlags::Value unmarkMovingAndWakeUpWaiters(Item &item, WriteHandle handle);
1737+
1738+
// Try to move the item down to the next memory tier
1739+
//
1740+
// @param item the item to evict
1741+
//
1742+
// @return valid handle to the item. This will be the last
1743+
// handle to the item. On failure an empty handle.
1744+
WriteHandle tryEvictToNextMemoryTier(Item& item);
17091745

17101746
// Deserializer CacheAllocatorMetadata and verify the version
17111747
//
@@ -1823,6 +1859,12 @@ class CacheAllocator : public CacheBase {
18231859

18241860
typename NvmCacheT::PutToken createPutToken(Item& item);
18251861

1862+
// Helper function to remove a item if predicates is true.
1863+
//
1864+
// @return last handle to the item on success. empty handle on failure.
1865+
template <typename Fn>
1866+
WriteHandle removeIf(Item& item, Fn&& predicate);
1867+
18261868
// Helper function to remove a item if expired.
18271869
//
18281870
// @return true if it item expire and removed successfully.
@@ -2008,6 +2050,87 @@ class CacheAllocator : public CacheBase {
20082050

20092051
size_t memoryTierSize(TierId tid) const;
20102052

2053+
WriteHandle handleWithWaitContextForMovingItem(Item& item);
2054+
2055+
size_t wakeUpWaitersLocked(folly::StringPiece key, WriteHandle&& handle);
2056+
2057+
class MoveCtx {
2058+
public:
2059+
MoveCtx() {}
2060+
2061+
~MoveCtx() {
2062+
// prevent any further enqueue to waiters
2063+
// Note: we don't need to hold locks since no one can enqueue
2064+
// after this point.
2065+
wakeUpWaiters();
2066+
}
2067+
2068+
// record the item handle. Upon destruction we will wake up the waiters
2069+
// and pass a clone of the handle to the callBack. By default we pass
2070+
// a null handle
2071+
void setItemHandle(WriteHandle _it) { it = std::move(_it); }
2072+
2073+
// enqueue a waiter into the waiter list
2074+
// @param waiter WaitContext
2075+
void addWaiter(std::shared_ptr<WaitContext<ReadHandle>> waiter) {
2076+
XDCHECK(waiter);
2077+
waiters.push_back(std::move(waiter));
2078+
}
2079+
2080+
size_t numWaiters() const { return waiters.size(); }
2081+
2082+
private:
2083+
// notify all pending waiters that are waiting for the fetch.
2084+
void wakeUpWaiters() {
2085+
bool refcountOverflowed = false;
2086+
for (auto& w : waiters) {
2087+
// If refcount overflowed earlier, then we will return miss to
2088+
// all subsequent waitors.
2089+
if (refcountOverflowed) {
2090+
w->set(WriteHandle{});
2091+
continue;
2092+
}
2093+
2094+
try {
2095+
w->set(it.clone());
2096+
} catch (const exception::RefcountOverflow&) {
2097+
// We'll return a miss to the user's pending read,
2098+
// so we should enqueue a delete via NvmCache.
2099+
// TODO: cache.remove(it);
2100+
refcountOverflowed = true;
2101+
}
2102+
}
2103+
}
2104+
2105+
WriteHandle it; // will be set when Context is being filled
2106+
std::vector<std::shared_ptr<WaitContext<ReadHandle>>> waiters; // list of
2107+
// waiters
2108+
};
2109+
using MoveMap =
2110+
folly::F14ValueMap<folly::StringPiece,
2111+
std::unique_ptr<MoveCtx>,
2112+
folly::HeterogeneousAccessHash<folly::StringPiece>>;
2113+
2114+
static size_t getShardForKey(folly::StringPiece key) {
2115+
return folly::Hash()(key) % kShards;
2116+
}
2117+
2118+
MoveMap& getMoveMapForShard(size_t shard) {
2119+
return movesMap_[shard].movesMap_;
2120+
}
2121+
2122+
MoveMap& getMoveMap(folly::StringPiece key) {
2123+
return getMoveMapForShard(getShardForKey(key));
2124+
}
2125+
2126+
std::unique_lock<std::mutex> getMoveLockForShard(size_t shard) {
2127+
return std::unique_lock<std::mutex>(moveLock_[shard].moveLock_);
2128+
}
2129+
2130+
std::unique_lock<std::mutex> getMoveLock(folly::StringPiece key) {
2131+
return getMoveLockForShard(getShardForKey(key));
2132+
}
2133+
20112134
// Whether the memory allocator for this cache allocator was created on shared
20122135
// memory. The hash table, chained item hash table etc is also created on
20132136
// shared memory except for temporary shared memory mode when they're created
@@ -2100,6 +2223,22 @@ class CacheAllocator : public CacheBase {
21002223
// poolResizer_, poolOptimizer_, memMonitor_, reaper_
21012224
mutable std::mutex workersMutex_;
21022225

2226+
static constexpr size_t kShards = 8192; // TODO: need to define right value
2227+
2228+
struct MovesMapShard {
2229+
alignas(folly::hardware_destructive_interference_size) MoveMap movesMap_;
2230+
};
2231+
2232+
struct MoveLock {
2233+
alignas(folly::hardware_destructive_interference_size) std::mutex moveLock_;
2234+
};
2235+
2236+
// a map of all pending moves
2237+
std::vector<MovesMapShard> movesMap_;
2238+
2239+
// a map of move locks for each shard
2240+
std::vector<MoveLock> moveLock_;
2241+
21032242
// time when the ram cache was first created
21042243
const uint32_t cacheCreationTime_{0};
21052244

cachelib/allocator/CacheItem-inl.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ bool CacheItem<CacheTrait>::markForEvictionWhenMoving() {
238238
}
239239

240240
template <typename CacheTrait>
241-
bool CacheItem<CacheTrait>::markMoving() {
242-
return ref_.markMoving();
241+
bool CacheItem<CacheTrait>::markMoving(bool failIfRefNotZero) {
242+
return ref_.markMoving(failIfRefNotZero);
243243
}
244244

245245
template <typename CacheTrait>

cachelib/allocator/CacheItem.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,9 @@ class CACHELIB_PACKED_ATTR CacheItem {
309309
//
310310
// @return true on success, failure if item is marked as exclusive
311311
// @throw exception::RefcountOverflow on ref count overflow
312-
FOLLY_ALWAYS_INLINE bool incRef() {
312+
FOLLY_ALWAYS_INLINE RefcountWithFlags::incResult incRef(bool failIfMoving) {
313313
try {
314-
return ref_.incRef();
314+
return ref_.incRef(failIfMoving);
315315
} catch (exception::RefcountOverflow& e) {
316316
throw exception::RefcountOverflow(
317317
folly::sformat("{} item: {}", e.what(), toString()));
@@ -378,7 +378,7 @@ class CACHELIB_PACKED_ATTR CacheItem {
378378
* Unmarking moving will also return the refcount at the moment of
379379
* unmarking.
380380
*/
381-
bool markMoving();
381+
bool markMoving(bool failIfRefNotZero);
382382
RefcountWithFlags::Value unmarkMoving() noexcept;
383383
bool isMoving() const noexcept;
384384
bool isOnlyMoving() const noexcept;

cachelib/allocator/Handle.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ struct ReadHandleImpl {
400400
}
401401
}
402402

403+
protected:
404+
friend class ReadHandleImpl;
405+
// Method used only by ReadHandleImpl ctor
406+
void discard() {
407+
it_.store(nullptr, std::memory_order_relaxed);
408+
}
403409
private:
404410
// we are waiting on Item* to be set to a value. One of the valid values is
405411
// nullptr. So choose something that we dont expect to indicate a ptr
@@ -479,7 +485,8 @@ struct ReadHandleImpl {
479485

480486
// Handle which has the item already
481487
FOLLY_ALWAYS_INLINE ReadHandleImpl(Item* it, CacheT& alloc) noexcept
482-
: alloc_(&alloc), it_(it) {}
488+
: alloc_(&alloc), it_(it) {
489+
}
483490

484491
// handle that has a wait context allocated. Used for async handles
485492
// In this case, the it_ will be filled in asynchronously and mulitple

cachelib/allocator/MMLru-inl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ void MMLru::Container<T, HookPtr>::withEvictionIterator(F&& fun) {
229229
}
230230
}
231231

232+
//template <typename T, MMLru::Hook<T> T::*HookPtr>
233+
//template <typename F>
234+
//void
235+
//MMLru::Container<T, HookPtr>::withPromotionIterator(F&& fun) {
236+
// if (config_.useCombinedLockForIterators) {
237+
// lruMutex_->lock_combine([this, &fun]() { fun(Iterator{lru_.begin()}); });
238+
// } else {
239+
// LockHolder lck{*lruMutex_};
240+
// fun(Iterator{lru_.begin()});
241+
// }
242+
//}
243+
232244
template <typename T, MMLru::Hook<T> T::*HookPtr>
233245
void MMLru::Container<T, HookPtr>::ensureNotInsertionPoint(T& node) noexcept {
234246
// If we are removing the insertion point node, grow tail before we remove

cachelib/allocator/MMLru.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,13 @@ class MMLru {
230230
// lruInsertionPointSpec = 2, we insert at a point 1/4th from tail
231231
uint8_t lruInsertionPointSpec{0};
232232

233+
// Whether to use combined locking for withEvictionIterator.
234+
bool useCombinedLockForIterators{true};
235+
233236
// Minimum interval between reconfigurations. If 0, reconfigure is never
234237
// called.
235238
std::chrono::seconds mmReconfigureIntervalSecs{};
236239

237-
// Whether to use combined locking for withEvictionIterator.
238-
bool useCombinedLockForIterators{false};
239240
};
240241

241242
// The container object which can be used to keep track of objects of type

cachelib/allocator/Refcount.h

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -130,30 +130,41 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
130130
RefcountWithFlags& operator=(const RefcountWithFlags&) = delete;
131131
RefcountWithFlags(RefcountWithFlags&&) = delete;
132132
RefcountWithFlags& operator=(RefcountWithFlags&&) = delete;
133-
133+
enum incResult {
134+
incOk,
135+
incFailedMoving,
136+
incFailedEviction
137+
};
134138
// Bumps up the reference count only if the new count will be strictly less
135139
// than or equal to the maxCount and the item is not exclusive
136140
// @return true if refcount is bumped. false otherwise (if item is exclusive)
137141
// @throw exception::RefcountOverflow if new count would be greater than
138142
// maxCount
139-
FOLLY_ALWAYS_INLINE bool incRef() {
140-
auto predicate = [](const Value curValue) {
141-
Value bitMask = getAdminRef<kExclusive>();
142-
143-
const bool exlusiveBitIsSet = curValue & bitMask;
144-
if (UNLIKELY((curValue & kAccessRefMask) == (kAccessRefMask))) {
145-
throw exception::RefcountOverflow("Refcount maxed out.");
146-
}
147-
148-
// Check if the item is not marked for eviction
149-
return !exlusiveBitIsSet || ((curValue & kAccessRefMask) != 0);
150-
};
151-
152-
auto newValue = [](const Value curValue) {
153-
return (curValue + static_cast<Value>(1));
154-
};
155-
156-
return atomicUpdateValue(predicate, newValue);
143+
FOLLY_ALWAYS_INLINE incResult incRef(bool failIfMoving) {
144+
incResult res = incOk;
145+
auto predicate = [failIfMoving, &res](const Value curValue) {
146+
Value bitMask = getAdminRef<kExclusive>();
147+
148+
const bool exlusiveBitIsSet = curValue & bitMask;
149+
if (UNLIKELY((curValue & kAccessRefMask) == (kAccessRefMask))) {
150+
throw exception::RefcountOverflow("Refcount maxed out.");
151+
} else if (exlusiveBitIsSet && (curValue & kAccessRefMask) == 0) {
152+
res = incFailedEviction;
153+
return false;
154+
} else if (exlusiveBitIsSet && failIfMoving) {
155+
res = incFailedMoving;
156+
return false;
157+
}
158+
res = incOk;
159+
return true;
160+
};
161+
162+
auto newValue = [](const Value curValue) {
163+
return (curValue + static_cast<Value>(1));
164+
};
165+
166+
atomicUpdateValue(predicate, newValue);
167+
return res;
157168
}
158169

159170
// Bumps down the reference count
@@ -309,12 +320,14 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
309320
*
310321
* Unmarking moving does not depend on `isInMMContainer`
311322
*/
312-
bool markMoving() {
313-
auto predicate = [](const Value curValue) {
323+
bool markMoving(bool failIfRefNotZero) {
324+
auto predicate = [failIfRefNotZero](const Value curValue) {
314325
Value conditionBitMask = getAdminRef<kLinked>();
315326
const bool flagSet = curValue & conditionBitMask;
316327
const bool alreadyExclusive = curValue & getAdminRef<kExclusive>();
317-
328+
if (failIfRefNotZero && (curValue & kAccessRefMask) != 0) {
329+
return false;
330+
}
318331
if (!flagSet || alreadyExclusive) {
319332
return false;
320333
}

cachelib/allocator/tests/AllocatorMemoryTiersTest.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ using LruAllocatorMemoryTiersTest = AllocatorMemoryTiersTest<LruAllocator>;
2626
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersInvalid) { this->testMultiTiersInvalid(); }
2727
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersValid) { this->testMultiTiersValid(); }
2828
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersValidMixed) { this->testMultiTiersValidMixed(); }
29+
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersRemoveDuringEviction) { this->testMultiTiersRemoveDuringEviction(); }
30+
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersReplaceDuringEviction) { this->testMultiTiersReplaceDuringEviction(); }
2931

3032
} // end of namespace tests
3133
} // end of namespace cachelib

0 commit comments

Comments
 (0)