Skip to content

Commit 66a317f

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

File tree

3 files changed

+30
-334
lines changed

3 files changed

+30
-334
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 24 additions & 223 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();
@@ -1290,159 +1292,11 @@ CacheAllocator<CacheTrait>::insertOrReplace(const ItemHandle& handle) {
12901292
return replaced;
12911293
}
12921294

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

@@ -1451,8 +1305,6 @@ bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
14511305
}
14521306

14531307
XDCHECK_EQ(newItemHdl->getSize(), oldItem.getSize());
1454-
XDCHECK_EQ(reinterpret_cast<uintptr_t>(&getMMContainer(oldItem)),
1455-
reinterpret_cast<uintptr_t>(&getMMContainer(*newItemHdl)));
14561308

14571309
// take care of the flags before we expose the item to be accessed. this
14581310
// will ensure that when another thread removes the item from RAM, we issue
@@ -1478,7 +1330,7 @@ bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
14781330
// this item through an API such as remove(ItemHandle). In this case,
14791331
// it is unsafe to replace the old item with a new one, so we should
14801332
// also abort.
1481-
if (!accessContainer_->replaceIf(oldItem, *newItemHdl, itemMovingPredicate)) {
1333+
if (!accessContainer_->replaceIf(oldItem, *newItemHdl, predicate)) {
14821334
return false;
14831335
}
14841336

@@ -1519,63 +1371,6 @@ bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
15191371
return true;
15201372
}
15211373

1522-
template <typename CacheTrait>
1523-
bool CacheAllocator<CacheTrait>::moveRegularItemForPromotion(Item& oldItem,
1524-
ItemHandle& newItemHdl) {
1525-
XDCHECK(config_.moveCb);
1526-
util::LatencyTracker tracker{stats_.moveRegularLatency_};
1527-
1528-
if (!oldItem.isAccessible() || oldItem.isExpired()) {
1529-
return false;
1530-
}
1531-
1532-
XDCHECK_EQ(newItemHdl->getSize(), oldItem.getSize());
1533-
1534-
if (config_.moveCb) {
1535-
// Execute the move callback. We cannot make any guarantees about the
1536-
// consistency of the old item beyond this point, because the callback can
1537-
// do more than a simple memcpy() e.g. update external references. If there
1538-
// are any remaining handles to the old item, it is the caller's
1539-
// responsibility to invalidate them. The move can only fail after this
1540-
// statement if the old item has been removed or replaced, in which case it
1541-
// should be fine for it to be left in an inconsistent state.
1542-
config_.moveCb(oldItem, *newItemHdl, nullptr);
1543-
} else {
1544-
std::memcpy(newItemHdl->getWritableMemory(), oldItem.getMemory(),
1545-
oldItem.getSize());
1546-
}
1547-
1548-
auto predicate = [this](const Item& item) {
1549-
// if inclusive cache is allowed, replace even if there are active users.
1550-
return config_.numDuplicateElements > 0 || item.getRefCount() == 0;
1551-
};
1552-
if (!accessContainer_->replaceIf(oldItem, *newItemHdl, predicate)) {
1553-
return false;
1554-
}
1555-
1556-
// Inside the MM container's lock, this checks if the old item exists to
1557-
// make sure that no other thread removed it, and only then replaces it.
1558-
if (!replaceInMMContainer(oldItem, *newItemHdl)) {
1559-
accessContainer_->remove(*newItemHdl);
1560-
return false;
1561-
}
1562-
1563-
// Replacing into the MM container was successful, but someone could have
1564-
// called insertOrReplace() or remove() before or after the
1565-
// replaceInMMContainer() operation, which would invalidate newItemHdl.
1566-
if (!newItemHdl->isAccessible()) {
1567-
removeFromMMContainer(*newItemHdl);
1568-
return false;
1569-
}
1570-
1571-
// no one can add or remove chained items at this point
1572-
if (oldItem.hasChainedItem()) {
1573-
throw std::runtime_error("Not supported");
1574-
}
1575-
newItemHdl.unmarkNascent();
1576-
return true;
1577-
}
1578-
15791374
template <typename CacheTrait>
15801375
bool CacheAllocator<CacheTrait>::moveChainedItem(ChainedItem& oldItem,
15811376
ItemHandle& newItemHdl) {
@@ -1815,8 +1610,9 @@ CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
18151610

18161611
if (newItemHdl) {
18171612
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1818-
1819-
return moveRegularItemOnEviction(item, newItemHdl);
1613+
if (tryMovingForSlabRelease(item, newItemHdl, itemMovingPredicate)) {
1614+
return acquire(&item);
1615+
}
18201616
}
18211617
}
18221618

@@ -1842,7 +1638,11 @@ CacheAllocator<CacheTrait>::tryPromoteToNextMemoryTier(
18421638

18431639
if (newItemHdl) {
18441640
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1845-
if (moveRegularItemForPromotion(item, newItemHdl)) {
1641+
auto predicate = [this](const Item& item) {
1642+
// if inclusive cache is allowed, replace even if there are active users.
1643+
return config_.numDuplicateElements > 0 || item.getRefCount() == 0;
1644+
};
1645+
if (tryMovingForSlabRelease(item, newItemHdl, std::move(predicate))) {
18461646
return true;
18471647
}
18481648
}
@@ -2919,7 +2719,7 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
29192719

29202720
// if we have a valid handle, try to move, if not, we retry.
29212721
if (newItemHdl) {
2922-
isMoved = tryMovingForSlabRelease(oldItem, newItemHdl);
2722+
isMoved = tryMovingForSlabRelease(oldItem, newItemHdl, itemMovingPredicate);
29232723
if (isMoved) {
29242724
break;
29252725
}
@@ -3042,8 +2842,9 @@ CacheAllocator<CacheTrait>::allocateNewItemForOldItem(const Item& oldItem) {
30422842
}
30432843

30442844
template <typename CacheTrait>
2845+
template <typename Predicate>
30452846
bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
3046-
Item& oldItem, ItemHandle& newItemHdl) {
2847+
Item& oldItem, ItemHandle& newItemHdl, Predicate&& predicate) {
30472848
// By holding onto a user-level synchronization object, we ensure moving
30482849
// a regular item or chained item is synchronized with any potential
30492850
// user-side mutation.
@@ -3075,7 +2876,7 @@ bool CacheAllocator<CacheTrait>::tryMovingForSlabRelease(
30752876

30762877
return oldItem.isChainedItem()
30772878
? moveChainedItem(oldItem.asChainedItem(), newItemHdl)
3078-
: moveRegularItem(oldItem, newItemHdl);
2879+
: moveRegularItem(oldItem, newItemHdl, std::move(predicate));
30792880
}
30802881

30812882
template <typename CacheTrait>

0 commit comments

Comments
 (0)