cachelib/allocator/CacheItem-inl.h (391 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.
 */
// CacheItem implementations.
namespace facebook {
namespace cachelib {
template <typename CacheTrait>
uint32_t CacheItem<CacheTrait>::getRequiredSize(Key key,
                                                uint32_t size) noexcept {
  const uint64_t requiredSize =
      static_cast<uint64_t>(size) + key.size() + sizeof(Item);
  XDCHECK_LE(requiredSize,
             static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()));
  if (requiredSize >
      static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
    return 0;
  }
  return static_cast<uint32_t>(requiredSize);
}
template <typename CacheTrait>
uint64_t CacheItem<CacheTrait>::getRefcountMax() noexcept {
  return RefcountWithFlags::kAccessRefMask;
}
template <typename CacheTrait>
CacheItem<CacheTrait>::CacheItem(Key key,
                                 uint32_t size,
                                 uint32_t creationTime,
                                 uint32_t expiryTime)
    : creationTime_(creationTime), expiryTime_(expiryTime), alloc_(key, size) {}
template <typename CacheTrait>
CacheItem<CacheTrait>::CacheItem(Key key, uint32_t size, uint32_t creationTime)
    : CacheItem(key, size, creationTime, 0 /* expiryTime_ */) {}
template <typename CacheTrait>
const typename CacheItem<CacheTrait>::Key CacheItem<CacheTrait>::getKey()
    const noexcept {
  return alloc_.getKey();
}
template <typename CacheTrait>
const void* CacheItem<CacheTrait>::getMemory() const noexcept {
  return getMemoryInternal();
}
template <typename CacheTrait>
void* CacheItem<CacheTrait>::getMemory() noexcept {
  return getMemoryInternal();
}
template <typename CacheTrait>
void* CacheItem<CacheTrait>::getMemoryInternal() const noexcept {
  if (isChainedItem()) {
    return asChainedItem().getMemory();
  } else {
    return alloc_.getMemory();
  }
}
template <typename CacheTrait>
uint32_t CacheItem<CacheTrait>::getOffsetForMemory() const noexcept {
  return reinterpret_cast<uintptr_t>(getMemory()) -
         reinterpret_cast<uintptr_t>(this);
}
template <typename CacheTrait>
uint32_t CacheItem<CacheTrait>::getSize() const noexcept {
  if (isChainedItem()) {
    return asChainedItem().getSize();
  } else {
    return alloc_.getSize();
  }
}
template <typename CacheTrait>
uint32_t CacheItem<CacheTrait>::getExpiryTime() const noexcept {
  return expiryTime_;
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isExpired() const noexcept {
  thread_local uint32_t staleTime = 0;
  if (expiryTime_ == 0) {
    return false;
  }
  if (expiryTime_ < staleTime) {
    return true;
  }
  uint32_t currentTime = static_cast<uint32_t>(util::getCurrentTimeSec());
  if (currentTime != staleTime) {
    staleTime = currentTime;
  }
  return expiryTime_ < currentTime;
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isExpired(uint32_t currentTimeSec) const noexcept {
  return (expiryTime_ > 0 && expiryTime_ < currentTimeSec);
}
template <typename CacheTrait>
uint32_t CacheItem<CacheTrait>::getCreationTime() const noexcept {
  return creationTime_;
}
template <typename CacheTrait>
std::chrono::seconds CacheItem<CacheTrait>::getConfiguredTTL() const noexcept {
  return std::chrono::seconds(expiryTime_ > 0 ? expiryTime_ - creationTime_
                                              : 0);
}
template <typename CacheTrait>
uint32_t CacheItem<CacheTrait>::getLastAccessTime() const noexcept {
  return mmHook_.getUpdateTime();
}
template <typename CacheTrait>
std::string CacheItem<CacheTrait>::toString() const {
  if (isChainedItem()) {
    return asChainedItem().toString();
  } else {
    return folly::sformat(
        "item: "
        "memory={}:raw-ref={}:size={}:key={}:hex-key={}:"
        "isInMMContainer={}:isAccessible={}:isMoving={}:references={}:ctime={}:"
        "expTime={}:updateTime={}:isNvmClean={}:isNvmEvicted={}:hasChainedItem="
        "{}",
        this, getRefCountAndFlagsRaw(), getSize(),
        folly::humanify(getKey().str()), folly::hexlify(getKey()),
        isInMMContainer(), isAccessible(), isMoving(), getRefCount(),
        getCreationTime(), getExpiryTime(), getLastAccessTime(), isNvmClean(),
        isNvmEvicted(), hasChainedItem());
  }
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::changeKey(Key key) {
  if (!isChainedItem()) {
    throw std::invalid_argument("Item is not chained type");
  }
  alloc_.changeKey(key);
  XDCHECK_EQ(key, getKey());
}
template <typename CacheTrait>
RefcountWithFlags::Value CacheItem<CacheTrait>::getRefCount() const noexcept {
  return ref_.getAccessRef();
}
template <typename CacheTrait>
RefcountWithFlags::Value CacheItem<CacheTrait>::getRefCountAndFlagsRaw()
    const noexcept {
  return ref_.getRaw();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isDrained() const noexcept {
  return ref_.isDrained();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isExclusive() const noexcept {
  return ref_.isExclusive();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::markAccessible() noexcept {
  ref_.markAccessible();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::unmarkAccessible() noexcept {
  ref_.unmarkAccessible();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::markInMMContainer() noexcept {
  ref_.markInMMContainer();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::unmarkInMMContainer() noexcept {
  ref_.unmarkInMMContainer();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isAccessible() const noexcept {
  return ref_.isAccessible();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isInMMContainer() const noexcept {
  return ref_.isInMMContainer();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::markMoving() noexcept {
  return ref_.markMoving();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::unmarkMoving() noexcept {
  ref_.unmarkMoving();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isMoving() const noexcept {
  return ref_.isMoving();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isOnlyMoving() const noexcept {
  return ref_.isOnlyMoving();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::markNvmClean() noexcept {
  ref_.markNvmClean();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::unmarkNvmClean() noexcept {
  ref_.unmarkNvmClean();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isNvmClean() const noexcept {
  return ref_.isNvmClean();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::markNvmEvicted() noexcept {
  ref_.markNvmEvicted();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::unmarkNvmEvicted() noexcept {
  ref_.unmarkNvmEvicted();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isNvmEvicted() const noexcept {
  return ref_.isNvmEvicted();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::markIsChainedItem() noexcept {
  XDCHECK(!hasChainedItem());
  ref_.markIsChainedItem();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::unmarkIsChainedItem() noexcept {
  XDCHECK(!hasChainedItem());
  ref_.unmarkIsChainedItem();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::markHasChainedItem() noexcept {
  XDCHECK(!isChainedItem());
  ref_.markHasChainedItem();
}
template <typename CacheTrait>
void CacheItem<CacheTrait>::unmarkHasChainedItem() noexcept {
  XDCHECK(!isChainedItem());
  ref_.unmarkHasChainedItem();
}
template <typename CacheTrait>
typename CacheItem<CacheTrait>::ChainedItem&
CacheItem<CacheTrait>::asChainedItem() noexcept {
  return *static_cast<ChainedItem*>(this);
}
template <typename CacheTrait>
const typename CacheItem<CacheTrait>::ChainedItem&
CacheItem<CacheTrait>::asChainedItem() const noexcept {
  return *static_cast<const ChainedItem*>(this);
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::isChainedItem() const noexcept {
  return ref_.isChainedItem();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::hasChainedItem() const noexcept {
  return ref_.hasChainedItem();
}
template <typename CacheTrait>
template <typename RefcountWithFlags::Flags flagBit>
void CacheItem<CacheTrait>::setFlag() noexcept {
  ref_.template setFlag<flagBit>();
}
template <typename CacheTrait>
template <typename RefcountWithFlags::Flags flagBit>
void CacheItem<CacheTrait>::unSetFlag() noexcept {
  ref_.template unSetFlag<flagBit>();
}
template <typename CacheTrait>
template <typename RefcountWithFlags::Flags flagBit>
bool CacheItem<CacheTrait>::isFlagSet() const noexcept {
  return ref_.template isFlagSet<flagBit>();
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::updateExpiryTime(uint32_t expiryTimeSecs) noexcept {
  // check for moving to make sure we are not updating the expiry time while at
  // the same time re-allocating the item with the old state of the expiry time
  // in moveRegularItem(). See D6852328
  if (isMoving() || !isInMMContainer() || isChainedItem()) {
    return false;
  }
  // attempt to atomically update the value of expiryTime
  while (true) {
    uint32_t currExpTime = expiryTime_;
    if (__sync_bool_compare_and_swap(&expiryTime_, currExpTime,
                                     expiryTimeSecs)) {
      return true;
    }
  }
}
template <typename CacheTrait>
bool CacheItem<CacheTrait>::extendTTL(std::chrono::seconds ttl) noexcept {
  return updateExpiryTime(util::getCurrentTimeSec() + ttl.count());
}
// Chained items are chained in a single linked list. The payload of each
// chained item is chained to the next item.
template <typename CacheTrait>
class CACHELIB_PACKED_ATTR ChainedItemPayload {
 public:
  using ChainedItem = CacheChainedItem<CacheTrait>;
  using Item = CacheItem<CacheTrait>;
  using PtrCompressor = typename ChainedItem::PtrCompressor;
  // Pointer to the next chained allocation. initialize to nullptr.
  SListHook<Item> hook_{};
  // Payload
  mutable unsigned char data_[0];
  // Usable memory for this allocation. The caller is free to do whatever he
  // wants with it and needs to ensure concurrency for access into this
  // piece of memory.
  void* getMemory() const noexcept { return &data_; }
  ChainedItem* getNext(const PtrCompressor& compressor) const noexcept {
    return static_cast<ChainedItem*>(hook_.getNext(compressor));
  }
  void setNext(const ChainedItem* next,
               const PtrCompressor& compressor) noexcept {
    hook_.setNext(static_cast<const Item*>(next), compressor);
    XDCHECK_EQ(reinterpret_cast<uintptr_t>(getNext(compressor)),
               reinterpret_cast<uintptr_t>(next));
  }
};
template <typename CacheTrait>
uint32_t CacheChainedItem<CacheTrait>::getRequiredSize(uint32_t size) noexcept {
  const uint64_t requiredSize = static_cast<uint64_t>(size) +
                                static_cast<uint64_t>(kKeySize) + sizeof(Item) +
                                sizeof(Payload);
  XDCHECK_LE(requiredSize,
             static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()));
  if (requiredSize >
      static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
    return 0;
  }
  return static_cast<uint32_t>(requiredSize);
}
template <typename CacheTrait>
CacheChainedItem<CacheTrait>::CacheChainedItem(CompressedPtr ptr,
                                               uint32_t size,
                                               uint32_t creationTime)
    : Item(Key{reinterpret_cast<const char*>(&ptr), kKeySize},
           size + sizeof(Payload),
           creationTime) {
  this->markIsChainedItem();
  // Explicitly call ChainedItemPayload's ctor to initialize it properly, since
  // ChainedItemPayload is not a member of CacheChainedItem.
  new (reinterpret_cast<void*>(&getPayload())) Payload();
}
template <typename CacheTrait>
void CacheChainedItem<CacheTrait>::changeKey(CompressedPtr newKey) {
  if (this->isAccessible()) {
    throw std::invalid_argument(folly::sformat(
        "chained item {} is still in access container while modifying the key ",
        toString()));
  }
  Item::changeKey(Key{reinterpret_cast<const char*>(&newKey), kKeySize});
}
template <typename CacheTrait>
typename CacheChainedItem<CacheTrait>::Item&
CacheChainedItem<CacheTrait>::getParentItem(
    const PtrCompressor& compressor) const noexcept {
  const auto compressedPtr =
      *reinterpret_cast<const CompressedPtr*>(this->getKey().begin());
  return *compressor.unCompress(compressedPtr);
}
template <typename CacheTrait>
void* CacheChainedItem<CacheTrait>::getMemory() const noexcept {
  return getPayload().getMemory();
}
template <typename CacheTrait>
uint32_t CacheChainedItem<CacheTrait>::getSize() const noexcept {
  // Chained Item has its own embedded payload in its KAllocation, so we
  // need to deduct its size here to give the user the accurate usable size
  return this->alloc_.getSize() - sizeof(Payload);
}
template <typename CacheTrait>
std::string CacheChainedItem<CacheTrait>::toString() const {
  const auto cPtr =
      *reinterpret_cast<const CompressedPtr*>(Item::getKey().data());
  return folly::sformat(
      "chained item: "
      "memory={}:raw-ref={}:size={}:parent-compressed-ptr={}:"
      "isInMMContainer={}:isAccessible={}:isMoving={}:references={}:ctime={}:"
      "expTime={}:updateTime={}",
      this, Item::getRefCountAndFlagsRaw(), Item::getSize(), cPtr.getRaw(),
      Item::isInMMContainer(), Item::isAccessible(), Item::isMoving(),
      Item::getRefCount(), Item::getCreationTime(), Item::getExpiryTime(),
      Item::getLastAccessTime());
}
template <typename CacheTrait>
void CacheChainedItem<CacheTrait>::appendChain(
    ChainedItem& newChain, const PtrCompressor& compressor) {
  if (getNext(compressor)) {
    throw std::invalid_argument(
        folly::sformat("Item: {} is not the last item in a chain. Next: {}",
                       toString(), getNext(compressor)->toString()));
  }
  setNext(&newChain, compressor);
}
template <typename CacheTrait>
typename CacheChainedItem<CacheTrait>::ChainedItem*
CacheChainedItem<CacheTrait>::getNext(
    const PtrCompressor& compressor) const noexcept {
  return getPayload().getNext(compressor);
}
template <typename CacheTrait>
void CacheChainedItem<CacheTrait>::setNext(
    const ChainedItem* next, const PtrCompressor& compressor) noexcept {
  getPayload().setNext(next, compressor);
  XDCHECK_EQ(reinterpret_cast<uintptr_t>(getNext(compressor)),
             reinterpret_cast<uintptr_t>(next));
}
template <typename CacheTrait>
typename CacheChainedItem<CacheTrait>::Payload&
CacheChainedItem<CacheTrait>::getPayload() {
  return *reinterpret_cast<Payload*>(this->alloc_.getMemory());
}
template <typename CacheTrait>
const typename CacheChainedItem<CacheTrait>::Payload&
CacheChainedItem<CacheTrait>::getPayload() const {
  return *reinterpret_cast<const Payload*>(this->alloc_.getMemory());
}
} // namespace cachelib
} // namespace facebook