fork of https://github.com/rust-rocksdb/rust-rocksdb for nextgraph
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
14 KiB
376 lines
14 KiB
1 year ago
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||
|
// This source code is licensed under both the GPLv2 (found in the
|
||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||
|
// (found in the LICENSE.Apache file in the root directory).
|
||
|
|
||
|
// APIs for accessing Cache in a type-safe and convenient way. Cache is kept
|
||
|
// at a low, thin level of abstraction so that different implementations can
|
||
|
// be plugged in, but these wrappers provide clean, convenient access to the
|
||
|
// most common operations.
|
||
|
//
|
||
|
// A number of template classes are needed for sharing common structure. The
|
||
|
// key classes are these:
|
||
|
//
|
||
|
// * PlaceholderCacheInterface - Used for making cache reservations, with
|
||
|
// entries that have a charge but no value.
|
||
|
// * BasicTypedCacheInterface<TValue> - Used for primary cache storage of
|
||
|
// objects of type TValue.
|
||
|
// * FullTypedCacheHelper<TValue, TCreateContext> - Used for secondary cache
|
||
|
// compatible storage of objects of type TValue.
|
||
|
// * For each of these, there's a "Shared" version
|
||
|
// (e.g. FullTypedSharedCacheInterface) that holds a shared_ptr to the Cache,
|
||
|
// rather than assuming external ownership by holding only a raw `Cache*`.
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include <algorithm>
|
||
|
#include <cstdint>
|
||
|
#include <memory>
|
||
|
#include <type_traits>
|
||
|
|
||
|
#include "cache/cache_helpers.h"
|
||
|
#include "rocksdb/advanced_cache.h"
|
||
|
#include "rocksdb/advanced_options.h"
|
||
|
|
||
|
namespace ROCKSDB_NAMESPACE {
|
||
|
|
||
|
// For future consideration:
|
||
|
// * Pass in value to Insert with std::unique_ptr& to simplify ownership
|
||
|
// transfer logic in callers
|
||
|
// * Make key type a template parameter (e.g. useful for table cache)
|
||
|
// * Closer integration with CacheHandleGuard (opt-in, so not always
|
||
|
// paying the extra overhead)
|
||
|
|
||
|
#define CACHE_TYPE_DEFS() \
|
||
|
using Priority = Cache::Priority; \
|
||
|
using Handle = Cache::Handle; \
|
||
|
using ObjectPtr = Cache::ObjectPtr; \
|
||
|
using CreateContext = Cache::CreateContext; \
|
||
|
using CacheItemHelper = Cache::CacheItemHelper /* caller ; */
|
||
|
|
||
|
template <typename CachePtr>
|
||
|
class BaseCacheInterface {
|
||
|
public:
|
||
|
CACHE_TYPE_DEFS();
|
||
|
|
||
|
/*implicit*/ BaseCacheInterface(CachePtr cache) : cache_(std::move(cache)) {}
|
||
|
|
||
|
inline void Release(Handle* handle) { cache_->Release(handle); }
|
||
|
|
||
|
inline void ReleaseAndEraseIfLastRef(Handle* handle) {
|
||
|
cache_->Release(handle, /*erase_if_last_ref*/ true);
|
||
|
}
|
||
|
|
||
|
inline void RegisterReleaseAsCleanup(Handle* handle, Cleanable& cleanable) {
|
||
|
cleanable.RegisterCleanup(&ReleaseCacheHandleCleanup, get(), handle);
|
||
|
}
|
||
|
|
||
|
inline Cache* get() const { return &*cache_; }
|
||
|
|
||
|
explicit inline operator bool() const noexcept { return cache_ != nullptr; }
|
||
|
|
||
|
protected:
|
||
|
CachePtr cache_;
|
||
|
};
|
||
|
|
||
|
// PlaceholderCacheInterface - Used for making cache reservations, with
|
||
|
// entries that have a charge but no value. CacheEntryRole is required as
|
||
|
// a template parameter.
|
||
|
template <CacheEntryRole kRole, typename CachePtr = Cache*>
|
||
|
class PlaceholderCacheInterface : public BaseCacheInterface<CachePtr> {
|
||
|
public:
|
||
|
CACHE_TYPE_DEFS();
|
||
|
using BaseCacheInterface<CachePtr>::BaseCacheInterface;
|
||
|
|
||
|
inline Status Insert(const Slice& key, size_t charge, Handle** handle) {
|
||
|
return this->cache_->Insert(key, /*value=*/nullptr, GetHelper(), charge,
|
||
|
handle);
|
||
|
}
|
||
|
|
||
|
static const Cache::CacheItemHelper* GetHelper() {
|
||
|
static const Cache::CacheItemHelper kHelper{kRole};
|
||
|
return &kHelper;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
template <CacheEntryRole kRole>
|
||
|
using PlaceholderSharedCacheInterface =
|
||
|
PlaceholderCacheInterface<kRole, std::shared_ptr<Cache>>;
|
||
|
|
||
|
template <class TValue>
|
||
|
class BasicTypedCacheHelperFns {
|
||
|
public:
|
||
|
CACHE_TYPE_DEFS();
|
||
|
// E.g. char* for char[]
|
||
|
using TValuePtr = std::remove_extent_t<TValue>*;
|
||
|
|
||
|
protected:
|
||
|
inline static ObjectPtr UpCastValue(TValuePtr value) { return value; }
|
||
|
inline static TValuePtr DownCastValue(ObjectPtr value) {
|
||
|
return static_cast<TValuePtr>(value);
|
||
|
}
|
||
|
|
||
|
static void Delete(ObjectPtr value, MemoryAllocator* allocator) {
|
||
|
// FIXME: Currently, no callers actually allocate the ObjectPtr objects
|
||
|
// using the custom allocator, just subobjects that keep a reference to
|
||
|
// the allocator themselves (with CacheAllocationPtr).
|
||
|
if (/*DISABLED*/ false && allocator) {
|
||
|
if constexpr (std::is_destructible_v<TValue>) {
|
||
|
DownCastValue(value)->~TValue();
|
||
|
}
|
||
|
allocator->Deallocate(value);
|
||
|
} else {
|
||
|
// Like delete but properly handles TValue=char[] etc.
|
||
|
std::default_delete<TValue>{}(DownCastValue(value));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// In its own class to try to minimize the number of distinct CacheItemHelper
|
||
|
// instances (e.g. don't vary by CachePtr)
|
||
|
template <class TValue, CacheEntryRole kRole>
|
||
|
class BasicTypedCacheHelper : public BasicTypedCacheHelperFns<TValue> {
|
||
|
public:
|
||
|
static const Cache::CacheItemHelper* GetBasicHelper() {
|
||
|
static const Cache::CacheItemHelper kHelper{kRole,
|
||
|
&BasicTypedCacheHelper::Delete};
|
||
|
return &kHelper;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// BasicTypedCacheInterface - Used for primary cache storage of objects of
|
||
|
// type TValue, which can be cleaned up with std::default_delete<TValue>. The
|
||
|
// role is provided by TValue::kCacheEntryRole or given in an optional
|
||
|
// template parameter.
|
||
|
template <class TValue, CacheEntryRole kRole = TValue::kCacheEntryRole,
|
||
|
typename CachePtr = Cache*>
|
||
|
class BasicTypedCacheInterface : public BaseCacheInterface<CachePtr>,
|
||
|
public BasicTypedCacheHelper<TValue, kRole> {
|
||
|
public:
|
||
|
CACHE_TYPE_DEFS();
|
||
|
using typename BasicTypedCacheHelperFns<TValue>::TValuePtr;
|
||
|
struct TypedHandle : public Handle {};
|
||
|
using BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper;
|
||
|
// ctor
|
||
|
using BaseCacheInterface<CachePtr>::BaseCacheInterface;
|
||
|
struct TypedAsyncLookupHandle : public Cache::AsyncLookupHandle {
|
||
|
TypedHandle* Result() {
|
||
|
return reinterpret_cast<TypedHandle*>(Cache::AsyncLookupHandle::Result());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
inline Status Insert(const Slice& key, TValuePtr value, size_t charge,
|
||
|
TypedHandle** handle = nullptr,
|
||
|
Priority priority = Priority::LOW) {
|
||
|
auto untyped_handle = reinterpret_cast<Handle**>(handle);
|
||
|
return this->cache_->Insert(
|
||
|
key, BasicTypedCacheHelperFns<TValue>::UpCastValue(value),
|
||
|
GetBasicHelper(), charge, untyped_handle, priority);
|
||
|
}
|
||
|
|
||
|
inline TypedHandle* Lookup(const Slice& key, Statistics* stats = nullptr) {
|
||
|
return reinterpret_cast<TypedHandle*>(
|
||
|
this->cache_->BasicLookup(key, stats));
|
||
|
}
|
||
|
|
||
|
inline void StartAsyncLookup(TypedAsyncLookupHandle& async_handle) {
|
||
|
assert(async_handle.helper == nullptr);
|
||
|
this->cache_->StartAsyncLookup(async_handle);
|
||
|
}
|
||
|
|
||
|
inline CacheHandleGuard<TValue> Guard(TypedHandle* handle) {
|
||
|
if (handle) {
|
||
|
return CacheHandleGuard<TValue>(&*this->cache_, handle);
|
||
|
} else {
|
||
|
return {};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline std::shared_ptr<TValue> SharedGuard(TypedHandle* handle) {
|
||
|
if (handle) {
|
||
|
return MakeSharedCacheHandleGuard<TValue>(&*this->cache_, handle);
|
||
|
} else {
|
||
|
return {};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline TValuePtr Value(TypedHandle* handle) {
|
||
|
return BasicTypedCacheHelperFns<TValue>::DownCastValue(
|
||
|
this->cache_->Value(handle));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// BasicTypedSharedCacheInterface - Like BasicTypedCacheInterface but with a
|
||
|
// shared_ptr<Cache> for keeping Cache alive.
|
||
|
template <class TValue, CacheEntryRole kRole = TValue::kCacheEntryRole>
|
||
|
using BasicTypedSharedCacheInterface =
|
||
|
BasicTypedCacheInterface<TValue, kRole, std::shared_ptr<Cache>>;
|
||
|
|
||
|
// TValue must implement ContentSlice() and ~TValue
|
||
|
// TCreateContext must implement Create(std::unique_ptr<TValue>*, ...)
|
||
|
template <class TValue, class TCreateContext>
|
||
|
class FullTypedCacheHelperFns : public BasicTypedCacheHelperFns<TValue> {
|
||
|
public:
|
||
|
CACHE_TYPE_DEFS();
|
||
|
|
||
|
protected:
|
||
|
using typename BasicTypedCacheHelperFns<TValue>::TValuePtr;
|
||
|
using BasicTypedCacheHelperFns<TValue>::DownCastValue;
|
||
|
using BasicTypedCacheHelperFns<TValue>::UpCastValue;
|
||
|
|
||
|
static size_t Size(ObjectPtr v) {
|
||
|
TValuePtr value = DownCastValue(v);
|
||
|
auto slice = value->ContentSlice();
|
||
|
return slice.size();
|
||
|
}
|
||
|
|
||
|
static Status SaveTo(ObjectPtr v, size_t from_offset, size_t length,
|
||
|
char* out) {
|
||
|
TValuePtr value = DownCastValue(v);
|
||
|
auto slice = value->ContentSlice();
|
||
|
assert(from_offset < slice.size());
|
||
|
assert(from_offset + length <= slice.size());
|
||
|
std::copy_n(slice.data() + from_offset, length, out);
|
||
|
return Status::OK();
|
||
|
}
|
||
|
|
||
|
static Status Create(const Slice& data, CreateContext* context,
|
||
|
MemoryAllocator* allocator, ObjectPtr* out_obj,
|
||
|
size_t* out_charge) {
|
||
|
std::unique_ptr<TValue> value = nullptr;
|
||
|
if constexpr (sizeof(TCreateContext) > 0) {
|
||
|
TCreateContext* tcontext = static_cast<TCreateContext*>(context);
|
||
|
tcontext->Create(&value, out_charge, data, allocator);
|
||
|
} else {
|
||
|
TCreateContext::Create(&value, out_charge, data, allocator);
|
||
|
}
|
||
|
*out_obj = UpCastValue(value.release());
|
||
|
return Status::OK();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// In its own class to try to minimize the number of distinct CacheItemHelper
|
||
|
// instances (e.g. don't vary by CachePtr)
|
||
|
template <class TValue, class TCreateContext, CacheEntryRole kRole>
|
||
|
class FullTypedCacheHelper
|
||
|
: public FullTypedCacheHelperFns<TValue, TCreateContext> {
|
||
|
public:
|
||
|
static const Cache::CacheItemHelper* GetFullHelper() {
|
||
|
static const Cache::CacheItemHelper kHelper{
|
||
|
kRole,
|
||
|
&FullTypedCacheHelper::Delete,
|
||
|
&FullTypedCacheHelper::Size,
|
||
|
&FullTypedCacheHelper::SaveTo,
|
||
|
&FullTypedCacheHelper::Create,
|
||
|
BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper()};
|
||
|
return &kHelper;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// FullTypedCacheHelper - Used for secondary cache compatible storage of
|
||
|
// objects of type TValue. In addition to BasicTypedCacheInterface constraints,
|
||
|
// we require TValue::ContentSlice() to return persistable data. This
|
||
|
// simplifies usage for the normal case of simple secondary cache compatibility
|
||
|
// (can give you a Slice to the data already in memory). In addition to
|
||
|
// TCreateContext performing the role of Cache::CreateContext, it is also
|
||
|
// expected to provide a function Create(std::unique_ptr<TValue>* value,
|
||
|
// size_t* out_charge, const Slice& data, MemoryAllocator* allocator) for
|
||
|
// creating new TValue.
|
||
|
template <class TValue, class TCreateContext,
|
||
|
CacheEntryRole kRole = TValue::kCacheEntryRole,
|
||
|
typename CachePtr = Cache*>
|
||
|
class FullTypedCacheInterface
|
||
|
: public BasicTypedCacheInterface<TValue, kRole, CachePtr>,
|
||
|
public FullTypedCacheHelper<TValue, TCreateContext, kRole> {
|
||
|
public:
|
||
|
CACHE_TYPE_DEFS();
|
||
|
using typename BasicTypedCacheInterface<TValue, kRole, CachePtr>::TypedHandle;
|
||
|
using typename BasicTypedCacheInterface<TValue, kRole,
|
||
|
CachePtr>::TypedAsyncLookupHandle;
|
||
|
using typename BasicTypedCacheHelperFns<TValue>::TValuePtr;
|
||
|
using BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper;
|
||
|
using FullTypedCacheHelper<TValue, TCreateContext, kRole>::GetFullHelper;
|
||
|
using BasicTypedCacheHelperFns<TValue>::UpCastValue;
|
||
|
using BasicTypedCacheHelperFns<TValue>::DownCastValue;
|
||
|
// ctor
|
||
|
using BasicTypedCacheInterface<TValue, kRole,
|
||
|
CachePtr>::BasicTypedCacheInterface;
|
||
|
|
||
|
// Insert with SecondaryCache compatibility (subject to CacheTier).
|
||
|
// (Basic Insert() also inherited.)
|
||
|
inline Status InsertFull(
|
||
|
const Slice& key, TValuePtr value, size_t charge,
|
||
|
TypedHandle** handle = nullptr, Priority priority = Priority::LOW,
|
||
|
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||
|
auto untyped_handle = reinterpret_cast<Handle**>(handle);
|
||
|
auto helper = lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier
|
||
|
? GetFullHelper()
|
||
|
: GetBasicHelper();
|
||
|
return this->cache_->Insert(key, UpCastValue(value), helper, charge,
|
||
|
untyped_handle, priority);
|
||
|
}
|
||
|
|
||
|
// Like SecondaryCache::InsertSaved, with SecondaryCache compatibility
|
||
|
// (subject to CacheTier).
|
||
|
inline Status InsertSaved(
|
||
|
const Slice& key, const Slice& data, TCreateContext* create_context,
|
||
|
Priority priority = Priority::LOW,
|
||
|
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier,
|
||
|
size_t* out_charge = nullptr) {
|
||
|
ObjectPtr value;
|
||
|
size_t charge;
|
||
|
Status st = GetFullHelper()->create_cb(data, create_context,
|
||
|
this->cache_->memory_allocator(),
|
||
|
&value, &charge);
|
||
|
if (out_charge) {
|
||
|
*out_charge = charge;
|
||
|
}
|
||
|
if (st.ok()) {
|
||
|
st = InsertFull(key, DownCastValue(value), charge, nullptr /*handle*/,
|
||
|
priority, lowest_used_cache_tier);
|
||
|
} else {
|
||
|
GetFullHelper()->del_cb(value, this->cache_->memory_allocator());
|
||
|
}
|
||
|
return st;
|
||
|
}
|
||
|
|
||
|
// Lookup with SecondaryCache support (subject to CacheTier).
|
||
|
// (Basic Lookup() also inherited.)
|
||
|
inline TypedHandle* LookupFull(
|
||
|
const Slice& key, TCreateContext* create_context = nullptr,
|
||
|
Priority priority = Priority::LOW, Statistics* stats = nullptr,
|
||
|
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||
|
if (lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier) {
|
||
|
return reinterpret_cast<TypedHandle*>(this->cache_->Lookup(
|
||
|
key, GetFullHelper(), create_context, priority, stats));
|
||
|
} else {
|
||
|
return BasicTypedCacheInterface<TValue, kRole, CachePtr>::Lookup(key,
|
||
|
stats);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void StartAsyncLookupFull(
|
||
|
TypedAsyncLookupHandle& async_handle,
|
||
|
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||
|
if (lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier) {
|
||
|
async_handle.helper = GetFullHelper();
|
||
|
this->cache_->StartAsyncLookup(async_handle);
|
||
|
} else {
|
||
|
BasicTypedCacheInterface<TValue, kRole, CachePtr>::StartAsyncLookup(
|
||
|
async_handle);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// FullTypedSharedCacheInterface - Like FullTypedCacheInterface but with a
|
||
|
// shared_ptr<Cache> for keeping Cache alive.
|
||
|
template <class TValue, class TCreateContext,
|
||
|
CacheEntryRole kRole = TValue::kCacheEntryRole>
|
||
|
using FullTypedSharedCacheInterface =
|
||
|
FullTypedCacheInterface<TValue, TCreateContext, kRole,
|
||
|
std::shared_ptr<Cache>>;
|
||
|
|
||
|
#undef CACHE_TYPE_DEFS
|
||
|
|
||
|
} // namespace ROCKSDB_NAMESPACE
|