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>
20#include <userver/utils/statistics/writer.hpp>
22USERVER_NAMESPACE_BEGIN
28template <
typename Value>
29struct ExpirableValue final {
31 std::chrono::steady_clock::time_point update_time;
34template <
typename Value>
35void Write(
dump::
Writer& writer,
const impl::ExpirableValue<Value>& value) {
36 const auto [now, steady_now] = utils::
impl::GetGlobalTime();
37 writer.Write(value.value);
38 writer.Write(value.update_time - steady_now + now);
41template <
typename Value>
42impl::ExpirableValue<Value> Read(
dump::
Reader& reader,
dump::
To<impl::ExpirableValue<Value>>) {
43 const auto [now, steady_now] = utils::
impl::GetGlobalTime();
45 return impl::ExpirableValue<
46 Value>{reader.Read<Value>(), reader
.Read<std
::chrono
::system_clock
::time_point
>() - now + steady_now};
58template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
59class ExpirableLruCache final {
61 using UpdateValueFunc = std::function<Value(
const Key&)>;
71 ExpirableLruCache(size_t ways, size_t way_size,
const Hash& hash = Hash(),
const Equal& equal = Equal());
79 std::chrono::milliseconds GetMaxLifetime()
const noexcept;
81 void SetMaxLifetime(std::chrono::milliseconds max_lifetime);
84
85
86
87
91
92
93
94
95 Value
Get(
const Key& key,
const UpdateValueFunc& update_func,
ReadMode read_mode =
ReadMode::kUseCache);
98
99
100
101
102
103 std::optional<Value>
GetOptional(
const Key& key,
const UpdateValueFunc& update_func);
106
107
108
109
113
114
115
116
120
121
124 std::optional<impl::ExpirableValue<Value>> GetOptionalNoUpdateWithLastUpdateTime(
const Key& key);
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;
141 template <
typename Predicate>
156 bool IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
const;
158 bool ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
161 cache::NWayLRU<Key, impl::ExpirableValue<Value>, Hash, Equal> lru_;
162 std::atomic<std::chrono::milliseconds> max_lifetime_{std::chrono::milliseconds(0)};
163 std::atomic<BackgroundUpdateMode> background_update_mode_{BackgroundUpdateMode::kDisabled};
164 impl::ExpirableLruCacheStatistics stats_;
165 concurrent::MutexSet<Key, Hash, Equal> mutex_set_;
166 utils::
impl::WaitTokenStorage wait_token_storage_;
169template <
typename Key,
typename Value,
typename Hash,
typename Equal>
175 : lru_(ways, way_size, hash, equal),
176 mutex_set_{ways, way_size, hash, equal}
179template <
typename Key,
typename Value,
typename Hash,
typename Equal>
180ExpirableLruCache<Key, Value, Hash, Equal>::~ExpirableLruCache() {
181 wait_token_storage_.WaitForAllTokens();
184template <
typename Key,
typename Value,
typename Hash,
typename Equal>
185void ExpirableLruCache<Key, Value, Hash, Equal>::
SetWaySize(size_t way_size) {
186 lru_.UpdateWaySize(way_size);
189template <
typename Key,
typename Value,
typename Hash,
typename Equal>
190std::chrono::milliseconds ExpirableLruCache<Key, Value, Hash, Equal>::GetMaxLifetime()
const noexcept {
191 return max_lifetime_.load();
194template <
typename Key,
typename Value,
typename Hash,
typename Equal>
195void ExpirableLruCache<Key, Value, Hash, Equal>::SetMaxLifetime(std::chrono::milliseconds max_lifetime) {
196 max_lifetime_ = max_lifetime;
199template <
typename Key,
typename Value,
typename Hash,
typename Equal>
200void ExpirableLruCache<Key, Value, Hash, Equal>::
SetBackgroundUpdate(BackgroundUpdateMode background_update) {
201 background_update_mode_ = background_update;
204template <
typename Key,
typename Value,
typename Hash,
typename Equal>
205Value ExpirableLruCache<
209 Equal>::
Get(
const Key& key,
const UpdateValueFunc& update_func,
ReadMode read_mode) {
213 return std::move(*opt_old_value);
216 auto mutex = mutex_set_.GetMutexForKey(key);
217 const std::lock_guard lock(mutex);
220 auto old_value = lru_.Get(key);
221 if (old_value && !IsExpired(old_value->update_time, now)) {
222 return std::move(old_value->value);
225 auto value = update_func(key);
226 if (read_mode ==
ReadMode::kUseCache) {
227 lru_.Put(key, {value, now});
232template <
typename Key,
typename Value,
typename Hash,
typename Equal>
233std::optional<Value> ExpirableLruCache<
237 Equal>::
GetOptional(
const Key& key,
const UpdateValueFunc& update_func) {
239 auto old_value = lru_.Get(key);
242 if (!IsExpired(old_value->update_time, now)) {
243 impl::CacheHit(stats_);
245 if (ShouldUpdate(old_value->update_time, now)) {
249 return std::move(old_value->value);
251 impl::CacheStale(stats_);
254 impl::CacheMiss(stats_);
259template <
typename Key,
typename Value,
typename Hash,
typename Equal>
261 auto old_value = lru_.Get(key);
264 impl::CacheHit(stats_);
265 return old_value->value;
267 impl::CacheMiss(stats_);
272template <
typename Key,
typename Value,
typename Hash,
typename Equal>
273std::optional<Value> ExpirableLruCache<
279 auto old_value = lru_.Get(key);
282 impl::CacheHit(stats_);
284 if (ShouldUpdate(old_value->update_time, now)) {
288 return old_value->value;
290 impl::CacheMiss(stats_);
295template <
typename Key,
typename Value,
typename Hash,
typename Equal>
296std::optional<impl::ExpirableValue<Value>> ExpirableLruCache<
300 Equal>::GetOptionalNoUpdateWithLastUpdateTime(
const Key& key) {
302 const auto old_value = lru_.Get(key);
304 if (!IsExpired(old_value->update_time, now)) {
305 impl::CacheHit(stats_);
308 impl::CacheStale(stats_);
311 impl::CacheMiss(stats_);
315template <
typename Key,
typename Value,
typename Hash,
typename Equal>
317 auto value_with_update_time = GetOptionalNoUpdateWithLastUpdateTime(key);
318 if (value_with_update_time.has_value()) {
319 return value_with_update_time->value;
324template <
typename Key,
typename Value,
typename Hash,
typename Equal>
325void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key,
const Value& value) {
329template <
typename Key,
typename Value,
typename Hash,
typename Equal>
330void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key, Value&& value) {
334template <
typename Key,
typename Value,
typename Hash,
typename Equal>
335const impl::ExpirableLruCacheStatistics& ExpirableLruCache<Key, Value, Hash, Equal>::GetStatistics()
const {
339template <
typename Key,
typename Value,
typename Hash,
typename Equal>
340size_t ExpirableLruCache<Key, Value, Hash, Equal>::GetSizeApproximate()
const {
341 return lru_.GetSize();
344template <
typename Key,
typename Value,
typename Hash,
typename Equal>
345void ExpirableLruCache<Key, Value, Hash, Equal>::
Invalidate() {
349template <
typename Key,
typename Value,
typename Hash,
typename Equal>
351 lru_.InvalidateByKey(key);
355template <
typename Predicate>
356void ExpirableLruCache<Key, Value, Hash, Equal>::
InvalidateByKeyIf(
const Key& key, Predicate pred) {
359 auto mutex = mutex_set_.GetMutexForKey(key);
360 const std::lock_guard lock(mutex);
361 const auto cur_value = lru_.Get(key);
363 if (cur_value.has_value() && !IsExpired(cur_value->update_time, now) && pred(cur_value->value)) {
368template <
typename Key,
typename Value,
typename Hash,
typename Equal>
369void ExpirableLruCache<Key, Value, Hash, Equal>::
UpdateInBackground(
const Key& key, UpdateValueFunc update_func) {
370 impl::CacheBackgroundUpdate(stats_);
374 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(),
this, key, update_func = std::move(update_func)] {
375 auto mutex = mutex_set_.GetMutexForKey(key);
376 const std::unique_lock lock(mutex, std::try_to_lock);
383 auto value = update_func(key);
384 lru_.Put(key, {value, now});
389template <
typename Key,
typename Value,
typename Hash,
typename Equal>
390bool ExpirableLruCache<
394 Equal>::IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
396 auto max_lifetime = max_lifetime_.load();
397 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
400template <
typename Key,
typename Value,
typename Hash,
typename Equal>
401bool ExpirableLruCache<
405 Equal>::ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
407 auto max_lifetime = max_lifetime_.load();
408 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) && max_lifetime.count() != 0 &&
409 update_time + max_lifetime / 2 < now;
412template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
413class LruCacheWrapper final {
415 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
416 using ReadMode =
typename Cache::ReadMode;
418 LruCacheWrapper(std::shared_ptr<Cache> cache,
typename Cache::UpdateValueFunc update_func)
419 : cache_(std::move(cache)),
420 update_func_(std::move(update_func))
424 Value
Get(
const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
425 return cache_->Get(key, update_func_, read_mode);
429 std::optional<Value>
GetOptional(
const Key& key) {
return cache_->GetOptional(key, update_func_); }
431 void InvalidateByKey(
const Key& key) { cache_->InvalidateByKey(key); }
433 template <
typename Predicate>
434 void InvalidateByKeyIf(
const Key& key, Predicate pred) {
435 cache_->InvalidateByKeyIf(key, pred);
442 std::shared_ptr<Cache>
GetCache() {
return cache_; }
445 std::shared_ptr<Cache> cache_;
446 typename Cache::UpdateValueFunc update_func_;
449template <
typename Key,
typename Value,
typename Hash,
typename Equal>
450void ExpirableLruCache<Key, Value, Hash, Equal>::Write(
dump::
Writer& writer)
const {
451 utils::
impl::UpdateGlobalTime();
455template <
typename Key,
typename Value,
typename Hash,
typename Equal>
456void ExpirableLruCache<Key, Value, Hash, Equal>::Read(
dump::
Reader& reader) {
457 utils::
impl::UpdateGlobalTime();
461template <
typename Key,
typename Value,
typename Hash,
typename Equal>
462void ExpirableLruCache<Key, Value, Hash, Equal>::
SetDumper(std::shared_ptr<
dump::Dumper> dumper) {
463 lru_.SetDumper(std::move(dumper));
466template <
typename Key,
typename Value,
typename Hash,
typename Equal>
467void DumpMetric(utils::statistics::Writer& writer,
const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
468 writer
["current-documents-count"] = cache.GetSizeApproximate();
469 writer = cache.GetStatistics();