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>
42 dump::To<
impl::ExpirableValue<Value>>) {
43 const auto [now, steady_now] =
utils::impl::GetGlobalTime();
45 return impl::ExpirableValue<Value>{
47 reader.Read<std::chrono::system_clock::time_point>() - now + steady_now};
59template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
60 typename Equal = std::equal_to<Key>>
61class ExpirableLruCache final {
63 using UpdateValueFunc = std::function<Value(
const Key&)>;
71 ExpirableLruCache(size_t ways, size_t way_size,
const Hash& hash = Hash(),
72 const Equal& equal = Equal());
76 void SetWaySize(size_t way_size);
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,
98
99
100
101
102
104 const UpdateValueFunc& update_func);
107
108
109
110
114
115
116
117
119 const Key& key,
const UpdateValueFunc& update_func);
122
123
126 void Put(
const Key& key,
const Value& value);
128 void Put(
const Key& key, Value&& value);
130 const impl::ExpirableLruCacheStatistics& GetStatistics()
const;
132 size_t GetSizeApproximate()
const;
152 bool IsExpired(std::chrono::steady_clock::time_point update_time,
153 std::chrono::steady_clock::time_point now)
const;
155 bool ShouldUpdate(std::chrono::steady_clock::time_point update_time,
156 std::chrono::steady_clock::time_point now)
const;
158 cache::NWayLRU<Key,
impl::ExpirableValue<Value>, Hash, Equal> lru_;
159 std::atomic<std::chrono::milliseconds> max_lifetime_{
160 std::chrono::milliseconds(0)};
161 std::atomic<BackgroundUpdateMode> background_update_mode_{
162 BackgroundUpdateMode::kDisabled};
163 impl::ExpirableLruCacheStatistics stats_;
164 concurrent::MutexSet<Key, Hash, Equal> mutex_set_;
165 utils::impl::WaitTokenStorage wait_token_storage_;
168template <
typename Key,
typename Value,
typename Hash,
typename Equal>
169ExpirableLruCache<Key, Value, Hash, Equal>::ExpirableLruCache(
170 size_t ways, size_t way_size,
const Hash& hash,
const Equal& equal)
171 : lru_(ways, way_size, hash, equal),
172 mutex_set_{ways, way_size, hash, equal} {}
174template <
typename Key,
typename Value,
typename Hash,
typename Equal>
175ExpirableLruCache<Key, Value, Hash, Equal>::~ExpirableLruCache() {
176 wait_token_storage_.WaitForAllTokens();
179template <
typename Key,
typename Value,
typename Hash,
typename Equal>
180void ExpirableLruCache<Key, Value, Hash, Equal>::SetWaySize(size_t way_size) {
181 lru_.UpdateWaySize(way_size);
184template <
typename Key,
typename Value,
typename Hash,
typename Equal>
185std::chrono::milliseconds
186ExpirableLruCache<Key, Value, Hash, Equal>::GetMaxLifetime()
const noexcept {
187 return max_lifetime_.load();
190template <
typename Key,
typename Value,
typename Hash,
typename Equal>
191void ExpirableLruCache<Key, Value, Hash, Equal>::SetMaxLifetime(
192 std::chrono::milliseconds max_lifetime) {
193 max_lifetime_ = max_lifetime;
196template <
typename Key,
typename Value,
typename Hash,
typename Equal>
198 BackgroundUpdateMode background_update) {
199 background_update_mode_ = background_update;
202template <
typename Key,
typename Value,
typename Hash,
typename Equal>
203Value ExpirableLruCache<Key, Value, Hash, Equal>::
Get(
204 const Key& key,
const UpdateValueFunc& update_func,
ReadMode read_mode) {
208 return std::move(*opt_old_value);
211 auto mutex = mutex_set_.GetMutexForKey(key);
212 std::lock_guard lock(mutex);
215 auto old_value = lru_.Get(key);
216 if (old_value && !IsExpired(old_value->update_time, now)) {
217 return std::move(old_value->value);
220 auto value = update_func(key);
221 if (read_mode ==
ReadMode::kUseCache) {
222 lru_.Put(key, {value, now});
227template <
typename Key,
typename Value,
typename Hash,
typename Equal>
228std::optional<Value> ExpirableLruCache<Key, Value, Hash, Equal>::
GetOptional(
229 const Key& key,
const UpdateValueFunc& update_func) {
231 auto old_value = lru_.Get(key);
234 if (!IsExpired(old_value->update_time, now)) {
235 impl::CacheHit(stats_);
237 if (ShouldUpdate(old_value->update_time, now)) {
241 return std::move(old_value->value);
243 impl::CacheStale(stats_);
246 impl::CacheMiss(stats_);
251template <
typename Key,
typename Value,
typename Hash,
typename Equal>
255 auto old_value = lru_.Get(key);
258 impl::CacheHit(stats_);
259 return old_value->value;
261 impl::CacheMiss(stats_);
266template <
typename Key,
typename Value,
typename Hash,
typename Equal>
269 const Key& key,
const UpdateValueFunc& update_func) {
271 auto old_value = lru_.Get(key);
274 impl::CacheHit(stats_);
276 if (ShouldUpdate(old_value->update_time, now)) {
280 return old_value->value;
282 impl::CacheMiss(stats_);
287template <
typename Key,
typename Value,
typename Hash,
typename Equal>
292 auto old_value = lru_.Get(key);
295 if (!IsExpired(old_value->update_time, now)) {
296 impl::CacheHit(stats_);
298 return old_value->value;
300 impl::CacheStale(stats_);
303 impl::CacheMiss(stats_);
308template <
typename Key,
typename Value,
typename Hash,
typename Equal>
309void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key,
310 const Value& value) {
314template <
typename Key,
typename Value,
typename Hash,
typename Equal>
315void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key,
320template <
typename Key,
typename Value,
typename Hash,
typename Equal>
321const impl::ExpirableLruCacheStatistics&
322ExpirableLruCache<Key, Value, Hash, Equal>::GetStatistics()
const {
326template <
typename Key,
typename Value,
typename Hash,
typename Equal>
327size_t ExpirableLruCache<Key, Value, Hash, Equal>::GetSizeApproximate()
const {
328 return lru_.GetSize();
331template <
typename Key,
typename Value,
typename Hash,
typename Equal>
332void ExpirableLruCache<Key, Value, Hash, Equal>::
Invalidate() {
336template <
typename Key,
typename Value,
typename Hash,
typename Equal>
339 lru_.InvalidateByKey(key);
342template <
typename Key,
typename Value,
typename Hash,
typename Equal>
344 const Key& key, UpdateValueFunc update_func) {
345 stats_.total.background_updates++;
346 stats_.recent.GetCurrentCounter().background_updates++;
349 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(),
this, key,
350 update_func = std::move(update_func)] {
351 auto mutex = mutex_set_.GetMutexForKey(key);
352 std::unique_lock lock(mutex, std::try_to_lock);
359 auto value = update_func(key);
360 lru_.Put(key, {value, now});
364template <
typename Key,
typename Value,
typename Hash,
typename Equal>
365bool ExpirableLruCache<Key, Value, Hash, Equal>::IsExpired(
366 std::chrono::steady_clock::time_point update_time,
367 std::chrono::steady_clock::time_point now)
const {
368 auto max_lifetime = max_lifetime_.load();
369 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
372template <
typename Key,
typename Value,
typename Hash,
typename Equal>
373bool ExpirableLruCache<Key, Value, Hash, Equal>::ShouldUpdate(
374 std::chrono::steady_clock::time_point update_time,
375 std::chrono::steady_clock::time_point now)
const {
376 auto max_lifetime = max_lifetime_.load();
377 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) &&
378 max_lifetime.count() != 0 && update_time + max_lifetime / 2 < now;
381template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
382 typename Equal = std::equal_to<Key>>
383class LruCacheWrapper final {
385 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
386 using ReadMode =
typename Cache::ReadMode;
388 LruCacheWrapper(std::shared_ptr<Cache> cache,
389 typename Cache::UpdateValueFunc update_func)
390 : cache_(std::move(cache)), update_func_(std::move(update_func)) {}
393 Value
Get(
const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
394 return cache_->Get(key, update_func_, read_mode);
399 return cache_->GetOptional(key, update_func_);
402 void InvalidateByKey(
const Key& key) { cache_->InvalidateByKey(key); }
406 cache_->UpdateInBackground(key, update_func_);
410 std::shared_ptr<Cache>
GetCache() {
return cache_; }
413 std::shared_ptr<Cache> cache_;
414 typename Cache::UpdateValueFunc update_func_;
417template <
typename Key,
typename Value,
typename Hash,
typename Equal>
418void ExpirableLruCache<Key, Value, Hash, Equal>::Write(
420 utils::impl::UpdateGlobalTime();
424template <
typename Key,
typename Value,
typename Hash,
typename Equal>
425void ExpirableLruCache<Key, Value, Hash, Equal>::Read(
dump::
Reader& reader) {
426 utils::impl::UpdateGlobalTime();
430template <
typename Key,
typename Value,
typename Hash,
typename Equal>
431void ExpirableLruCache<Key, Value, Hash, Equal>::
SetDumper(
432 std::shared_ptr<
dump::Dumper> dumper) {
433 lru_.SetDumper(std::move(dumper));
436template <
typename Key,
typename Value,
typename Hash,
typename Equal>
437void DumpMetric(
utils::statistics::Writer& writer,
438 const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
439 writer[
"current-documents-count"] = cache.GetSizeApproximate();
440 writer = cache.GetStatistics();