Skip to content

Commit dcce4be

Browse files
committed
Transparent Item movements
1 parent 8b3d4fe commit dcce4be

14 files changed

+672
-253
lines changed

cachelib/allocator/CacheAllocator-inl.h

+434-201
Large diffs are not rendered by default.

cachelib/allocator/CacheAllocator.h

+136-4
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,13 +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,
1482+
WriteHandle allocateChainedItemInternal(const Item& parent,
14831483
uint32_t size);
14841484

14851485
// Given an item and its parentKey, validate that the parentKey
@@ -1609,7 +1609,7 @@ class CacheAllocator : public CacheBase {
16091609
// @param newParent the new parent for the chain
16101610
//
16111611
// @throw if any of the conditions for parent or newParent are not met.
1612-
void transferChainLocked(WriteHandle& parent, WriteHandle& newParent);
1612+
void transferChainLocked(Item& parent, WriteHandle& newParent);
16131613

16141614
// replace a chained item in the existing chain. This needs to be called
16151615
// with the chained item lock held exclusive
@@ -1623,6 +1623,24 @@ class CacheAllocator : public CacheBase {
16231623
WriteHandle newItemHdl,
16241624
const Item& parent);
16251625

1626+
//
1627+
// Performs the actual inplace replace - it is called from
1628+
// moveChainedItem and replaceChainedItemLocked
1629+
// must hold chainedItemLock
1630+
//
1631+
// @param oldItem the item we are replacing in the chain
1632+
// @param newItem the item we are replacing it with
1633+
// @param parent the parent for the chain
1634+
// @param fromMove used to determine if the replaced was called from
1635+
// moveChainedItem - we avoid the handle destructor
1636+
// in this case.
1637+
//
1638+
// @return handle to the oldItem
1639+
void replaceInChainLocked(Item& oldItem,
1640+
WriteHandle& newItemHdl,
1641+
const Item& parent,
1642+
bool fromMove);
1643+
16261644
// Insert an item into MM container. The caller must hold a valid handle for
16271645
// the item.
16281646
//
@@ -1731,6 +1749,18 @@ class CacheAllocator : public CacheBase {
17311749

17321750
using EvictionIterator = typename MMContainer::LockedIterator;
17331751

1752+
// Wakes up waiters if there are any
1753+
//
1754+
// @param item wakes waiters that are waiting on that item
1755+
// @param handle handle to pass to the waiters
1756+
void wakeUpWaiters(Item& item, WriteHandle handle);
1757+
1758+
// Unmarks item as moving and wakes up any waiters waiting on that item
1759+
//
1760+
// @param item wakes waiters that are waiting on that item
1761+
// @param handle handle to pass to the waiters
1762+
typename RefcountWithFlags::Value unmarkMovingAndWakeUpWaiters(Item &item, WriteHandle handle);
1763+
17341764
// Deserializer CacheAllocatorMetadata and verify the version
17351765
//
17361766
// @param deserializer Deserializer object
@@ -1844,6 +1874,11 @@ class CacheAllocator : public CacheBase {
18441874
Item& item,
18451875
util::Throttler& throttler);
18461876

1877+
// Helper function to create PutToken
1878+
//
1879+
// @return valid token if the item should be written to NVM cache.
1880+
typename NvmCacheT::PutToken createPutToken(Item& item);
1881+
18471882
// Helper function to evict a normal item for slab release
18481883
//
18491884
// @return last handle for corresponding to item on success. empty handle on
@@ -2082,6 +2117,87 @@ class CacheAllocator : public CacheBase {
20822117

20832118
// BEGIN private members
20842119

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

2294+
static constexpr size_t kShards = 8192; // TODO: need to define right value
2295+
2296+
struct MovesMapShard {
2297+
alignas(folly::hardware_destructive_interference_size) MoveMap movesMap_;
2298+
};
2299+
2300+
struct MoveLock {
2301+
alignas(folly::hardware_destructive_interference_size) std::mutex moveLock_;
2302+
};
2303+
2304+
// a map of all pending moves
2305+
std::vector<MovesMapShard> movesMap_;
2306+
2307+
// a map of move locks for each shard
2308+
std::vector<MoveLock> moveLock_;
2309+
21782310
// time when the ram cache was first created
21792311
const uint32_t cacheCreationTime_{0};
21802312

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

+37-20
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() {
144+
incResult res = incOk;
145+
auto predicate = [&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) {
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
@@ -322,11 +333,17 @@ class FOLLY_PACK_ATTR RefcountWithFlags {
322333
bool markMoving() {
323334
Value linkedBitMask = getAdminRef<kLinked>();
324335
Value exclusiveBitMask = getAdminRef<kExclusive>();
336+
Value isChainedItemFlag = getFlag<kIsChainedItem>();
325337

326-
auto predicate = [linkedBitMask, exclusiveBitMask](const Value curValue) {
338+
auto predicate = [linkedBitMask, exclusiveBitMask, 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 the chain
344+
if ((curValue & kAccessRefMask) > isChained ? 1 : 0) {
345+
return false;
346+
}
330347
if (unlinked || alreadyExclusive) {
331348
return false;
332349
}

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)