void NvmCache::evictCB()

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();
      }
    }
  }
}