cachelib/allocator/CacheAllocator.h (665 lines of code) (raw):
/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once
#include <folly/CPortability.h>
#include <folly/Likely.h>
#include <folly/ScopeGuard.h>
#include <folly/logging/xlog.h>
#include <folly/synchronization/SanitizeThread.h>
#include <gtest/gtest.h>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <utility>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#include <folly/Format.h>
#include <folly/Range.h>
#pragma GCC diagnostic pop
#include "cachelib/allocator/CCacheManager.h"
#include "cachelib/allocator/Cache.h"
#include "cachelib/allocator/CacheAllocatorConfig.h"
#include "cachelib/allocator/CacheChainedItemIterator.h"
#include "cachelib/allocator/CacheItem.h"
#include "cachelib/allocator/CacheStats.h"
#include "cachelib/allocator/CacheStatsInternal.h"
#include "cachelib/allocator/CacheTraits.h"
#include "cachelib/allocator/CacheVersion.h"
#include "cachelib/allocator/ChainedAllocs.h"
#include "cachelib/allocator/ICompactCache.h"
#include "cachelib/allocator/KAllocation.h"
#include "cachelib/allocator/MemoryMonitor.h"
#include "cachelib/allocator/NvmAdmissionPolicy.h"
#include "cachelib/allocator/NvmCacheState.h"
#include "cachelib/allocator/PoolOptimizeStrategy.h"
#include "cachelib/allocator/PoolOptimizer.h"
#include "cachelib/allocator/PoolRebalancer.h"
#include "cachelib/allocator/PoolResizer.h"
#include "cachelib/allocator/ReadOnlySharedCacheView.h"
#include "cachelib/allocator/Reaper.h"
#include "cachelib/allocator/RebalanceStrategy.h"
#include "cachelib/allocator/Refcount.h"
#include "cachelib/allocator/TempShmMapping.h"
#include "cachelib/allocator/TlsActiveItemRing.h"
#include "cachelib/allocator/TypedHandle.h"
#include "cachelib/allocator/Util.h"
#include "cachelib/allocator/memory/MemoryAllocator.h"
#include "cachelib/allocator/memory/MemoryAllocatorStats.h"
#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h"
#include "cachelib/allocator/nvmcache/NvmCache.h"
#include "cachelib/allocator/serialize/gen-cpp2/objects_types.h"
#include "cachelib/common/Exceptions.h"
#include "cachelib/common/Hash.h"
#include "cachelib/common/Mutex.h"
#include "cachelib/common/PeriodicWorker.h"
#include "cachelib/common/Serialization.h"
#include "cachelib/common/Throttler.h"
#include "cachelib/common/Utils.h"
#include "cachelib/shm/ShmManager.h"
namespace facebook {
namespace cachelib {
template <typename AllocatorT>
class FbInternalRuntimeUpdateWrapper;
template <typename AllocatorT>
class CacheAllocatorFindApiWrapper;
namespace cachebench {
template <typename Allocator>
class Cache;
namespace tests {
class CacheTest;
}
} // namespace cachebench
namespace tests {
template <typename AllocatorT>
class BaseAllocatorTest;
template <typename AllocatorT>
class AllocatorHitStatsTest;
template <typename AllocatorT>
class AllocatorResizeTest;
template <typename AllocatorT>
class FixedSizeArrayTest;
template <typename AllocatorT>
class MapTest;
class NvmCacheTest;
template <typename AllocatorT>
class PoolOptimizeStrategyTest;
class NvmAdmissionPolicyTest;
class CacheAllocatorTestWrapper;
class PersistenceCache;
} // namespace tests
namespace objcache {
template <typename CacheDescriptor, typename AllocatorRes>
class ObjectCache;
namespace test {
#define GET_CLASS_NAME(test_case_name, test_name) \
  test_case_name##_##test_name##_Test
#define GET_DECORATED_CLASS_NAME(namespace, test_case_name, test_name) \
  namespace ::GET_CLASS_NAME(test_case_name, test_name)
class GET_CLASS_NAME(ObjectCache, ObjectHandleInvalid);
} // namespace test
} // namespace objcache
// CacheAllocator can provide an interface to make Keyed Allocations(Item) and
// takes two templated types that control how the allocation is
// maintained(MMType aka MemoryManagementType) and accessed(AccessType). The
// cache allocator internally has an allocator that it interacts with to make
// allocations. All active allocations are put into the AccessContainer and
// the MMContainer for maintenance. When the cache is full, allocations are
// garbage collected by the implementation of MMType.
//
// The MMType is used for keeping track of allocations that are currently
// under the control of cache allocator. The MMType is required to provide a
// data structure MMContainer with a well defined interface. For example,
// check MMLru.h's Container (TODO use boost concepts to enforce the
// interface if possible and have it defined in a .h file). The MMType must
// provide a Hook type that will be used to instrument the resultant Item to
// be compatible for use with the MMContainer similar to a boost intrusive
// member hook. MMType::Hook must be sufficient for MMType::Container<T> to
// operate. The MMContainer is expected to implement interfaces to
// add/remove/evict/recordAccess a T& object into the container. This allows
// us to change/abstract away the memory management implementation of the
// cache from the other parts of the cache.
//
// Similar to the MMType, the AccessType is an intrusive data type that
// provides a container to access the keyed allocations. AccessType must
// provide an AccessType::Hook and AccessType::Container with
// find/insert/remove interface similar to a hash table.
//
template <typename CacheTrait>
class CacheAllocator : public CacheBase {
 public:
  using CacheT = CacheAllocator<CacheTrait>;
  using MMType = typename CacheTrait::MMType;
  using AccessType = typename CacheTrait::AccessType;
  using Config = CacheAllocatorConfig<CacheT>;
  // configs for the MMtype and AccessType.
  using MMConfig = typename MMType::Config;
  using AccessConfig = typename AccessType::Config;
  using Item = CacheItem<CacheTrait>;
  using ChainedItem = typename Item::ChainedItem;
  // the holder for the item when we hand it to the caller. This ensures
  // that the reference count is maintained when the caller is done with the
  // item. The ItemHandle provides a getMemory() and getKey() interface. The
  // caller is free to use the result of these two as long as the handle is
  // active/alive. Using the result of the above interfaces after destroying
  // the ItemHandle is UB. The ItemHandle safely wraps a pointer to the Item.
  using ReadHandle = typename Item::ReadHandle;
  using WriteHandle = typename Item::WriteHandle;
  using ItemHandle = WriteHandle;
  template <typename UserType,
            typename Converter =
                detail::DefaultUserTypeConverter<Item, UserType>>
  using TypedHandle = TypedHandleImpl<Item, UserType, Converter>;
  // TODO (sathya) some types take CacheT and some take CacheTrait. need to
  // clean this up and come up with a consistent policy that is intuitive.
  using ChainedItemIter = CacheChainedItemIterator<CacheT, const Item>;
  using WritableChainedItemIter = CacheChainedItemIterator<CacheT, Item>;
  using ChainedAllocs = CacheChainedAllocs<CacheT, ReadHandle, ChainedItemIter>;
  using WritableChainedAllocs =
      CacheChainedAllocs<CacheT, WriteHandle, WritableChainedItemIter>;
  using Key = typename Item::Key;
  using PoolIds = std::set<PoolId>;
  using EventTracker = EventInterface<Key>;
  // holds information about removal, used in RemoveCb
  struct RemoveCbData {
    // remove or eviction
    RemoveContext context;
    // item about to be freed back to allocator
    Item& item;
    // Iterator range pointing to chained allocs associated with @item
    folly::Range<ChainedItemIter> chainedAllocs;
  };
  struct DestructorData {
    DestructorData(DestructorContext ctx,
                   Item& it,
                   folly::Range<ChainedItemIter> iter,
                   PoolId id)
        : context(ctx), item(it), chainedAllocs(iter), pool(id) {}
    // helps to convert RemoveContext to DestructorContext,
    // the context for RemoveCB is re-used to create DestructorData,
    // this can be removed if RemoveCB is dropped.
    DestructorData(RemoveContext ctx,
                   Item& it,
                   folly::Range<ChainedItemIter> iter,
                   PoolId id)
        : item(it), chainedAllocs(iter), pool(id) {
      if (ctx == RemoveContext::kEviction) {
        context = DestructorContext::kEvictedFromRAM;
      } else {
        context = DestructorContext::kRemovedFromRAM;
      }
    }
    // remove or eviction
    DestructorContext context;
    // item about to be freed back to allocator
    // when the item is evicted/removed from NVM, the item is created on the
    // heap, functions (e.g. CacheAllocator::getAllocInfo) that assumes item is
    // located in cache slab doesn't work in such case.
    // chained items must be iterated though @chainedAllocs.
    // Other APIs used to access chained items are not compatible and should not
    // be used.
    Item& item;
    // Iterator range pointing to chained allocs associated with @item
    // when chained items are evicted/removed from NVM, items are created on the
    // heap, functions (e.g. CacheAllocator::getAllocInfo) that assumes items
    // are located in cache slab doesn't work in such case.
    folly::Range<ChainedItemIter> chainedAllocs;
    // the pool that this item is/was
    PoolId pool;
  };
  // call back to execute when moving an item, this could be a simple memcpy
  // or something more complex.
  // An optional parentItem pointer is provided if the item being moved is a
  // chained item.
  using MoveCb =
      std::function<void(Item& oldItem, Item& newItem, Item* parentItem)>;
  // call back type that is executed when the cache item is removed
  // (evicted / freed) from RAM, only items inserted into cache (not nascent)
  // successfully are tracked
  using RemoveCb = std::function<void(const RemoveCbData& data)>;
  // the destructor being executed when the item is removed from cache (both RAM
  // and NVM), only items inserted into cache (not nascent) successfully are
  // tracked.
  using ItemDestructor = std::function<void(const DestructorData& data)>;
  using NvmCacheT = NvmCache<CacheT>;
  using NvmCacheConfig = typename NvmCacheT::Config;
  using DeleteTombStoneGuard = typename NvmCacheT::DeleteTombStoneGuard;
  // Interface for the sync object provided by the user if movingSync is turned
  // on.
  // SyncObj is for CacheLib to obtain exclusive access to an item when
  // it is moving it during slab release. Once held, the user should guarantee
  // the item will not be accessed from another thread.
  struct SyncObj {
    virtual ~SyncObj() = default;
    // Override this function to indicate success/failure of the sync obj,
    // if user-supplied SyncObj can fail. e.g. if a lock can timeout.
    virtual bool isValid() const { return true; }
  };
  using ChainedItemMovingSync = std::function<std::unique_ptr<SyncObj>(Key)>;
  using AccessContainer = typename Item::AccessContainer;
  using MMContainer = typename Item::MMContainer;
  // serialization types
  using MMSerializationType = typename MMType::SerializationType;
  using MMSerializationConfigType = typename MMType::SerializationConfigType;
  using MMSerializationTypeContainer =
      typename MMType::SerializationTypeContainer;
  using AccessSerializationType = typename AccessType::SerializationType;
  using ShmManager = facebook::cachelib::ShmManager;
  // The shared memory segments that can be persisted and re-attached to
  enum SharedMemNewT { SharedMemNew };
  // Attach to a persisted shared memory segment
  enum SharedMemAttachT { SharedMemAttach };
  // instantiates a cache allocator on heap memory
  //
  // @param config        the configuration for the whole cache allocator
  explicit CacheAllocator(Config config);
  // instantiates a cache allocator on shared memory
  //
  // @param config        the configuration for the whole cache allocator
  CacheAllocator(SharedMemNewT, Config config);
  // restore a cache allocator from shared memory
  //
  // @param config        the configuration for the whole cache allocator
  //
  // @throw std::invalid_argument if cannot restore successful
  CacheAllocator(SharedMemAttachT, Config config);
  // Shared segments will be detached upon destruction
  ~CacheAllocator() override;
  // create a new cache allocation. The allocation can be initialized
  // appropriately and made accessible through insert or insertOrReplace.
  // If the handle returned from this api is not passed on to
  // insert/insertOrReplace, the allocation gets destroyed when the handle
  // goes out of scope.
  //
  // @param id              the pool id for the allocation that was previously
  //                        created through addPool
  // @param key             the key for the allocation. This will be made a
  //                        part of the Item and be available through getKey().
  // @param size            the size of the allocation, exclusive of the key
  //                        size.
  // @param ttlSecs         Time To Live(second) for the item,
  //                        default with 0 means no expiration time.
  //
  // @return      the handle for the item or an invalid handle(nullptr) if the
  //              allocation failed. Allocation can fail if we are out of memory
  //              and can not find an eviction.
  // @throw   std::invalid_argument if the poolId is invalid or the size
  //          requested is invalid or if the key is invalid(key.size() == 0 or
  //          key.size() > 255)
  WriteHandle allocate(PoolId id,
                       Key key,
                       uint32_t size,
                       uint32_t ttlSecs = 0,
                       uint32_t creationTime = 0);
  // Allocate a chained item
  //
  // The resulting chained item does not have a parent item and
  // will be freed once the handle is dropped
  //
  // The parent handle parameter here is mainly used to find the
  // correct pool to allocate memory for this chained item
  //
  // @param parent    handle to the cache item
  // @param size      the size for the chained allocation
  //
  // @return    handle to the chained allocation
  // @throw     std::invalid_argument if the size requested is invalid or
  //            if the item is invalid
  WriteHandle allocateChainedItem(const ReadHandle& parent, uint32_t size);
  // Link a chained item to a parent item and mark this parent handle as having
  // chained allocations.
  // The parent handle is not reset (to become a null handle) so that the caller
  // can continue using it as before calling this api.
  //
  // @param parent  handle to the parent item
  // @param child   chained item that will be linked to the parent
  //
  // @throw std::invalid_argument if parent is nullptr
  void addChainedItem(WriteHandle& parent, WriteHandle child);
  // Pop the first chained item assocaited with this parent and unmark this
  // parent handle as having chained allocations.
  // The parent handle is not reset (to become a null handle) so that the caller
  // can continue using it as before calling this api.
  //
  // @param parent  handle to the parent item
  //
  // @return ChainedItem  head if there exists one
  //         nullptr      otherwise
  ItemHandle popChainedItem(ItemHandle& parent);
  // Return the key to the parent item.
  //
  // This API is racy with transferChainedAndReplace and also with moving during
  // a slab release. To use this safely. user needs to synchronize calls to this
  // API using their user level lock (in exclusive mode). The same user level
  // lock should've been provided via movingSync to CacheLib if moving is
  // enabled for slab rebalancing.
  //
  // @throw std::invalid_argument if chainedItem is not actually a chained item.
  Key getParentKey(const Item& chainedItem);
  // replace a chained item in the existing chain. old and new item must be
  // chained items that have been allocated with the same parent that is
  // passed in. oldItem must be in the chain and newItem must not be.
  //
  // Upon success a handle to the oldItem is returned for the caller
  //
  // @param oldItem  the item we are replacing in the chain
  // @param newItem  the item we are replacing it with
  // @param parent   the parent for the chain
  //
  // @return  handle to the oldItem on return.
  //
  // @throw std::invalid_argument if any of the pre-conditions fails
  ItemHandle replaceChainedItem(Item& oldItem,
                                ItemHandle newItem,
                                Item& parent);
  // Transfers the ownership of the chain from the current parent to the new
  // parent and inserts the new parent into the cache. Parent will be unmarked
  // as having chained allocations and its nvmCache will be invalidated. Parent
  // will not be null after calling this API.
  //
  // Caller must synchronize with any modifications to the parent's chain and
  // any calls to find() for the same key to ensure there are no more concurrent
  // parent handle while doing this. While calling this method, the cache does
  // not guarantee a consistent view for the key and the caller must not rely on
  // this. The new parent and old parent must be allocations for the same key.
  // New parent must also be an allocation that is not added to the cache.
  //
  //
  // @param parent    the current parent of the chain we want to transfer
  // @param newParent the new parent for the chain
  //
  // @throw   std::invalid_argument if the parent does not have chained item or
  //          incorrect state of chained item or if any of the pre-conditions
  //          are not met
  void transferChainAndReplace(ItemHandle& parent, ItemHandle& newParent);
  // Inserts the allocated handle into the AccessContainer, making it
  // accessible for everyone. This needs to be the handle that the caller
  // allocated through _allocate_. If this call fails, the allocation will be
  // freed back when the handle gets out of scope in the caller.
  //
  // @param  handle  the handle for the allocation.
  //
  // @return true if the handle was successfully inserted into the hashtable
  //         and is now accessible to everyone. False if there was an error.
  //
  // @throw std::invalid_argument if the handle is already accessible.
  bool insert(const WriteHandle& handle);
  // Replaces the allocated handle into the AccessContainer, making it
  // accessible for everyone. If an existing handle is already in the
  // container, remove that handle. This needs to be the handle that the caller
  // allocated through _allocate_. If this call fails, the allocation will be
  // freed back when the handle gets out of scope in the caller.
  //
  // @param  handle  the handle for the allocation.
  //
  // @throw std::invalid_argument if the handle is already accessible.
  // @throw cachelib::exception::RefcountOverflow if the item we are replacing
  //        is already out of refcounts.
  // @return handle to the old item that had been replaced
  WriteHandle insertOrReplace(const WriteHandle& handle);
  // look up an item by its key across the nvm cache as well if enabled.
  //
  // @param key       the key for lookup
  //
  // @return          the read handle for the item or a handle to nullptr if the
  //                  key does not exist.
  ReadHandle find(Key key);
  // look up an item by its key across the nvm cache as well if enabled. Users
  // should call this API only when they are going to mutate the item data.
  //
  // @param key               the key for lookup
  // @param isNvmInvalidate   whether to do nvm invalidation;
  //                          defaults to be true
  //
  // @return      the write handle for the item or a handle to nullptr if the
  //              key does not exist.
  WriteHandle findToWrite(Key key, bool doNvmInvalidation = true);
  // look up an item by its key. This ignores the nvm cache and only does RAM
  // lookup.
  //
  // @param key         the key for lookup
  //
  // @return      the read handle for the item or a handle to nullptr if the key
  //              does not exist.
  FOLLY_ALWAYS_INLINE ReadHandle findFast(Key key);
  // look up an item by its key. This ignores the nvm cache and only does RAM
  // lookup. Users should call this API only when they are going to mutate the
  // item data.
  //
  // @param key         the key for lookup
  // @param isNvmInvalidate   whether to do nvm invalidation;
  //                          defaults to be true
  //
  // @return      the write handle for the item or a handle to nullptr if the
  //              key does not exist.
  FOLLY_ALWAYS_INLINE WriteHandle
  findFastToWrite(Key key, bool doNvmInvalidation = true);
  // look up an item by its key. This ignores the nvm cache and only does RAM
  // lookup. This API does not update the stats related to cache gets and misses
  // nor mark the item as useful (see markUseful below).
  //
  // @param key   the key for lookup
  // @return      the handle for the item or a handle to nullptr if the key does
  //              not exist.
  FOLLY_ALWAYS_INLINE ItemHandle peek(Key key);
  // Mark an item that was fetched through peek as useful. This is useful when
  // users want to look into the cache and only mark items as useful when they
  // inspect the contents of it.
  //
  // @param handle        the item handle
  // @param mode          the mode of access for the lookup. defaults to
  //                      AccessMode::kRead
  void markUseful(const ReadHandle& handle, AccessMode mode);
  using AccessIterator = typename AccessContainer::Iterator;
  // Iterator interface for the cache. It guarantees that all keys that were
  // present when the iteration started will be accessible unless they are
  // removed. Keys that are removed/inserted during the lifetime of an
  // iterator are not guaranteed to be either visited or not-visited.
  // Adding/Removing from the hash table while the iterator is alive will not
  // inivalidate any iterator or the element that the iterator points at
  // currently. The iterator internally holds a Handle to the item and hence
  // the keys that the iterator holds reference to, will not be evictable
  // until the iterator is destroyed.
  AccessIterator begin() { return accessContainer_->begin(); }
  // return an iterator with a throttler for throttled iteration
  AccessIterator begin(util::Throttler::Config config) {
    return accessContainer_->begin(config);
  }
  AccessIterator end() { return accessContainer_->end(); }
  enum class RemoveRes : uint8_t {
    kSuccess,
    kNotFoundInRam,
  };
  // removes the allocation corresponding to the key, if present in the hash
  // table. The key will not be accessible through find() after this returns
  // success. The allocation for the key will be recycled once all active
  // ItemHandles are released.
  //
  // @param key   the key for the allocation.
  // @return      kSuccess if the key exists and was successfully removed.
  //              kNotFoundInRam if the key was not present in memory (doesn't
  //              check nvm)
  RemoveRes remove(Key key);
  // remove the key that the iterator is pointing to. The element will
  // not be accessible upon success. However, the elemenet will not actually be
  // recycled until the iterator destroys the internal handle.
  //
  // @param  it   the iterator to the key to be destroyed.
  // @return      kSuccess if the element was still in the hashtable and it was
  //              successfully removed.
  //              kNotFoundInRam if the element the iterator was pointing to was
  //              deleted already.
  RemoveRes remove(AccessIterator& it);
  // removes the allocation corresponding to the handle. The allocation will
  // be freed when all the existing handles are released.
  //
  // @param  it   item read handle
  //
  // @return      kSuccess if the item exists and was successfully removed.
  //              kNotFoundInRam otherwise
  //
  // @throw std::invalid_argument if item handle is null
  RemoveRes remove(const ReadHandle& it);
  // view a read-only parent item as a chain of allocations if it has chained
  // alloc. The returned chained-alloc is good to iterate upon, but will block
  // any concurrent addChainedItem or popChainedItem for the same key until the
  // ChainedAllocs object is released. This is ideal for use cases which do
  // very brief operations on the chain of allocations.
  //
  // The ordering of the iteration for the chain is LIFO. Check
  // CacheChainedAllocs.h for the API and usage.
  //
  // @param parent  the parent allocation of the chain from a ReadHandle.
  // @return        read-only chained alloc view of the parent
  //
  // @throw std::invalid_argument if the parent does not have chained allocs
  ChainedAllocs viewAsChainedAllocs(const ReadHandle& parent) {
    return viewAsChainedAllocsT<ReadHandle, ChainedItemIter>(parent);
  }
  // view a writable parent item as a chain of allocations if it has chained
  // alloc. The returned chained-alloc is good to iterate upon, but will block
  // any concurrent addChainedItem or popChainedItem for the same key until the
  // ChainedAllocs object is released. This is ideal for use cases which do
  // very brief operations on the chain of allocations.
  //
  // The ordering of the iteration for the chain is LIFO. Check
  // CacheChainedAllocs.h for the API and usage.
  //
  // @param parent  the parent allocation of the chain from a WriteHandle.
  // @return        writable chained alloc view of the parent
  //
  // @throw std::invalid_argument if the parent does not have chained allocs
  WritableChainedAllocs viewAsWritableChainedAllocs(const WriteHandle& parent) {
    return viewAsChainedAllocsT<WriteHandle, WritableChainedItemIter>(parent);
  }
  // Returns the full usable size for this item
  // This can be bigger than item.getSize()
  //
  // @param item    reference to an item
  //
  // @return    the full usable size for this item
  uint32_t getUsableSize(const Item& item) const;
  // Get a random item from memory
  // This is useful for profiling and sampling cachelib managed memory
  //
  // @return ItemHandle if an valid item is found
  //
  //         nullptr if the randomly chosen memory does not belong
  //                 to an valid item
  ItemHandle getSampleItem();
  // Convert a Read Handle to an IOBuf. The returned IOBuf gives a
  // read-only view to the user. The item's ownership is retained by
  // the IOBuf until its destruction.
  //
  // When the read handle has one or more chained items attached to it,
  // user will also get a series of IOBufs (first of which is the Parent).
  //
  // **WARNING**: folly::IOBuf allows mutation to a cachelib item even when the
  // item is read-only. User is responsible to ensure no mutation occurs (i.e.
  // only const functions are called). If mutation is required, please use
  // `convertToIOBufForWrite`.
  //
  // @param handle    read handle that will transfer its ownership to an IOBuf
  //
  // @return   an IOBuf that contains the value of the item.
  //           This IOBuf acts as a Read Handle, on destruction, it will
  //           properly decrement the refcount (to release the item).
  // @throw   std::invalid_argument if ReadHandle is nullptr
  folly::IOBuf convertToIOBuf(ReadHandle handle) {
    return convertToIOBufT<ReadHandle>(handle);
  }
  // Convert a Write Handle to an IOBuf. The returned IOBuf gives a
  // writable view to the user. The item's ownership is retained by
  // the IOBuf until its destruction.
  //
  // When the write handle has one or more chained items attached to it,
  // user will also get a series of IOBufs (first of which is the Parent).
  //
  // @param handle    write handle that will transfer its ownership to an IOBuf
  //
  // @return   an IOBuf that contains the value of the item.
  //           This IOBuf acts as a Write Handle, on destruction, it will
  //           properly decrement the refcount (to release the item).
  // @throw   std::invalid_argument if WriteHandle is nullptr
  folly::IOBuf convertToIOBufForWrite(WriteHandle handle) {
    return convertToIOBufT<WriteHandle>(handle);
  }
  // TODO: When Read/Write Handles are ready, change this to allow
  //       const-only access to data manged by iobuf and offer a
  //       wrapAsWritableIOBuf() API.
  //
  // wrap an IOBuf over the data for an item. This IOBuf does not own the item
  // and the caller is responsible for ensuring that the IOBuf is valid with
  // the item lifetime. If the item has chained allocations, the chains are
  // also wrapped into the iobuf as chained iobufs
  //
  // @param item  the item to wrap around
  //
  // @return   an IOBuf that contains the value of the item.
  folly::IOBuf wrapAsIOBuf(const Item& item);
  // creates a pool for the cache allocator with the corresponding name.
  //
  // @param name                  name of the pool
  // @param size                  size of the pool
  // @param allocSizes            allocation class sizes, if empty, a default
  //                              one from the memory allocator will be used
  // @param config                MMConfig for the MMContainer,
  //                              default constructed if user doesn't supply one
  // @param rebalanceStrategy     rebalance strategy for the pool. If not set,
  //                              the default one will be used.
  // @param resizeStrategy        resize strategy for the pool. If not set,
  //                              the default one will be used.
  // @param ensureProvisionable   ensures that the size of the pool is enough
  //                              to give one slab to each allocation class,
  //                              false by default.
  //
  // @return a valid PoolId that the caller can use.
  // @throw   std::invalid_argument if the size is invalid or there is not
  //          enough space for creating the pool.
  //          std::logic_error if we have run out of pools.
  PoolId addPool(folly::StringPiece name,
                 size_t size,
                 const std::set<uint32_t>& allocSizes = {},
                 MMConfig config = {},
                 std::shared_ptr<RebalanceStrategy> rebalanceStrategy = nullptr,
                 std::shared_ptr<RebalanceStrategy> resizeStrategy = nullptr,
                 bool ensureProvisionable = false);
  // update an existing pool's config
  //
  // @param pid       pool id for the pool to be updated
  // @param config    new config for the pool
  //
  // @throw std::invalid_argument if the poolId is invalid
  void overridePoolConfig(PoolId pid, const MMConfig& config);
  // update an existing pool's rebalance strategy
  //
  // @param pid                 pool id for the pool to be updated
  // @param rebalanceStrategy   new rebalance strategy for the pool
  //
  // @throw std::invalid_argument if the poolId is invalid
  void overridePoolRebalanceStrategy(
      PoolId pid, std::shared_ptr<RebalanceStrategy> rebalanceStrategy);
  // update an existing pool's resize strategy
  //
  // @param pid                 pool id for the pool to be updated
  // @param resizeStrategy      new resize strategy for the pool
  //
  // @throw std::invalid_argument if the poolId is invalid
  void overridePoolResizeStrategy(
      PoolId pid, std::shared_ptr<RebalanceStrategy> resizeStrategy);
  // update pool size optimization strategy for this cache
  // @param optimizeStrategy    new resize strategy
  void overridePoolOptimizeStrategy(
      std::shared_ptr<PoolOptimizeStrategy> optimizeStrategy);
  /**
   * PoolResizing can be done online while the cache allocator is being used
   * to do allocations. Pools can be grown or shrunk using the following api.
   * The actual resizing happens asynchronously and is controlled by the
   * config parameters poolResizeIntervalSecs and poolResizeSlabsPerIter. The
   * pool resizer releases slabs from pools that are over limit when the
   * memory allocator is out of memory. If there is enough free memory
   * available, the pool resizer does not do any resizing until the memory is
   * exhausted and there is some pool that is over the limit
   */
  // shrink the existing pool by _bytes_ .
  // @param bytes  the number of bytes to be taken away from the pool
  // @return  true if the operation succeeded. false if the size of the pool is
  //          smaller than _bytes_
  // @throw   std::invalid_argument if the poolId is invalid.
  bool shrinkPool(PoolId pid, size_t bytes) {
    return allocator_->shrinkPool(pid, bytes);
  }
  // grow an existing pool by _bytes_. This will fail if there is no
  // available memory across all the pools to provide for this pool
  // @param bytes  the number of bytes to be added to the pool.
  // @return    true if the pool was grown. false if the necessary number of
  //            bytes were not available.
  // @throw     std::invalid_argument if the poolId is invalid.
  bool growPool(PoolId pid, size_t bytes) {
    return allocator_->growPool(pid, bytes);
  }
  // move bytes from one pool to another. The source pool should be at least
  // _bytes_ in size.
  //
  // @param src     the pool to be sized down and giving the memory.
  // @param dest    the pool receiving the memory.
  // @param bytes   the number of bytes to move from src to dest.
  // @param   true if the resize succeeded. false if src does does not have
  //          correct size to do the transfer.
  // @throw   std::invalid_argument if src or dest is invalid pool
  bool resizePools(PoolId src, PoolId dest, size_t bytes) override {
    return allocator_->resizePools(src, dest, bytes);
  }
  // Add a new compact cache with given name and size
  //
  // @param name  name of the compact cache pool
  // @param size  size of the compact cache pool
  // @param args  All of the arguments in CompactCache afer allocator
  //              So the signature of addCompactCache is:
  //              addCompactCache(folly::StringPiece name,
  //                              size_t size,
  //                              RemoveCb removeCb,
  //                              ReplaceCb replaceCb,
  //                              ValidCb validCb,
  //                              bool allowPromotions = true);
  //              addCompactCache(folly::StringPiece name,
  //                              size_t size,
  //                              bool allowPromotions = true);
  //
  // @return pointer to CompactCache instance of the template type
  //
  // @throw std::logic_error        if compact cache is not enabled
  // @throw std::invalid_argument   There is a memory pool that has the same
  //                                name as the compact cache we are adding or
  //                                if there is no sufficient space to create
  //                                a compact cache.
  template <typename CCacheT, typename... Args>
  CCacheT* addCompactCache(folly::StringPiece name,
                           size_t size,
                           Args&&... args);
  // Attach a compact cache to the given pool after warm roll
  //
  // @param name  name of the compact cache pool
  // @param args  All of the arguments in CompactCache afer allocator
  //              So the signature of attachCompactCache is:
  //              attachCompactCache(folly::StringPiece name,
  //                                 RemoveCb removeCb,
  //                                 ReplaceCb replaceCb,
  //                                 ValidCb validCb,
  //                                 bool allowPromotions = true);
  //              attachCompactCache(folly::StringPiece name,
  //                                 bool allowPromotions = true);
  //
  // @return  pointer to CompactCache instance of the template type.
  //
  // @throw std::out_of_range       if the pool does not exist
  // @throw std::invalid_argument   if the compact key/value size does not match
  //                                from warm roll
  template <typename CCacheT, typename... Args>
  CCacheT* attachCompactCache(folly::StringPiece name, Args&&... args);
  // Return the base iterface of an attached compact cache to pull out its
  // stats. For non-active compact cache, this would throw
  // std::invalid_argument.
  const ICompactCache& getCompactCache(PoolId pid) const override;
  // The enum value that indicates the CacheAllocator's shutdown status.
  enum class ShutDownStatus {
    kSuccess = 0, // Successfully persisted the DRAM cache, and the NvmCache if
                  // enabled.
    kSavedOnlyDRAM, // Successfully persisted the DRAM cache only; NvmCache is
                    // enabled but failed to persist it.
    kSavedOnlyNvmCache, // Successfully persisted the enabled NvM cache only;
                        // Failed to persist DRAM cache.
    kFailed // Failed to persist both the DRAM cache and the enabled NvmCache.
  };
  // Persists the state of the cache allocator. On a successful shutdown,
  // this cache allocator can be restored on restart.
  //
  // precondition:  serialization must happen without any reader or writer
  // present. Any modification of this object afterwards will result in an
  // invalid, inconsistent state for the serialized data. There must not be
  // any outstanding active handles
  //
  // @throw   std::invalid_argument if the cache allocator isn't using shared
  //          memory
  // @throw   std::logic_error if any component is not restorable.
  // @return  A ShutDownStatus value indicating the result of the shutDown
  //          operation.
  //          kSuccess - successfully shut down and can be re-attached
  //          kFailed - failure due to outstanding active handle or error with
  //                    cache dir
  //          kSavedOnlyDRAM and kSavedOnlyNvmCache - partial content saved
  ShutDownStatus shutDown();
  // Functions that stop existing ones (if any) and create new workers
  // start pool rebalancer
  // @param interval            the period this worker fires.
  // @param strategy            rebalancing strategy
  // @param freeAllocThreshold  threshold for free-alloc-slab for picking victim
  // allocation class. free-alloc-slab is calculated by the number of free
  // allocation divided by the number of allocations in one slab. Only
  // allocation classes with a higher free-alloc-slab than the threshold would
  // be picked as a victim.
  //
  //
  bool startNewPoolRebalancer(std::chrono::milliseconds interval,
                              std::shared_ptr<RebalanceStrategy> strategy,
                              unsigned int freeAllocThreshold);
  // start pool resizer
  // @param interval                the period this worker fires.
  // @param poolResizeSlabsPerIter  maximum number of slabs each pool may remove
  //                                in resizing.
  // @param strategy                resizing strategy
  bool startNewPoolResizer(std::chrono::milliseconds interval,
                           unsigned int poolResizeSlabsPerIter,
                           std::shared_ptr<RebalanceStrategy> strategy);
  // start pool optimizer
  // @param regularInterval         the period for optimizing regular cache
  // @param ccacheInterval          the period for optimizing compact cache
  // @param strategy                pool optimizing strategy
  // @param ccacheStepSizePercent   the percentage number that controls the size
  //                                of each movement in a compact cache
  //                                optimization.
  bool startNewPoolOptimizer(std::chrono::seconds regularInterval,
                             std::chrono::seconds ccacheInterval,
                             std::shared_ptr<PoolOptimizeStrategy> strategy,
                             unsigned int ccacheStepSizePercent);
  // start memory monitor
  // @param memMonitorMode                  memory monitor mode
  // @param interval                        the period this worker fires
  // @param memAdvisePercentPerIter         Percentage of
  //                                        upperLimitGB-lowerLimitGB to be
  //                                        advised every poll period. This
  //                                        governs the rate of advise
  // @param memReclaimPercentPerIter        Percentage of
  //                                        upperLimitGB-lowerLimitGB to be
  //                                        reclaimed every poll period. This
  //                                        governs the rate of reclaim
  // @param memLowerLimit                   The lower limit of resident memory
  //                                        in GBytes
  //                                        that triggers reclaiming of
  //                                        previously advised away of memory
  //                                        from cache
  // @param memUpperLimit                   The upper limit of resident memory
  //                                         in GBytes
  //                                        that triggers advising of memory
  //                                        from cache
  // @param memMaxAdvisePercent             Maximum percentage of item cache
  //                                        limit that
  //                                        can be advised away before advising
  //                                        is disabled leading to a probable
  //                                        OOM.
  // @param strategy                        strategy to find an allocation class
  //                                        to release slab from
  // @param reclaimRateLimitWindowSecs      specifies window in seconds over
  //                                        which free/resident memory values
  //                                        are tracked to determine rate of
  //                                        change to rate limit reclaim
  bool startNewMemMonitor(MemoryMonitor::Mode memMonitorMode,
                          std::chrono::milliseconds interval,
                          unsigned int memAdvisePercentPerIter,
                          unsigned int memReclaimPercentPerIter,
                          unsigned int memLowerLimitGB,
                          unsigned int memUpperLimitGB,
                          unsigned int memMaxAdvisePercent,
                          std::shared_ptr<RebalanceStrategy> strategy,
                          std::chrono::seconds reclaimRateLimitWindowSecs);
  // start memory monitor
  // @param interval                        the period this worker fires
  // @param config                          memory monitoring config
  // @param strategy                        strategy to find an allocation class
  //                                        to release slab from
  bool startNewMemMonitor(std::chrono::milliseconds interval,
                          MemoryMonitor::Config config,
                          std::shared_ptr<RebalanceStrategy> strategy);
  // start reaper
  // @param interval                the period this worker fires
  // @param reaperThrottleConfig    throttling config
  bool startNewReaper(std::chrono::milliseconds interval,
                      util::Throttler::Config reaperThrottleConfig);
  // Stop existing workers with a timeout
  bool stopPoolRebalancer(std::chrono::seconds timeout = std::chrono::seconds{
                              0});
  bool stopPoolResizer(std::chrono::seconds timeout = std::chrono::seconds{0});
  bool stopPoolOptimizer(std::chrono::seconds timeout = std::chrono::seconds{
                             0});
  bool stopMemMonitor(std::chrono::seconds timeout = std::chrono::seconds{0});
  bool stopReaper(std::chrono::seconds timeout = std::chrono::seconds{0});
  // Set pool optimization to either true or false
  //
  // @param poolId The ID of the pool to optimize
  void setPoolOptimizerFor(PoolId poolId, bool enableAutoResizing);
  // stop the background workers
  // returns true if all workers have been successfully stopped
  bool stopWorkers(std::chrono::seconds timeout = std::chrono::seconds{0});
  // get the allocation information for the specified memory address.
  // @throw std::invalid_argument if the memory does not belong to this
  //        cache allocator
  AllocInfo getAllocInfo(const void* memory) const {
    return allocator_->getAllocInfo(memory);
  }
  // return the ids for the set of existing pools in this cache.
  std::set<PoolId> getPoolIds() const override final {
    return allocator_->getPoolIds();
  }
  // return a list of pool ids that are backing compact caches. This includes
  // both attached and un-attached compact caches.
  std::set<PoolId> getCCachePoolIds() const override final;
  // return a list of pool ids for regular pools.
  std::set<PoolId> getRegularPoolIds() const override final;
  // return the pool with speicified id.
  const MemoryPool& getPool(PoolId pid) const override final {
    return allocator_->getPool(pid);
  }
  // calculate the number of slabs to be advised/reclaimed in each pool
  PoolAdviseReclaimData calcNumSlabsToAdviseReclaim() override final {
    auto regularPoolIds = getRegularPoolIds();
    return allocator_->calcNumSlabsToAdviseReclaim(regularPoolIds);
  }
  // update number of slabs to advise in the cache
  void updateNumSlabsToAdvise(int32_t numSlabsToAdvise) override final {
    allocator_->updateNumSlabsToAdvise(numSlabsToAdvise);
  }
  // returns a valid PoolId corresponding to the name or kInvalidPoolId if the
  // name is not a recognized pool
  PoolId getPoolId(folly::StringPiece name) const noexcept;
  // returns the pool's name by its poolId.
  std::string getPoolName(PoolId poolId) const {
    return allocator_->getPoolName(poolId);
  }
  // get stats related to all kinds of slab release events.
  SlabReleaseStats getSlabReleaseStats() const noexcept override final;
  // return the distribution of the keys in the cache. This is expensive to
  // compute at times even with caching. So use with caution.
  // TODO think of a way to abstract this since it only makes sense for
  // bucketed hashtables with chaining.
  using DistributionStats = typename AccessContainer::DistributionStats;
  DistributionStats getAccessContainerDistributionStats() const {
    return accessContainer_->getDistributionStats();
  }
  // Provide stats on the number of keys cached and the access container for
  // this cache.
  using AccessStats = typename AccessContainer::Stats;
  AccessStats getAccessContainerStats() const {
    return accessContainer_->getStats();
  }
  // returns the reaper stats
  ReaperStats getReaperStats() const {
    auto stats = reaper_ ? reaper_->getStats() : ReaperStats{};
    return stats;
  }
  // return the LruType of an item
  typename MMType::LruType getItemLruType(const Item& item) const;
  // return the recent slab release events for a pool for rebalancer, Resizer
  // and Memory monitor workers.
  AllSlabReleaseEvents getAllSlabReleaseEvents(PoolId pid) const override final;
  // get cache name
  const std::string getCacheName() const override final;
  // pool stats by pool id
  PoolStats getPoolStats(PoolId pid) const override final;
  // This can be expensive so it is not part of PoolStats
  PoolEvictionAgeStats getPoolEvictionAgeStats(
      PoolId pid, unsigned int slabProjectionLength) const override final;
  // return the cache's metadata
  CacheMetadata getCacheMetadata() const noexcept override final;
  // return the overall cache stats
  GlobalCacheStats getGlobalCacheStats() const override final;
  // return cache's memory usage stats
  CacheMemoryStats getCacheMemoryStats() const override final;
  // return the nvm cache stats map
  std::unordered_map<std::string, double> getNvmCacheStatsMap()
      const override final;
  // return the event tracker stats map
  std::unordered_map<std::string, uint64_t> getEventTrackerStatsMap()
      const override {
    std::unordered_map<std::string, uint64_t> eventTrackerStats;
    if (auto eventTracker = getEventTracker()) {
      eventTracker->getStats(eventTrackerStats);
    }
    return eventTrackerStats;
  }
  // Whether this cache allocator was created on shared memory.
  bool isOnShm() const noexcept { return isOnShm_; }
  // Whether NvmCache is currently enabled
  bool isNvmCacheEnabled() const noexcept {
    return nvmCache_ && nvmCache_->isEnabled();
  }
  // unix timestamp when the cache was created. If 0, the cache creation
  // time was not recorded from the older version of the binary.
  //
  // @return  time when the cache was created.
  time_t getCacheCreationTime() const noexcept { return cacheCreationTime_; }
  time_t getNVMCacheCreationTime() const {
    return nvmCacheState_.getCreationTime();
  }
  // Inspects the cache without changing its state.
  //
  // @param key     for the cache item
  // @return  std::pair<ItemHandle, ItemHandle> the first represents the state
  //          in the RAM and the second is a copy of the state in NVM
  std::pair<ItemHandle, ItemHandle> inspectCache(Key key);
  // blocks until the inflight operations are flushed to nvmcache. Used for
  // benchmarking when we want to load up the cache first with some data and
  // run the benchmarks after flushing.
  void flushNvmCache();
  // Dump the last N items for an evictable MM Container
  // @return  vector of the string of each item. Empty if nothing in LRU
  // @throw  std::invalid_argument if <pid, cid> does not exist
  std::vector<std::string> dumpEvictionIterator(PoolId pid,
                                                ClassId cid,
                                                size_t numItems = 10);
  // returns the current count of the active handles that are handed out
  // through the API. This also includes the handles that are maintained by
  // the iterator and internal rebalancing threads.
  int64_t getNumActiveHandles() const;
  // returns the current count of handle at an given time for this thread. If
  // the threads do not transfer handles between them, the caller can rely on
  // this being the current active outstanding handles. If handles can be
  // transferred, then the caller needs to take snapshot of the count before
  // and after and make sure that the count reflects any transfer.
  //
  // TODO (sathya) wrap this into a guard object
  //
  // @return  the current handle count. If handles are never transferred
  //          between threads, this will always be >=0. If handles are
  //          transferred, this can be negative  on threads that give away
  //          handles and always non zero on threads that acquire a handle from
  //          another thread.
  int64_t getHandleCountForThread() const;
  // (Deprecated) reset and adjust handle count for the current thread.
  // Please do not use this API as it will be moved to a private function.
  void resetHandleCountForThread_private();
  void adjustHandleCountForThread_private(int64_t delta);
  // madvise(MADV_DODUMP) all recently accessed items.
  // this function is intended for signal handler which can mprotect other
  // code. Hence, it is  inlined to make sure that the code lives in the
  // caller's text segment
  void FOLLY_ALWAYS_INLINE madviseRecentlyAccessedItems() const {
    for (const auto& tlring : ring_.accessAllThreads()) {
      tlring.madviseItems();
    }
  }
  // Mark the item as dirty and enqueue for deletion from nvmcache
  // @param item         item to invalidate.
  void invalidateNvm(Item& item);
  // Attempts to clean up left-over shared memory from preivous instance of
  // cachelib cache for the cache directory. If there are other processes
  // using the same directory, we don't touch it. If the directory is not
  // present, we do our best to clean up based on what is possible.
  // It is hard to determine if we actually cleaned up something.
  //
  // returns true if there was no error in trying to cleanup the segment
  // because another process was attached. False if the user tried to clean up
  // and the cache was actually attached.
  static bool cleanupStrayShmSegments(const std::string& cacheDir, bool posix);
  // gives a relative offset to a pointer within the cache.
  uint64_t getItemPtrAsOffset(const void* ptr);
  // this ensures that we dont introduce any more hidden fields like vtable by
  // inheriting from the Hooks and their bool interface.
  static_assert((sizeof(typename MMType::template Hook<Item>) +
                 sizeof(typename AccessType::template Hook<Item>) +
                 sizeof(typename RefcountWithFlags::Value) + sizeof(uint32_t) +
                 sizeof(uint32_t) + sizeof(KAllocation)) == sizeof(Item),
                "vtable overhead");
  static_assert(32 == sizeof(Item), "item overhead is 32 bytes");
  // make sure there is no overhead in ChainedItem on top of a regular Item
  static_assert(sizeof(Item) == sizeof(ChainedItem),
                "Item and ChainedItem must be the same size");
  static_assert(std::is_standard_layout<KAllocation>::value,
                "KAllocation not standard layout");
  static_assert(std::is_standard_layout<ChainedItemPayload<CacheTrait>>::value,
                "ChainedItemPayload not standard layout");
// ensure that Item::alloc_ is the last member of Item. If not,
// Item::alloc_::data[0] will not work as a variable sized struct.
// gcc is strict about using offsetof in Item when Item has a default
// constructor, hence becoming a non-POD. Suppress -Winvalid-offsetof for
// this sake.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
  static_assert(sizeof(Item) - offsetof(Item, alloc_) == sizeof(Item::alloc_),
                "alloc_ incorrectly arranged");
#pragma GCC diagnostic pop
 private:
  // wrapper around Item's refcount and active handle tracking
  FOLLY_ALWAYS_INLINE void incRef(Item& it);
  FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef(Item& it);
  // drops the refcount and if needed, frees the allocation back to the memory
  // allocator and executes the necessary callbacks. no-op if it is nullptr.
  FOLLY_ALWAYS_INLINE void release(Item* it, bool isNascent);
  // This is the last step in item release. We also use this for the eviction
  // scenario where we have to do everything, but not release the allocation
  // to the allocator and instead recycle it for another new allocation. If
  // toRecycle is present, the item to be released and the item to be recycled
  // will be the same if we want to recycle a parent item. If we want to
  // recycle a child item, the parent would be the item to release and the
  // child will be the item to recycle. If it is a chained item, we simply
  // free it back.
  //
  // 1. It calls the remove callback
  // 2. It releases the item and chained allocation memory back to allocator
  //
  // An item should have its refcount dropped to zero, and unlinked from
  // access and mm container before being passed to this function.
  //
  // @param it    item to be released.
  // @param ctx   removal context
  // @param toRecycle  An item that will be recycled, this item is to be
  //                   ignored if it's found in the process of freeing
  //                   a chained allocation
  //
  // @return One of ReleaseRes. In all cases, _it_ is always released back to
  // the allocator unless an exception is thrown
  //
  // @throw   runtime_error if _it_ has pending refs or is not a regular item.
  //          runtime_error if parent->chain is broken
  enum class ReleaseRes {
    kRecycled,    // _it_ was released and _toRecycle_ was recycled
    kNotRecycled, // _it_ was released and _toRecycle_ was not recycled
    kReleased,    // toRecycle == nullptr and it was released
  };
  ReleaseRes releaseBackToAllocator(Item& it,
                                    RemoveContext ctx,
                                    bool nascent = false,
                                    const Item* toRecycle = nullptr);
  // acquires an handle on the item. returns an empty handle if it is null.
  // @param it    pointer to an item
  // @return ItemHandle   return a handle to this item
  // @throw std::overflow_error is the maximum item refcount is execeeded by
  //        creating this item handle.
  ItemHandle acquire(Item* it);
  // creates an item handle with wait context.
  ItemHandle createNvmCacheFillHandle() { return ItemHandle{*this}; }
  // acquires the wait context for the handle. This is used by NvmCache to
  // maintain a list of waiters
  std::shared_ptr<WaitContext<ReadHandle>> getWaitContext(
      ItemHandle& hdl) const {
    return hdl.getItemWaitContext();
  }
  using MMContainerPtr = std::unique_ptr<MMContainer>;
  using MMContainers =
      std::array<std::array<MMContainerPtr, MemoryAllocator::kMaxClasses>,
                 MemoryPoolManager::kMaxPools>;
  void createMMContainers(const PoolId pid, MMConfig config);
  // acquire the MMContainer corresponding to the the Item's class and pool.
  //
  // @return pointer to the MMContainer.
  // @throw  std::invalid_argument if the Item does not point to a valid
  // allocation from the memory allocator.
  MMContainer& getMMContainer(const Item& item) const noexcept;
  MMContainer& getMMContainer(PoolId pid, ClassId cid) const noexcept;
  // acquire the MMContainer for the give pool and class id and creates one
  // if it does not exist.
  //
  // @return pointer to a valid MMContainer that is initialized.
  MMContainer& getEvictableMMContainer(PoolId pid, ClassId cid) const noexcept;
  // create a new cache allocation. The allocation can be initialized
  // appropriately and made accessible through insert or insertOrReplace.
  // If the handle returned from this api is not passed on to
  // insert/insertOrReplace, the allocation gets destroyed when the handle
  // goes out of scope.
  //
  // @param id              the pool id for the allocation that was previously
  //                        created through addPool
  // @param key             the key for the allocation. This will be made a
  //                        part of the Item and be available through getKey().
  // @param size            the size of the allocation, exclusive of the key
  //                        size.
  // @param creationTime    Timestamp when this item was created
  // @param expiryTime      set an expiry timestamp for the item (0 means no
  //                        expiration time).
  //
  // @return      the handle for the item or an invalid handle(nullptr) if the
  //              allocation failed. Allocation can fail if one such
  //              allocation already exists or if we are out of memory and
  //              can not find an eviction. Handle must be destroyed *before*
  //              the instance of the CacheAllocator gets destroyed
  // @throw   std::invalid_argument if the poolId is invalid or the size
  //          requested is invalid or if the key is invalid(key.size() == 0 or
  //          key.size() > 255)
  WriteHandle allocateInternal(PoolId id,
                               Key key,
                               uint32_t size,
                               uint32_t creationTime,
                               uint32_t expiryTime);
  // Allocate a chained item
  //
  // The resulting chained item does not have a parent item and
  // will be freed once the handle is dropped
  //
  // The parent handle parameter here is mainly used to find the
  // correct pool to allocate memory for this chained item
  //
  // @param parent    handle to the cache item
  // @param size      the size for the chained allocation
  //
  // @return    handle to the chained allocation
  // @throw     std::invalid_argument if the size requested is invalid or
  //            if the item is invalid
  WriteHandle allocateChainedItemInternal(const ReadHandle& parent,
                                          uint32_t size);
  // Given an item and its parentKey, validate that the parentKey
  // corresponds to an item that's the parent of the supplied item.
  //
  // @param item       item that we want to get the parent handle for
  // @param parentKey  key of the item's parent
  //
  // @return  handle to the parent item if the validations pass
  //          otherwise, an empty ItemHandle is returned.
  //
  ItemHandle validateAndGetParentHandleForChainedMoveLocked(
      const ChainedItem& item, const Key& parentKey);
  // Given an existing item, allocate a new one for the
  // existing one to later be moved into.
  //
  // @param oldItem    the item we want to allocate a new item for
  //
  // @return  handle to the newly allocated item
  //
  WriteHandle allocateNewItemForOldItem(const Item& oldItem);
  // internal helper that grabs a refcounted handle to the item. This does
  // not record the access to reflect in the mmContainer.
  //
  // @param key   key to look up in the access container
  //
  // @return handle if item is found, nullptr otherwise
  //
  // @throw std::overflow_error is the maximum item refcount is execeeded by
  //        creating this item handle.
  ItemHandle findInternal(Key key) {
    // Note: this method can not be const because we need  a non-const
    // reference to create the ItemReleaser.
    return accessContainer_->find(key);
  }
  // look up an item by its key. This ignores the nvm cache and only does RAM
  // lookup.
  //
  // @param key         the key for lookup
  // @param mode        the mode of access for the lookup.
  //                    AccessMode::kRead or AccessMode::kWrite
  //
  // @return      the handle for the item or a handle to nullptr if the key does
  //              not exist.
  FOLLY_ALWAYS_INLINE ItemHandle findFastInternal(Key key, AccessMode mode);
  // look up an item by its key across the nvm cache as well if enabled.
  //
  // @param key         the key for lookup
  // @param mode        the mode of access for the lookup.
  //                    AccessMode::kRead or AccessMode::kWrite
  //
  // @return      the handle for the item or a handle to nullptr if the key does
  //              not exist.
  FOLLY_ALWAYS_INLINE ItemHandle findImpl(Key key, AccessMode mode);
  // look up an item by its key. This ignores the nvm cache and only does RAM
  // lookup.
  //
  // @param key         the key for lookup
  // @param mode        the mode of access for the lookup.
  //                    AccessMode::kRead or AccessMode::kWrite
  //
  // @return      the handle for the item or a handle to nullptr if the key does
  //              not exist.
  FOLLY_ALWAYS_INLINE ItemHandle findFastImpl(Key key, AccessMode mode);
  // Moves a regular item to a different slab. This should only be used during
  // slab release after the item's moving bit has been set. The user supplied
  // callback is responsible for copying the contents and fixing the semantics
  // of chained item.
  //
  // @param oldItem     Reference to the item being moved
  // @param newItemHdl  Reference to the handle of the new item being moved into
  //
  // @return true  If the move was completed, and the containers were updated
  //               successfully.
  bool moveRegularItem(Item& oldItem, ItemHandle& newItemHdl);
  // template class for viewAsChainedAllocs that takes either ReadHandle or
  // WriteHandle
  template <typename Handle, typename Iter>
  CacheChainedAllocs<CacheT, Handle, Iter> viewAsChainedAllocsT(
      const Handle& parent);
  // template class for convertToIOBuf that takes either ReadHandle or
  // WriteHandle
  template <typename Handle>
  folly::IOBuf convertToIOBufT(Handle& handle);
  // Moves a chained item to a different slab. This should only be used during
  // slab release after the item's moving bit has been set. The user supplied
  // callback is responsible for copying the contents and fixing the semantics
  // of chained item.
  //
  // Note: If we have successfully moved the old item into the new, the
  //       newItemHdl is reset and no longer usable by the caller.
  //
  // @param oldItem       Reference to the item being moved
  // @param newItemHdl    Reference to the handle of the new item being
  //                      moved into
  //
  // @return true  If the move was completed, and the containers were updated
  //               successfully.
  bool moveChainedItem(ChainedItem& oldItem, ItemHandle& newItemHdl);
  // Transfers the chain ownership from parent to newParent. Parent
  // will be unmarked as having chained allocations. Parent will not be null
  // after calling this API.
  //
  // Parent and NewParent must be valid handles to items with same key and
  // parent must have chained items and parent handle must be the only
  // outstanding handle for parent. New parent must be without any chained item
  // handles.
  //
  // Chained item lock for the parent's key needs to be held in exclusive mode.
  //
  // @param parent    the current parent of the chain we want to transfer
  // @param newParent the new parent for the chain
  //
  // @throw if any of the conditions for parent or newParent are not met.
  void transferChainLocked(ItemHandle& parent, ItemHandle& newParent);
  // replace a chained item in the existing chain. This needs to be called
  // with the chained item lock held exclusive
  //
  // @param oldItem  the item we are replacing in the chain
  // @param newItem  the item we are replacing it with
  // @param parent   the parent for the chain
  //
  // @return handle to the oldItem
  ItemHandle replaceChainedItemLocked(Item& oldItem,
                                      ItemHandle newItemHdl,
                                      const Item& parent);
  // Insert an item into MM container. The caller must hold a valid handle for
  // the item.
  //
  // @param  item  Item that we want to insert.
  //
  // @throw std::runtime_error if the handle is already in the mm container
  void insertInMMContainer(Item& item);
  // Removes an item from the corresponding MMContainer if it is in the
  // container. The caller must hold a valid handle for the item.
  //
  // @param  item  Item that we want to remove.
  //
  // @return true   if the item is successfully removed
  //         false  if the item is not in MMContainer
  bool removeFromMMContainer(Item& item);
  // Replaces an item in the MMContainer with another item, at the same
  // position.
  //
  // @param oldItem   item being replaced
  // @param newItem   item to replace oldItem with
  //
  // @return true  If the replace was successful. Returns false if the
  //               destination item did not exist in the container, or if the
  //               source item already existed.
  bool replaceInMMContainer(Item& oldItem, Item& newItem);
  // Replaces an item in the MMContainer with another item, at the same
  // position. Or, if the two chained items belong to two different MM
  // containers, remove the old one from its MM container and add the new
  // one to its MM container.
  //
  // @param oldItem   item being replaced
  // @param newItem   item to replace oldItem with
  //
  // @return true  If the replace was successful. Returns false if the
  //               destination item did not exist in the container, or if the
  //               source item already existed.
  bool replaceChainedItemInMMContainer(Item& oldItem, Item& newItem);
  // Only replaces an item if it is accessible
  bool replaceIfAccessible(Item& oldItem, Item& newItem);
  // Inserts the allocated handle into the AccessContainer, making it
  // accessible for everyone. This needs to be the handle that the caller
  // allocated through _allocate_. If this call fails, the allocation will be
  // freed back when the handle gets out of scope in the caller.
  //
  // @param handle the handle for the allocation.
  // @param event AllocatorApiEvent that corresponds to the current operation.
  //              supported events are INSERT, corresponding to the client
  //              insert call, and INSERT_FROM_NVM, cooresponding to the insert
  //              call that happens when an item is promoted from NVM storage
  //              to memory.
  //
  // @return true if the handle was successfully inserted into the hashtable
  //         and is now accessible to everyone. False if there was an error.
  //
  // @throw std::invalid_argument if the handle is already accessible or invalid
  bool insertImpl(const WriteHandle& handle, AllocatorApiEvent event);
  // Removes an item from the access container and MM container.
  //
  // @param it               Item to remove
  // @param tombstone        A tombstone for nvm::remove job created by
  //                         nvm::createDeleteTombStone, can be empty if nvm is
  //                         not enable, or removeFromNvm is false
  // @param removeFromNvm    if true clear key from nvm
  // @param recordApiEvent   should we record API event for this operation.
  RemoveRes removeImpl(Item& it,
                       DeleteTombStoneGuard tombstone,
                       bool removeFromNvm = true,
                       bool recordApiEvent = true);
  // Implementation to find a suitable eviction from the container. The
  // two parameters together identify a single container.
  //
  // @param  pid  the id of the pool to look for evictions inside
  // @param  cid  the id of the class to look for evictions inside
  // @return An evicted item or nullptr  if there is no suitable candidate.
  Item* findEviction(PoolId pid, ClassId cid);
  using EvictionIterator = typename MMContainer::Iterator;
  // Advance the current iterator and try to evict a regular item
  //
  // @param  mmContainer  the container to look for evictions.
  // @param  itr          iterator holding the item
  //
  // @return  valid handle to regular item on success. This will be the last
  //          handle to the item. On failure an empty handle.
  ItemHandle advanceIteratorAndTryEvictRegularItem(MMContainer& mmContainer,
                                                   EvictionIterator& itr);
  // Advance the current iterator and try to evict a chained item
  // Iterator may also be reset during the course of this function
  //
  // @param  itr          iterator holding the item
  //
  // @return  valid handle to the parent item on success. This will be the last
  //          handle to the item
  ItemHandle advanceIteratorAndTryEvictChainedItem(EvictionIterator& itr);
  // Deserializer CacheAllocatorMetadata and verify the version
  //
  // @param  deserializer   Deserializer object
  // @throws                runtime_error
  serialization::CacheAllocatorMetadata deserializeCacheAllocatorMetadata(
      Deserializer& deserializer);
  MMContainers deserializeMMContainers(
      Deserializer& deserializer,
      const typename Item::PtrCompressor& compressor);
  // Create a copy of empty MMContainers according to the configs of
  // mmContainers_ This function is used when serilizing for persistence for the
  // reason of backward compatibility. A copy of empty MMContainers from
  // mmContainers_ will be created and serialized as unevictable mm containers
  // and written to metadata so that previous CacheLib versions can restore from
  // such a serialization. This function will be removed in the next version.
  MMContainers createEmptyMMContainers();
  unsigned int reclaimSlabs(PoolId id, size_t numSlabs) final {
    return allocator_->reclaimSlabsAndGrow(id, numSlabs);
  }
  FOLLY_ALWAYS_INLINE EventTracker* getEventTracker() const {
    return config_.eventTracker.get();
  }
  // Releases a slab from a pool into its corresponding memory pool
  // or back to the slab allocator, depending on SlabReleaseMode.
  //  SlabReleaseMode::kRebalance -> back to the pool
  //  SlabReleaseMode::kResize -> back to the slab allocator
  //
  // @param pid        Pool that will make make one slab available
  // @param cid        Class that will release a slab back to its pool
  //                   or slab allocator
  // @param mode       the mode for slab release (rebalance/resize)
  // @param hint       hint referring to the slab. this can be an allocation
  //                   that the user knows to exist in the slab. If this is
  //                   nullptr, a random slab is selected from the pool and
  //                   allocation class.
  //
  // @throw std::invalid_argument if the hint is invalid or if the pid or cid
  //        is invalid.
  // @throw std::runtime_error if fail to release a slab due to internal error
  void releaseSlab(PoolId pid,
                   ClassId cid,
                   SlabReleaseMode mode,
                   const void* hint = nullptr) final;
  // Releasing a slab from this allocation class id and pool id. The release
  // could be for a pool resizing or allocation class rebalancing.
  //
  // All active allocations will be evicted in the process.
  //
  // This function will be blocked until a slab is freed
  //
  // @param pid        Pool that will make one slab available
  // @param victim     Class that will release a slab back to its pool
  //                   or slab allocator or a receiver if defined
  // @param receiver   Class that will receive when the mode is set to rebalance
  // @param mode  the mode for slab release (rebalance/resize)
  //              the mode must be rebalance if an valid receiver is specified
  // @param hint  hint referring to the slab. this can be an allocation
  //              that the user knows to exist in the slab. If this is
  //              nullptr, a random slab is selected from the pool and
  //              allocation class.
  //
  // @throw std::invalid_argument if the hint is invalid or if the pid or cid
  //        is invalid. Or if the mode is set to kResize but the receiver is
  //        also specified. Receiver class id can only be specified if the mode
  //        is set to kRebalance.
  // @throw std::runtime_error if fail to release a slab due to internal error
  void releaseSlab(PoolId pid,
                   ClassId victim,
                   ClassId receiver,
                   SlabReleaseMode mode,
                   const void* hint = nullptr) final;
  // @param releaseContext  slab release context
  void releaseSlabImpl(const SlabReleaseContext& releaseContext);
  // @return  true when successfully marked as moving,
  //          fasle when this item has already been freed
  bool markMovingForSlabRelease(const SlabReleaseContext& ctx,
                                void* alloc,
                                util::Throttler& throttler);
  // "Move" (by copying) the content in this item to another memory
  // location by invoking the move callback.
  //
  //
  // @param ctx         slab release context
  // @param item        old item to be moved elsewhere
  // @param throttler   slow this function down as not to take too much cpu
  //
  // @return    true  if the item has been moved
  //            false if we have exhausted moving attempts
  bool moveForSlabRelease(const SlabReleaseContext& ctx,
                          Item& item,
                          util::Throttler& throttler);
  // "Move" (by copying) the content in this item to another memory
  // location by invoking the move callback.
  //
  // @param item         old item to be moved elsewhere
  // @param newItemHdl   handle of new item to be moved into
  //
  // @return    true  if the item has been moved
  //            false if we have exhausted moving attempts
  bool tryMovingForSlabRelease(Item& item, ItemHandle& newItemHdl);
  // Evict an item from access and mm containers and
  // ensure it is safe for freeing.
  //
  // @param ctx         slab release context
  // @param item        old item to be moved elsewhere
  // @param throttler   slow this function down as not to take too much cpu
  void evictForSlabRelease(const SlabReleaseContext& ctx,
                           Item& item,
                           util::Throttler& throttler);
  // Helper function to evict a normal item for slab release
  //
  // @return last handle for corresponding to item on success. empty handle on
  // failure. caller can retry if needed.
  ItemHandle evictNormalItemForSlabRelease(Item& item);
  // Helper function to evict a child item for slab release
  // As a side effect, the parent item is also evicted
  //
  // @return  last handle to the parent item of the child on success. empty
  // handle on failure. caller can retry.
  ItemHandle evictChainedItemForSlabRelease(ChainedItem& item);
  // Helper function to remove a item if expired.
  //
  // @return true if it item expire and removed successfully.
  bool removeIfExpired(const ItemHandle& handle);
  // exposed for the Reaper to iterate through the memory and find items to
  // reap under the super charged mode. This is faster if there are lots of
  // items in cache and only a small fraction of them are expired at any given
  // time.
  template <typename Fn>
  void traverseAndExpireItems(Fn&& f) {
    // The intent here is to scan the memory to identify candidates for reaping
    // without holding any locks. Candidates that are identified as potential
    // ones are further processed by holding the right synchronization
    // primitives. So we consciously exempt ourselves here from TSAN data race
    // detection.
    folly::annotate_ignore_thread_sanitizer_guard g(__FILE__, __LINE__);
    allocator_->forEachAllocation(std::forward<Fn>(f));
  }
  // returns true if nvmcache is enabled and we should write this item to
  // nvmcache.
  bool shouldWriteToNvmCache(const Item& item);
  // (this should be only called when we're the only thread accessing item)
  // returns true if nvmcache is enabled and we should write this item.
  bool shouldWriteToNvmCacheExclusive(const Item& item);
  // Serialize the metadata for the cache into an IOBUf. The caller can now
  // use this to serialize into a serializer by estimating the size and
  // calling writeToBuffer.
  folly::IOBufQueue saveStateToIOBuf();
  static typename MemoryAllocator::Config getAllocatorConfig(
      const Config& config) {
    return MemoryAllocator::Config{
        config.defaultAllocSizes.empty()
            ? util::generateAllocSizes(
                  config.allocationClassSizeFactor,
                  config.maxAllocationClassSize,
                  config.minAllocationClassSize,
                  config.reduceFragmentationInAllocationClass)
            : config.defaultAllocSizes,
        config.enableZeroedSlabAllocs, config.disableFullCoredump,
        config.lockMemory};
  }
  // starts one of the cache workers passing the current instance and the args
  template <typename T, typename... Args>
  bool startNewWorker(folly::StringPiece name,
                      std::unique_ptr<T>& worker,
                      std::chrono::milliseconds interval,
                      Args&&... args);
  // stops one of the workers belonging to this instance.
  template <typename T>
  bool stopWorker(folly::StringPiece name,
                  std::unique_ptr<T>& worker,
                  std::chrono::seconds timeout = std::chrono::seconds{0});
  std::unique_ptr<MemoryAllocator> createNewMemoryAllocator();
  std::unique_ptr<MemoryAllocator> restoreMemoryAllocator();
  std::unique_ptr<CCacheManager> restoreCCacheManager();
  PoolIds filterCompactCachePools(const PoolIds& poolIds) const;
  // returns a list of pools excluding compact cache pools that are over the
  // limit
  PoolIds getRegularPoolIdsForResize() const override final;
  // return whether a pool participates in auto-resizing
  bool autoResizeEnabledForPool(PoolId) const override final;
  // resize all compact caches to their configured size
  void resizeCompactCaches() override;
  std::map<std::string, std::string> serializeConfigParams()
      const override final {
    return config_.serialize();
  }
  typename Item::PtrCompressor createPtrCompressor() const {
    return allocator_->createPtrCompressor<Item>();
  }
  // helper utility to throttle and optionally log.
  static void throttleWith(util::Throttler& t, std::function<void()> fn);
  // Write the item to nvm cache if enabled. This is called on eviction.
  void pushToNvmCache(const Item& item);
  // Test utility function to move a key from ram into nvmcache.
  bool pushToNvmCacheFromRamForTesting(Key key);
  // Test utility functions to remove things from individual caches.
  bool removeFromRamForTesting(Key key);
  void removeFromNvmForTesting(Key key);
  // @param dramCacheAttached   boolean indicating if the dram cache was
  //                            restored from previous state
  void initCommon(bool dramCacheAttached);
  void initNvmCache(bool dramCacheAttached);
  void initWorkers();
  std::optional<bool> saveNvmCache();
  void saveRamCache();
  static bool itemMovingPredicate(const Item& item) {
    return item.getRefCount() == 0;
  }
  static bool itemEvictionPredicate(const Item& item) {
    return item.getRefCount() == 0 && !item.isMoving();
  }
  static bool itemExpiryPredicate(const Item& item) {
    return item.getRefCount() == 1 && item.isExpired();
  }
  static bool parentEvictForSlabReleasePredicate(const Item& item) {
    return item.getRefCount() == 1 && !item.isMoving();
  }
  std::unique_ptr<Deserializer> createDeserializer();
  // Execute func on each item. `func` can throw exception but must ensure
  // the item remains in a valid state
  //
  // @param item      Parent item
  // @param func      Function that gets executed on each chained item
  void forEachChainedItem(const Item& item,
                          std::function<void(ChainedItem&)> func);
  // @param item    Record the item has been accessed in its mmContainer
  // @param mode    the mode of access
  // @param stats   stats object to avoid a thread local lookup.
  // @return true   if successfully recorded in MMContainer
  bool recordAccessInMMContainer(Item& item, AccessMode mode);
  ItemHandle findChainedItem(const Item& parent) const;
  // Get the thread local version of the Stats
  detail::Stats& stats() const noexcept { return stats_; }
  void initStats();
  // return a read-only iterator to the item's chained allocations. The order of
  // iteration on the item will be LIFO of the addChainedItem calls.
  folly::Range<ChainedItemIter> viewAsChainedAllocsRange(
      const Item& parent) const;
  // updates the maxWriteRate for DynamicRandomAdmissionPolicy
  // returns true if update successfully
  //         false if no AdmissionPolicy is set or it is not DynamicRandom
  bool updateMaxRateForDynamicRandomAP(uint64_t maxRate) {
    return nvmCache_ ? nvmCache_->updateMaxRateForDynamicRandomAP(maxRate)
                     : false;
  }
  // BEGIN private members
  // Whether the memory allocator for this cache allocator was created on shared
  // memory. The hash table, chained item hash table etc is also created on
  // shared memory except for temporary shared memory mode when they're created
  // on heap.
  const bool isOnShm_{false};
  const Config config_{};
  // Manages the temporary shared memory segment for memory allocator that
  // is not persisted when cache process exits.
  std::unique_ptr<TempShmMapping> tempShm_;
  std::unique_ptr<ShmManager> shmManager_;
  // Deserialize data to restore cache allocator. Used only while attaching to
  // existing shared memory.
  std::unique_ptr<Deserializer> deserializer_;
  // used only while attaching to existing shared memory.
  serialization::CacheAllocatorMetadata metadata_{};
  // configs for the access container and the mm container.
  const MMConfig mmConfig_{};
  // the memory allocator for allocating out of the available memory.
  std::unique_ptr<MemoryAllocator> allocator_;
  // compact cache allocator manager
  std::unique_ptr<CCacheManager> compactCacheManager_;
  // compact cache instances reside here when user "add" or "attach" compact
  // caches. The lifetime is tied to CacheAllocator.
  // The bool is an indicator for whether the compact cache participates in
  // size optimization in PoolOptimizer
  std::unordered_map<PoolId, std::unique_ptr<ICompactCache>> compactCaches_;
  // check whether a pool is enabled for optimizing
  std::array<std::atomic<bool>, MemoryPoolManager::kMaxPools>
      optimizerEnabled_{};
  // ptr compressor
  typename Item::PtrCompressor compressor_;
  // Lock to synchronize addition of a new pool and its resizing/rebalancing
  folly::SharedMutex poolsResizeAndRebalanceLock_;
  // container for the allocations which are currently being memory managed by
  // the cache allocator.
  // we need mmcontainer per allocator pool/allocation class.
  MMContainers mmContainers_;
  // container that is used for accessing the allocations by their key.
  std::unique_ptr<AccessContainer> accessContainer_;
  // container that is used for accessing the chained allocation
  std::unique_ptr<AccessContainer> chainedItemAccessContainer_{nullptr};
  friend ChainedAllocs;
  friend WritableChainedAllocs;
  // ensure any modification to a chain of chained items are synchronized
  using ChainedItemLock = facebook::cachelib::SharedMutexBuckets;
  ChainedItemLock chainedItemLocks_;
  // nvmCache
  std::unique_ptr<NvmCacheT> nvmCache_;
  // rebalancer for the pools
  std::unique_ptr<PoolRebalancer> poolRebalancer_;
  // resizer for the pools.
  std::unique_ptr<PoolResizer> poolResizer_;
  // automatic arena resizing i.e. pool optimization
  std::unique_ptr<PoolOptimizer> poolOptimizer_;
  // free memory monitor
  std::unique_ptr<MemoryMonitor> memMonitor_;
  // check whether a pool is a slabs pool
  std::array<bool, MemoryPoolManager::kMaxPools> isCompactCachePool_{};
  // lock to serilize access of isCompactCachePool_ array, including creation of
  // compact cache pools
  folly::SharedMutex compactCachePoolsLock_;
  // mutex protecting the creation and destruction of workers poolRebalancer_,
  // poolResizer_, poolOptimizer_, memMonitor_, reaper_
  mutable std::mutex workersMutex_;
  // time when the ram cache was first created
  const time_t cacheCreationTime_{0};
  // thread local accumulation of handle counts
  mutable util::FastStats<int64_t> handleCount_{};
  mutable detail::Stats stats_{};
  // allocator's items reaper to evict expired items in bg checking
  std::unique_ptr<Reaper<CacheT>> reaper_;
  class DummyTlsActiveItemRingTag {};
  folly::ThreadLocal<TlsActiveItemRing, DummyTlsActiveItemRingTag> ring_;
  // state for the nvmcache
  NvmCacheState nvmCacheState_;
  // admission policy for nvmcache
  std::shared_ptr<NvmAdmissionPolicy<CacheT>> nvmAdmissionPolicy_;
  // indicates if the shutdown of cache is in progress or not
  std::atomic<bool> shutDownInProgress_{false};
  // END private members
  // Make this friend to give access to acquire and release
  friend ReadHandle;
  friend ReaperAPIWrapper<CacheT>;
  friend class CacheAPIWrapperForNvm<CacheT>;
  friend class FbInternalRuntimeUpdateWrapper<CacheT>;
  friend class CacheAllocatorFindApiWrapper<CacheT>;
  // tests
  friend class facebook::cachelib::tests::NvmCacheTest;
  template <typename AllocatorT>
  friend class facebook::cachelib::tests::BaseAllocatorTest;
  template <typename AllocatorT>
  friend class facebook::cachelib::tests::AllocatorHitStatsTest;
  template <typename AllocatorT>
  friend class facebook::cachelib::tests::AllocatorResizeTest;
  template <typename AllocatorT>
  friend class facebook::cachelib::tests::PoolOptimizeStrategyTest;
  friend class facebook::cachelib::tests::NvmAdmissionPolicyTest;
  friend class facebook::cachelib::tests::CacheAllocatorTestWrapper;
  friend class facebook::cachelib::tests::PersistenceCache;
  template <typename AllocatorT>
  friend class facebook::cachelib::tests::FixedSizeArrayTest;
  template <typename AllocatorT>
  friend class facebook::cachelib::tests::MapTest;
  // benchmarks
  template <typename Allocator>
  friend class facebook::cachelib::cachebench::Cache;
  friend class facebook::cachelib::cachebench::tests::CacheTest;
  friend void lookupCachelibBufferManagerOnly();
  friend void lookupCachelibMap();
  friend void benchCachelibMap();
  friend void benchCachelibRangeMap();
  // objectCache
  template <typename CacheDescriptor, typename AllocatorRes>
  friend class facebook::cachelib::objcache::ObjectCache;
  friend class GET_DECORATED_CLASS_NAME(objcache::test,
                                        ObjectCache,
                                        ObjectHandleInvalid);
};
} // namespace cachelib
} // namespace facebook
#include "cachelib/allocator/CacheAllocator-inl.h"
namespace facebook {
namespace cachelib {
// Declare templates ahead of use to reduce compilation time
extern template class CacheAllocator<LruCacheTrait>;
extern template class CacheAllocator<LruCacheWithSpinBucketsTrait>;
extern template class CacheAllocator<Lru2QCacheTrait>;
extern template class CacheAllocator<TinyLFUCacheTrait>;
// CacheAllocator with an LRU eviction policy
// LRU policy can be configured to act as a segmented LRU as well
using LruAllocator = CacheAllocator<LruCacheTrait>;
using LruAllocatorSpinBuckets = CacheAllocator<LruCacheWithSpinBucketsTrait>;
// CacheAllocator with 2Q eviction policy
// Hot, Warm, Cold queues are maintained
// Item Life Time:
//  0. On access, each item is promoted to the head of its current
//  queue
//  1. first enter Hot queue
//  2. if accessed while in Hot, item will qualify entry to Warm queue
//     otherwise, item will enter cold queue
//  3. items in cold queue are evicted to make room for new items
using Lru2QAllocator = CacheAllocator<Lru2QCacheTrait>;
// CacheAllocator with Tiny LFU eviction policy
// It has a window initially to gauage the frequency of accesses of newly
// inserted items. And eventually it will onl admit items that are accessed
// beyond a threshold into the warm cache.
using TinyLFUAllocator = CacheAllocator<TinyLFUCacheTrait>;
} // namespace cachelib
} // namespace facebook