#pragma once #include "cache.h" #include #include 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 List, EGettersPromotionPolicy GettersPromotionPolicy, class... TArgs> class TThreadSafeCache { public: using TPtr = TAtomicSharedPtr; class ICallbacks { public: using TKey = Key; using TValue = Value; using TOwner = TThreadSafeCache; 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()) : 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(args...); } const TPtr GetUnsafe(TArgs... args) const { return GetValue(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 static const TPtr Get(TArgs... args) { return TThreadSafeCacheSingleton::Get(args...); } template static const TPtr Erase(TArgs... args) { return TThreadSafeCacheSingleton::Erase(args...); } template static void Clear() { return TThreadSafeCacheSingleton::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 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, TNoopDelete>; template class TThreadSafeCacheSingleton { public: static const TPtr Get(TArgs... args) { return Singleton()->Cache.Get(args...); } static const TPtr Erase(TArgs... args) { return Singleton()->Cache.Erase(args...); } static void Clear() { return Singleton()->Cache.Clear(); } TThreadSafeCacheSingleton() : Cache(Callbacks) { } private: TCallbacks Callbacks; typename TCallbacks::TOwner Cache; }; private: TRWMutex Mutex; const ICallbacks& Callbacks; mutable TInternalCache Cache; }; struct TLWHelper { template struct TConstWeighter { static int Weight(const TValue& /*value*/) { return 0; } }; template using TListType = TLWList>; template using TCache = TThreadSafeCache; }; struct TLRUHelper { template using TListType = TLRUList; template using TCache = TThreadSafeCache; }; struct TLFUHelper { template using TListType = TLFUList; template using TCache = TThreadSafeCache; }; template struct TSizeProviderRemoveAtomic : TSizeProvider { // TValue in this signature is TCache::TPtr, using this wrapper user don't need // to handle TPtr (which is TAtomicSharedPtr) 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