Skip to content

Commit ab70b7d

Browse files
committed
Transparent Item movements
The new algorithm relies on the moving bit and does not require external synchronization. Data movement happens transparently for the client: if the client thread attempts to get a handle for the item being moved it will get a handle with wait context to wait till the movement is completed.
1 parent b828c2b commit ab70b7d

14 files changed

+627
-357
lines changed

cachelib/allocator/CacheAllocator-inl.h

+404-305
Large diffs are not rendered by default.

cachelib/allocator/CacheAllocator.h

+138-17
Original file line numberDiff line numberDiff line change
@@ -1349,7 +1349,7 @@ class CacheAllocator : public CacheBase {
13491349

13501350
private:
13511351
// wrapper around Item's refcount and active handle tracking
1352-
FOLLY_ALWAYS_INLINE bool incRef(Item& it);
1352+
FOLLY_ALWAYS_INLINE RefcountWithFlags::incResult incRef(Item& it);
13531353
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef(Item& it);
13541354

13551355
// drops the refcount and if needed, frees the allocation back to the memory
@@ -1473,26 +1473,13 @@ class CacheAllocator : public CacheBase {
14731473
// The parent handle parameter here is mainly used to find the
14741474
// correct pool to allocate memory for this chained item
14751475
//
1476-
// @param parent handle to the cache item
1476+
// @param parent the parent item
14771477
// @param size the size for the chained allocation
14781478
//
14791479
// @return handle to the chained allocation
14801480
// @throw std::invalid_argument if the size requested is invalid or
14811481
// if the item is invalid
1482-
WriteHandle allocateChainedItemInternal(const ReadHandle& parent,
1483-
uint32_t size);
1484-
1485-
// Given an item and its parentKey, validate that the parentKey
1486-
// corresponds to an item that's the parent of the supplied item.
1487-
//
1488-
// @param item item that we want to get the parent handle for
1489-
// @param parentKey key of the item's parent
1490-
//
1491-
// @return handle to the parent item if the validations pass
1492-
// otherwise, an empty Handle is returned.
1493-
//
1494-
ReadHandle validateAndGetParentHandleForChainedMoveLocked(
1495-
const ChainedItem& item, const Key& parentKey);
1482+
WriteHandle allocateChainedItemInternal(const Item& parent, uint32_t size);
14961483

14971484
// Given an existing item, allocate a new one for the
14981485
// existing one to later be moved into.
@@ -1609,7 +1596,7 @@ class CacheAllocator : public CacheBase {
16091596
// @param newParent the new parent for the chain
16101597
//
16111598
// @throw if any of the conditions for parent or newParent are not met.
1612-
void transferChainLocked(WriteHandle& parent, WriteHandle& newParent);
1599+
void transferChainLocked(Item& parent, WriteHandle& newParent);
16131600

16141601
// replace a chained item in the existing chain. This needs to be called
16151602
// with the chained item lock held exclusive
@@ -1623,6 +1610,24 @@ class CacheAllocator : public CacheBase {
16231610
WriteHandle newItemHdl,
16241611
const Item& parent);
16251612

1613+
//
1614+
// Performs the actual inplace replace - it is called from
1615+
// moveChainedItem and replaceChainedItemLocked
1616+
// must hold chainedItemLock
1617+
//
1618+
// @param oldItem the item we are replacing in the chain
1619+
// @param newItem the item we are replacing it with
1620+
// @param parent the parent for the chain
1621+
// @param fromMove used to determine if the replaced was called from
1622+
// moveChainedItem - we avoid the handle destructor
1623+
// in this case.
1624+
//
1625+
// @return handle to the oldItem
1626+
void replaceInChainLocked(Item& oldItem,
1627+
WriteHandle& newItemHdl,
1628+
const Item& parent,
1629+
bool fromMove);
1630+
16261631
// Insert an item into MM container. The caller must hold a valid handle for
16271632
// the item.
16281633
//
@@ -1731,6 +1736,19 @@ class CacheAllocator : public CacheBase {
17311736

17321737
using EvictionIterator = typename MMContainer::LockedIterator;
17331738

1739+
// Wakes up waiters if there are any
1740+
//
1741+
// @param item wakes waiters that are waiting on that item
1742+
// @param handle handle to pass to the waiters
1743+
void wakeUpWaiters(Item& item, WriteHandle handle);
1744+
1745+
// Unmarks item as moving and wakes up any waiters waiting on that item
1746+
//
1747+
// @param item wakes waiters that are waiting on that item
1748+
// @param handle handle to pass to the waiters
1749+
typename RefcountWithFlags::Value unmarkMovingAndWakeUpWaiters(
1750+
Item& item, WriteHandle handle);
1751+
17341752
// Deserializer CacheAllocatorMetadata and verify the version
17351753
//
17361754
// @param deserializer Deserializer object
@@ -1844,6 +1862,11 @@ class CacheAllocator : public CacheBase {
18441862
Item& item,
18451863
util::Throttler& throttler);
18461864

1865+
// Helper function to create PutToken
1866+
//
1867+
// @return valid token if the item should be written to NVM cache.
1868+
typename NvmCacheT::PutToken createPutToken(Item& item);
1869+
18471870
// Helper function to evict a normal item for slab release
18481871
//
18491872
// @return last handle for corresponding to item on success. empty handle on
@@ -2082,6 +2105,88 @@ class CacheAllocator : public CacheBase {
20822105

20832106
// BEGIN private members
20842107

2108+
bool tryGetHandleWithWaitContextForMovingItem(Item& item,
2109+
WriteHandle& handle);
2110+
2111+
size_t wakeUpWaitersLocked(folly::StringPiece key, WriteHandle&& handle);
2112+
2113+
class MoveCtx {
2114+
public:
2115+
MoveCtx() {}
2116+
2117+
~MoveCtx() {
2118+
// prevent any further enqueue to waiters
2119+
// Note: we don't need to hold locks since no one can enqueue
2120+
// after this point.
2121+
wakeUpWaiters();
2122+
}
2123+
2124+
// record the item handle. Upon destruction we will wake up the waiters
2125+
// and pass a clone of the handle to the callBack. By default we pass
2126+
// a null handle
2127+
void setItemHandle(WriteHandle _it) { it = std::move(_it); }
2128+
2129+
// enqueue a waiter into the waiter list
2130+
// @param waiter WaitContext
2131+
void addWaiter(std::shared_ptr<WaitContext<ReadHandle>> waiter) {
2132+
XDCHECK(waiter);
2133+
waiters.push_back(std::move(waiter));
2134+
}
2135+
2136+
size_t numWaiters() const { return waiters.size(); }
2137+
2138+
private:
2139+
// notify all pending waiters that are waiting for the fetch.
2140+
void wakeUpWaiters() {
2141+
bool refcountOverflowed = false;
2142+
for (auto& w : waiters) {
2143+
// If refcount overflowed earlier, then we will return miss to
2144+
// all subsequent waiters.
2145+
if (refcountOverflowed) {
2146+
w->set(WriteHandle{});
2147+
continue;
2148+
}
2149+
2150+
try {
2151+
w->set(it.clone());
2152+
} catch (const exception::RefcountOverflow&) {
2153+
// We'll return a miss to the user's pending read,
2154+
// so we should enqueue a delete via NvmCache.
2155+
// TODO: cache.remove(it);
2156+
refcountOverflowed = true;
2157+
}
2158+
}
2159+
}
2160+
2161+
WriteHandle it; // will be set when Context is being filled
2162+
std::vector<std::shared_ptr<WaitContext<ReadHandle>>> waiters; // list of
2163+
// waiters
2164+
};
2165+
using MoveMap =
2166+
folly::F14ValueMap<folly::StringPiece,
2167+
std::unique_ptr<MoveCtx>,
2168+
folly::HeterogeneousAccessHash<folly::StringPiece>>;
2169+
2170+
static size_t getShardForKey(folly::StringPiece key) {
2171+
return folly::Hash()(key) % kShards;
2172+
}
2173+
2174+
MoveMap& getMoveMapForShard(size_t shard) {
2175+
return movesMap_[shard].movesMap_;
2176+
}
2177+
2178+
MoveMap& getMoveMap(folly::StringPiece key) {
2179+
return getMoveMapForShard(getShardForKey(key));
2180+
}
2181+
2182+
std::unique_lock<std::mutex> getMoveLockForShard(size_t shard) {
2183+
return std::unique_lock<std::mutex>(moveLock_[shard].moveLock_);
2184+
}
2185+
2186+
std::unique_lock<std::mutex> getMoveLock(folly::StringPiece key) {
2187+
return getMoveLockForShard(getShardForKey(key));
2188+
}
2189+
20852190
// Whether the memory allocator for this cache allocator was created on shared
20862191
// memory. The hash table, chained item hash table etc is also created on
20872192
// shared memory except for temporary shared memory mode when they're created
@@ -2175,6 +2280,22 @@ class CacheAllocator : public CacheBase {
21752280
// poolResizer_, poolOptimizer_, memMonitor_, reaper_
21762281
mutable std::mutex workersMutex_;
21772282

2283+
static constexpr size_t kShards = 8192; // TODO: need to define right value
2284+
2285+
struct MovesMapShard {
2286+
alignas(folly::hardware_destructive_interference_size) MoveMap movesMap_;
2287+
};
2288+
2289+
struct MoveLock {
2290+
alignas(folly::hardware_destructive_interference_size) std::mutex moveLock_;
2291+
};
2292+
2293+
// a map of all pending moves
2294+
std::vector<MovesMapShard> movesMap_;
2295+
2296+
// a map of move locks for each shard
2297+
std::vector<MoveLock> moveLock_;
2298+
21782299
// time when the ram cache was first created
21792300
const uint32_t cacheCreationTime_{0};
21802301

cachelib/allocator/CacheItem.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ 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() {
313313
try {
314314
return ref_.incRef();
315315
} catch (exception::RefcountOverflow& e) {

cachelib/allocator/MM2Q-inl.h

+6
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,12 @@ void MM2Q::Container<T, HookPtr>::withEvictionIterator(F&& fun) {
258258
}
259259
}
260260

261+
template <typename T, MM2Q::Hook<T> T::*HookPtr>
262+
template <typename F>
263+
void MM2Q::Container<T, HookPtr>::withContainerLock(F&& fun) {
264+
lruMutex_->lock_combine([this, &fun]() { fun(); });
265+
}
266+
261267
template <typename T, MM2Q::Hook<T> T::*HookPtr>
262268
void MM2Q::Container<T, HookPtr>::removeLocked(T& node,
263269
bool doRebalance) noexcept {

cachelib/allocator/MM2Q.h

+4
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,10 @@ class MM2Q {
502502
template <typename F>
503503
void withEvictionIterator(F&& f);
504504

505+
// Execute provided function under container lock.
506+
template <typename F>
507+
void withContainerLock(F&& f);
508+
505509
// get the current config as a copy
506510
Config getConfig() const;
507511

cachelib/allocator/MMLru-inl.h

+6
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ 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 MMLru::Container<T, HookPtr>::withContainerLock(F&& fun) {
235+
lruMutex_->lock_combine([this, &fun]() { fun(); });
236+
}
237+
232238
template <typename T, MMLru::Hook<T> T::*HookPtr>
233239
void MMLru::Container<T, HookPtr>::ensureNotInsertionPoint(T& node) noexcept {
234240
// If we are removing the insertion point node, grow tail before we remove

cachelib/allocator/MMLru.h

+4
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,10 @@ class MMLru {
376376
template <typename F>
377377
void withEvictionIterator(F&& f);
378378

379+
// Execute provided function under container lock.
380+
template <typename F>
381+
void withContainerLock(F&& f);
382+
379383
// get copy of current config
380384
Config getConfig() const;
381385

cachelib/allocator/MMTinyLFU-inl.h

+7
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ void MMTinyLFU::Container<T, HookPtr>::withEvictionIterator(F&& fun) {
227227
fun(getEvictionIterator());
228228
}
229229

230+
template <typename T, MMTinyLFU::Hook<T> T::*HookPtr>
231+
template <typename F>
232+
void MMTinyLFU::Container<T, HookPtr>::withContainerLock(F&& fun) {
233+
LockHolder l(lruMutex_);
234+
fun();
235+
}
236+
230237
template <typename T, MMTinyLFU::Hook<T> T::*HookPtr>
231238
void MMTinyLFU::Container<T, HookPtr>::removeLocked(T& node) noexcept {
232239
if (isTiny(node)) {

cachelib/allocator/MMTinyLFU.h

+4
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ class MMTinyLFU {
497497
template <typename F>
498498
void withEvictionIterator(F&& f);
499499

500+
// Execute provided function under container lock.
501+
template <typename F>
502+
void withContainerLock(F&& f);
503+
500504
// for saving the state of the lru
501505
//
502506
// precondition: serialization must happen without any reader or writer

cachelib/allocator/Refcount.h

+21-8
Original file line numberDiff line numberDiff line change
@@ -130,30 +130,35 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
130130
RefcountWithFlags& operator=(const RefcountWithFlags&) = delete;
131131
RefcountWithFlags(RefcountWithFlags&&) = delete;
132132
RefcountWithFlags& operator=(RefcountWithFlags&&) = delete;
133-
133+
enum incResult { incOk, incFailedMoving, incFailedEviction };
134134
// Bumps up the reference count only if the new count will be strictly less
135135
// than or equal to the maxCount and the item is not exclusive
136136
// @return true if refcount is bumped. false otherwise (if item is exclusive)
137137
// @throw exception::RefcountOverflow if new count would be greater than
138138
// maxCount
139-
FOLLY_ALWAYS_INLINE bool incRef() {
140-
auto predicate = [](const Value curValue) {
139+
FOLLY_ALWAYS_INLINE incResult incRef() {
140+
incResult res = incOk;
141+
auto predicate = [&res](const Value curValue) {
141142
Value bitMask = getAdminRef<kExclusive>();
142143

143144
const bool exlusiveBitIsSet = curValue & bitMask;
144145
if (UNLIKELY((curValue & kAccessRefMask) == (kAccessRefMask))) {
145146
throw exception::RefcountOverflow("Refcount maxed out.");
147+
} else if (exlusiveBitIsSet) {
148+
res = (curValue & kAccessRefMask) == 0 ? incFailedEviction
149+
: incFailedMoving;
150+
return false;
146151
}
147-
148-
// Check if the item is not marked for eviction
149-
return !exlusiveBitIsSet || ((curValue & kAccessRefMask) != 0);
152+
res = incOk;
153+
return true;
150154
};
151155

152156
auto newValue = [](const Value curValue) {
153157
return (curValue + static_cast<Value>(1));
154158
};
155159

156-
return atomicUpdateValue(predicate, newValue);
160+
atomicUpdateValue(predicate, newValue);
161+
return res;
157162
}
158163

159164
// Bumps down the reference count
@@ -322,11 +327,19 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
322327
bool markMoving() {
323328
Value linkedBitMask = getAdminRef<kLinked>();
324329
Value exclusiveBitMask = getAdminRef<kExclusive>();
330+
Value isChainedItemFlag = getFlag<kIsChainedItem>();
325331

326-
auto predicate = [linkedBitMask, exclusiveBitMask](const Value curValue) {
332+
auto predicate = [linkedBitMask, exclusiveBitMask,
333+
isChainedItemFlag](const Value curValue) {
327334
const bool unlinked = !(curValue & linkedBitMask);
328335
const bool alreadyExclusive = curValue & exclusiveBitMask;
336+
const bool isChained = curValue & isChainedItemFlag;
329337

338+
// chained item can have ref count == 1, this just means it's linked in
339+
// the chain
340+
if ((curValue & kAccessRefMask) > isChained ? 1 : 0) {
341+
return false;
342+
}
330343
if (unlinked || alreadyExclusive) {
331344
return false;
332345
}

cachelib/allocator/tests/AllocatorTypeTest.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,8 @@ TYPED_TEST(BaseAllocatorTest, AddChainedItemMultiThreadWithMovingAndSync) {
288288
this->testAddChainedItemMultithreadWithMovingAndSync();
289289
}
290290

291-
TYPED_TEST(BaseAllocatorTest, TransferChainWhileMoving) {
292-
this->testTransferChainWhileMoving();
291+
TYPED_TEST(BaseAllocatorTest, TransferChainAfterMoving) {
292+
this->testTransferChainAfterMoving();
293293
}
294294

295295
TYPED_TEST(BaseAllocatorTest, AddAndPopChainedItemMultithread) {

0 commit comments

Comments
 (0)