Skip to content

Commit c6e0fd2

Browse files
committed
Increment item refCount under AC lock
to avoid races with remove/replace with predicate. Also, refact tryEvictRegularItem and tryEvictChainedItem into a single function.
1 parent 7661b20 commit c6e0fd2

File tree

4 files changed

+83
-113
lines changed

4 files changed

+83
-113
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 55 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,51 +1466,58 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
14661466
++itr;
14671467
continue;
14681468
}
1469+
1470+
if (candidate->isChainedItem()) {
1471+
candidate = &candidate->asChainedItem().getParentItem(compressor_);
1472+
}
1473+
1474+
{
1475+
auto toReleaseHandle = accessContainer_->find(*candidate);
1476+
if (!toReleaseHandle) {
1477+
++itr;
1478+
continue;
1479+
}
1480+
toReleaseHandle.release();
1481+
}
1482+
// incRef(*candidate);
14691483

1470-
incRef(*candidate);
14711484
itr.destroy();
14721485

14731486
// for chained items, the ownership of the parent can change. We try to
14741487
// evict what we think as parent and see if the eviction of parent
14751488
// recycles the child we intend to.
14761489

1477-
WriteHandle toReleaseHandle = tryEvictToNextMemoryTier(tid, pid, candidate);
14781490
bool movedToNextTier = false;
1479-
if(toReleaseHandle) {
1480-
movedToNextTier = true;
1481-
} else {
1482-
toReleaseHandle = candidate->isChainedItem()
1483-
? tryEvictChainedItem(mmContainer, *candidate)
1484-
: tryEvictRegularItem(mmContainer, *candidate);
1485-
// XXX: fix chained item
1491+
bool evicted = false;
1492+
{
1493+
auto h = tryEvictToNextMemoryTier(tid, pid, candidate);
1494+
if (h) {
1495+
movedToNextTier = true;
1496+
evicted = true;
1497+
h.release();
1498+
decRef(*candidate);
1499+
}
14861500
}
1487-
1488-
if (toReleaseHandle) {
1489-
if (toReleaseHandle->hasChainedItem()) {
1501+
if(!movedToNextTier) {
1502+
evicted = tryEvictItem(mmContainer, *candidate);
1503+
}
1504+
if (evicted) {
1505+
if (candidate->hasChainedItem()) {
14901506
(*stats_.chainedItemEvictions)[pid][cid].inc();
14911507
} else {
14921508
(*stats_.regularItemEvictions)[pid][cid].inc();
14931509
}
14941510

1495-
// we must be the last handle and for chained items, this will be
1496-
// the parent.
1497-
XDCHECK(toReleaseHandle.get() == candidate || candidate->isChainedItem());
1498-
XDCHECK_EQ(2u, toReleaseHandle->getRefCount());
1499-
1500-
// We manually release the item here because we don't want to
1501-
// invoke the Item Handle's destructor which will be decrementing
1502-
// an already zero refcount, which will throw exception
1503-
auto& itemToRelease = *toReleaseHandle.release();
1511+
XDCHECK_EQ(1u, candidate->getRefCount());
15041512

15051513
// Decrementing the refcount because we want to recycle the item
1506-
decRef(itemToRelease);
1507-
const auto ref = decRef(itemToRelease);
1514+
const auto ref = decRef(*candidate);
15081515
XDCHECK_EQ(0u, ref);
15091516

15101517
// check if by releasing the item we intend to, we actually
15111518
// recycle the candidate.
15121519
if (ReleaseRes::kRecycled ==
1513-
releaseBackToAllocator(itemToRelease, RemoveContext::kEviction,
1520+
releaseBackToAllocator(*candidate, RemoveContext::kEviction,
15141521
/* isNascent */ movedToNextTier, candidate)) {
15151522
return candidate;
15161523
}
@@ -1611,9 +1618,8 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item* item) {
16111618
}
16121619

16131620
template <typename CacheTrait>
1614-
typename CacheAllocator<CacheTrait>::WriteHandle
1615-
CacheAllocator<CacheTrait>::tryEvictRegularItem(MMContainer& mmContainer,
1616-
Item& item) {
1621+
bool CacheAllocator<CacheTrait>::tryEvictItem(MMContainer& mmContainer,
1622+
Item &item) {
16171623
// we should flush this to nvmcache if it is not already present in nvmcache
16181624
// and the item is not expired.
16191625
const bool evictToNvmCache = shouldWriteToNvmCache(item);
@@ -1623,21 +1629,25 @@ CacheAllocator<CacheTrait>::tryEvictRegularItem(MMContainer& mmContainer,
16231629
// record the in-flight eviciton. If not, we move on to next item to avoid
16241630
// stalling eviction.
16251631
if (evictToNvmCache && !token.isValid()) {
1626-
stats_.evictFailConcurrentFill.inc();
1632+
if (item.isChainedItem())
1633+
stats_.evictFailConcurrentFill.inc();
1634+
else
1635+
stats_.evictFailConcurrentFill.inc();
16271636
decRef(item);
1628-
return WriteHandle{};
1637+
return false;
16291638
}
16301639

1631-
// If there are other accessors, we should abort. Acquire a handle here since
1632-
// if we remove the item from both access containers and mm containers
1633-
// below, we will need a handle to ensure proper cleanup in case we end up
1634-
// not evicting this item
1640+
// If there are other accessors, we should abort. We should be the only
1641+
// handle owner.
16351642
auto evictHandle = accessContainer_->removeIf(item, &itemEvictionPredicate);
1643+
decRef(item);
16361644

16371645
if (!evictHandle) {
1638-
stats_.evictFailAC.inc();
1639-
decRef(item);
1640-
return evictHandle;
1646+
if (item.isChainedItem())
1647+
stats_.evictFailParentAC.inc();
1648+
else
1649+
stats_.evictFailAC.inc();
1650+
return false;
16411651
}
16421652

16431653
auto removed = mmContainer.remove(item);
@@ -1652,81 +1662,24 @@ CacheAllocator<CacheTrait>::tryEvictRegularItem(MMContainer& mmContainer,
16521662
// for eviction. It is safe to destroy the handle here since the moving bit
16531663
// is set. Iterator was already advance by the remove call above.
16541664
if (evictHandle->isMoving()) {
1655-
stats_.evictFailMove.inc();
1656-
decRef(item);
1657-
return WriteHandle{};
1665+
if (item.isChainedItem())
1666+
stats_.evictFailParentMove.inc();
1667+
else
1668+
stats_.evictFailMove.inc();
1669+
return false;
16581670
}
16591671

16601672
// Ensure that there are no accessors after removing from the access
1661-
// container
1662-
XDCHECK(evictHandle->getRefCount() == 2);
1673+
// container. handle and evictHandle should be the only two handles.
1674+
XDCHECK(evictHandle->getRefCount() == 1);
16631675

16641676
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(item)) {
16651677
XDCHECK(token.isValid());
16661678
nvmCache_->put(evictHandle, std::move(token));
16671679
}
1668-
return evictHandle;
1669-
}
1670-
1671-
template <typename CacheTrait>
1672-
typename CacheAllocator<CacheTrait>::WriteHandle
1673-
CacheAllocator<CacheTrait>::tryEvictChainedItem(MMContainer& mmContainer, Item& item) {
1674-
XDCHECK(item.isChainedItem());
1675-
1676-
ChainedItem* candidate = &item.asChainedItem();
1677-
1678-
// The parent could change at any point through transferChain. However, if
1679-
// that happens, we would realize that the releaseBackToAllocator return
1680-
// kNotRecycled and we would try another chained item, leading to transient
1681-
// failure.
1682-
auto& parent = candidate->getParentItem(compressor_);
1683-
1684-
const bool evictToNvmCache = shouldWriteToNvmCache(parent);
16851680

1686-
auto token = evictToNvmCache ? nvmCache_->createPutToken(parent.getKey())
1687-
: typename NvmCacheT::PutToken{};
1688-
1689-
// if token is invalid, return. iterator is already advanced.
1690-
if (evictToNvmCache && !token.isValid()) {
1691-
stats_.evictFailConcurrentFill.inc();
1692-
return ItemHandle{};
1693-
}
1694-
1695-
// check if the parent exists in the hashtable and refcount is drained.
1696-
auto parentHandle =
1697-
accessContainer_->removeIf(parent, &itemEvictionPredicate);
1698-
if (!parentHandle) {
1699-
stats_.evictFailParentAC.inc();
1700-
return parentHandle;
1701-
}
1702-
1703-
// Ensure we have the correct parent and we're the only user of the
1704-
// parent, then free it from access container. Otherwise, we abort
1705-
auto removed = mmContainer.remove(item);
1706-
XDCHECK(removed);
1707-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(&parent),
1708-
reinterpret_cast<uintptr_t>(parentHandle.get()));
1709-
XDCHECK_EQ(2u, parent.getRefCount()); // XXX?
1710-
XDCHECK(!parent.isInMMContainer());
1711-
XDCHECK(!parent.isAccessible());
1712-
1713-
// TODO: add multi-tier support (similar as for unchained items)
1714-
1715-
// We need to make sure the parent is not marked as moving
1716-
// and we're the only holder of the parent item. Safe to destroy the handle
1717-
// here since moving bit is set.
1718-
if (parentHandle->isMoving()) {
1719-
stats_.evictFailParentMove.inc();
1720-
return ItemHandle{};
1721-
}
1722-
1723-
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(*parentHandle)) {
1724-
XDCHECK(token.isValid());
1725-
XDCHECK(parentHandle->hasChainedItem());
1726-
nvmCache_->put(parentHandle, std::move(token));
1727-
}
1728-
1729-
return parentHandle;
1681+
evictHandle.release();
1682+
return true;
17301683
}
17311684

17321685
template <typename CacheTrait>

cachelib/allocator/CacheAllocator.h

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,17 +1566,8 @@ class CacheAllocator : public CacheBase {
15661566
// @param mmContainer the container to look for evictions.
15671567
// @param item item to evict
15681568
//
1569-
// @return valid handle to regular item on success. This will be the last
1570-
// handle to the item. On failure an empty handle.
1571-
WriteHandle tryEvictRegularItem(MMContainer& mmContainer, Item& item);
1572-
1573-
// Try to evict a chained item.
1574-
//
1575-
// @param item item to evict
1576-
//
1577-
// @return valid handle to the parent item on success. This will be the last
1578-
// handle to the item
1579-
WriteHandle tryEvictChainedItem(MMContainer& mmContainer, Item& item);
1569+
// @return whther eviction succeeded
1570+
bool tryEvictItem(MMContainer& mmContainer, Item& item);
15801571

15811572
// Try to move the item down to the next memory tier
15821573
//

cachelib/allocator/ChainedHashTable-inl.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,21 @@ typename T::Handle ChainedHashTable::Container<T, HookPtr, LockT>::find(
460460
return handleMaker_(ht_.findInBucket(key, bucket));
461461
}
462462

463+
template <typename T,
464+
typename ChainedHashTable::Hook<T> T::*HookPtr,
465+
typename LockT>
466+
typename T::Handle ChainedHashTable::Container<T, HookPtr, LockT>::find(
467+
T& node) const {
468+
const auto bucket = ht_.getBucket(node.getKey());
469+
auto l = locks_.lockShared(bucket);
470+
471+
if (!node.isAccessible()) {
472+
return {};
473+
}
474+
475+
return handleMaker_(&node);
476+
}
477+
463478
template <typename T,
464479
typename ChainedHashTable::Hook<T> T::*HookPtr,
465480
typename LockT>

cachelib/allocator/ChainedHashTable.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,17 @@ class ChainedHashTable {
490490
// creating this item handle.
491491
Handle find(Key key) const;
492492

493+
// returns a handle to specified node.
494+
//
495+
// @param node requested node
496+
//
497+
// @return handle to the node if find was sucessfull. returns a
498+
// null handle if the node was not in the container.
499+
//
500+
// @throw std::overflow_error is the maximum item refcount is execeeded by
501+
// creating this item handle.
502+
Handle find(T& node) const;
503+
493504
// for saving the state of the hash table
494505
//
495506
// precondition: serialization must happen without any reader or writer

0 commit comments

Comments
 (0)