cachelib/allocator/CacheItem.h (214 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/String.h>
#include <atomic>
#include <utility>
#include "cachelib/allocator/Cache.h"
#include "cachelib/allocator/CacheChainedItemIterator.h"
#include "cachelib/allocator/Handle.h"
#include "cachelib/allocator/KAllocation.h"
#include "cachelib/allocator/Refcount.h"
#include "cachelib/allocator/TypedHandle.h"
#include "cachelib/allocator/datastruct/SList.h"
#include "cachelib/allocator/memory/CompressedPtr.h"
#include "cachelib/allocator/memory/MemoryAllocator.h"
#include "cachelib/common/CompilerUtils.h"
#include "cachelib/common/Exceptions.h"
#include "cachelib/common/Mutex.h"
namespace facebook {
namespace cachelib {
namespace tests {
template <typename AllocatorT>
class BaseAllocatorTest;
template <typename AllocatorT>
class AllocatorHitStatsTest;
template <typename AllocatorT>
class MapTest;
class CacheAllocatorTestWrapper;
} // namespace tests
// forward declaration
template <typename CacheTrait>
class CacheAllocator;
template <typename CacheTrait>
class CacheChainedItem;
template <typename CacheTrait>
class ChainedItemPayload;
template <typename C>
class NvmCache;
template <typename Cache, typename Handle, typename Iter>
class CacheChainedAllocs;
template <typename K, typename V, typename C>
class Map;
// This is the actual representation of the cache item. It has two member
// hooks of type MMType::Hook and AccessType::Hook to ensure that the CacheItem
// can be put in the MMType::Container and AccessType::Container.
template <typename CacheTrait>
class CACHELIB_PACKED_ATTR CacheItem {
public:
/**
* CacheAllocator is what the user will be interacting to cache
* anything. NvmCache is an abstraction that abstracts away NVM
* devices. It is abstracted inside CacheAllocator and user does
* not deal with it directly. An item in NVM takes longer to fetch
* than one that resides in RAM.
*/
using CacheT = CacheAllocator<CacheTrait>;
using NvmCacheT = NvmCache<CacheT>;
using Flags = RefcountWithFlags::Flags;
/**
* Currently there are two types of items that can be cached.
* A ChainedItem is a dependent item on a regular item. A chained
* item does not have a real key but is instead associated with
* a regular item (its parent item). Multiple chained items can be
* linked to the same regular item and together they can cache data
* much bigger that of a single item.
*/
using Item = CacheItem<CacheTrait>;
using ChainedItem = CacheChainedItem<CacheTrait>;
/**
* A cache item is roughly consisted of the following parts:
*
* ---------------------
* | Intrusive Hooks |
* | Reference & Flags |
* | Creation Time |
* | Expiry Time |
* | Payload |
* ---------------------
*
* Intrusive hooks are used for access/mm containers. They contain
* compressed pointers that link an item to said container in addition
* to other metadata that the container itself deems useful to keep.
*
* Payload in this case is KAllocation which contains its own metadata
* that describes the length of the payload, the size of the key in
* addition to the actual key and the data.
*/
using AccessHook = typename CacheTrait::AccessType::template Hook<Item>;
using MMHook = typename CacheTrait::MMType::template Hook<Item>;
using Key = KAllocation::Key;
/**
* User primarily interacts with an item through its handle.
* An item handle is essentially a std::shared_ptr like structure
* that ensures the item's refcount is properly maintained and ensures
* the item is freed when it is not linked to access/mm containers
* and its refcount drops to 0.
*/
using ReadHandle = detail::ReadHandleImpl<CacheItem>;
using WriteHandle = detail::WriteHandleImpl<CacheItem>;
using Handle = WriteHandle;
using HandleMaker = std::function<Handle(CacheItem*)>;
/**
* Item* and ChainedItem* are represented in this compressed form
* inside the access and mm containers. They are more efficient to
* store than raw pointers and can be leveraged to allow the cache
* to be mapped to different addresses on shared memory.
*/
using CompressedPtr = facebook::cachelib::CompressedPtr;
using PtrCompressor = MemoryAllocator::PtrCompressor<Item>;
// Get the required size for a cache item given the size of memory
// user wants to allocate and the key size for the item
//
// @return required size if it's within the max size of uint32_t,
// 0 otherwise
static uint32_t getRequiredSize(Key key, uint32_t size) noexcept;
// Get the number of maximum outstanding handles there can be at any given
// time for an item
static uint64_t getRefcountMax() noexcept;
// Copying or moving this can launch a nuclear missile and blow up everything
CacheItem(const CacheItem&) = delete;
CacheItem(CacheItem&&) = delete;
void operator=(const CacheItem&) = delete;
void operator=(CacheItem&&) = delete;
// Fetch the key corresponding to the allocation
const Key getKey() const noexcept;
// Readonly memory for this allocation.
const void* getMemory() const noexcept;
// Writable memory for this allocation. The caller is free to do whatever he
// wants with it and needs to ensure thread safety for access into this
// piece of memory.
void* getMemory() noexcept;
// Cast item's readonly memory to a readonly user type
template <typename T>
const T* getMemoryAs() const noexcept {
return reinterpret_cast<const T*>(getMemory());
}
// Cast item's writable memory to a writable user type
template <typename T>
T* getMemoryAs() noexcept {
return reinterpret_cast<T*>(getMemory());
}
// This is the size of the memory allocation requested by the user.
// The memory range [getMemory(), getMemory() + getSize()) is usable.
uint32_t getSize() const noexcept;
// Return timestamp of when this item was created
uint32_t getCreationTime() const noexcept;
// return the original configured time to live in seconds.
std::chrono::seconds getConfiguredTTL() const noexcept;
// Return the last time someone accessed this item
uint32_t getLastAccessTime() const noexcept;
// Convenience method for debug purposes.
std::string toString() const;
// return the expiry time of the item
uint32_t getExpiryTime() const noexcept;
// check if the item reaches the expiry timestamp
// expiryTime_ == 0 means no time limitation for this Item
bool isExpired() const noexcept;
// Check if the item is expired relative to the provided timestamp.
bool isExpired(uint32_t currentTimeSecs) const noexcept;
/**
* Access specific flags for an item
*
* These flags are set atomically and any of the APIs here will give a
* consistent view on all the flags that are set or unset at that moment.
*
* However the content of the flag can be changed after any of these calls
* are returned, so to reliably rely on them, the user needs to make sure
* they're either the sole owner of this item or every one accessing this
* item is only reading its content.
*/
bool isChainedItem() const noexcept;
bool hasChainedItem() const noexcept;
/**
* Keep track of whether the item was modified while in ram cache
*/
bool isNvmClean() const noexcept;
void markNvmClean() noexcept;
void unmarkNvmClean() noexcept;
/**
* Marks that the item was potentially evicted from the nvmcache and might
* need to be rewritten even if it was nvm-clean
*/
void markNvmEvicted() noexcept;
void unmarkNvmEvicted() noexcept;
bool isNvmEvicted() const noexcept;
/**
* Function to set the timestamp for when to expire an item
*
* This API will only succeed when an item is a regular item, and user
* has already inserted it into the cache (via @insert or @insertOrReplace).
* In addition, the item cannot be in a "moving" state.
*
* @param expiryTime the expiryTime value to update to
*
* @return boolean indicating whether expiry time was successfully updated
* false when item is not linked in cache, or in moving state, or a
* chained item
*/
bool updateExpiryTime(uint32_t expiryTimeSecs) noexcept;
// Same as @updateExpiryTime, but sets expiry time to @ttl seconds from now.
// It has the same restrictions as @updateExpiryTime. An item must be a
// regular item and is part of the cache and NOT in the moving state.
//
// @param ttl TTL (from now)
// @return boolean indicating whether expiry time was successfully updated
// false when item is not linked in cache, or in moving state, or a
// chained item
bool extendTTL(std::chrono::seconds ttl) noexcept;
// Return the refcount of an item
RefcountWithFlags::Value getRefCount() const noexcept;
// Returns true if the item is in access container, false otherwise
bool isAccessible() const noexcept;
protected:
// construct an item without expiry timestamp.
CacheItem(Key key, uint32_t size, uint32_t creationTime);
// @param key Key for this item
// @param size Size allocated by the user. This may be smaller than
// the full usable size
// @param creationTime Timestamp when this item was created
// @param expiryTime Timestamp when this item will be expired.
CacheItem(Key key, uint32_t size, uint32_t creationTime, uint32_t expiryTime);
// changes the item's key. This is only supported for ChainedItems. For
// regular items, the key does not change with the lifetime of the item. For
// ChainedItems since the key is the parent item, the key can change when
// the parent item is being moved or tranferred.
//
// @throw std::invalid_argument if item is not a chained item or the key
// size does not match with the current key
void changeKey(Key key);
void* getMemoryInternal() const noexcept;
/**
* CacheItem's refcount contain admin references, access referneces, and
* flags, refer to Refcount.h for details.
*
* Currently we support up to 2^18 references on any given item.
* Increment and decrement may throw the following due to over/under-flow.
* cachelib::exception::RefcountOverflow
* cachelib::exception::RefcountUnderflow
*/
RefcountWithFlags::Value getRefCountAndFlagsRaw() const noexcept;
FOLLY_ALWAYS_INLINE void incRef() {
if (LIKELY(ref_.incRef())) {
return;
}
throw exception::RefcountOverflow(
folly::sformat("Refcount maxed out. item: {}", toString()));
}
FOLLY_ALWAYS_INLINE RefcountWithFlags::Value decRef() {
return ref_.decRef();
}
// Whether or not an item is completely drained of all references including
// the internal ones. This means there is no access refcount bits and zero
// admin bits set. I.e. refcount is 0 and the item is not linked, accessible,
// nor moving
bool isDrained() const noexcept;
// Whether or not we hold the last exclusive access to this item
// Refcount is 1 and the item is not linked, accessible, nor moving
bool isExclusive() const noexcept;
/**
* The following three functions correspond to the state of the allocation
* in the memory management container. This is protected by the
* MMContainer's internal locking. Inspecting this outside the mm
* container will be racy.
*/
void markInMMContainer() noexcept;
void unmarkInMMContainer() noexcept;
bool isInMMContainer() const noexcept;
/**
* The following three functions correspond to the state of the allocation
* in the access container. This will be protected by the access container
* lock. Depending on their state outside of the access container might be
* racy
*/
void markAccessible() noexcept;
void unmarkAccessible() noexcept;
/**
* The following two functions corresond to whether or not an item is
* currently in the process of being moved. This happens during a slab
* rebalance or resize operation.
*
* An item can only be marked moving when `isInMMContainer` returns true.
* This operation is atomic.
*
* User can also query if an item "isOnlyMoving". This returns true only
* if the refcount is 0 and only the moving bit is set.
*
* Unmarking moving does not depend on `isInMMContainer`
*/
bool markMoving() noexcept;
void unmarkMoving() noexcept;
bool isMoving() const noexcept;
bool isOnlyMoving() const noexcept;
/**
* Item cannot be marked both chained allocation and
* marked as having chained allocations at the same time
*/
void markIsChainedItem() noexcept;
void unmarkIsChainedItem() noexcept;
void markHasChainedItem() noexcept;
void unmarkHasChainedItem() noexcept;
ChainedItem& asChainedItem() noexcept;
const ChainedItem& asChainedItem() const noexcept;
// Returns the offset of the beginning of usable memory for an item
uint32_t getOffsetForMemory() const noexcept;
/**
* Functions to set, unset and get bits
*/
template <RefcountWithFlags::Flags flagBit>
void setFlag() noexcept;
template <RefcountWithFlags::Flags flagBit>
void unSetFlag() noexcept;
template <RefcountWithFlags::Flags flagBit>
bool isFlagSet() const noexcept;
/**
* The following are the data members of CacheItem
*
* Hooks to access and mm containers are public since other parts of the
* code need access to them. Everything else should be private.
*/
public:
// Hook for the access type
AccessHook accessHook_;
using AccessContainer = typename CacheTrait::AccessType::template Container<
Item,
&Item::accessHook_,
typename CacheTrait::AccessTypeLocks>;
// Hook for the mm type
MMHook mmHook_;
using MMContainer =
typename CacheTrait::MMType::template Container<Item, &Item::mmHook_>;
protected:
// Refcount for the item and also flags on the items state
RefcountWithFlags ref_;
// Time when this cache item is created
const uint32_t creationTime_{0};
// Expiry timestamp for the item
// 0 means no time limitation
uint32_t expiryTime_{0};
// The actual allocation.
KAllocation alloc_;
friend ChainedItem;
friend CacheT;
friend AccessContainer;
friend MMContainer;
friend NvmCacheT;
template <typename Cache, typename Handle, typename Iter>
friend class CacheChainedAllocs;
template <typename Cache, typename Item>
friend class CacheChainedItemIterator;
friend class facebook::cachelib::tests::CacheAllocatorTestWrapper;
template <typename K, typename V, typename C>
friend class Map;
// tests
template <typename AllocatorT>
friend class facebook::cachelib::tests::BaseAllocatorTest;
friend class facebook::cachelib::tests::MapTest<CacheAllocator<CacheTrait>>;
FRIEND_TEST(LruAllocatorTest, ItemSampling);
FRIEND_TEST(LruAllocatorTest, AddChainedAllocationSimple);
FRIEND_TEST(ItemTest, ChangeKey);
FRIEND_TEST(ItemTest, ToString);
FRIEND_TEST(ItemTest, CreationTime);
FRIEND_TEST(ItemTest, ExpiryTime);
FRIEND_TEST(ItemTest, ChainedItemConstruction);
FRIEND_TEST(ItemTest, NonStringKey);
template <typename AllocatorT>
friend class facebook::cachelib::tests::AllocatorHitStatsTest;
};
// A chained item has a hook pointing to the next chained item. The hook is
// a compressed pointer stored at the beginning of KAllocation's data.
// A chained item's key is a compressed pointer to its parent item.
//
// Memory layout:
// | --------------------- |
// | AccessHook |
// | MMHook |
// | RefCountWithFlags |
// | creationTime_ |
// | --------------------- |
// | K | size_ |
// | A | ---------------- |
// | l | | keyData | <-- sizeof(CompressedPtr)
// | l | | -------- |
// | o | | P | hook | <-- sizeof(SlistHook<ChainedItem>)
// | c | data_ | a | data |
// | a | | y | |
// | t | | l | |
// | i | | o | |
// | o | | a | |
// | n | | d | |
// | --------------------- |
template <typename CacheTrait>
class CACHELIB_PACKED_ATTR CacheChainedItem : public CacheItem<CacheTrait> {
public:
using Item = CacheItem<CacheTrait>;
using ChainedItem = CacheChainedItem<CacheTrait>;
using Payload = ChainedItemPayload<CacheTrait>;
using CompressedPtr = typename Item::CompressedPtr;
using PtrCompressor = typename Item::PtrCompressor;
/**
* Key for CacheChainedItem is the raw pointer to the parent item,
* so it is 8 bytes big.
*/
using Key = typename Item::Key;
static constexpr uint32_t kKeySize = sizeof(CompressedPtr);
// Get the required size for a cache item given the size of memory
// user wants to allocate
//
// @return required size if it's within the max size of uint32_t,
// 0 otherwise
static uint32_t getRequiredSize(uint32_t size) noexcept;
// Get the parent item this chained allocation is associated with
Item& getParentItem(const PtrCompressor& compressor) const noexcept;
// 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;
// This is the size of the memory allocation requested by the user.
// The memory range [getMemory(), getMemory() + getSize()) is usable.
uint32_t getSize() const noexcept;
// Convenience method for debug purposes.
std::string toString() const;
protected:
// The key of a chained allocation is the address of its parent item
//
// @param ptr Compressed ptr to parent item
// @param allocSize This is the size of the entire allocation for
// constructing this item
// @param creationTime Timestamp when this item was created
CacheChainedItem(CompressedPtr key, uint32_t size, uint32_t creationTime);
// reset the key of the ChainedItem. For regular Items, we dont allow doing
// this. However for chained items since the parent is the key, we need to
// allow this for transferring the ownership from one parent to another.
//
// @throw std::invalid_argument if the chained item is still in accessible
// state.
void changeKey(CompressedPtr newKey);
// Append chain to this item. The new chain can contain one or more items
// but this item to which the new chain is being appended must be a single
// item, or the last item of an existing chain
//
// @throw std::invalid_argument if this item is already part of a chain
// and is not the last item
void appendChain(ChainedItem& newChain, const PtrCompressor& compressor);
// get the next in the chain for this chained item.
ChainedItem* getNext(const PtrCompressor& compressor) const noexcept;
// set the next in chain for this chained Item
void setNext(const ChainedItem* next,
const PtrCompressor& compressor) noexcept;
Payload& getPayload();
const Payload& getPayload() const;
friend Payload;
friend CacheAllocator<CacheTrait>;
template <typename Cache, typename Handle, typename Iter>
friend class CacheChainedAllocs;
template <typename Cache, typename Item>
friend class CacheChainedItemIterator;
friend NvmCache<CacheAllocator<CacheTrait>>;
template <typename AllocatorT>
friend class facebook::cachelib::tests::BaseAllocatorTest;
FRIEND_TEST(ItemTest, ChainedItemConstruction);
FRIEND_TEST(ItemTest, ToString);
FRIEND_TEST(ItemTest, ChangeKey);
};
} // namespace cachelib
} // namespace facebook
#include "cachelib/allocator/CacheItem-inl.h"