Skip to content

Commit

Permalink
Expose 'eviction' and 'moving' states for an item
Browse files Browse the repository at this point in the history
markForEviction is used only in findEviction and
evictForSlabRelease but not for item movement.

moveForSlabRelease relies on markMoving().

Only allow to mark item for eviction if ref count
is 0. This ensures that after item is marked, eviction
cannot fail. This makes it possible to return NULL handle
immediately from find if item is marked for eviction.

markMoving() does have those restrictions and still allows
readers to obtain a handle to a moving item.

Also, add option to use combined locking for MMContainer
iteration.

Pass item ref to NavyCache::put
  • Loading branch information
igchor committed Jan 9, 2023
1 parent 9fe02ed commit 6aa4078
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 589 deletions.
613 changes: 232 additions & 381 deletions cachelib/allocator/CacheAllocator-inl.h

Large diffs are not rendered by default.

61 changes: 17 additions & 44 deletions cachelib/allocator/CacheAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,7 @@ class CacheAllocator : public CacheBase {

private:
// wrapper around Item's refcount and active handle tracking
FOLLY_ALWAYS_INLINE void incRef(Item& it);
FOLLY_ALWAYS_INLINE bool incRef(Item& it);
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef(Item& it);

// drops the refcount and if needed, frees the allocation back to the memory
Expand Down Expand Up @@ -1359,6 +1359,12 @@ class CacheAllocator : public CacheBase {
bool nascent = false,
const Item* toRecycle = nullptr);

// Must be called by the thread which called markForEviction and
// succeeded. After this call, the item is unlinked from Access and
// MM Containers. The item is no longer marked as exclusive and it's
// ref count is 0 - it's available for recycling.
void unlinkItemForEviction(Item& it);

// acquires an handle on the item. returns an empty handle if it is null.
// @param it pointer to an item
// @return WriteHandle return a handle to this item
Expand Down Expand Up @@ -1448,17 +1454,17 @@ class CacheAllocator : public CacheBase {
// @return handle to the parent item if the validations pass
// otherwise, an empty Handle is returned.
//
ReadHandle validateAndGetParentHandleForChainedMoveLocked(
WriteHandle validateAndGetParentHandleForChainedMoveLocked(
const ChainedItem& item, const Key& parentKey);

// Given an existing item, allocate a new one for the
// existing one to later be moved into.
//
// @param oldItem the item we want to allocate a new item for
// @param item reference to the item we want to allocate a new item for
//
// @return handle to the newly allocated item
//
WriteHandle allocateNewItemForOldItem(const Item& oldItem);
WriteHandle allocateNewItemForOldItem(const Item& item);

// internal helper that grabs a refcounted handle to the item. This does
// not record the access to reflect in the mmContainer.
Expand Down Expand Up @@ -1512,7 +1518,7 @@ class CacheAllocator : public CacheBase {
// callback is responsible for copying the contents and fixing the semantics
// of chained item.
//
// @param oldItem Reference to the item being moved
// @param oldItem item being moved
// @param newItemHdl Reference to the handle of the new item being moved into
//
// @return true If the move was completed, and the containers were updated
Expand Down Expand Up @@ -1662,25 +1668,6 @@ class CacheAllocator : public CacheBase {

using EvictionIterator = typename MMContainer::LockedIterator;

// Advance the current iterator and try to evict a regular item
//
// @param mmContainer the container to look for evictions.
// @param itr iterator holding the item
//
// @return valid handle to regular item on success. This will be the last
// handle to the item. On failure an empty handle.
WriteHandle advanceIteratorAndTryEvictRegularItem(MMContainer& mmContainer,
EvictionIterator& itr);

// Advance the current iterator and try to evict a chained item
// Iterator may also be reset during the course of this function
//
// @param itr iterator holding the item
//
// @return valid handle to the parent item on success. This will be the last
// handle to the item
WriteHandle advanceIteratorAndTryEvictChainedItem(EvictionIterator& itr);

// Deserializer CacheAllocatorMetadata and verify the version
//
// @param deserializer Deserializer object
Expand Down Expand Up @@ -1765,13 +1752,14 @@ class CacheAllocator : public CacheBase {
//
//
// @param ctx slab release context
// @param item old item to be moved elsewhere
// @param oldItem old item to be moved elsewhere
// @param handle handle to the item or to it's parent (if chained)
// @param throttler slow this function down as not to take too much cpu
//
// @return true if the item has been moved
// false if we have exhausted moving attempts
bool moveForSlabRelease(const SlabReleaseContext& ctx,
Item& item,
Item& oldItem,
util::Throttler& throttler);

// "Move" (by copying) the content in this item to another memory
Expand All @@ -1794,18 +1782,7 @@ class CacheAllocator : public CacheBase {
Item& item,
util::Throttler& throttler);

// Helper function to evict a normal item for slab release
//
// @return last handle for corresponding to item on success. empty handle on
// failure. caller can retry if needed.
WriteHandle evictNormalItemForSlabRelease(Item& item);

// Helper function to evict a child item for slab release
// As a side effect, the parent item is also evicted
//
// @return last handle to the parent item of the child on success. empty
// handle on failure. caller can retry.
WriteHandle evictChainedItemForSlabRelease(ChainedItem& item);
typename NvmCacheT::PutToken createPutToken(Item& item);

// Helper function to remove a item if expired.
//
Expand Down Expand Up @@ -1927,18 +1904,14 @@ class CacheAllocator : public CacheBase {
std::optional<bool> saveNvmCache();
void saveRamCache();

static bool itemExclusivePredicate(const Item& item) {
return item.getRefCount() == 0;
static bool itemSlabMovePredicate(const Item& item) {
return item.isMoving() && item.getRefCount() == 0;
}

static bool itemExpiryPredicate(const Item& item) {
return item.getRefCount() == 1 && item.isExpired();
}

static bool parentEvictForSlabReleasePredicate(const Item& item) {
return item.getRefCount() == 1 && !item.isMoving();
}

std::unique_ptr<Deserializer> createDeserializer();

// Execute func on each item. `func` can throw exception but must ensure
Expand Down
1 change: 1 addition & 0 deletions cachelib/allocator/MM2Q.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class MM2Q {
enum LruType { Warm, WarmTail, Hot, Cold, ColdTail, NumTypes };

// Config class for MM2Q
// TODO: implement support for useCombinedLockForIterators
struct Config {
// Create from serialized config
explicit Config(SerializationConfigType configState)
Expand Down
7 changes: 2 additions & 5 deletions cachelib/allocator/MMTinyLFU.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ class MMTinyLFU {
Iterator(const Iterator&) = delete;
Iterator& operator=(const Iterator&) = delete;
Iterator(Iterator&&) noexcept = default;
Iterator& operator=(Iterator&&) noexcept = default;

Iterator& operator++() noexcept {
++getIter();
Expand Down Expand Up @@ -407,11 +408,7 @@ class MMTinyLFU {
mIter_.resetToBegin();
}

protected:
// private because it's easy to misuse and cause deadlock for MMTinyLFU
Iterator& operator=(Iterator&&) noexcept = default;

// create an lru iterator
private:
explicit Iterator(const Container<T, HookPtr>& c) noexcept;

const ListIterator& getIter() const noexcept {
Expand Down
17 changes: 7 additions & 10 deletions cachelib/allocator/nvmcache/NvmCache-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -460,19 +460,18 @@ uint32_t NvmCache<C>::getStorageSizeInNvm(const Item& it) {
}

template <typename C>
std::unique_ptr<NvmItem> NvmCache<C>::makeNvmItem(const WriteHandle& hdl) {
const auto& item = *hdl;
std::unique_ptr<NvmItem> NvmCache<C>::makeNvmItem(const Item& item) {
auto poolId = cache_.getAllocInfo((void*)(&item)).poolId;

if (item.isChainedItem()) {
throw std::invalid_argument(folly::sformat(
"Chained item can not be flushed separately {}", hdl->toString()));
"Chained item can not be flushed separately {}", item.toString()));
}

auto chainedItemRange =
CacheAPIWrapperForNvm<C>::viewAsChainedAllocsRange(cache_, *hdl);
CacheAPIWrapperForNvm<C>::viewAsChainedAllocsRange(cache_, item);
if (config_.encodeCb && !config_.encodeCb(EncodeDecodeContext{
*(hdl.getInternal()), chainedItemRange})) {
const_cast<Item&>(item), chainedItemRange})) {
return nullptr;
}

Expand All @@ -496,12 +495,10 @@ std::unique_ptr<NvmItem> NvmCache<C>::makeNvmItem(const WriteHandle& hdl) {
}

template <typename C>
void NvmCache<C>::put(WriteHandle& hdl, PutToken token) {
void NvmCache<C>::put(Item& item, PutToken token) {
util::LatencyTracker tracker(stats().nvmInsertLatency_);
HashedKey hk{hdl->getKey()};
HashedKey hk{item.getKey()};

XDCHECK(hdl);
auto& item = *hdl;
// for regular items that can only write to nvmcache upon eviction, we
// should not be recording a write for an nvmclean item unless it is marked
// as evicted from nvmcache.
Expand All @@ -526,7 +523,7 @@ void NvmCache<C>::put(WriteHandle& hdl, PutToken token) {
return;
}

auto nvmItem = makeNvmItem(hdl);
auto nvmItem = makeNvmItem(item);
if (!nvmItem) {
stats().numNvmPutEncodeFailure.inc();
return;
Expand Down
6 changes: 3 additions & 3 deletions cachelib/allocator/nvmcache/NvmCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,11 @@ class NvmCache {
PutToken createPutToken(folly::StringPiece key);

// store the given item in navy
// @param hdl handle to cache item. should not be null
// @param item cache item
// @param token the put token for the item. this must have been
// obtained before enqueueing the put to maintain
// consistency
void put(WriteHandle& hdl, PutToken token);
void put(Item& item, PutToken token);

// returns the current state of whether nvmcache is enabled or not. nvmcache
// can be disabled if the backend implementation ends up in a corrupt state
Expand Down Expand Up @@ -286,7 +286,7 @@ class NvmCache {
// returns true if there is tombstone entry for the key.
bool hasTombStone(HashedKey hk);

std::unique_ptr<NvmItem> makeNvmItem(const WriteHandle& handle);
std::unique_ptr<NvmItem> makeNvmItem(const Item& item);

// wrap an item into a blob for writing into navy.
Blob makeBlob(const Item& it);
Expand Down
4 changes: 2 additions & 2 deletions cachelib/allocator/nvmcache/tests/NvmTestBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class NvmCacheTest : public testing::Test {
void pushToNvmCacheFromRamForTesting(WriteHandle& handle) {
auto nvmCache = getNvmCache();
if (nvmCache) {
nvmCache->put(handle, nvmCache->createPutToken(handle->getKey()));
nvmCache->put(*handle, nvmCache->createPutToken(handle->getKey()));
}
}

Expand All @@ -127,7 +127,7 @@ class NvmCacheTest : public testing::Test {
}

std::unique_ptr<NvmItem> makeNvmItem(const WriteHandle& handle) {
return getNvmCache()->makeNvmItem(handle);
return getNvmCache()->makeNvmItem(*handle);
}

std::unique_ptr<folly::IOBuf> createItemAsIOBuf(folly::StringPiece key,
Expand Down
30 changes: 19 additions & 11 deletions cachelib/allocator/tests/BaseAllocatorTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -4080,15 +4080,16 @@ class BaseAllocatorTest : public AllocatorTest<AllocatorT> {
// Check that item is in the expected container.
bool findItem(AllocatorT& allocator, typename AllocatorT::Item* item) {
auto& container = allocator.getMMContainer(*item);
auto itr = container.getEvictionIterator();
bool found = false;
while (itr) {
if (itr.get() == item) {
found = true;
break;
container.withEvictionIterator([&found, &item](auto&& itr) {
while (itr) {
if (itr.get() == item) {
found = true;
break;
}
++itr;
}
++itr;
}
});
return found;
}

Expand Down Expand Up @@ -5476,8 +5477,12 @@ class BaseAllocatorTest : public AllocatorTest<AllocatorT> {
ASSERT_TRUE(big->isInMMContainer());

auto& mmContainer = alloc.getMMContainer(*big);
auto itr = mmContainer.getEvictionIterator();
ASSERT_EQ(big.get(), &(*itr));

typename AllocatorT::Item* evictionCandidate = nullptr;
mmContainer.withEvictionIterator(
[&evictionCandidate](auto&& itr) { evictionCandidate = itr.get(); });

ASSERT_EQ(big.get(), evictionCandidate);

alloc.remove("hello");
}
Expand All @@ -5491,8 +5496,11 @@ class BaseAllocatorTest : public AllocatorTest<AllocatorT> {
ASSERT_TRUE(small2->isInMMContainer());

auto& mmContainer = alloc.getMMContainer(*small2);
auto itr = mmContainer.getEvictionIterator();
ASSERT_EQ(small2.get(), &(*itr));

typename AllocatorT::Item* evictionCandidate = nullptr;
mmContainer.withEvictionIterator(
[&evictionCandidate](auto&& itr) { evictionCandidate = itr.get(); });
ASSERT_EQ(small2.get(), evictionCandidate);

alloc.remove("hello");
}
Expand Down
Loading

0 comments on commit 6aa4078

Please sign in to comment.