Skip to content

Commit 3d73cfe

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 8b0a460 commit 3d73cfe

14 files changed

+620
-480
lines changed

cachelib/allocator/CacheAllocator-inl.h

+391-323
Large diffs are not rendered by default.

cachelib/allocator/CacheAllocator.h

+138-27
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
@@ -1824,16 +1842,6 @@ class CacheAllocator : public CacheBase {
18241842
Item& item,
18251843
util::Throttler& throttler);
18261844

1827-
// "Move" (by copying) the content in this item to another memory
1828-
// location by invoking the move callback.
1829-
//
1830-
// @param item old item to be moved elsewhere
1831-
// @param newItemHdl handle of new item to be moved into
1832-
//
1833-
// @return true if the item has been moved
1834-
// false if we have exhausted moving attempts
1835-
bool tryMovingForSlabRelease(Item& item, WriteHandle& newItemHdl);
1836-
18371845
// Evict an item from access and mm containers and
18381846
// ensure it is safe for freeing.
18391847
//
@@ -1844,6 +1852,11 @@ class CacheAllocator : public CacheBase {
18441852
Item& item,
18451853
util::Throttler& throttler);
18461854

1855+
// Helper function to create PutToken
1856+
//
1857+
// @return valid token if the item should be written to NVM cache.
1858+
typename NvmCacheT::PutToken createPutToken(Item& item);
1859+
18471860
// Helper function to evict a normal item for slab release
18481861
//
18491862
// @return last handle for corresponding to item on success. empty handle on
@@ -2082,6 +2095,88 @@ class CacheAllocator : public CacheBase {
20822095

20832096
// BEGIN private members
20842097

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

2273+
static constexpr size_t kShards = 8192; // TODO: need to define right value
2274+
2275+
struct MovesMapShard {
2276+
alignas(folly::hardware_destructive_interference_size) MoveMap movesMap_;
2277+
};
2278+
2279+
struct MoveLock {
2280+
alignas(folly::hardware_destructive_interference_size) std::mutex moveLock_;
2281+
};
2282+
2283+
// a map of all pending moves
2284+
std::vector<MovesMapShard> movesMap_;
2285+
2286+
// a map of move locks for each shard
2287+
std::vector<MoveLock> moveLock_;
2288+
21782289
// time when the ram cache was first created
21792290
const uint32_t cacheCreationTime_{0};
21802291

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

+27-9
Original file line numberDiff line numberDiff line change
@@ -130,30 +130,40 @@ 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
136-
// @return true if refcount is bumped. false otherwise (if item is exclusive)
136+
// @return incResult::incOk if refcount is bumped. The refcount is not bumped
137+
// if Exclusive bit is set and appropriate error code is returned.
138+
// incResult::incFailedMoving if item is moving (exclusive bit is set
139+
// and refcount > 0).
140+
// incResult::incFailedEviction if Item is evicted
141+
// (only exclusive bit is set).
137142
// @throw exception::RefcountOverflow if new count would be greater than
138143
// maxCount
139-
FOLLY_ALWAYS_INLINE bool incRef() {
140-
auto predicate = [](const Value curValue) {
144+
FOLLY_ALWAYS_INLINE incResult incRef() {
145+
incResult res = incOk;
146+
auto predicate = [&res](const Value curValue) {
141147
Value bitMask = getAdminRef<kExclusive>();
142148

143149
const bool exlusiveBitIsSet = curValue & bitMask;
144150
if (UNLIKELY((curValue & kAccessRefMask) == (kAccessRefMask))) {
145151
throw exception::RefcountOverflow("Refcount maxed out.");
152+
} else if (exlusiveBitIsSet) {
153+
res = (curValue & kAccessRefMask) == 0 ? incFailedEviction
154+
: incFailedMoving;
155+
return false;
146156
}
147-
148-
// Check if the item is not marked for eviction
149-
return !exlusiveBitIsSet || ((curValue & kAccessRefMask) != 0);
157+
res = incOk;
158+
return true;
150159
};
151160

152161
auto newValue = [](const Value curValue) {
153162
return (curValue + static_cast<Value>(1));
154163
};
155164

156-
return atomicUpdateValue(predicate, newValue);
165+
atomicUpdateValue(predicate, newValue);
166+
return res;
157167
}
158168

159169
// Bumps down the reference count
@@ -322,11 +332,19 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
322332
bool markMoving() {
323333
Value linkedBitMask = getAdminRef<kLinked>();
324334
Value exclusiveBitMask = getAdminRef<kExclusive>();
335+
Value isChainedItemFlag = getFlag<kIsChainedItem>();
325336

326-
auto predicate = [linkedBitMask, exclusiveBitMask](const Value curValue) {
337+
auto predicate = [linkedBitMask, exclusiveBitMask,
338+
isChainedItemFlag](const Value curValue) {
327339
const bool unlinked = !(curValue & linkedBitMask);
328340
const bool alreadyExclusive = curValue & exclusiveBitMask;
341+
const bool isChained = curValue & isChainedItemFlag;
329342

343+
// chained item can have ref count == 1, this just means it's linked in
344+
// the chain
345+
if ((curValue & kAccessRefMask) > isChained ? 1 : 0) {
346+
return false;
347+
}
330348
if (unlinked || alreadyExclusive) {
331349
return false;
332350
}

0 commit comments

Comments
 (0)