10#include <userver/cache/lru_cache_config.hpp>
11#include <userver/cache/lru_cache_statistics.hpp>
12#include <userver/cache/nway_lru_cache.hpp>
13#include <userver/concurrent/mutex_set.hpp>
14#include <userver/dump/common.hpp>
15#include <userver/dump/dumper.hpp>
16#include <userver/engine/async.hpp>
17#include <userver/utils/datetime.hpp>
18#include <userver/utils/impl/cached_time.hpp>
19#include <userver/utils/impl/wait_token_storage.hpp>
21USERVER_NAMESPACE_BEGIN
27template <
typename Value>
28struct ExpirableValue final {
30 std::chrono::steady_clock::time_point update_time;
33template <
typename Value>
34void Write(
dump::
Writer& writer,
const impl::ExpirableValue<Value>& value) {
35 const auto [now, steady_now] =
utils::impl::GetGlobalTime();
36 writer.Write(value.value);
37 writer.Write(value.update_time - steady_now + now);
40template <
typename Value>
41impl::ExpirableValue<Value> Read(
dump::
Reader& reader,
dump::To<impl::ExpirableValue<Value>>) {
42 const auto [now, steady_now] =
utils::impl::GetGlobalTime();
44 return impl::ExpirableValue<Value>{
45 reader.Read<Value>(), reader.Read<std::chrono::system_clock::time_point>() - now + steady_now};
57template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
58class ExpirableLruCache final {
60 using UpdateValueFunc = std::function<Value(
const Key&)>;
70 ExpirableLruCache(size_t ways, size_t way_size,
const Hash& hash = Hash(),
const Equal& equal = Equal());
78 std::chrono::milliseconds GetMaxLifetime()
const noexcept;
80 void SetMaxLifetime(std::chrono::milliseconds max_lifetime);
83
84
85
86
90
91
92
93
94 Value
Get(
const Key& key,
const UpdateValueFunc& update_func,
ReadMode read_mode =
ReadMode::kUseCache);
97
98
99
100
101
102 std::optional<Value>
GetOptional(
const Key& key,
const UpdateValueFunc& update_func);
105
106
107
108
112
113
114
115
119
120
123 void Put(
const Key& key,
const Value& value);
125 void Put(
const Key& key, Value&& value);
127 const impl::ExpirableLruCacheStatistics& GetStatistics()
const;
129 size_t GetSizeApproximate()
const;
149 bool IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
const;
151 bool ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
154 cache::NWayLRU<Key, impl::ExpirableValue<Value>, Hash, Equal> lru_;
155 std::atomic<std::chrono::milliseconds> max_lifetime_{std::chrono::milliseconds(0)};
156 std::atomic<BackgroundUpdateMode> background_update_mode_{BackgroundUpdateMode::kDisabled};
157 impl::ExpirableLruCacheStatistics stats_;
158 concurrent::MutexSet<Key, Hash, Equal> mutex_set_;
159 utils::impl::WaitTokenStorage wait_token_storage_;
162template <
typename Key,
typename Value,
typename Hash,
typename Equal>
171template <
typename Key,
typename Value,
typename Hash,
typename Equal>
172ExpirableLruCache<Key, Value, Hash, Equal>::~ExpirableLruCache() {
173 wait_token_storage_.WaitForAllTokens();
176template <
typename Key,
typename Value,
typename Hash,
typename Equal>
177void ExpirableLruCache<Key, Value, Hash, Equal>::
SetWaySize(size_t way_size) {
178 lru_.UpdateWaySize(way_size);
181template <
typename Key,
typename Value,
typename Hash,
typename Equal>
182std::chrono::milliseconds ExpirableLruCache<Key, Value, Hash, Equal>::GetMaxLifetime()
const noexcept {
183 return max_lifetime_.load();
186template <
typename Key,
typename Value,
typename Hash,
typename Equal>
187void ExpirableLruCache<Key, Value, Hash, Equal>::SetMaxLifetime(std::chrono::milliseconds max_lifetime) {
188 max_lifetime_ = max_lifetime;
191template <
typename Key,
typename Value,
typename Hash,
typename Equal>
192void ExpirableLruCache<Key, Value, Hash, Equal>::
SetBackgroundUpdate(BackgroundUpdateMode background_update) {
193 background_update_mode_ = background_update;
196template <
typename Key,
typename Value,
typename Hash,
typename Equal>
197Value ExpirableLruCache<Key, Value, Hash, Equal>::
Get(
199 const UpdateValueFunc& update_func,
205 return std::move(*opt_old_value);
208 auto mutex = mutex_set_.GetMutexForKey(key);
209 std::lock_guard lock(mutex);
212 auto old_value = lru_.Get(key);
213 if (old_value && !IsExpired(old_value->update_time, now)) {
214 return std::move(old_value->value);
217 auto value = update_func(key);
218 if (read_mode ==
ReadMode::kUseCache) {
219 lru_.Put(key, {value, now});
224template <
typename Key,
typename Value,
typename Hash,
typename Equal>
226ExpirableLruCache<Key, Value, Hash, Equal>::
GetOptional(
const Key& key,
const UpdateValueFunc& update_func) {
228 auto old_value = lru_.Get(key);
231 if (!IsExpired(old_value->update_time, now)) {
232 impl::CacheHit(stats_);
234 if (ShouldUpdate(old_value->update_time, now)) {
238 return std::move(old_value->value);
240 impl::CacheStale(stats_);
243 impl::CacheMiss(stats_);
248template <
typename Key,
typename Value,
typename Hash,
typename Equal>
250 auto old_value = lru_.Get(key);
253 impl::CacheHit(stats_);
254 return old_value->value;
256 impl::CacheMiss(stats_);
261template <
typename Key,
typename Value,
typename Hash,
typename Equal>
264 const UpdateValueFunc& update_func
267 auto old_value = lru_.Get(key);
270 impl::CacheHit(stats_);
272 if (ShouldUpdate(old_value->update_time, now)) {
276 return old_value->value;
278 impl::CacheMiss(stats_);
283template <
typename Key,
typename Value,
typename Hash,
typename Equal>
286 auto old_value = lru_.Get(key);
289 if (!IsExpired(old_value->update_time, now)) {
290 impl::CacheHit(stats_);
292 return old_value->value;
294 impl::CacheStale(stats_);
297 impl::CacheMiss(stats_);
302template <
typename Key,
typename Value,
typename Hash,
typename Equal>
303void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key,
const Value& value) {
307template <
typename Key,
typename Value,
typename Hash,
typename Equal>
308void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key, Value&& value) {
312template <
typename Key,
typename Value,
typename Hash,
typename Equal>
313const impl::ExpirableLruCacheStatistics& ExpirableLruCache<Key, Value, Hash, Equal>::GetStatistics()
const {
317template <
typename Key,
typename Value,
typename Hash,
typename Equal>
318size_t ExpirableLruCache<Key, Value, Hash, Equal>::GetSizeApproximate()
const {
319 return lru_.GetSize();
322template <
typename Key,
typename Value,
typename Hash,
typename Equal>
323void ExpirableLruCache<Key, Value, Hash, Equal>::
Invalidate() {
327template <
typename Key,
typename Value,
typename Hash,
typename Equal>
329 lru_.InvalidateByKey(key);
332template <
typename Key,
typename Value,
typename Hash,
typename Equal>
333void ExpirableLruCache<Key, Value, Hash, Equal>::
UpdateInBackground(
const Key& key, UpdateValueFunc update_func) {
334 stats_.total.background_updates++;
335 stats_.recent.GetCurrentCounter().background_updates++;
338 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(),
this, key, update_func = std::move(update_func)] {
339 auto mutex = mutex_set_.GetMutexForKey(key);
340 std::unique_lock lock(mutex, std::try_to_lock);
347 auto value = update_func(key);
348 lru_.Put(key, {value, now});
352template <
typename Key,
typename Value,
typename Hash,
typename Equal>
353bool ExpirableLruCache<Key, Value, Hash, Equal>::IsExpired(
354 std::chrono::steady_clock::time_point update_time,
355 std::chrono::steady_clock::time_point now
357 auto max_lifetime = max_lifetime_.load();
358 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
361template <
typename Key,
typename Value,
typename Hash,
typename Equal>
362bool ExpirableLruCache<Key, Value, Hash, Equal>::ShouldUpdate(
363 std::chrono::steady_clock::time_point update_time,
364 std::chrono::steady_clock::time_point now
366 auto max_lifetime = max_lifetime_.load();
367 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) && max_lifetime.count() != 0 &&
368 update_time + max_lifetime / 2 < now;
371template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
372class LruCacheWrapper final {
374 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
375 using ReadMode =
typename Cache::ReadMode;
377 LruCacheWrapper(std::shared_ptr<Cache> cache,
typename Cache::UpdateValueFunc update_func)
378 : cache_(std::move(cache)), update_func_(std::move(update_func)) {}
381 Value
Get(
const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
382 return cache_->Get(key, update_func_, read_mode);
386 std::optional<Value>
GetOptional(
const Key& key) {
return cache_->GetOptional(key, update_func_); }
388 void InvalidateByKey(
const Key& key) { cache_->InvalidateByKey(key); }
394 std::shared_ptr<Cache>
GetCache() {
return cache_; }
397 std::shared_ptr<Cache> cache_;
398 typename Cache::UpdateValueFunc update_func_;
401template <
typename Key,
typename Value,
typename Hash,
typename Equal>
402void ExpirableLruCache<Key, Value, Hash, Equal>::Write(
dump::
Writer& writer)
const {
403 utils::impl::UpdateGlobalTime();
407template <
typename Key,
typename Value,
typename Hash,
typename Equal>
408void ExpirableLruCache<Key, Value, Hash, Equal>::Read(
dump::
Reader& reader) {
409 utils::impl::UpdateGlobalTime();
413template <
typename Key,
typename Value,
typename Hash,
typename Equal>
414void ExpirableLruCache<Key, Value, Hash, Equal>::
SetDumper(std::shared_ptr<
dump::Dumper> dumper) {
415 lru_.SetDumper(std::move(dumper));
418template <
typename Key,
typename Value,
typename Hash,
typename Equal>
419void DumpMetric(
utils::statistics::Writer& writer,
const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
420 writer[
"current-documents-count"] = cache.GetSizeApproximate();
421 writer = cache.GetStatistics();