@@ -47,6 +47,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(Config config)
47
47
[this ](Item* it) -> ItemHandle { return acquire (it); })),
48
48
chainedItemLocks_ (config_.chainedItemsLockPower,
49
49
std::make_shared<MurmurHash2>()),
50
+ movesMap_(kShards ),
51
+ moveLock_(kShards ),
50
52
cacheCreationTime_{util::getCurrentTimeSec ()} {
51
53
52
54
if (numTiers_ > 1 || std::holds_alternative<FileShmSegmentOpts>(
@@ -133,6 +135,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(SharedMemNewT, Config config)
133
135
[this](Item* it) -> ItemHandle { return acquire (it); })),
134
136
chainedItemLocks_(config_.chainedItemsLockPower,
135
137
std::make_shared<MurmurHash2>()),
138
+ movesMap_(kShards ),
139
+ moveLock_(kShards ),
136
140
cacheCreationTime_{util::getCurrentTimeSec ()} {
137
141
initCommon (false );
138
142
shmManager_->removeShm (detail::kShmInfoName ,
@@ -169,6 +173,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(SharedMemAttachT, Config config)
169
173
[this](Item* it) -> ItemHandle { return acquire (it); })),
170
174
chainedItemLocks_(config_.chainedItemsLockPower,
171
175
std::make_shared<MurmurHash2>()),
176
+ movesMap_(kShards ),
177
+ moveLock_(kShards ),
172
178
cacheCreationTime_{*metadata_.cacheCreationTime_ref ()} {
173
179
/* TODO - per tier? */
174
180
for (auto pid : *metadata_.compactCachePools_ref ()) {
@@ -970,6 +976,25 @@ bool CacheAllocator<CacheTrait>::replaceInMMContainer(Item& oldItem,
970
976
}
971
977
}
972
978
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
+
973
998
template <typename CacheTrait>
974
999
bool CacheAllocator<CacheTrait>::replaceChainedItemInMMContainer(
975
1000
Item& oldItem, Item& newItem) {
@@ -1104,6 +1129,157 @@ CacheAllocator<CacheTrait>::insertOrReplace(const ItemHandle& handle) {
1104
1129
return replaced;
1105
1130
}
1106
1131
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
+
1107
1283
template <typename CacheTrait>
1108
1284
bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
1109
1285
ItemHandle& newItemHdl) {
@@ -1358,10 +1534,47 @@ bool CacheAllocator<CacheTrait>::shouldWriteToNvmCacheExclusive(
1358
1534
return true ;
1359
1535
}
1360
1536
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
+
1361
1571
template <typename CacheTrait>
1362
1572
typename CacheAllocator<CacheTrait>::ItemHandle
1363
1573
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
1364
1574
TierId tid, PoolId pid, MMContainer& mmContainer, EvictionIterator& itr) {
1575
+ auto evictHandle = tryEvictToNextMemoryTier (tid, pid, itr);
1576
+ if (evictHandle) return evictHandle;
1577
+
1365
1578
Item& item = *itr;
1366
1579
1367
1580
const bool evictToNvmCache = shouldWriteToNvmCache (item);
@@ -1380,7 +1593,7 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
1380
1593
// if we remove the item from both access containers and mm containers
1381
1594
// below, we will need a handle to ensure proper cleanup in case we end up
1382
1595
// not evicting this item
1383
- auto evictHandle = accessContainer_->removeIf (item, &itemEvictionPredicate);
1596
+ evictHandle = accessContainer_->removeIf (item, &itemEvictionPredicate);
1384
1597
1385
1598
if (!evictHandle) {
1386
1599
++itr;
@@ -2717,6 +2930,9 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
2717
2930
return ItemHandle{};
2718
2931
}
2719
2932
2933
+ auto evictHandle = tryEvictToNextMemoryTier (&item);
2934
+ if (evictHandle) return evictHandle;
2935
+
2720
2936
auto predicate = [](const Item& it) { return it.getRefCount () == 0 ; };
2721
2937
2722
2938
const bool evictToNvmCache = shouldWriteToNvmCache (item);
0 commit comments