@@ -1101,12 +1101,42 @@ CacheAllocator<CacheTrait>::insertOrReplace(const ItemHandle& handle) {
11011101 return replaced;
11021102}
11031103
1104+ /* Next two methods are used to asynchronously move Item between memory tiers.
1105+ *
1106+ * The thread, which moves Item, allocates new Item in the tier we are moving to
1107+ * and calls moveRegularItemOnEviction() method. This method does the following:
1108+ * 1. Create MoveCtx and put it to the movesMap.
1109+ * 2. Update the access container with the new item from the tier we are
1110+ * moving to. This Item has kIncomplete flag set.
1111+ * 3. Copy data from the old Item to the new one.
1112+ * 4. Unset the kIncomplete flag and Notify MoveCtx
1113+ *
1114+ * Concurrent threads which are getting handle to the same key:
1115+ * 1. When a handle is created it checks if the kIncomplete flag is set
1116+ * 2. If so, Handle implementation creates waitContext and adds it to the
1117+ * MoveCtx by calling addWaitContextForMovingItem() method.
1118+ * 3. Wait until the moving thread will complete its job.
1119+ */
1120+ template <typename CacheTrait>
1121+ bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
1122+ folly::StringPiece key, std::shared_ptr<WaitContext<ItemHandle>> waiter) {
1123+ auto shard = getShardForKey (key);
1124+ auto & movesMap = getMoveMapForShard (shard);
1125+ auto lock = getMoveLockForShard (shard);
1126+ auto it = movesMap.find (key);
1127+ if (it == movesMap.end ()) {
1128+ return false ;
1129+ }
1130+ auto ctx = it->second .get ();
1131+ ctx->addWaiter (std::move (waiter));
1132+ return true ;
1133+ }
1134+
11041135template <typename CacheTrait>
11051136bool CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
1106- Item& oldItem,
1107- ItemHandle& newItemHdl) {
1108- // TODO: should we introduce new latency tracker. E.g. evictRegularLatency_ ???
1109- // util::LatencyTracker tracker{stats_.evictRegularLatency_};
1137+ Item& oldItem, ItemHandle& newItemHdl) {
1138+ // TODO: should we introduce new latency tracker. E.g. evictRegularLatency_
1139+ // ??? util::LatencyTracker tracker{stats_.evictRegularLatency_};
11101140
11111141 if (!oldItem.isAccessible () || oldItem.isExpired ()) {
11121142 return false ;
@@ -1122,22 +1152,36 @@ bool CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
11221152 newItemHdl->markNvmClean ();
11231153 }
11241154
1125- if (config_. moveCb ) {
1126- // Execute the move callback. We cannot make any guarantees about the
1127- // consistency of the old item beyond this point, because the callback can
1128- // do more than a simple memcpy() e.g. update external references. If there
1129- // are any remaining handles to the old item, it is the caller's
1130- // responsibility to invalidate them. The move can only fail after this
1131- // statement if the old item has been removed or replaced, in which case it
1132- // should be fine for it to be left in an inconsistent state.
1133- config_. moveCb (oldItem, *newItemHdl, nullptr ) ;
1134- } else {
1135- std::memcpy (newItemHdl-> getWritableMemory (), oldItem. getMemory (), oldItem. getSize () );
1155+ folly::StringPiece key (oldItem. getKey ());
1156+ auto shard = getShardForKey (key);
1157+ auto & movesMap = getMoveMapForShard (shard);
1158+ MoveCtx* ctx ( nullptr );
1159+ {
1160+ auto lock = getMoveLockForShard (shard);
1161+ auto res = movesMap. try_emplace (key, std::make_unique<MoveCtx>());
1162+ if (!res. second ) {
1163+ return false ;
1164+ }
1165+ ctx = res. first -> second . get ( );
11361166 }
11371167
1138- // TODO: Possible data race. We copied Item's memory to the newItemHdl
1139- // but have not updated accessContainer yet. Concurrent threads might get handle
1140- // to the old Item.
1168+ auto resHdl = ItemHandle{};
1169+ auto guard = folly::makeGuard ([key, this , ctx, shard, &resHdl]() {
1170+ auto & movesMap = getMoveMapForShard (shard);
1171+ resHdl->unmarkIncomplete ();
1172+ auto lock = getMoveLockForShard (shard);
1173+ ctx->setItemHandle (std::move (resHdl));
1174+ movesMap.erase (key);
1175+ });
1176+
1177+ // TODO: Possibly we can use markMoving() instead. But today
1178+ // moveOnSlabRelease logic assume that we mark as moving old Item
1179+ // and than do copy and replace old Item with the new one in access
1180+ // container. Furthermore, Item can be marked as Moving only
1181+ // if it is linked to MM container. In our case we mark the new Item
1182+ // and update access container before the new Item is ready (content is
1183+ // copied).
1184+ newItemHdl->markIncomplete ();
11411185
11421186 // Inside the access container's lock, this checks if the old item is
11431187 // accessible and its refcount is zero. If the item is not accessible,
@@ -1147,10 +1191,25 @@ bool CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
11471191 // this item through an API such as remove(ItemHandle). In this case,
11481192 // it is unsafe to replace the old item with a new one, so we should
11491193 // also abort.
1150- if (!accessContainer_->replaceIf (oldItem, *newItemHdl, itemEvictionPredicate)) {
1194+ if (!accessContainer_->replaceIf (oldItem, *newItemHdl,
1195+ itemEvictionPredicate)) {
11511196 return false ;
11521197 }
11531198
1199+ if (config_.moveCb ) {
1200+ // Execute the move callback. We cannot make any guarantees about the
1201+ // consistency of the old item beyond this point, because the callback can
1202+ // do more than a simple memcpy() e.g. update external references. If there
1203+ // are any remaining handles to the old item, it is the caller's
1204+ // responsibility to invalidate them. The move can only fail after this
1205+ // statement if the old item has been removed or replaced, in which case it
1206+ // should be fine for it to be left in an inconsistent state.
1207+ config_.moveCb (oldItem, *newItemHdl, nullptr );
1208+ } else {
1209+ std::memcpy (newItemHdl->getWritableMemory (), oldItem.getMemory (),
1210+ oldItem.getSize ());
1211+ }
1212+
11541213 // Inside the MM container's lock, this checks if the old item exists to
11551214 // make sure that no other thread removed it, and only then replaces it.
11561215 if (!replaceInMMContainer (oldItem, *newItemHdl)) {
@@ -1185,6 +1244,7 @@ bool CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
11851244 XDCHECK (newItemHdl->hasChainedItem ());
11861245 }
11871246 newItemHdl.unmarkNascent ();
1247+ resHdl = newItemHdl.clone (); // guard will assign it to ctx under lock
11881248 return true ;
11891249}
11901250
@@ -1463,13 +1523,20 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
14631523 if (moveRegularItemOnEviction (item, newItemHdl)) {
14641524 return acquire (&item);
14651525 }
1466- // TODO: should we free the newItemHdl if moveRegularItemOnEviction returns false???
14671526 }
14681527 }
14691528
14701529 return {};
14711530}
14721531
1532+ template <typename CacheTrait>
1533+ typename CacheAllocator<CacheTrait>::ItemHandle
1534+ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item& item) {
1535+ auto tid = getTierId (item);
1536+ auto pid = allocator_[tid]->getAllocInfo (item.getMemory ()).poolId ;
1537+ return tryEvictToNextMemoryTier (tid, pid, item);
1538+ }
1539+
14731540template <typename CacheTrait>
14741541typename CacheAllocator<CacheTrait>::ItemHandle
14751542CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
@@ -2830,6 +2897,9 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
28302897 return ItemHandle{};
28312898 }
28322899
2900+ auto evictHandle = tryEvictToNextMemoryTier (item);
2901+ if (evictHandle) return evictHandle;
2902+
28332903 auto predicate = [](const Item& it) { return it.getRefCount () == 0 ; };
28342904
28352905 const bool evictToNvmCache = shouldWriteToNvmCache (item);
0 commit comments