in cachelib/allocator/nvmcache/NvmCache-inl.h [228:359]
void NvmCache<C>::evictCB(navy::BufferView key,
navy::BufferView value,
navy::DestructorEvent event) {
folly::StringPiece itemKey{reinterpret_cast<const char*>(key.data()),
key.size()};
// invalidate any inflight lookup that is on flight since we are evicting it.
invalidateFill(itemKey);
const auto& nvmItem = *reinterpret_cast<const NvmItem*>(value.data());
if (event == cachelib::navy::DestructorEvent::Recycled) {
// Recycled means item is evicted
// update stats for eviction
stats().numNvmEvictions.inc();
const auto timeNow = util::getCurrentTimeSec();
const auto lifetime = timeNow - nvmItem.getCreationTime();
const auto expiryTime = nvmItem.getExpiryTime();
if (expiryTime != 0) {
if (expiryTime < timeNow) {
stats().numNvmExpiredEvict.inc();
stats().nvmEvictionSecondsPastExpiry_.trackValue(timeNow - expiryTime);
} else {
stats().nvmEvictionSecondsToExpiry_.trackValue(expiryTime - timeNow);
}
}
navyCache_->isItemLarge(key, value)
? stats().nvmLargeLifetimeSecs_.trackValue(lifetime)
: stats().nvmSmallLifetimeSecs_.trackValue(lifetime);
}
bool needDestructor = true;
{
// The ItemDestructorLock is to protect:
// 1. peek item in DRAM cache,
// 2. check it's NvmClean flag
// 3. mark NvmEvicted flag
// 4. lookup itemRemoved_ set.
// Concurrent DRAM cache remove/replace/update for same item could
// modify DRAM index, check NvmClean/NvmEvicted flag, update itemRemoved_
// set, and unmark NvmClean flag.
auto lock = getItemDestructorLock(itemKey);
ItemHandle hdl;
try {
hdl = cache_.peek(itemKey);
} catch (const exception::RefcountOverflow& ex) {
// TODO(zixuan) item exists in DRAM, but we can't obtain the handle
// and mark it as NvmEvicted. In this scenario, there are two
// possibilities when the item is removed from nvm.
// 1. destructor is not executed: The item in DRAM is still marked
// NvmClean, so when it is evicted from DRAM, destructor is also skipped
// since we infer nvm copy exists (NvmClean && !NvmEvicted). In this
// case, we incorrectly skip executing an item destructor and it is also
// possible to leak the itemRemoved_ state if this item is
// removed/replaced from DRAM before this happens.
// 2. destructor is executed here: In addition to destructor being
// executed here, it could also be executed if the item was removed from
// DRAM and the handle goes out of scope. Among the two, (1) is preferred,
// until we can solve this, since executing destructor here while item
// handle being outstanding and being possibly used is dangerous.
XLOGF(ERR,
"Refcount overflowed when trying peek at an item in "
"NvmCache::evictCB. key: {}, ex: {}",
folly::StringPiece{reinterpret_cast<const char*>(key.data()),
key.size()},
ex.what());
stats().numNvmDestructorRefcountOverflow.inc();
return;
}
if (hdl && hdl->isNvmClean()) {
// item found in RAM and it is NvmClean
// this means it is the same copy as what we are evicting/removing
needDestructor = false;
if (hdl->isNvmEvicted()) {
// this means we evicted something twice. This should not happen even we
// could have two copies in the nvm cache, since we only have one copy
// in index, the one not in index should not reach here.
stats().numNvmCleanDoubleEvict.inc();
} else {
hdl->markNvmEvicted();
stats().numNvmCleanEvict.inc();
}
} else {
if (hdl) {
// item found in RAM but is NOT NvmClean
// this happens when RAM copy is in-place updated, or replaced with a
// new item.
stats().numNvmUncleanEvict.inc();
}
// If we can't find item from DRAM or isNvmClean flag not set, it might be
// removed/replaced. Check if it is in itemRemoved_, item existing in
// itemRemoved_ means it was in DRAM, was removed/replaced and
// destructor should have been executed by the DRAM copy.
//
// PutFailed event can skip the check because when item was in flight put
// and failed it was the latest copy and item was not removed/replaced
// but it could exist in itemRemoved_ due to in-place mutation and the
// legacy copy in NVM is still pending to be removed.
if (event != cachelib::navy::DestructorEvent::PutFailed &&
checkAndUnmarkItemRemovedLocked(itemKey)) {
needDestructor = false;
}
}
}
// ItemDestructor
if (itemDestructor_ && needDestructor) {
// create the item on heap instead of memory pool to avoid allocation
// failure and evictions from cache for a temporary item.
auto iobuf = createItemAsIOBuf(itemKey, nvmItem);
if (iobuf) {
auto& item = *reinterpret_cast<Item*>(iobuf->writableData());
// make chained items
auto chained = viewAsChainedAllocsRange(iobuf.get());
auto context = event == cachelib::navy::DestructorEvent::Removed
? DestructorContext::kRemovedFromNVM
: DestructorContext::kEvictedFromNVM;
try {
itemDestructor_(DestructorData{context, item, std::move(chained),
nvmItem.poolId()});
stats().numNvmDestructorCalls.inc();
} catch (const std::exception& e) {
stats().numDestructorExceptions.inc();
XLOG_EVERY_N(INFO, 100)
<< "Catch exception from user's item destructor: " << e.what();
}
}
}
}