Skip to content

Commit fa8c300

Browse files
committed
Use existing (upstream) code for moving items between tiers
instead of relying on transparent synchronization mechanims (wait context).
1 parent 9021bba commit fa8c300

File tree

3 files changed

+30
-333
lines changed

3 files changed

+30
-333
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 24 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ CacheAllocator<CacheTrait>::CacheAllocator(Config config)
4444
[this](Item* it) -> ItemHandle { return acquire(it); })),
4545
chainedItemLocks_(config_.chainedItemsLockPower,
4646
std::make_shared<MurmurHash2>()),
47-
movesMap_(kShards),
48-
moveLock_(kShards),
4947
cacheCreationTime_{util::getCurrentTimeSec()} {
5048

5149
if (numTiers_ > 1 || std::holds_alternative<FileShmSegmentOpts>(
@@ -132,8 +130,6 @@ CacheAllocator<CacheTrait>::CacheAllocator(SharedMemNewT, Config config)
132130
[this](Item* it) -> ItemHandle { return acquire(it); })),
133131
chainedItemLocks_(config_.chainedItemsLockPower,
134132
std::make_shared<MurmurHash2>()),
135-
movesMap_(kShards),
136-
moveLock_(kShards),
137133
cacheCreationTime_{util::getCurrentTimeSec()} {
138134
initCommon(false);
139135
shmManager_->removeShm(detail::kShmInfoName,
@@ -170,8 +166,6 @@ CacheAllocator<CacheTrait>::CacheAllocator(SharedMemAttachT, Config config)
170166
[this](Item* it) -> ItemHandle { return acquire(it); })),
171167
chainedItemLocks_(config_.chainedItemsLockPower,
172168
std::make_shared<MurmurHash2>()),
173-
movesMap_(kShards),
174-
moveLock_(kShards),
175169
cacheCreationTime_{*metadata_.cacheCreationTime_ref()} {
176170
/* TODO - per tier? */
177171
for (auto pid : *metadata_.compactCachePools_ref()) {
@@ -272,6 +266,14 @@ void CacheAllocator<CacheTrait>::initCommon(bool dramCacheAttached) {
272266
nvmAdmissionPolicy_->initMinTTL(config_.nvmAdmissionMinTTL);
273267
}
274268
}
269+
if (numTiers_ > 1 && !config_.moveCb) {
270+
XLOG(WARN, "No moveCb set, using memcpy for moving items between tiers.");
271+
config_.moveCb = [](Item& oldItem, Item& newItem, Item* parentItem){
272+
if (parentItem != nullptr)
273+
throw std::runtime_error("TODO: chained items not supported");
274+
std::memcpy(newItem.getMemory(), oldItem.getMemory(), oldItem.getSize());
275+
};
276+
}
275277
initStats();
276278
initNvmCache(dramCacheAttached);
277279
initWorkers();
@@ -1257,159 +1259,11 @@ CacheAllocator<CacheTrait>::insertOrReplace(const ItemHandle& handle) {
12571259
return replaced;
12581260
}
12591261

1260-
/* Next two methods are used to asynchronously move Item between memory tiers.
1261-
*
1262-
* The thread, which moves Item, allocates new Item in the tier we are moving to
1263-
* and calls moveRegularItemOnEviction() method. This method does the following:
1264-
* 1. Create MoveCtx and put it to the movesMap.
1265-
* 2. Update the access container with the new item from the tier we are
1266-
* moving to. This Item has kIncomplete flag set.
1267-
* 3. Copy data from the old Item to the new one.
1268-
* 4. Unset the kIncomplete flag and Notify MoveCtx
1269-
*
1270-
* Concurrent threads which are getting handle to the same key:
1271-
* 1. When a handle is created it checks if the kIncomplete flag is set
1272-
* 2. If so, Handle implementation creates waitContext and adds it to the
1273-
* MoveCtx by calling addWaitContextForMovingItem() method.
1274-
* 3. Wait until the moving thread will complete its job.
1275-
*/
1276-
template <typename CacheTrait>
1277-
bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
1278-
folly::StringPiece key, std::shared_ptr<WaitContext<ReadHandle>> waiter) {
1279-
auto shard = getShardForKey(key);
1280-
auto& movesMap = getMoveMapForShard(shard);
1281-
auto lock = getMoveLockForShard(shard);
1282-
auto it = movesMap.find(key);
1283-
if (it == movesMap.end()) {
1284-
return false;
1285-
}
1286-
auto ctx = it->second.get();
1287-
ctx->addWaiter(std::move(waiter));
1288-
return true;
1289-
}
1290-
1291-
template <typename CacheTrait>
1292-
typename CacheAllocator<CacheTrait>::ItemHandle
1293-
CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
1294-
Item& oldItem, ItemHandle& newItemHdl) {
1295-
XDCHECK(oldItem.isMoving());
1296-
// TODO: should we introduce new latency tracker. E.g. evictRegularLatency_
1297-
// ??? util::LatencyTracker tracker{stats_.evictRegularLatency_};
1298-
1299-
if (!oldItem.isAccessible() || oldItem.isExpired()) {
1300-
return {};
1301-
}
1302-
1303-
XDCHECK_EQ(newItemHdl->getSize(), oldItem.getSize());
1304-
XDCHECK_NE(getTierId(oldItem), getTierId(*newItemHdl));
1305-
1306-
// take care of the flags before we expose the item to be accessed. this
1307-
// will ensure that when another thread removes the item from RAM, we issue
1308-
// a delete accordingly. See D7859775 for an example
1309-
if (oldItem.isNvmClean()) {
1310-
newItemHdl->markNvmClean();
1311-
}
1312-
1313-
folly::StringPiece key(oldItem.getKey());
1314-
auto shard = getShardForKey(key);
1315-
auto& movesMap = getMoveMapForShard(shard);
1316-
MoveCtx* ctx(nullptr);
1317-
{
1318-
auto lock = getMoveLockForShard(shard);
1319-
auto res = movesMap.try_emplace(key, std::make_unique<MoveCtx>());
1320-
if (!res.second) {
1321-
return {};
1322-
}
1323-
ctx = res.first->second.get();
1324-
}
1325-
1326-
auto resHdl = ItemHandle{};
1327-
auto guard = folly::makeGuard([key, this, ctx, shard, &resHdl]() {
1328-
auto& movesMap = getMoveMapForShard(shard);
1329-
if (resHdl)
1330-
resHdl->unmarkIncomplete();
1331-
auto lock = getMoveLockForShard(shard);
1332-
ctx->setItemHandle(std::move(resHdl));
1333-
movesMap.erase(key);
1334-
});
1335-
1336-
// TODO: Possibly we can use markMoving() instead. But today
1337-
// moveOnSlabRelease logic assume that we mark as moving old Item
1338-
// and than do copy and replace old Item with the new one in access
1339-
// container. Furthermore, Item can be marked as Moving only
1340-
// if it is linked to MM container. In our case we mark the new Item
1341-
// and update access container before the new Item is ready (content is
1342-
// copied).
1343-
newItemHdl->markIncomplete();
1344-
1345-
// Inside the access container's lock, this checks if the old item is
1346-
// accessible and its refcount is zero. If the item is not accessible,
1347-
// there is no point to replace it since it had already been removed
1348-
// or in the process of being removed. If the item is in cache but the
1349-
// refcount is non-zero, it means user could be attempting to remove
1350-
// this item through an API such as remove(ItemHandle). In this case,
1351-
// it is unsafe to replace the old item with a new one, so we should
1352-
// also abort.
1353-
if (!accessContainer_->replaceIf(oldItem, *newItemHdl,
1354-
itemMovingPredicate)) {
1355-
return {};
1356-
}
1357-
1358-
if (config_.moveCb) {
1359-
// Execute the move callback. We cannot make any guarantees about the
1360-
// consistency of the old item beyond this point, because the callback can
1361-
// do more than a simple memcpy() e.g. update external references. If there
1362-
// are any remaining handles to the old item, it is the caller's
1363-
// responsibility to invalidate them. The move can only fail after this
1364-
// statement if the old item has been removed or replaced, in which case it
1365-
// should be fine for it to be left in an inconsistent state.
1366-
config_.moveCb(oldItem, *newItemHdl, nullptr);
1367-
} else {
1368-
std::memcpy(newItemHdl->getWritableMemory(), oldItem.getMemory(),
1369-
oldItem.getSize());
1370-
}
1371-
1372-
// Inside the MM container's lock, this checks if the old item exists to
1373-
// make sure that no other thread removed it, and only then replaces it.
1374-
if (!replaceInMMContainer(oldItem, *newItemHdl)) {
1375-
accessContainer_->remove(*newItemHdl);
1376-
return {};
1377-
}
1378-
1379-
// Replacing into the MM container was successful, but someone could have
1380-
// called insertOrReplace() or remove() before or after the
1381-
// replaceInMMContainer() operation, which would invalidate newItemHdl.
1382-
if (!newItemHdl->isAccessible()) {
1383-
removeFromMMContainer(*newItemHdl);
1384-
return {};
1385-
}
1386-
1387-
// no one can add or remove chained items at this point
1388-
if (oldItem.hasChainedItem()) {
1389-
// safe to acquire handle for a moving Item
1390-
auto oldHandle = acquire(&oldItem);
1391-
XDCHECK_EQ(1u, oldHandle->getRefCount()) << oldHandle->toString();
1392-
XDCHECK(!newItemHdl->hasChainedItem()) << newItemHdl->toString();
1393-
try {
1394-
auto l = chainedItemLocks_.lockExclusive(oldItem.getKey());
1395-
transferChainLocked(oldHandle, newItemHdl);
1396-
} catch (const std::exception& e) {
1397-
// this should never happen because we drained all the handles.
1398-
XLOGF(DFATAL, "{}", e.what());
1399-
throw;
1400-
}
1401-
1402-
XDCHECK(!oldItem.hasChainedItem());
1403-
XDCHECK(newItemHdl->hasChainedItem());
1404-
}
1405-
newItemHdl.unmarkNascent();
1406-
resHdl = std::move(newItemHdl); // guard will assign it to ctx under lock
1407-
return acquire(&oldItem);
1408-
}
1409-
14101262
template <typename CacheTrait>
1263+
template <typename Predicate>
14111264
bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
1412-
ItemHandle& newItemHdl) {
1265+
ItemHandle& newItemHdl,
1266+
Predicate &&predicate) {
14131267
XDCHECK(config_.moveCb);
14141268
util::LatencyTracker tracker{stats_.moveRegularLatency_};
14151269

@@ -1418,8 +1272,6 @@ bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
14181272
}
14191273

14201274
XDCHECK_EQ(newItemHdl->getSize(), oldItem.getSize());
1421-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(&getMMContainer(oldItem)),
1422-
reinterpret_cast<uintptr_t>(&getMMContainer(*newItemHdl)));
14231275

14241276
// take care of the flags before we expose the item to be accessed. this
14251277
// will ensure that when another thread removes the item from RAM, we issue
@@ -1445,7 +1297,7 @@ bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
14451297
// this item through an API such as remove(ItemHandle). In this case,
14461298
// it is unsafe to replace the old item with a new one, so we should
14471299
// also abort.
1448-
if (!accessContainer_->replaceIf(oldItem, *newItemHdl, itemMovingPredicate)) {
1300+
if (!accessContainer_->replaceIf(oldItem, *newItemHdl, predicate)) {
14491301
return false;
14501302
}
14511303

@@ -1486,62 +1338,6 @@ bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
14861338
return true;
14871339
}
14881340

1489-
template <typename CacheTrait>
1490-
bool CacheAllocator<CacheTrait>::moveRegularItemForPromotion(Item& oldItem,
1491-
ItemHandle& newItemHdl) {
1492-
util::LatencyTracker tracker{stats_.moveRegularLatency_};
1493-
1494-
if (!oldItem.isAccessible() || oldItem.isExpired()) {
1495-
return false;
1496-
}
1497-
1498-
XDCHECK_EQ(newItemHdl->getSize(), oldItem.getSize());
1499-
1500-
if (config_.moveCb) {
1501-
// Execute the move callback. We cannot make any guarantees about the
1502-
// consistency of the old item beyond this point, because the callback can
1503-
// do more than a simple memcpy() e.g. update external references. If there
1504-
// are any remaining handles to the old item, it is the caller's
1505-
// responsibility to invalidate them. The move can only fail after this
1506-
// statement if the old item has been removed or replaced, in which case it
1507-
// should be fine for it to be left in an inconsistent state.
1508-
config_.moveCb(oldItem, *newItemHdl, nullptr);
1509-
} else {
1510-
std::memcpy(newItemHdl->getWritableMemory(), oldItem.getMemory(),
1511-
oldItem.getSize());
1512-
}
1513-
1514-
auto predicate = [this](const Item& item) {
1515-
// if inclusive cache is allowed, replace even if there are active users.
1516-
return config_.numDuplicateElements > 0 || item.getRefCount() == 0;
1517-
};
1518-
if (!accessContainer_->replaceIf(oldItem, *newItemHdl, predicate)) {
1519-
return false;
1520-
}
1521-
1522-
// Inside the MM container's lock, this checks if the old item exists to
1523-
// make sure that no other thread removed it, and only then replaces it.
1524-
if (!replaceInMMContainer(oldItem, *newItemHdl)) {
1525-
accessContainer_->remove(*newItemHdl);
1526-
return false;
1527-
}
1528-
1529-
// Replacing into the MM container was successful, but someone could have
1530-
// called insertOrReplace() or remove() before or after the
1531-
// replaceInMMContainer() operation, which would invalidate newItemHdl.
1532-
if (!newItemHdl->isAccessible()) {
1533-
removeFromMMContainer(*newItemHdl);
1534-
return false;
1535-
}
1536-
1537-
// no one can add or remove chained items at this point
1538-
if (oldItem.hasChainedItem()) {
1539-
throw std::runtime_error("Not supported");
1540-
}
1541-
newItemHdl.unmarkNascent();
1542-
return true;
1543-
}
1544-
15451341
template <typename CacheTrait>
15461342
bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
15471343
ItemHandle& newItemHdl) {
@@ -1781,8 +1577,9 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
17811577

17821578
if (newItemHdl) {
17831579
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1784-
1785-
return moveRegularItemOnEviction(item, newItemHdl);
1580+
if (tryMovingForSlabRelease(item, newItemHdl, itemMovingPredicate)) {
1581+
return acquire(&item);
1582+
}
17861583
}
17871584
}
17881585

@@ -1808,7 +1605,11 @@ CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(
18081605

18091606
if (newItemHdl) {
18101607
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1811-
if (moveRegularItemForPromotion(item, newItemHdl)) {
1608+
auto predicate = [this](const Item& item) {
1609+
// if inclusive cache is allowed, replace even if there are active users.
1610+
return config_.numDuplicateElements > 0 || item.getRefCount() == 0;
1611+
};
1612+
if (tryMovingForSlabRelease(item, newItemHdl, std::move(predicate))) {
18121613
return true;
18131614
}
18141615
}
@@ -2923,7 +2724,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
29232724

29242725
// if we have a valid handle, try to move, if not, we retry.
29252726
if (newItemHdl) {
2926-
isMoved = tryMovingForSlabRelease(oldItem, newItemHdl);
2727+
isMoved = tryMovingForSlabRelease(oldItem, newItemHdl, itemMovingPredicate);
29272728
if (isMoved) {
29282729
break;
29292730
}
@@ -3046,8 +2847,9 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
30462847
}
30472848

30482849
template <typename CacheTrait>
2850+
template <typename Predicate>
30492851
bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
3050-
Item& oldItem, ItemHandle& newItemHdl) {
2852+
Item& oldItem, ItemHandle& newItemHdl, Predicate&& predicate) {
30512853
// By holding onto a user-level synchronization object, we ensure moving
30522854
// a regular item or chained item is synchronized with any potential
30532855
// user-side mutation.
@@ -3079,7 +2881,7 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
30792881

30802882
return oldItem.isChainedItem()
30812883
? moveChainedItem(oldItem.asChainedItem(), newItemHdl)
3082-
: moveRegularItem(oldItem, newItemHdl);
2884+
: moveRegularItem(oldItem, newItemHdl, std::move(predicate));
30832885
}
30842886

30852887
template <typename CacheTrait>

0 commit comments

Comments
 (0)