@@ -524,7 +524,7 @@ CacheAllocator<CacheTrait>::allocateInternalTier(TierId tid,
524524}
525525
526526template <typename CacheTrait>
527- typename CacheAllocator<CacheTrait>::ItemHandle
527+ typename CacheAllocator<CacheTrait>::WriteHandle
528528CacheAllocator<CacheTrait>::allocateInternal(PoolId pid,
529529 typename Item::Key key,
530530 uint32_t size,
@@ -1296,14 +1296,13 @@ bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
12961296}
12971297
12981298template <typename CacheTrait>
1299- template <typename ItemPtr>
13001299typename CacheAllocator<CacheTrait>::ItemHandle
13011300CacheAllocator<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
16851692template <typename CacheTrait>
1686- template <typename ItemPtr>
1687- typename CacheAllocator<CacheTrait>::ItemHandle
1693+ typename CacheAllocator<CacheTrait>::WriteHandle
16881694CacheAllocator<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-
17851718template <typename CacheTrait>
17861719typename 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
18581726template <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
31553023template <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