Skip to content

Commit e33e0fb

Browse files
committed
Transparent Item movements
1 parent 498206d commit e33e0fb

File tree

11 files changed

+546
-178
lines changed

11 files changed

+546
-178
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 347 additions & 154 deletions
Large diffs are not rendered by default.

cachelib/allocator/CacheAllocator.h

Lines changed: 131 additions & 4 deletions
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
@@ -2082,6 +2112,87 @@ class CacheAllocator : public CacheBase {
20822112

20832113
// BEGIN private members
20842114

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

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

cachelib/allocator/CacheItem.h

Lines changed: 1 addition & 1 deletion
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 30 additions & 19 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() {
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

cachelib/common/Mutex.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ class RWBucketLocks : public BaseBucketLocks<LockType, LockAlignmentType> {
341341
using Lock = LockType;
342342
using ReadLockHolder = ReadLockHolderType;
343343
using WriteLockHolder = WriteLockHolderType;
344+
using LockHolder = std::unique_lock<Lock>;
344345

345346
RWBucketLocks(uint32_t locksPower, std::shared_ptr<Hash> hasher)
346347
: Base::BaseBucketLocks(locksPower, std::move(hasher)) {}
@@ -357,6 +358,11 @@ class RWBucketLocks : public BaseBucketLocks<LockType, LockAlignmentType> {
357358
return WriteLockHolder{Base::getLock(args...)};
358359
}
359360

361+
template <typename... Args>
362+
LockHolder tryLockExclusive(Args... args) noexcept {
363+
return LockHolder(Base::getLock(args...), std::try_to_lock);
364+
}
365+
360366
// try to grab the reader lock for a limit _timeout_ duration
361367
template <typename... Args>
362368
ReadLockHolder lockShared(const std::chrono::microseconds& timeout,

0 commit comments

Comments
 (0)