#pragma once
#include "cache.h"
#include <util/generic/singleton.h>
#include <util/generic/ylimits.h>
#include <util/system/rwlock.h>
namespace NPrivate {
// We are interested in getters promotion policy _here_ because of Read-Write-Lock optimizations.
enum class EGettersPromotionPolicy {
Promoted, // LRU, TLRU, MRU, etc.
Unpromoted // FIFO, LIFO, LW, etc.
};
template <class Key, class Value, template <class, class> class List, EGettersPromotionPolicy GettersPromotionPolicy, class... TArgs>
class TThreadSafeCache {
public:
using TPtr = TAtomicSharedPtr<Value>;
class ICallbacks {
public:
using TKey = Key;
using TValue = Value;
using TOwner = TThreadSafeCache<Key, Value, List, GettersPromotionPolicy, TArgs...>;
public:
virtual ~ICallbacks() = default;
virtual TKey GetKey(TArgs... args) const = 0;
virtual TValue* CreateObject(TArgs... args) const = 0;
};
public:
TThreadSafeCache(const ICallbacks& callbacks, size_t maxSize = Max<size_t>())
: Callbacks(callbacks)
, Cache(maxSize)
{
}
bool Insert(const Key& key, const TPtr& value) {
if (!Contains(key)) {
TWriteGuard w(Mutex);
return Cache.Insert(key, value);
}
return false;
}
void Update(const Key& key, const TPtr& value) {
TWriteGuard w(Mutex);
Cache.Update(key, value);
}
const TPtr GetOrNull(TArgs... args) {
Key key = Callbacks.GetKey(args...);
TReadGuard r(Mutex);
auto iter = Cache.Find(key);
if (iter == Cache.End()) {
return nullptr;
}
return iter.Value();
}
const TPtr Get(TArgs... args) const {
return GetValue<true>(args...);
}
const TPtr GetUnsafe(TArgs... args) const {
return GetValue<false>(args...);
}
void Clear() {
TWriteGuard w(Mutex);
Cache.Clear();
}
void Erase(TArgs... args) {
Key key = Callbacks.GetKey(args...);
if (!Contains(key)) {
return;
}
TWriteGuard w(Mutex);
typename TInternalCache::TIterator i = Cache.Find(key);
if (i == Cache.End()) {
return;
}
Cache.Erase(i);
}
bool Contains(const Key& key) const {
TReadGuard r(Mutex);
auto iter = Cache.FindWithoutPromote(key);
return iter != Cache.End();
}
template <class TCallbacks>
static const TPtr Get(TArgs... args) {
return TThreadSafeCacheSingleton<TCallbacks>::Get(args...);
}
template <class TCallbacks>
static const TPtr Erase(TArgs... args) {
return TThreadSafeCacheSingleton<TCallbacks>::Erase(args...);
}
template <class TCallbacks>
static void Clear() {
return TThreadSafeCacheSingleton<TCallbacks>::Clear();
}
size_t Size() const {
TReadGuard r(Mutex);
return Cache.Size();
}
size_t TotalSize() const {
TReadGuard r(Mutex);
return Cache.TotalSize();
}
size_t GetMaxSize() const {
TReadGuard w(Mutex);
return Cache.GetMaxSize();
}
void SetMaxSize(size_t newSize) {
TWriteGuard w(Mutex);
Cache.SetMaxSize(newSize);
}
private:
template <bool AllowNullValues>
const TPtr GetValue(TArgs... args) const {
Key key = Callbacks.GetKey(args...);
switch (GettersPromotionPolicy) {
case EGettersPromotionPolicy::Promoted:
break;
case EGettersPromotionPolicy::Unpromoted: {
TReadGuard r(Mutex);
typename TInternalCache::TIterator i = Cache.FindWithoutPromote(key);
if (i != Cache.End()) {
return i.Value();
}
break;
}
}
TWriteGuard w(Mutex);
typename TInternalCache::TIterator i = Cache.Find(key);
if (i != Cache.End()) {
return i.Value();
}
TPtr value = Callbacks.CreateObject(args...);
if (value || AllowNullValues) {
Cache.Insert(key, value);
}
return value;
}
private:
using TInternalCache = TCache<Key, TPtr, List<Key, TPtr>, TNoopDelete>;
template <class TCallbacks>
class TThreadSafeCacheSingleton {
public:
static const TPtr Get(TArgs... args) {
return Singleton<TThreadSafeCacheSingleton>()->Cache.Get(args...);
}
static const TPtr Erase(TArgs... args) {
return Singleton<TThreadSafeCacheSingleton>()->Cache.Erase(args...);
}
static void Clear() {
return Singleton<TThreadSafeCacheSingleton>()->Cache.Clear();
}
TThreadSafeCacheSingleton()
: Cache(Callbacks)
{
}
private:
TCallbacks Callbacks;
typename TCallbacks::TOwner Cache;
};
private:
TRWMutex Mutex;
const ICallbacks& Callbacks;
mutable TInternalCache Cache;
};
struct TLWHelper {
template <class TValue>
struct TConstWeighter {
static int Weight(const TValue& /*value*/) {
return 0;
}
};
template <class TKey, class TValue>
using TListType = TLWList<TKey, TValue, int, TConstWeighter<TValue>>;
template <class TKey, class TValue, class... TArgs>
using TCache = TThreadSafeCache<TKey, TValue, TListType, EGettersPromotionPolicy::Unpromoted, TArgs...>;
};
struct TLRUHelper {
template <class TKey, class TValue>
using TListType = TLRUList<TKey, TValue>;
template <class TKey, class TValue, class... TArgs>
using TCache = TThreadSafeCache<TKey, TValue, TListType, EGettersPromotionPolicy::Promoted, TArgs...>;
};
struct TLFUHelper {
template <class TKey, class TValue>
using TListType = TLFUList<TKey, TValue>;
template <class TKey, class TValue, class... TArgs>
using TCache = TThreadSafeCache<TKey, TValue, TListType, EGettersPromotionPolicy::Promoted, TArgs...>;
};
template <class TSizeProvider, class TValue>
struct TSizeProviderRemoveAtomic : TSizeProvider {
// TValue in this signature is TCache::TPtr, using this wrapper user don't need
// to handle TPtr (which is TAtomicSharedPtr<TValue>) and can just accept TValue
// in custom size provider. See example in unittests
size_t operator()(const TValue& value) const {
// We can pass reference to value without synchronization, because TSizeProvider::operator()
// is always called from methods secured by a guard
return TSizeProvider::operator()(*value);
}
};
template <template <class, class, class> class TTemplateListType, EGettersPromotionPolicy GettersPromotionPolicy>
struct TCacheWithSizeProviderHelper {
private:
template <class TSizeProvider>
struct TListWithProvider {
template <class TKey, class TValue>
using TListType = TTemplateListType<TKey, TValue, TSizeProviderRemoveAtomic<TSizeProvider, TValue>>;
};
public:
template <class TKey, class TValue, class TSizeProvider, class... TArgs>
using TCache = TThreadSafeCache<TKey, TValue, TListWithProvider<TSizeProvider>::template TListType, GettersPromotionPolicy, TArgs...>;
};
using TLRUWithSizeProviderHelper = TCacheWithSizeProviderHelper<TLRUList, EGettersPromotionPolicy::Promoted>;
using TLFUWithSizeProviderHelper = TCacheWithSizeProviderHelper<TLFUList, EGettersPromotionPolicy::Promoted>;
}
template <class TKey, class TValue, class... TArgs>
using TThreadSafeCache = typename NPrivate::TLWHelper::template TCache<TKey, TValue, TArgs...>;
template <class TKey, class TValue, class... TArgs>
using TThreadSafeLRUCache = typename NPrivate::TLRUHelper::template TCache<TKey, TValue, TArgs...>;
template <class TKey, class TValue, class... TArgs>
using TThreadSafeLFUCache = typename NPrivate::TLFUHelper::template TCache<TKey, TValue, TArgs...>;
template <class TKey, class TValue, class TSizeProvider, class... TArgs>
using TThreadSafeLRUCacheWithSizeProvider = typename NPrivate::TLRUWithSizeProviderHelper::template TCache<TKey, TValue, TSizeProvider, TArgs...>;
template <class TKey, class TValue, class TSizeProvider, class... TArgs>
using TThreadSafeLFUCacheWithSizeProvider = typename NPrivate::TLFUWithSizeProviderHelper::template TCache<TKey, TValue, TSizeProvider, TArgs...>;