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};
57template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
58class ExpirableLruCache final {
61 using UpdateValueFunc = std::function<Value(
const Key&)>;
75 ExpirableLruCache(size_t ways, size_t way_size,
const Hash& hash = Hash(),
const Equal& equal = Equal());
130 std::optional<Value>
GetOptional(
const Key& key,
const UpdateValueFunc& update_func);
168 void Put(
const Key& key,
const Value& value);
175 void Put(
const Key& key, Value&& value);
201 template <
typename Predicate>
231 bool IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
const;
233 bool ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
236 cache::NWayLRU<Key, impl::ExpirableValue<Value>, Hash, Equal> lru_;
237 std::atomic<std::chrono::milliseconds> max_lifetime_{std::chrono::milliseconds(0)};
238 std::atomic<BackgroundUpdateMode> background_update_mode_{BackgroundUpdateMode::kDisabled};
239 impl::ExpirableLruCacheStatistics stats_;
240 concurrent::MutexSet<Key, Hash, Equal> mutex_set_;
241 utils::impl::WaitTokenStorage wait_token_storage_;
244template <
typename Key,
typename Value,
typename Hash,
typename Equal>
250 : lru_(ways, way_size, hash, equal),
251 mutex_set_{ways, way_size, hash, equal}
254template <
typename Key,
typename Value,
typename Hash,
typename Equal>
256 wait_token_storage_.WaitForAllTokens();
259template <
typename Key,
typename Value,
typename Hash,
typename Equal>
260void ExpirableLruCache<Key, Value, Hash, Equal>::
SetWaySize(size_t way_size) {
261 lru_.UpdateWaySize(way_size);
264template <
typename Key,
typename Value,
typename Hash,
typename Equal>
265std::chrono::milliseconds ExpirableLruCache<Key, Value, Hash, Equal>::
GetMaxLifetime()
const noexcept {
266 return max_lifetime_.load();
269template <
typename Key,
typename Value,
typename Hash,
typename Equal>
270void ExpirableLruCache<Key, Value, Hash, Equal>::
SetMaxLifetime(std::chrono::milliseconds max_lifetime) {
271 max_lifetime_ = max_lifetime;
274template <
typename Key,
typename Value,
typename Hash,
typename Equal>
275void ExpirableLruCache<Key, Value, Hash, Equal>::
SetBackgroundUpdate(BackgroundUpdateMode background_update) {
276 background_update_mode_ = background_update;
279template <
typename Key,
typename Value,
typename Hash,
typename Equal>
280Value ExpirableLruCache<
284 Equal>::
Get(
const Key& key,
const UpdateValueFunc& update_func,
ReadMode read_mode) {
288 return std::move(*opt_old_value);
291 auto mutex = mutex_set_.GetMutexForKey(key);
292 const std::lock_guard lock(mutex);
295 auto old_value = lru_.Get(key);
296 if (old_value && !IsExpired(old_value->update_time, now)) {
297 return std::move(old_value->value);
300 auto value = update_func(key);
301 if (read_mode ==
ReadMode::kUseCache) {
302 lru_.Put(key, {value, now});
307template <
typename Key,
typename Value,
typename Hash,
typename Equal>
308std::optional<Value> ExpirableLruCache<
312 Equal>::
GetOptional(
const Key& key,
const UpdateValueFunc& update_func) {
314 auto old_value = lru_.Get(key);
317 if (!IsExpired(old_value->update_time, now)) {
318 impl::CacheHit(stats_);
320 if (ShouldUpdate(old_value->update_time, now)) {
324 return std::move(old_value->value);
326 impl::CacheStale(stats_);
329 impl::CacheMiss(stats_);
334template <
typename Key,
typename Value,
typename Hash,
typename Equal>
336 auto old_value = lru_.Get(key);
339 impl::CacheHit(stats_);
340 return old_value->value;
342 impl::CacheMiss(stats_);
347template <
typename Key,
typename Value,
typename Hash,
typename Equal>
348std::optional<Value> ExpirableLruCache<
354 auto old_value = lru_.Get(key);
357 impl::CacheHit(stats_);
359 if (ShouldUpdate(old_value->update_time, now)) {
363 return old_value->value;
365 impl::CacheMiss(stats_);
370template <
typename Key,
typename Value,
typename Hash,
typename Equal>
371std::optional<impl::ExpirableValue<Value>> ExpirableLruCache<
377 const auto old_value = lru_.Get(key);
379 if (!IsExpired(old_value->update_time, now)) {
380 impl::CacheHit(stats_);
383 impl::CacheStale(stats_);
386 impl::CacheMiss(stats_);
390template <
typename Key,
typename Value,
typename Hash,
typename Equal>
393 if (value_with_update_time.has_value()) {
394 return value_with_update_time->value;
399template <
typename Key,
typename Value,
typename Hash,
typename Equal>
400void ExpirableLruCache<Key, Value, Hash, Equal>::
Put(
const Key& key,
const Value& value) {
404template <
typename Key,
typename Value,
typename Hash,
typename Equal>
405void ExpirableLruCache<Key, Value, Hash, Equal>::
Put(
const Key& key, Value&& value) {
409template <
typename Key,
typename Value,
typename Hash,
typename Equal>
410const impl::ExpirableLruCacheStatistics& ExpirableLruCache<Key, Value, Hash, Equal>::
GetStatistics()
const {
414template <
typename Key,
typename Value,
typename Hash,
typename Equal>
416 return lru_.GetSize();
419template <
typename Key,
typename Value,
typename Hash,
typename Equal>
420void ExpirableLruCache<Key, Value, Hash, Equal>::
Invalidate() {
424template <
typename Key,
typename Value,
typename Hash,
typename Equal>
426 lru_.InvalidateByKey(key);
430template <
typename Predicate>
431void ExpirableLruCache<Key, Value, Hash, Equal>::
InvalidateByKeyIf(
const Key& key, Predicate pred) {
434 auto mutex = mutex_set_.GetMutexForKey(key);
435 const std::lock_guard lock(mutex);
436 const auto cur_value = lru_.Get(key);
438 if (cur_value.has_value() && !IsExpired(cur_value->update_time, now) && pred(cur_value->value)) {
443template <
typename Key,
typename Value,
typename Hash,
typename Equal>
444void ExpirableLruCache<Key, Value, Hash, Equal>::
UpdateInBackground(
const Key& key, UpdateValueFunc update_func) {
445 impl::CacheBackgroundUpdate(stats_);
449 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(),
this, key, update_func = std::move(update_func)] {
450 auto mutex = mutex_set_.GetMutexForKey(key);
451 const std::unique_lock lock(mutex, std::try_to_lock);
458 auto value = update_func(key);
459 lru_.Put(key, {value, now});
464template <
typename Key,
typename Value,
typename Hash,
typename Equal>
465bool ExpirableLruCache<
469 Equal>::IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
471 auto max_lifetime = max_lifetime_.load();
472 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
475template <
typename Key,
typename Value,
typename Hash,
typename Equal>
476bool ExpirableLruCache<
480 Equal>::ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
482 auto max_lifetime = max_lifetime_.load();
483 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) && max_lifetime.count() != 0 &&
484 update_time + max_lifetime / 2 < now;
491template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
492class LruCacheWrapper final {
495 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
497 using ReadMode =
typename Cache::ReadMode;
507 LruCacheWrapper(std::shared_ptr<Cache> cache,
typename Cache::UpdateValueFunc update_func)
508 : cache_(std::move(cache)),
509 update_func_(std::move(update_func))
517 Value
Get(
const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
518 return cache_->Get(key, update_func_, read_mode);
525 std::optional<Value>
GetOptional(
const Key& key) {
return cache_->GetOptional(key, update_func_); }
537 template <
typename Predicate>
539 cache_->InvalidateByKeyIf(key, pred);
551 std::shared_ptr<Cache>
GetCache() {
return cache_; }
554 std::shared_ptr<Cache> cache_;
555 typename Cache::UpdateValueFunc update_func_;
558template <
typename Key,
typename Value,
typename Hash,
typename Equal>
560 utils::impl::UpdateGlobalTime();
564template <
typename Key,
typename Value,
typename Hash,
typename Equal>
566 utils::impl::UpdateGlobalTime();
570template <
typename Key,
typename Value,
typename Hash,
typename Equal>
571void ExpirableLruCache<Key, Value, Hash, Equal>::
SetDumper(std::shared_ptr<
dump::Dumper> dumper) {
572 lru_.SetDumper(std::move(dumper));
575template <
typename Key,
typename Value,
typename Hash,
typename Equal>
576void DumpMetric(
utils::statistics::Writer& writer,
const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
577 writer
["current-documents-count"] = cache.GetSizeApproximate();
578 writer = cache.GetStatistics();