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<Value>{
46 .value = reader.Read<Value>(),
47 .update_time = reader
.Read<std
::chrono
::system_clock
::time_point
>() - now + steady_now,
59template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
60class ExpirableLruCache final {
63 using UpdateValueFunc = std::function<Value(
const Key&)>;
77 ExpirableLruCache(size_t ways, size_t way_size,
const Hash& hash = Hash(),
const Equal& equal = Equal());
132 std::optional<Value>
GetOptional(
const Key& key,
const UpdateValueFunc& update_func);
170 void Put(
const Key& key,
const Value& value);
177 void Put(
const Key& key, Value&& value);
203 template <
typename Predicate>
233 bool IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
const;
235 bool ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
238 cache::NWayLRU<Key, impl::ExpirableValue<Value>, Hash, Equal> lru_;
239 std::atomic<std::chrono::milliseconds> max_lifetime_{std::chrono::milliseconds(0)};
240 std::atomic<BackgroundUpdateMode> background_update_mode_{BackgroundUpdateMode::kDisabled};
241 impl::ExpirableLruCacheStatistics stats_;
242 concurrent::MutexSet<Key, Hash, Equal> mutex_set_;
243 utils::impl::WaitTokenStorage wait_token_storage_;
246template <
typename Key,
typename Value,
typename Hash,
typename Equal>
252 : lru_(ways, way_size, hash, equal),
253 mutex_set_{ways, way_size, hash, equal}
256template <
typename Key,
typename Value,
typename Hash,
typename Equal>
258 wait_token_storage_.WaitForAllTokens();
261template <
typename Key,
typename Value,
typename Hash,
typename Equal>
262void ExpirableLruCache<Key, Value, Hash, Equal>::
SetWaySize(size_t way_size) {
263 lru_.UpdateWaySize(way_size);
266template <
typename Key,
typename Value,
typename Hash,
typename Equal>
267std::chrono::milliseconds ExpirableLruCache<Key, Value, Hash, Equal>::
GetMaxLifetime()
const noexcept {
268 return max_lifetime_.load();
271template <
typename Key,
typename Value,
typename Hash,
typename Equal>
272void ExpirableLruCache<Key, Value, Hash, Equal>::
SetMaxLifetime(std::chrono::milliseconds max_lifetime) {
273 max_lifetime_ = max_lifetime;
276template <
typename Key,
typename Value,
typename Hash,
typename Equal>
277void ExpirableLruCache<Key, Value, Hash, Equal>::
SetBackgroundUpdate(BackgroundUpdateMode background_update) {
278 background_update_mode_ = background_update;
281template <
typename Key,
typename Value,
typename Hash,
typename Equal>
282Value ExpirableLruCache<
286 Equal>::
Get(
const Key& key,
const UpdateValueFunc& update_func,
ReadMode read_mode) {
290 return std::move(*opt_old_value);
293 auto mutex = mutex_set_.GetMutexForKey(key);
294 const std::lock_guard lock(mutex);
297 auto old_value = lru_.Get(key);
298 if (old_value && !IsExpired(old_value->update_time, now)) {
299 return std::move(old_value->value);
302 auto value = update_func(key);
303 if (read_mode ==
ReadMode::kUseCache) {
304 lru_.Put(key, {.value = value, .update_time = now});
309template <
typename Key,
typename Value,
typename Hash,
typename Equal>
310std::optional<Value> ExpirableLruCache<
314 Equal>::
GetOptional(
const Key& key,
const UpdateValueFunc& update_func) {
316 auto old_value = lru_.Get(key);
319 if (!IsExpired(old_value->update_time, now)) {
320 impl::CacheHit(stats_);
322 if (ShouldUpdate(old_value->update_time, now)) {
326 return std::move(old_value->value);
328 impl::CacheStale(stats_);
331 impl::CacheMiss(stats_);
336template <
typename Key,
typename Value,
typename Hash,
typename Equal>
338 auto old_value = lru_.Get(key);
341 impl::CacheHit(stats_);
342 return old_value->value;
344 impl::CacheMiss(stats_);
349template <
typename Key,
typename Value,
typename Hash,
typename Equal>
350std::optional<Value> ExpirableLruCache<
356 auto old_value = lru_.Get(key);
359 impl::CacheHit(stats_);
361 if (ShouldUpdate(old_value->update_time, now)) {
365 return old_value->value;
367 impl::CacheMiss(stats_);
372template <
typename Key,
typename Value,
typename Hash,
typename Equal>
373std::optional<impl::ExpirableValue<Value>> ExpirableLruCache<
379 const auto old_value = lru_.Get(key);
381 if (!IsExpired(old_value->update_time, now)) {
382 impl::CacheHit(stats_);
385 impl::CacheStale(stats_);
388 impl::CacheMiss(stats_);
392template <
typename Key,
typename Value,
typename Hash,
typename Equal>
395 if (value_with_update_time.has_value()) {
396 return value_with_update_time->value;
401template <
typename Key,
typename Value,
typename Hash,
typename Equal>
402void ExpirableLruCache<Key, Value, Hash, Equal>::
Put(
const Key& key,
const Value& value) {
406template <
typename Key,
typename Value,
typename Hash,
typename Equal>
407void ExpirableLruCache<Key, Value, Hash, Equal>::
Put(
const Key& key, Value&& value) {
411template <
typename Key,
typename Value,
typename Hash,
typename Equal>
412const impl::ExpirableLruCacheStatistics& ExpirableLruCache<Key, Value, Hash, Equal>::
GetStatistics()
const {
416template <
typename Key,
typename Value,
typename Hash,
typename Equal>
418 return lru_.GetSize();
421template <
typename Key,
typename Value,
typename Hash,
typename Equal>
422void ExpirableLruCache<Key, Value, Hash, Equal>::
Invalidate() {
426template <
typename Key,
typename Value,
typename Hash,
typename Equal>
428 lru_.InvalidateByKey(key);
432template <
typename Predicate>
433void ExpirableLruCache<Key, Value, Hash, Equal>::
InvalidateByKeyIf(
const Key& key, Predicate pred) {
436 auto mutex = mutex_set_.GetMutexForKey(key);
437 const std::lock_guard lock(mutex);
438 const auto cur_value = lru_.Get(key);
440 if (cur_value.has_value() && !IsExpired(cur_value->update_time, now) && pred(cur_value->value)) {
445template <
typename Key,
typename Value,
typename Hash,
typename Equal>
446void ExpirableLruCache<Key, Value, Hash, Equal>::
UpdateInBackground(
const Key& key, UpdateValueFunc update_func) {
447 impl::CacheBackgroundUpdate(stats_);
451 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(),
this, key, update_func = std::move(update_func)] {
452 auto mutex = mutex_set_.GetMutexForKey(key);
453 const std::unique_lock lock(mutex, std::try_to_lock);
460 auto value = update_func(key);
461 lru_.Put(key, {.value = value, .update_time = now});
466template <
typename Key,
typename Value,
typename Hash,
typename Equal>
467bool ExpirableLruCache<
471 Equal>::IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
473 auto max_lifetime = max_lifetime_.load();
474 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
477template <
typename Key,
typename Value,
typename Hash,
typename Equal>
478bool ExpirableLruCache<
482 Equal>::ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
484 auto max_lifetime = max_lifetime_.load();
485 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) && max_lifetime.count() != 0 &&
486 update_time + max_lifetime / 2 < now;
493template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
494class LruCacheWrapper final {
497 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
499 using ReadMode =
typename Cache::ReadMode;
509 LruCacheWrapper(std::shared_ptr<Cache> cache,
typename Cache::UpdateValueFunc update_func)
510 : cache_(std::move(cache)),
511 update_func_(std::move(update_func))
519 Value
Get(
const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
520 return cache_->Get(key, update_func_, read_mode);
527 std::optional<Value>
GetOptional(
const Key& key) {
return cache_->GetOptional(key, update_func_); }
539 template <
typename Predicate>
541 cache_->InvalidateByKeyIf(key, pred);
553 std::shared_ptr<Cache>
GetCache() {
return cache_; }
556 std::shared_ptr<Cache> cache_;
557 typename Cache::UpdateValueFunc update_func_;
560template <
typename Key,
typename Value,
typename Hash,
typename Equal>
562 utils::impl::UpdateGlobalTime();
566template <
typename Key,
typename Value,
typename Hash,
typename Equal>
568 utils::impl::UpdateGlobalTime();
572template <
typename Key,
typename Value,
typename Hash,
typename Equal>
573void ExpirableLruCache<Key, Value, Hash, Equal>::
SetDumper(std::shared_ptr<
dump::Dumper> dumper) {
574 lru_.SetDumper(std::move(dumper));
577template <
typename Key,
typename Value,
typename Hash,
typename Equal>
578void DumpMetric(
utils::statistics::Writer& writer,
const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
579 writer
["current-documents-count"] = cache.GetSizeApproximate();
580 writer = cache.GetStatistics();