@@ -968,6 +968,25 @@ bool CacheAllocator<CacheTrait>::replaceInMMContainer(Item& oldItem,
968968 }
969969}
970970
971+ template <typename CacheTrait>
972+ bool CacheAllocator<CacheTrait>::replaceInMMContainer(Item* oldItem,
973+ Item& newItem) {
974+ return replaceInMMContainer (*oldItem, newItem);
975+ }
976+
977+ template <typename CacheTrait>
978+ bool CacheAllocator<CacheTrait>::replaceInMMContainer(EvictionIterator& oldItemIt,
979+ Item& newItem) {
980+ auto & oldContainer = getMMContainer (*oldItemIt);
981+ auto & newContainer = getMMContainer (newItem);
982+
983+ // This function is used for eviction across tiers
984+ XDCHECK (&oldContainer != &newContainer);
985+ oldContainer.remove (oldItemIt);
986+
987+ return newContainer.add (newItem);
988+ }
989+
971990template <typename CacheTrait>
972991bool CacheAllocator<CacheTrait>::replaceChainedItemInMMContainer(
973992 Item& oldItem, Item& newItem) {
@@ -1102,6 +1121,156 @@ CacheAllocator<CacheTrait>::insertOrReplace(const ItemHandle& handle) {
11021121 return replaced;
11031122}
11041123
1124+ /* Next two methods are used to asynchronously move Item between memory tiers.
1125+ *
1126+ * The thread, which moves Item, allocates new Item in the tier we are moving to
1127+ * and calls moveRegularItemOnEviction() method. This method does the following:
1128+ * 1. Create MoveCtx and put it to the movesMap.
1129+ * 2. Update the access container with the new item from the tier we are
1130+ * moving to. This Item has kIncomplete flag set.
1131+ * 3. Copy data from the old Item to the new one.
1132+ * 4. Unset the kIncomplete flag and Notify MoveCtx
1133+ *
1134+ * Concurrent threads which are getting handle to the same key:
1135+ * 1. When a handle is created it checks if the kIncomplete flag is set
1136+ * 2. If so, Handle implementation creates waitContext and adds it to the
1137+ * MoveCtx by calling addWaitContextForMovingItem() method.
1138+ * 3. Wait until the moving thread will complete its job.
1139+ */
1140+ template <typename CacheTrait>
1141+ bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
1142+ folly::StringPiece key, std::shared_ptr<WaitContext<ItemHandle>> waiter) {
1143+ auto shard = getShardForKey (key);
1144+ auto & movesMap = getMoveMapForShard (shard);
1145+ auto lock = getMoveLockForShard (shard);
1146+ auto it = movesMap.find (key);
1147+ if (it == movesMap.end ()) {
1148+ return false ;
1149+ }
1150+ auto ctx = it->second .get ();
1151+ ctx->addWaiter (std::move (waiter));
1152+ return true ;
1153+ }
1154+
1155+ template <typename CacheTrait>
1156+ template <typename ItemPtr>
1157+ typename CacheAllocator<CacheTrait>::ItemHandle
1158+ CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
1159+ ItemPtr& oldItemPtr, ItemHandle& newItemHdl) {
1160+ // TODO: should we introduce new latency tracker. E.g. evictRegularLatency_
1161+ // ??? util::LatencyTracker tracker{stats_.evictRegularLatency_};
1162+
1163+ Item& oldItem = *oldItemPtr;
1164+ if (!oldItem.isAccessible () || oldItem.isExpired ()) {
1165+ return {};
1166+ }
1167+
1168+ XDCHECK_EQ (newItemHdl->getSize (), oldItem.getSize ());
1169+ XDCHECK_NE (getTierId (oldItem), getTierId (*newItemHdl));
1170+
1171+ // take care of the flags before we expose the item to be accessed. this
1172+ // will ensure that when another thread removes the item from RAM, we issue
1173+ // a delete accordingly. See D7859775 for an example
1174+ if (oldItem.isNvmClean ()) {
1175+ newItemHdl->markNvmClean ();
1176+ }
1177+
1178+ folly::StringPiece key (oldItem.getKey ());
1179+ auto shard = getShardForKey (key);
1180+ auto & movesMap = getMoveMapForShard (shard);
1181+ MoveCtx* ctx (nullptr );
1182+ {
1183+ auto lock = getMoveLockForShard (shard);
1184+ auto res = movesMap.try_emplace (key, std::make_unique<MoveCtx>());
1185+ if (!res.second ) {
1186+ return {};
1187+ }
1188+ ctx = res.first ->second .get ();
1189+ }
1190+
1191+ auto resHdl = ItemHandle{};
1192+ auto guard = folly::makeGuard ([key, this , ctx, shard, &resHdl]() {
1193+ auto & movesMap = getMoveMapForShard (shard);
1194+ resHdl->unmarkIncomplete ();
1195+ auto lock = getMoveLockForShard (shard);
1196+ ctx->setItemHandle (std::move (resHdl));
1197+ movesMap.erase (key);
1198+ });
1199+
1200+ // TODO: Possibly we can use markMoving() instead. But today
1201+ // moveOnSlabRelease logic assume that we mark as moving old Item
1202+ // and than do copy and replace old Item with the new one in access
1203+ // container. Furthermore, Item can be marked as Moving only
1204+ // if it is linked to MM container. In our case we mark the new Item
1205+ // and update access container before the new Item is ready (content is
1206+ // copied).
1207+ newItemHdl->markIncomplete ();
1208+
1209+ // Inside the access container's lock, this checks if the old item is
1210+ // accessible and its refcount is zero. If the item is not accessible,
1211+ // there is no point to replace it since it had already been removed
1212+ // or in the process of being removed. If the item is in cache but the
1213+ // refcount is non-zero, it means user could be attempting to remove
1214+ // this item through an API such as remove(ItemHandle). In this case,
1215+ // it is unsafe to replace the old item with a new one, so we should
1216+ // also abort.
1217+ if (!accessContainer_->replaceIf (oldItem, *newItemHdl,
1218+ itemEvictionPredicate)) {
1219+ return {};
1220+ }
1221+
1222+ if (config_.moveCb ) {
1223+ // Execute the move callback. We cannot make any guarantees about the
1224+ // consistency of the old item beyond this point, because the callback can
1225+ // do more than a simple memcpy() e.g. update external references. If there
1226+ // are any remaining handles to the old item, it is the caller's
1227+ // responsibility to invalidate them. The move can only fail after this
1228+ // statement if the old item has been removed or replaced, in which case it
1229+ // should be fine for it to be left in an inconsistent state.
1230+ config_.moveCb (oldItem, *newItemHdl, nullptr );
1231+ } else {
1232+ std::memcpy (newItemHdl->getWritableMemory (), oldItem.getMemory (),
1233+ oldItem.getSize ());
1234+ }
1235+
1236+ // Inside the MM container's lock, this checks if the old item exists to
1237+ // make sure that no other thread removed it, and only then replaces it.
1238+ if (!replaceInMMContainer (oldItemPtr, *newItemHdl)) {
1239+ accessContainer_->remove (*newItemHdl);
1240+ return {};
1241+ }
1242+
1243+ // Replacing into the MM container was successful, but someone could have
1244+ // called insertOrReplace() or remove() before or after the
1245+ // replaceInMMContainer() operation, which would invalidate newItemHdl.
1246+ if (!newItemHdl->isAccessible ()) {
1247+ removeFromMMContainer (*newItemHdl);
1248+ return {};
1249+ }
1250+
1251+ // no one can add or remove chained items at this point
1252+ if (oldItem.hasChainedItem ()) {
1253+ // safe to acquire handle for a moving Item
1254+ auto oldHandle = acquire (&oldItem);
1255+ XDCHECK_EQ (1u , oldHandle->getRefCount ()) << oldHandle->toString ();
1256+ XDCHECK (!newItemHdl->hasChainedItem ()) << newItemHdl->toString ();
1257+ try {
1258+ auto l = chainedItemLocks_.lockExclusive (oldItem.getKey ());
1259+ transferChainLocked (oldHandle, newItemHdl);
1260+ } catch (const std::exception& e) {
1261+ // this should never happen because we drained all the handles.
1262+ XLOGF (DFATAL, " {}" , e.what ());
1263+ throw ;
1264+ }
1265+
1266+ XDCHECK (!oldItem.hasChainedItem ());
1267+ XDCHECK (newItemHdl->hasChainedItem ());
1268+ }
1269+ newItemHdl.unmarkNascent ();
1270+ resHdl = std::move (newItemHdl); // guard will assign it to ctx under lock
1271+ return acquire (&oldItem);
1272+ }
1273+
11051274template <typename CacheTrait>
11061275bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
11071276 ItemHandle& newItemHdl) {
@@ -1356,10 +1525,47 @@ bool CacheAllocator<CacheTrait>::shouldWriteToNvmCacheExclusive(
13561525 return true ;
13571526}
13581527
1528+ template <typename CacheTrait>
1529+ template <typename ItemPtr>
1530+ typename CacheAllocator<CacheTrait>::ItemHandle
1531+ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
1532+ TierId tid, PoolId pid, ItemPtr& item) {
1533+ if (item->isExpired ()) return acquire (item);
1534+
1535+ TierId nextTier = tid; // TODO - calculate this based on some admission policy
1536+ while (++nextTier < numTiers_) { // try to evict down to the next memory tiers
1537+ // allocateInternal might trigger another eviction
1538+ auto newItemHdl = allocateInternalTier (nextTier, pid,
1539+ item->getKey (),
1540+ item->getSize (),
1541+ item->getCreationTime (),
1542+ item->getExpiryTime ());
1543+
1544+ if (newItemHdl) {
1545+ XDCHECK_EQ (newItemHdl->getSize (), item->getSize ());
1546+
1547+ return moveRegularItemOnEviction (item, newItemHdl);
1548+ }
1549+ }
1550+
1551+ return {};
1552+ }
1553+
1554+ template <typename CacheTrait>
1555+ typename CacheAllocator<CacheTrait>::ItemHandle
1556+ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item* item) {
1557+ auto tid = getTierId (*item);
1558+ auto pid = allocator_[tid]->getAllocInfo (item->getMemory ()).poolId ;
1559+ return tryEvictToNextMemoryTier (tid, pid, item);
1560+ }
1561+
13591562template <typename CacheTrait>
13601563typename CacheAllocator<CacheTrait>::ItemHandle
13611564CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
13621565 TierId tid, PoolId pid, MMContainer& mmContainer, EvictionIterator& itr) {
1566+ auto evictHandle = tryEvictToNextMemoryTier (tid, pid, itr);
1567+ if (evictHandle) return evictHandle;
1568+
13631569 Item& item = *itr;
13641570
13651571 const bool evictToNvmCache = shouldWriteToNvmCache (item);
@@ -1378,7 +1584,7 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
13781584 // if we remove the item from both access containers and mm containers
13791585 // below, we will need a handle to ensure proper cleanup in case we end up
13801586 // not evicting this item
1381- auto evictHandle = accessContainer_->removeIf (item, &itemEvictionPredicate);
1587+ evictHandle = accessContainer_->removeIf (item, &itemEvictionPredicate);
13821588
13831589 if (!evictHandle) {
13841590 ++itr;
@@ -2715,6 +2921,9 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
27152921 return ItemHandle{};
27162922 }
27172923
2924+ auto evictHandle = tryEvictToNextMemoryTier (&item);
2925+ if (evictHandle) return evictHandle;
2926+
27182927 auto predicate = [](const Item& it) { return it.getRefCount () == 0 ; };
27192928
27202929 const bool evictToNvmCache = shouldWriteToNvmCache (item);
0 commit comments