Skip to content

Commit 4b5ffd0

Browse files
byrnedjvinser52
authored andcommitted
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 8d7b4e8 commit 4b5ffd0

14 files changed

+670
-102
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 346 additions & 56 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>
@@ -1326,7 +1328,7 @@ class CacheAllocator : public CacheBase {
13261328

13271329
private:
13281330
// wrapper around Item's refcount and active handle tracking
1329-
FOLLY_ALWAYS_INLINE bool incRef(Item& it);
1331+
FOLLY_ALWAYS_INLINE RefcountWithFlags::incResult incRef(Item& it, bool failIfMoving);
13301332
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef(Item& it);
13311333

13321334
// drops the refcount and if needed, frees the allocation back to the memory
@@ -1556,7 +1558,7 @@ class CacheAllocator : public CacheBase {
15561558
//
15571559
// @return true If the move was completed, and the containers were updated
15581560
// successfully.
1559-
bool moveRegularItemOnEviction(Item& oldItem, WriteHandle& newItemHdl);
1561+
void moveRegularItemWithSync(Item& oldItem, WriteHandle& newItemHdl);
15601562

15611563
// Moves a regular item to a different slab. This should only be used during
15621564
// slab release after the item's exclusive bit has been set. The user supplied
@@ -1643,6 +1645,10 @@ class CacheAllocator : public CacheBase {
16431645
// false if the item is not in MMContainer
16441646
bool removeFromMMContainer(Item& item);
16451647

1648+
using EvictionIterator = typename MMContainer::LockedIterator;
1649+
1650+
WriteHandle acquire(EvictionIterator& it) { return acquire(it.get()); }
1651+
16461652
// Replaces an item in the MMContainer with another item, at the same
16471653
// position.
16481654
//
@@ -1653,6 +1659,8 @@ class CacheAllocator : public CacheBase {
16531659
// destination item did not exist in the container, or if the
16541660
// source item already existed.
16551661
bool replaceInMMContainer(Item& oldItem, Item& newItem);
1662+
bool replaceInMMContainer(Item* oldItem, Item& newItem);
1663+
bool replaceInMMContainer(EvictionIterator& oldItemIt, Item& newItem);
16561664

16571665
// Replaces an item in the MMContainer with another item, at the same
16581666
// position. Or, if the two chained items belong to two different MM
@@ -1735,7 +1743,35 @@ class CacheAllocator : public CacheBase {
17351743
ClassId cid,
17361744
unsigned int& searchTries);
17371745

1738-
using EvictionIterator = typename MMContainer::LockedIterator;
1746+
// Try to move the item down to the next memory tier
1747+
//
1748+
// @param tid current tier ID of the item
1749+
// @param pid the pool ID the item belong to.
1750+
// @param item the item to evict
1751+
//
1752+
// @return valid handle to the item. This will be the last
1753+
// handle to the item. On failure an empty handle.
1754+
WriteHandle tryEvictToNextMemoryTier(TierId tid, PoolId pid, Item& item);
1755+
1756+
// Wakes up waiters if there are any
1757+
//
1758+
// @param item wakes waiters that are waiting on that item
1759+
// @param handle handle to pass to the waiters
1760+
void wakeUpWaiters(Item& item, WriteHandle handle);
1761+
1762+
// Unmarks item as moving and wakes up any waiters waiting on that item
1763+
//
1764+
// @param item wakes waiters that are waiting on that item
1765+
// @param handle handle to pass to the waiters
1766+
typename RefcountWithFlags::Value unmarkMovingAndWakeUpWaiters(Item &item, WriteHandle handle);
1767+
1768+
// Try to move the item down to the next memory tier
1769+
//
1770+
// @param item the item to evict
1771+
//
1772+
// @return valid handle to the item. This will be the last
1773+
// handle to the item. On failure an empty handle.
1774+
WriteHandle tryEvictToNextMemoryTier(Item& item);
17391775

17401776
// Deserializer CacheAllocatorMetadata and verify the version
17411777
//
@@ -1853,6 +1889,12 @@ class CacheAllocator : public CacheBase {
18531889

18541890
typename NvmCacheT::PutToken createPutToken(Item& item);
18551891

1892+
// Helper function to remove a item if predicates is true.
1893+
//
1894+
// @return last handle to the item on success. empty handle on failure.
1895+
template <typename Fn>
1896+
WriteHandle removeIf(Item& item, Fn&& predicate);
1897+
18561898
// Helper function to remove a item if expired.
18571899
//
18581900
// @return true if it item expire and removed successfully.
@@ -2038,6 +2080,87 @@ class CacheAllocator : public CacheBase {
20382080

20392081
size_t memoryTierSize(TierId tid) const;
20402082

2083+
WriteHandle handleWithWaitContextForMovingItem(Item& item);
2084+
2085+
size_t wakeUpWaitersLocked(folly::StringPiece key, WriteHandle&& handle);
2086+
2087+
class MoveCtx {
2088+
public:
2089+
MoveCtx() {}
2090+
2091+
~MoveCtx() {
2092+
// prevent any further enqueue to waiters
2093+
// Note: we don't need to hold locks since no one can enqueue
2094+
// after this point.
2095+
wakeUpWaiters();
2096+
}
2097+
2098+
// record the item handle. Upon destruction we will wake up the waiters
2099+
// and pass a clone of the handle to the callBack. By default we pass
2100+
// a null handle
2101+
void setItemHandle(WriteHandle _it) { it = std::move(_it); }
2102+
2103+
// enqueue a waiter into the waiter list
2104+
// @param waiter WaitContext
2105+
void addWaiter(std::shared_ptr<WaitContext<ReadHandle>> waiter) {
2106+
XDCHECK(waiter);
2107+
waiters.push_back(std::move(waiter));
2108+
}
2109+
2110+
size_t numWaiters() const { return waiters.size(); }
2111+
2112+
private:
2113+
// notify all pending waiters that are waiting for the fetch.
2114+
void wakeUpWaiters() {
2115+
bool refcountOverflowed = false;
2116+
for (auto& w : waiters) {
2117+
// If refcount overflowed earlier, then we will return miss to
2118+
// all subsequent waitors.
2119+
if (refcountOverflowed) {
2120+
w->set(WriteHandle{});
2121+
continue;
2122+
}
2123+
2124+
try {
2125+
w->set(it.clone());
2126+
} catch (const exception::RefcountOverflow&) {
2127+
// We'll return a miss to the user's pending read,
2128+
// so we should enqueue a delete via NvmCache.
2129+
// TODO: cache.remove(it);
2130+
refcountOverflowed = true;
2131+
}
2132+
}
2133+
}
2134+
2135+
WriteHandle it; // will be set when Context is being filled
2136+
std::vector<std::shared_ptr<WaitContext<ReadHandle>>> waiters; // list of
2137+
// waiters
2138+
};
2139+
using MoveMap =
2140+
folly::F14ValueMap<folly::StringPiece,
2141+
std::unique_ptr<MoveCtx>,
2142+
folly::HeterogeneousAccessHash<folly::StringPiece>>;
2143+
2144+
static size_t getShardForKey(folly::StringPiece key) {
2145+
return folly::Hash()(key) % kShards;
2146+
}
2147+
2148+
MoveMap& getMoveMapForShard(size_t shard) {
2149+
return movesMap_[shard].movesMap_;
2150+
}
2151+
2152+
MoveMap& getMoveMap(folly::StringPiece key) {
2153+
return getMoveMapForShard(getShardForKey(key));
2154+
}
2155+
2156+
std::unique_lock<std::mutex> getMoveLockForShard(size_t shard) {
2157+
return std::unique_lock<std::mutex>(moveLock_[shard].moveLock_);
2158+
}
2159+
2160+
std::unique_lock<std::mutex> getMoveLock(folly::StringPiece key) {
2161+
return getMoveLockForShard(getShardForKey(key));
2162+
}
2163+
20412164
// Whether the memory allocator for this cache allocator was created on shared
20422165
// memory. The hash table, chained item hash table etc is also created on
20432166
// shared memory except for temporary shared memory mode when they're created
@@ -2128,6 +2251,22 @@ class CacheAllocator : public CacheBase {
21282251
// poolResizer_, poolOptimizer_, memMonitor_, reaper_
21292252
mutable std::mutex workersMutex_;
21302253

2254+
static constexpr size_t kShards = 8192; // TODO: need to define right value
2255+
2256+
struct MovesMapShard {
2257+
alignas(folly::hardware_destructive_interference_size) MoveMap movesMap_;
2258+
};
2259+
2260+
struct MoveLock {
2261+
alignas(folly::hardware_destructive_interference_size) std::mutex moveLock_;
2262+
};
2263+
2264+
// a map of all pending moves
2265+
std::vector<MovesMapShard> movesMap_;
2266+
2267+
// a map of move locks for each shard
2268+
std::vector<MoveLock> moveLock_;
2269+
21312270
// time when the ram cache was first created
21322271
const uint32_t cacheCreationTime_{0};
21332272

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
@@ -319,14 +330,16 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
319330
* Unmarking clears `kExclusive` bit and decreses the interanl refCount by 1.
320331
* `unmarkMoving` does does not depend on `isInMMContainer`
321332
*/
322-
bool markMoving() {
333+
bool markMoving(bool failIfRefNotZero) {
323334
Value linkedBitMask = getAdminRef<kLinked>();
324335
Value exclusiveBitMask = getAdminRef<kExclusive>();
325336

326-
auto predicate = [linkedBitMask, exclusiveBitMask](const Value curValue) {
337+
auto predicate = [failIfRefNotZero, linkedBitMask, exclusiveBitMask](const Value curValue) {
327338
const bool unlinked = !(curValue & linkedBitMask);
328339
const bool alreadyExclusive = curValue & exclusiveBitMask;
329-
340+
if (failIfRefNotZero && (curValue & kAccessRefMask) != 0) {
341+
return false;
342+
}
330343
if (unlinked || alreadyExclusive) {
331344
return false;
332345
}

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)