Skip to content

Commit 4c3eeab

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

File tree

3 files changed

+30
-333
lines changed

3 files changed

+30
-333
lines changed

Diff for: cachelib/allocator/CacheAllocator-inl.h

+24-222
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();
@@ -1260,159 +1262,11 @@ CacheAllocator<CacheTrait>::insertOrReplace(const ItemHandle& handle) {
12601262
return replaced;
12611263
}
12621264

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

@@ -1421,8 +1275,6 @@ bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
14211275
}
14221276

14231277
XDCHECK_EQ(newItemHdl->getSize(), oldItem.getSize());
1424-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(&getMMContainer(oldItem)),
1425-
reinterpret_cast<uintptr_t>(&getMMContainer(*newItemHdl)));
14261278

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

@@ -1489,62 +1341,6 @@ bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
14891341
return true;
14901342
}
14911343

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

17851581
if (newItemHdl) {
17861582
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1787-
1788-
return moveRegularItemOnEviction(item, newItemHdl);
1583+
if (tryMovingForSlabRelease(item, newItemHdl, itemMovingPredicate)) {
1584+
return acquire(&item);
1585+
}
17891586
}
17901587
}
17911588

@@ -1811,7 +1608,11 @@ CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(
18111608

18121609
if (newItemHdl) {
18131610
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1814-
if (moveRegularItemForPromotion(item, newItemHdl)) {
1611+
auto predicate = [this](const Item& item) {
1612+
// if inclusive cache is allowed, replace even if there are active users.
1613+
return config_.numDuplicateElements > 0 || item.getRefCount() == 0;
1614+
};
1615+
if (tryMovingForSlabRelease(item, newItemHdl, std::move(predicate))) {
18151616
return true;
18161617
}
18171618
}
@@ -2927,7 +2728,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
29272728

29282729
// if we have a valid handle, try to move, if not, we retry.
29292730
if (newItemHdl) {
2930-
isMoved = tryMovingForSlabRelease(oldItem, newItemHdl);
2731+
isMoved = tryMovingForSlabRelease(oldItem, newItemHdl, itemMovingPredicate);
29312732
if (isMoved) {
29322733
break;
29332734
}
@@ -3050,8 +2851,9 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
30502851
}
30512852

30522853
template <typename CacheTrait>
2854+
template <typename Predicate>
30532855
bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
3054-
Item& oldItem, ItemHandle& newItemHdl) {
2856+
Item& oldItem, ItemHandle& newItemHdl, Predicate&& predicate) {
30552857
// By holding onto a user-level synchronization object, we ensure moving
30562858
// a regular item or chained item is synchronized with any potential
30572859
// user-side mutation.
@@ -3083,7 +2885,7 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
30832885

30842886
return oldItem.isChainedItem()
30852887
? moveChainedItem(oldItem.asChainedItem(), newItemHdl)
3086-
: moveRegularItem(oldItem, newItemHdl);
2888+
: moveRegularItem(oldItem, newItemHdl, std::move(predicate));
30872889
}
30882890

30892891
template <typename CacheTrait>

0 commit comments

Comments
 (0)