Skip to content

Commit d98757a

Browse files
igchorguptask
authored andcommitted
Shorten critical section in findEviction
Remove the item from mmContainer and drop the lock before attempting eviction. Use moving bit for synchronization in findEviction moving bit is used to give exclusive right to evict the item to a particular thread. Originially, there was an assumption that whoever marked the item as moving will try to free it until he succeeds. Since we don't want to do that in findEviction (potentially can take a long time) we need to make sure that unmarking is safe. This patch checks the flags after unmarking (atomically) and if ref is zero it also recyles the item. This is needed as there might be some concurrent thread releasing the item (and decrementing ref count). If moving bit is set, that thread would not free the memory back to allocator, resulting in memory leak on unmarkMoving().
1 parent ab25b3c commit d98757a

File tree

5 files changed

+83
-233
lines changed

5 files changed

+83
-233
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 69 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ CacheAllocator<CacheTrait>::allocateInternalTier(TierId tid,
524524
}
525525

526526
template <typename CacheTrait>
527-
typename CacheAllocator<CacheTrait>::ItemHandle
527+
typename CacheAllocator<CacheTrait>::WriteHandle
528528
CacheAllocator<CacheTrait>::allocateInternal(PoolId pid,
529529
typename Item::Key key,
530530
uint32_t size,
@@ -1296,14 +1296,13 @@ bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
12961296
}
12971297

12981298
template <typename CacheTrait>
1299-
template <typename ItemPtr>
13001299
typename CacheAllocator<CacheTrait>::ItemHandle
13011300
CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
1302-
ItemPtr& oldItemPtr, ItemHandle& newItemHdl) {
1301+
Item& oldItem, ItemHandle& newItemHdl) {
1302+
XDCHECK(oldItem.isMoving());
13031303
// TODO: should we introduce new latency tracker. E.g. evictRegularLatency_
13041304
// ??? util::LatencyTracker tracker{stats_.evictRegularLatency_};
13051305

1306-
Item& oldItem = *oldItemPtr;
13071306
if (!oldItem.isAccessible() || oldItem.isExpired()) {
13081307
return {};
13091308
}
@@ -1359,7 +1358,7 @@ CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
13591358
// it is unsafe to replace the old item with a new one, so we should
13601359
// also abort.
13611360
if (!accessContainer_->replaceIf(oldItem, *newItemHdl,
1362-
itemEvictionPredicate)) {
1361+
itemMovingPredicate)) {
13631362
return {};
13641363
}
13651364

@@ -1379,7 +1378,7 @@ CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
13791378

13801379
// Inside the MM container's lock, this checks if the old item exists to
13811380
// make sure that no other thread removed it, and only then replaces it.
1382-
if (!replaceInMMContainer(oldItemPtr, *newItemHdl)) {
1381+
if (!replaceInMMContainer(oldItem, *newItemHdl)) {
13831382
accessContainer_->remove(*newItemHdl);
13841383
return {};
13851384
}
@@ -1569,42 +1568,45 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
15691568
itr) {
15701569
++searchTries;
15711570

1572-
Item* candidate = itr.get();
1571+
Item* toRecycle = itr.get();
1572+
1573+
Item* candidate =
1574+
toRecycle->isChainedItem()
1575+
? &toRecycle->asChainedItem().getParentItem(compressor_)
1576+
: toRecycle;
1577+
1578+
// make sure no other thead is evicting the item
1579+
if (candidate->getRefCount() != 0 || !candidate->markMoving()) {
1580+
++itr;
1581+
continue;
1582+
}
1583+
1584+
itr.destroy();
1585+
15731586
// for chained items, the ownership of the parent can change. We try to
15741587
// evict what we think as parent and see if the eviction of parent
15751588
// recycles the child we intend to.
1576-
1577-
ItemHandle toReleaseHandle = tryEvictToNextMemoryTier(tid, pid, itr);
1578-
bool movedToNextTier = false;
1579-
if(toReleaseHandle) {
1580-
movedToNextTier = true;
1581-
} else {
1582-
toReleaseHandle =
1583-
itr->isChainedItem()
1584-
? advanceIteratorAndTryEvictChainedItem(tid, pid, itr)
1585-
: advanceIteratorAndTryEvictRegularItem(tid, pid, mmContainer, itr);
1586-
}
1589+
auto toReleaseHandle =
1590+
evictNormalItem(*candidate, true /* skipIfTokenInvalid */);
1591+
auto ref = candidate->unmarkMoving();
15871592

1588-
if (toReleaseHandle) {
1589-
if (toReleaseHandle->hasChainedItem()) {
1593+
if (toReleaseHandle || ref == 0u) {
1594+
if (candidate->hasChainedItem()) {
15901595
(*stats_.chainedItemEvictions)[pid][cid].inc();
15911596
} else {
15921597
(*stats_.regularItemEvictions)[pid][cid].inc();
15931598
}
1594-
1595-
if (auto eventTracker = getEventTracker()) {
1596-
eventTracker->record(
1597-
AllocatorApiEvent::DRAM_EVICT, toReleaseHandle->getKey(),
1598-
AllocatorApiResult::EVICTED, toReleaseHandle->getSize(),
1599-
toReleaseHandle->getConfiguredTTL().count());
1599+
} else {
1600+
if (candidate->hasChainedItem()) {
1601+
stats_.evictFailParentAC.inc();
1602+
} else {
1603+
stats_.evictFailAC.inc();
16001604
}
1601-
// Invalidate iterator since later on we may use this mmContainer
1602-
// again, which cannot be done unless we drop this iterator
1603-
itr.destroy();
1605+
}
16041606

1605-
// we must be the last handle and for chained items, this will be
1606-
// the parent.
1607-
XDCHECK(toReleaseHandle.get() == candidate || candidate->isChainedItem());
1607+
if (toReleaseHandle) {
1608+
XDCHECK(toReleaseHandle.get() == candidate);
1609+
XDCHECK(toRecycle == candidate || toRecycle->isChainedItem());
16081610
XDCHECK_EQ(1u, toReleaseHandle->getRefCount());
16091611

16101612
// We manually release the item here because we don't want to
@@ -1620,16 +1622,21 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
16201622
// recycle the candidate.
16211623
if (ReleaseRes::kRecycled ==
16221624
releaseBackToAllocator(itemToRelease, RemoveContext::kEviction,
1623-
/* isNascent */ movedToNextTier, candidate)) {
1624-
return candidate;
1625+
/* isNascent */ false, toRecycle)) {
1626+
return toRecycle;
1627+
}
1628+
} else if (ref == 0u) {
1629+
// it's safe to recycle the item here as there are no more
1630+
// references and the item could not been marked as moving
1631+
// by other thread since it's detached from MMContainer.
1632+
if (ReleaseRes::kRecycled ==
1633+
releaseBackToAllocator(*candidate, RemoveContext::kEviction,
1634+
/* isNascent */ false, toRecycle)) {
1635+
return toRecycle;
16251636
}
16261637
}
16271638

1628-
// If we destroyed the itr to possibly evict and failed, we restart
1629-
// from the beginning again
1630-
if (!itr) {
1631-
itr.resetToBegin();
1632-
}
1639+
itr.resetToBegin();
16331640
}
16341641
return nullptr;
16351642
}
@@ -1683,24 +1690,23 @@ bool CacheAllocator<CacheTrait>::shouldWriteToNvmCacheExclusive(
16831690
}
16841691

16851692
template <typename CacheTrait>
1686-
template <typename ItemPtr>
1687-
typename CacheAllocator<CacheTrait>::ItemHandle
1693+
typename CacheAllocator<CacheTrait>::WriteHandle
16881694
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
1689-
TierId tid, PoolId pid, ItemPtr& item) {
1690-
if(item->isChainedItem()) return {}; // TODO: We do not support ChainedItem yet
1691-
if(item->isExpired()) return acquire(item);
1695+
TierId tid, PoolId pid, Item& item) {
1696+
if(item.isChainedItem()) return {}; // TODO: We do not support ChainedItem yet
1697+
if(item.isExpired()) return acquire(&item);
16921698

16931699
TierId nextTier = tid; // TODO - calculate this based on some admission policy
16941700
while (++nextTier < numTiers_) { // try to evict down to the next memory tiers
16951701
// allocateInternal might trigger another eviction
16961702
auto newItemHdl = allocateInternalTier(nextTier, pid,
1697-
item->getKey(),
1698-
item->getSize(),
1699-
item->getCreationTime(),
1700-
item->getExpiryTime());
1703+
item.getKey(),
1704+
item.getSize(),
1705+
item.getCreationTime(),
1706+
item.getExpiryTime());
17011707

17021708
if (newItemHdl) {
1703-
XDCHECK_EQ(newItemHdl->getSize(), item->getSize());
1709+
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
17041710

17051711
return moveRegularItemOnEviction(item, newItemHdl);
17061712
}
@@ -1709,150 +1715,12 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
17091715
return {};
17101716
}
17111717

1712-
template <typename CacheTrait>
1713-
typename CacheAllocator<CacheTrait>::ItemHandle
1714-
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item* item) {
1715-
auto tid = getTierId(*item);
1716-
auto pid = allocator_[tid]->getAllocInfo(item->getMemory()).poolId;
1717-
return tryEvictToNextMemoryTier(tid, pid, item);
1718-
}
1719-
1720-
template <typename CacheTrait>
1721-
typename CacheAllocator<CacheTrait>::ItemHandle
1722-
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
1723-
TierId tid, PoolId pid, MMContainer& mmContainer, EvictionIterator& itr) {
1724-
Item& item = *itr;
1725-
1726-
const bool evictToNvmCache = shouldWriteToNvmCache(item);
1727-
1728-
auto token = evictToNvmCache ? nvmCache_->createPutToken(item.getKey())
1729-
: typename NvmCacheT::PutToken{};
1730-
// record the in-flight eviciton. If not, we move on to next item to avoid
1731-
// stalling eviction.
1732-
if (evictToNvmCache && !token.isValid()) {
1733-
++itr;
1734-
stats_.evictFailConcurrentFill.inc();
1735-
return WriteHandle{};
1736-
}
1737-
1738-
// If there are other accessors, we should abort. Acquire a handle here since
1739-
// if we remove the item from both access containers and mm containers
1740-
// below, we will need a handle to ensure proper cleanup in case we end up
1741-
// not evicting this item
1742-
auto evictHandle = accessContainer_->removeIf(item, &itemEvictionPredicate);
1743-
1744-
if (!evictHandle) {
1745-
++itr;
1746-
stats_.evictFailAC.inc();
1747-
return evictHandle;
1748-
}
1749-
1750-
mmContainer.remove(itr);
1751-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(evictHandle.get()),
1752-
reinterpret_cast<uintptr_t>(&item));
1753-
XDCHECK(!evictHandle->isInMMContainer());
1754-
XDCHECK(!evictHandle->isAccessible());
1755-
1756-
// If the item is now marked as moving, that means its corresponding slab is
1757-
// being released right now. So, we look for the next item that is eligible
1758-
// for eviction. It is safe to destroy the handle here since the moving bit
1759-
// is set. Iterator was already advance by the remove call above.
1760-
if (evictHandle->isMoving()) {
1761-
stats_.evictFailMove.inc();
1762-
return WriteHandle{};
1763-
}
1764-
1765-
// Invalidate iterator since later on if we are not evicting this
1766-
// item, we may need to rely on the handle we created above to ensure
1767-
// proper cleanup if the item's raw refcount has dropped to 0.
1768-
// And since this item may be a parent item that has some child items
1769-
// in this very same mmContainer, we need to make sure we drop this
1770-
// exclusive iterator so we can gain access to it when we're cleaning
1771-
// up the child items
1772-
itr.destroy();
1773-
1774-
// Ensure that there are no accessors after removing from the access
1775-
// container
1776-
XDCHECK(evictHandle->getRefCount() == 1);
1777-
1778-
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(item)) {
1779-
XDCHECK(token.isValid());
1780-
nvmCache_->put(evictHandle, std::move(token));
1781-
}
1782-
return evictHandle;
1783-
}
1784-
17851718
template <typename CacheTrait>
17861719
typename CacheAllocator<CacheTrait>::WriteHandle
1787-
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictChainedItem(
1788-
TierId tid, PoolId pid, EvictionIterator& itr) {
1789-
XDCHECK(itr->isChainedItem());
1790-
1791-
ChainedItem* candidate = &itr->asChainedItem();
1792-
++itr;
1793-
1794-
// The parent could change at any point through transferChain. However, if
1795-
// that happens, we would realize that the releaseBackToAllocator return
1796-
// kNotRecycled and we would try another chained item, leading to transient
1797-
// failure.
1798-
auto& parent = candidate->getParentItem(compressor_);
1799-
1800-
const bool evictToNvmCache = shouldWriteToNvmCache(parent);
1801-
1802-
auto token = evictToNvmCache ? nvmCache_->createPutToken(parent.getKey())
1803-
: typename NvmCacheT::PutToken{};
1804-
1805-
// if token is invalid, return. iterator is already advanced.
1806-
if (evictToNvmCache && !token.isValid()) {
1807-
stats_.evictFailConcurrentFill.inc();
1808-
return WriteHandle{};
1809-
}
1810-
1811-
// check if the parent exists in the hashtable and refcount is drained.
1812-
auto parentHandle =
1813-
accessContainer_->removeIf(parent, &itemEvictionPredicate);
1814-
if (!parentHandle) {
1815-
stats_.evictFailParentAC.inc();
1816-
return parentHandle;
1817-
}
1818-
1819-
// Invalidate iterator since later on we may use the mmContainer
1820-
// associated with this iterator which cannot be done unless we
1821-
// drop this iterator
1822-
//
1823-
// This must be done once we know the parent is not nullptr.
1824-
// Since we can very well be the last holder of this parent item,
1825-
// which may have a chained item that is linked in this MM container.
1826-
itr.destroy();
1827-
1828-
// Ensure we have the correct parent and we're the only user of the
1829-
// parent, then free it from access container. Otherwise, we abort
1830-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(&parent),
1831-
reinterpret_cast<uintptr_t>(parentHandle.get()));
1832-
XDCHECK_EQ(1u, parent.getRefCount());
1833-
1834-
removeFromMMContainer(*parentHandle);
1835-
1836-
XDCHECK(!parent.isInMMContainer());
1837-
XDCHECK(!parent.isAccessible());
1838-
1839-
// TODO: add multi-tier support (similar as for unchained items)
1840-
1841-
// We need to make sure the parent is not marked as moving
1842-
// and we're the only holder of the parent item. Safe to destroy the handle
1843-
// here since moving bit is set.
1844-
if (parentHandle->isMoving()) {
1845-
stats_.evictFailParentMove.inc();
1846-
return WriteHandle{};
1847-
}
1848-
1849-
if (evictToNvmCache && shouldWriteToNvmCacheExclusive(*parentHandle)) {
1850-
XDCHECK(token.isValid());
1851-
XDCHECK(parentHandle->hasChainedItem());
1852-
nvmCache_->put(parentHandle, std::move(token));
1853-
}
1854-
1855-
return parentHandle;
1720+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item& item) {
1721+
auto tid = getTierId(item);
1722+
auto pid = allocator_[tid]->getAllocInfo(item.getMemory()).poolId;
1723+
return tryEvictToNextMemoryTier(tid, pid, item);
18561724
}
18571725

18581726
template <typename CacheTrait>
@@ -3097,7 +2965,7 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
30972965
auto owningHandle =
30982966
item.isChainedItem()
30992967
? evictChainedItemForSlabRelease(item.asChainedItem())
3100-
: evictNormalItemForSlabRelease(item);
2968+
: evictNormalItem(item);
31012969

31022970
// we managed to evict the corresponding owner of the item and have the
31032971
// last handle for the owner.
@@ -3153,15 +3021,16 @@ void CacheAllocator<CacheTrait>::evictForSlabRelease(
31533021
}
31543022

31553023
template <typename CacheTrait>
3156-
typename CacheAllocator<CacheTrait>::WriteHandle
3157-
CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
3024+
typename CacheAllocator<CacheTrait>::ItemHandle
3025+
CacheAllocator<CacheTrait>::evictNormalItem(Item& item,
3026+
bool skipIfTokenInvalid) {
31583027
XDCHECK(item.isMoving());
31593028

31603029
if (item.isOnlyMoving()) {
31613030
return WriteHandle{};
31623031
}
31633032

3164-
auto evictHandle = tryEvictToNextMemoryTier(&item);
3033+
auto evictHandle = tryEvictToNextMemoryTier(item);
31653034
if(evictHandle) return evictHandle;
31663035

31673036
auto predicate = [](const Item& it) { return it.getRefCount() == 0; };
@@ -3170,6 +3039,11 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
31703039
auto token = evictToNvmCache ? nvmCache_->createPutToken(item.getKey())
31713040
: typename NvmCacheT::PutToken{};
31723041

3042+
if (skipIfTokenInvalid && evictToNvmCache && !token.isValid()) {
3043+
stats_.evictFailConcurrentFill.inc();
3044+
return ItemHandle{};
3045+
}
3046+
31733047
// We remove the item from both access and mm containers. It doesn't matter
31743048
// if someone else calls remove on the item at this moment, the item cannot
31753049
// be freed as long as we have the moving bit set.

0 commit comments

Comments
 (0)