@@ -47,6 +47,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(Config config)
4747 [this ](Item* it) -> ItemHandle { return acquire (it); })),
4848 chainedItemLocks_ (config_.chainedItemsLockPower,
4949 std::make_shared<MurmurHash2>()),
50+ movesMap_(kShards ),
51+ moveLock_(kShards ),
5052 cacheCreationTime_{util::getCurrentTimeSec ()} {
5153
5254 if (numTiers_ > 1 || std::holds_alternative<FileShmSegmentOpts>(
@@ -133,6 +135,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(SharedMemNewT, Config config)
133135 [this](Item* it) -> ItemHandle { return acquire (it); })),
134136 chainedItemLocks_(config_.chainedItemsLockPower,
135137 std::make_shared<MurmurHash2>()),
138+ movesMap_(kShards ),
139+ moveLock_(kShards ),
136140 cacheCreationTime_{util::getCurrentTimeSec ()} {
137141 initCommon (false );
138142 shmManager_->removeShm (detail::kShmInfoName ,
@@ -169,6 +173,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(SharedMemAttachT, Config config)
169173 [this](Item* it) -> ItemHandle { return acquire (it); })),
170174 chainedItemLocks_(config_.chainedItemsLockPower,
171175 std::make_shared<MurmurHash2>()),
176+ movesMap_(kShards ),
177+ moveLock_(kShards ),
172178 cacheCreationTime_{*metadata_.cacheCreationTime_ref ()} {
173179 /* TODO - per tier? */
174180 for (auto pid : *metadata_.compactCachePools_ref ()) {
@@ -970,6 +976,25 @@ bool CacheAllocator<CacheTrait>::replaceInMMContainer(Item& oldItem,
970976 }
971977}
972978
979+ template <typename CacheTrait>
980+ bool CacheAllocator<CacheTrait>::replaceInMMContainer(Item* oldItem,
981+ Item& newItem) {
982+ return replaceInMMContainer (*oldItem, newItem);
983+ }
984+
985+ template <typename CacheTrait>
986+ bool CacheAllocator<CacheTrait>::replaceInMMContainer(EvictionIterator& oldItemIt,
987+ Item& newItem) {
988+ auto & oldContainer = getMMContainer (*oldItemIt);
989+ auto & newContainer = getMMContainer (newItem);
990+
991+ // This function is used for eviction across tiers
992+ XDCHECK (&oldContainer != &newContainer);
993+ oldContainer.remove (oldItemIt);
994+
995+ return newContainer.add (newItem);
996+ }
997+
973998template <typename CacheTrait>
974999bool CacheAllocator<CacheTrait>::replaceChainedItemInMMContainer(
9751000 Item& oldItem, Item& newItem) {
@@ -1104,6 +1129,157 @@ CacheAllocator<CacheTrait>::insertOrReplace(const ItemHandle& handle) {
11041129 return replaced;
11051130}
11061131
1132+ /* Next two methods are used to asynchronously move Item between memory tiers.
1133+ *
1134+ * The thread, which moves Item, allocates new Item in the tier we are moving to
1135+ * and calls moveRegularItemOnEviction() method. This method does the following:
1136+ * 1. Create MoveCtx and put it to the movesMap.
1137+ * 2. Update the access container with the new item from the tier we are
1138+ * moving to. This Item has kIncomplete flag set.
1139+ * 3. Copy data from the old Item to the new one.
1140+ * 4. Unset the kIncomplete flag and Notify MoveCtx
1141+ *
1142+ * Concurrent threads which are getting handle to the same key:
1143+ * 1. When a handle is created it checks if the kIncomplete flag is set
1144+ * 2. If so, Handle implementation creates waitContext and adds it to the
1145+ * MoveCtx by calling addWaitContextForMovingItem() method.
1146+ * 3. Wait until the moving thread will complete its job.
1147+ */
1148+ template <typename CacheTrait>
1149+ bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
1150+ folly::StringPiece key, std::shared_ptr<WaitContext<ItemHandle>> waiter) {
1151+ auto shard = getShardForKey (key);
1152+ auto & movesMap = getMoveMapForShard (shard);
1153+ auto lock = getMoveLockForShard (shard);
1154+ auto it = movesMap.find (key);
1155+ if (it == movesMap.end ()) {
1156+ return false ;
1157+ }
1158+ auto ctx = it->second .get ();
1159+ ctx->addWaiter (std::move (waiter));
1160+ return true ;
1161+ }
1162+
1163+ template <typename CacheTrait>
1164+ template <typename ItemPtr>
1165+ typename CacheAllocator<CacheTrait>::ItemHandle
1166+ CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
1167+ ItemPtr& oldItemPtr, ItemHandle& newItemHdl) {
1168+ // TODO: should we introduce new latency tracker. E.g. evictRegularLatency_
1169+ // ??? util::LatencyTracker tracker{stats_.evictRegularLatency_};
1170+
1171+ Item& oldItem = *oldItemPtr;
1172+ if (!oldItem.isAccessible () || oldItem.isExpired ()) {
1173+ return {};
1174+ }
1175+
1176+ XDCHECK_EQ (newItemHdl->getSize (), oldItem.getSize ());
1177+ XDCHECK_NE (getTierId (oldItem), getTierId (*newItemHdl));
1178+
1179+ // take care of the flags before we expose the item to be accessed. this
1180+ // will ensure that when another thread removes the item from RAM, we issue
1181+ // a delete accordingly. See D7859775 for an example
1182+ if (oldItem.isNvmClean ()) {
1183+ newItemHdl->markNvmClean ();
1184+ }
1185+
1186+ folly::StringPiece key (oldItem.getKey ());
1187+ auto shard = getShardForKey (key);
1188+ auto & movesMap = getMoveMapForShard (shard);
1189+ MoveCtx* ctx (nullptr );
1190+ {
1191+ auto lock = getMoveLockForShard (shard);
1192+ auto res = movesMap.try_emplace (key, std::make_unique<MoveCtx>());
1193+ if (!res.second ) {
1194+ return {};
1195+ }
1196+ ctx = res.first ->second .get ();
1197+ }
1198+
1199+ auto resHdl = ItemHandle{};
1200+ auto guard = folly::makeGuard ([key, this , ctx, shard, &resHdl]() {
1201+ auto & movesMap = getMoveMapForShard (shard);
1202+ if (resHdl)
1203+ resHdl->unmarkIncomplete ();
1204+ auto lock = getMoveLockForShard (shard);
1205+ ctx->setItemHandle (std::move (resHdl));
1206+ movesMap.erase (key);
1207+ });
1208+
1209+ // TODO: Possibly we can use markMoving() instead. But today
1210+ // moveOnSlabRelease logic assume that we mark as moving old Item
1211+ // and than do copy and replace old Item with the new one in access
1212+ // container. Furthermore, Item can be marked as Moving only
1213+ // if it is linked to MM container. In our case we mark the new Item
1214+ // and update access container before the new Item is ready (content is
1215+ // copied).
1216+ newItemHdl->markIncomplete ();
1217+
1218+ // Inside the access container's lock, this checks if the old item is
1219+ // accessible and its refcount is zero. If the item is not accessible,
1220+ // there is no point to replace it since it had already been removed
1221+ // or in the process of being removed. If the item is in cache but the
1222+ // refcount is non-zero, it means user could be attempting to remove
1223+ // this item through an API such as remove(ItemHandle). In this case,
1224+ // it is unsafe to replace the old item with a new one, so we should
1225+ // also abort.
1226+ if (!accessContainer_->replaceIf (oldItem, *newItemHdl,
1227+ itemEvictionPredicate)) {
1228+ return {};
1229+ }
1230+
1231+ if (config_.moveCb ) {
1232+ // Execute the move callback. We cannot make any guarantees about the
1233+ // consistency of the old item beyond this point, because the callback can
1234+ // do more than a simple memcpy() e.g. update external references. If there
1235+ // are any remaining handles to the old item, it is the caller's
1236+ // responsibility to invalidate them. The move can only fail after this
1237+ // statement if the old item has been removed or replaced, in which case it
1238+ // should be fine for it to be left in an inconsistent state.
1239+ config_.moveCb (oldItem, *newItemHdl, nullptr );
1240+ } else {
1241+ std::memcpy (newItemHdl->getWritableMemory (), oldItem.getMemory (),
1242+ oldItem.getSize ());
1243+ }
1244+
1245+ // Inside the MM container's lock, this checks if the old item exists to
1246+ // make sure that no other thread removed it, and only then replaces it.
1247+ if (!replaceInMMContainer (oldItemPtr, *newItemHdl)) {
1248+ accessContainer_->remove (*newItemHdl);
1249+ return {};
1250+ }
1251+
1252+ // Replacing into the MM container was successful, but someone could have
1253+ // called insertOrReplace() or remove() before or after the
1254+ // replaceInMMContainer() operation, which would invalidate newItemHdl.
1255+ if (!newItemHdl->isAccessible ()) {
1256+ removeFromMMContainer (*newItemHdl);
1257+ return {};
1258+ }
1259+
1260+ // no one can add or remove chained items at this point
1261+ if (oldItem.hasChainedItem ()) {
1262+ // safe to acquire handle for a moving Item
1263+ auto oldHandle = acquire (&oldItem);
1264+ XDCHECK_EQ (1u , oldHandle->getRefCount ()) << oldHandle->toString ();
1265+ XDCHECK (!newItemHdl->hasChainedItem ()) << newItemHdl->toString ();
1266+ try {
1267+ auto l = chainedItemLocks_.lockExclusive (oldItem.getKey ());
1268+ transferChainLocked (oldHandle, newItemHdl);
1269+ } catch (const std::exception& e) {
1270+ // this should never happen because we drained all the handles.
1271+ XLOGF (DFATAL, " {}" , e.what ());
1272+ throw ;
1273+ }
1274+
1275+ XDCHECK (!oldItem.hasChainedItem ());
1276+ XDCHECK (newItemHdl->hasChainedItem ());
1277+ }
1278+ newItemHdl.unmarkNascent ();
1279+ resHdl = std::move (newItemHdl); // guard will assign it to ctx under lock
1280+ return acquire (&oldItem);
1281+ }
1282+
11071283template <typename CacheTrait>
11081284bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
11091285 ItemHandle& newItemHdl) {
@@ -1358,10 +1534,47 @@ bool CacheAllocator<CacheTrait>::shouldWriteToNvmCacheExclusive(
13581534 return true ;
13591535}
13601536
1537+ template <typename CacheTrait>
1538+ template <typename ItemPtr>
1539+ typename CacheAllocator<CacheTrait>::ItemHandle
1540+ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
1541+ TierId tid, PoolId pid, ItemPtr& item) {
1542+ if (item->isExpired ()) return acquire (item);
1543+
1544+ TierId nextTier = tid; // TODO - calculate this based on some admission policy
1545+ while (++nextTier < numTiers_) { // try to evict down to the next memory tiers
1546+ // allocateInternal might trigger another eviction
1547+ auto newItemHdl = allocateInternalTier (nextTier, pid,
1548+ item->getKey (),
1549+ item->getSize (),
1550+ item->getCreationTime (),
1551+ item->getExpiryTime ());
1552+
1553+ if (newItemHdl) {
1554+ XDCHECK_EQ (newItemHdl->getSize (), item->getSize ());
1555+
1556+ return moveRegularItemOnEviction (item, newItemHdl);
1557+ }
1558+ }
1559+
1560+ return {};
1561+ }
1562+
1563+ template <typename CacheTrait>
1564+ typename CacheAllocator<CacheTrait>::ItemHandle
1565+ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item* item) {
1566+ auto tid = getTierId (*item);
1567+ auto pid = allocator_[tid]->getAllocInfo (item->getMemory ()).poolId ;
1568+ return tryEvictToNextMemoryTier (tid, pid, item);
1569+ }
1570+
13611571template <typename CacheTrait>
13621572typename CacheAllocator<CacheTrait>::ItemHandle
13631573CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
13641574 TierId tid, PoolId pid, MMContainer& mmContainer, EvictionIterator& itr) {
1575+ auto evictHandle = tryEvictToNextMemoryTier (tid, pid, itr);
1576+ if (evictHandle) return evictHandle;
1577+
13651578 Item& item = *itr;
13661579
13671580 const bool evictToNvmCache = shouldWriteToNvmCache (item);
@@ -1380,7 +1593,7 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
13801593 // if we remove the item from both access containers and mm containers
13811594 // below, we will need a handle to ensure proper cleanup in case we end up
13821595 // not evicting this item
1383- auto evictHandle = accessContainer_->removeIf (item, &itemEvictionPredicate);
1596+ evictHandle = accessContainer_->removeIf (item, &itemEvictionPredicate);
13841597
13851598 if (!evictHandle) {
13861599 ++itr;
@@ -2717,6 +2930,9 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
27172930 return ItemHandle{};
27182931 }
27192932
2933+ auto evictHandle = tryEvictToNextMemoryTier (&item);
2934+ if (evictHandle) return evictHandle;
2935+
27202936 auto predicate = [](const Item& it) { return it.getRefCount () == 0 ; };
27212937
27222938 const bool evictToNvmCache = shouldWriteToNvmCache (item);
0 commit comments