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 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>
176 : lru_(ways, way_size, hash, equal), mutex_set_{ways, way_size, hash, equal} {}
178template <
typename Key,
typename Value,
typename Hash,
typename Equal>
179ExpirableLruCache<Key, Value, Hash, Equal>::~ExpirableLruCache() {
180 wait_token_storage_.WaitForAllTokens();
183template <
typename Key,
typename Value,
typename Hash,
typename Equal>
184void ExpirableLruCache<Key, Value, Hash, Equal>::
SetWaySize(size_t way_size) {
185 lru_.UpdateWaySize(way_size);
188template <
typename Key,
typename Value,
typename Hash,
typename Equal>
189std::chrono::milliseconds ExpirableLruCache<Key, Value, Hash, Equal>::GetMaxLifetime()
const noexcept {
190 return max_lifetime_.load();
193template <
typename Key,
typename Value,
typename Hash,
typename Equal>
194void ExpirableLruCache<Key, Value, Hash, Equal>::SetMaxLifetime(std::chrono::milliseconds max_lifetime) {
195 max_lifetime_ = max_lifetime;
198template <
typename Key,
typename Value,
typename Hash,
typename Equal>
199void ExpirableLruCache<Key, Value, Hash, Equal>::
SetBackgroundUpdate(BackgroundUpdateMode background_update) {
200 background_update_mode_ = background_update;
203template <
typename Key,
typename Value,
typename Hash,
typename Equal>
204Value ExpirableLruCache<Key, Value, Hash, Equal>::
Get(
206 const UpdateValueFunc& update_func,
212 return std::move(*opt_old_value);
215 auto mutex = mutex_set_.GetMutexForKey(key);
216 const std::lock_guard lock(mutex);
219 auto old_value = lru_.Get(key);
220 if (old_value && !IsExpired(old_value->update_time, now)) {
221 return std::move(old_value->value);
224 auto value = update_func(key);
225 if (read_mode ==
ReadMode::kUseCache) {
226 lru_.Put(key, {value, now});
231template <
typename Key,
typename Value,
typename Hash,
typename Equal>
233ExpirableLruCache<Key, Value, Hash, Equal>::
GetOptional(
const Key& key,
const UpdateValueFunc& update_func) {
235 auto old_value = lru_.Get(key);
238 if (!IsExpired(old_value->update_time, now)) {
239 impl::CacheHit(stats_);
241 if (ShouldUpdate(old_value->update_time, now)) {
245 return std::move(old_value->value);
247 impl::CacheStale(stats_);
250 impl::CacheMiss(stats_);
255template <
typename Key,
typename Value,
typename Hash,
typename Equal>
257 auto old_value = lru_.Get(key);
260 impl::CacheHit(stats_);
261 return old_value->value;
263 impl::CacheMiss(stats_);
268template <
typename Key,
typename Value,
typename Hash,
typename Equal>
271 const UpdateValueFunc& update_func
274 auto old_value = lru_.Get(key);
277 impl::CacheHit(stats_);
279 if (ShouldUpdate(old_value->update_time, now)) {
283 return old_value->value;
285 impl::CacheMiss(stats_);
290template <
typename Key,
typename Value,
typename Hash,
typename Equal>
291std::optional<impl::ExpirableValue<Value>>
292ExpirableLruCache<Key, Value, Hash, Equal>::GetOptionalNoUpdateWithLastUpdateTime(
const Key& key) {
294 const auto old_value = lru_.Get(key);
296 if (!IsExpired(old_value->update_time, now)) {
297 impl::CacheHit(stats_);
300 impl::CacheStale(stats_);
303 impl::CacheMiss(stats_);
307template <
typename Key,
typename Value,
typename Hash,
typename Equal>
309 auto value_with_update_time = GetOptionalNoUpdateWithLastUpdateTime(key);
310 if (value_with_update_time.has_value()) {
311 return value_with_update_time->value;
316template <
typename Key,
typename Value,
typename Hash,
typename Equal>
317void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key,
const Value& value) {
321template <
typename Key,
typename Value,
typename Hash,
typename Equal>
322void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key, Value&& value) {
326template <
typename Key,
typename Value,
typename Hash,
typename Equal>
327const impl::ExpirableLruCacheStatistics& ExpirableLruCache<Key, Value, Hash, Equal>::GetStatistics()
const {
331template <
typename Key,
typename Value,
typename Hash,
typename Equal>
332size_t ExpirableLruCache<Key, Value, Hash, Equal>::GetSizeApproximate()
const {
333 return lru_.GetSize();
336template <
typename Key,
typename Value,
typename Hash,
typename Equal>
337void ExpirableLruCache<Key, Value, Hash, Equal>::
Invalidate() {
341template <
typename Key,
typename Value,
typename Hash,
typename Equal>
343 lru_.InvalidateByKey(key);
347template <
typename Predicate>
348void ExpirableLruCache<Key, Value, Hash, Equal>::
InvalidateByKeyIf(
const Key& key, Predicate pred) {
351 auto mutex = mutex_set_.GetMutexForKey(key);
352 const std::lock_guard lock(mutex);
353 const auto cur_value = lru_.Get(key);
355 if (cur_value.has_value() && !IsExpired(cur_value->update_time, now) && pred(cur_value->value)) {
360template <
typename Key,
typename Value,
typename Hash,
typename Equal>
361void ExpirableLruCache<Key, Value, Hash, Equal>::
UpdateInBackground(
const Key& key, UpdateValueFunc update_func) {
362 impl::CacheBackgroundUpdate(stats_);
366 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(),
this, key, update_func = std::move(update_func)] {
367 auto mutex = mutex_set_.GetMutexForKey(key);
368 const std::unique_lock lock(mutex, std::try_to_lock);
375 auto value = update_func(key);
376 lru_.Put(key, {value, now});
381template <
typename Key,
typename Value,
typename Hash,
typename Equal>
382bool ExpirableLruCache<Key, Value, Hash, Equal>::IsExpired(
383 std::chrono::steady_clock::time_point update_time,
384 std::chrono::steady_clock::time_point now
386 auto max_lifetime = max_lifetime_.load();
387 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
390template <
typename Key,
typename Value,
typename Hash,
typename Equal>
391bool ExpirableLruCache<Key, Value, Hash, Equal>::ShouldUpdate(
392 std::chrono::steady_clock::time_point update_time,
393 std::chrono::steady_clock::time_point now
395 auto max_lifetime = max_lifetime_.load();
396 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) && max_lifetime.count() != 0 &&
397 update_time + max_lifetime / 2 < now;
400template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
typename Equal = std::equal_to<Key>>
401class LruCacheWrapper final {
403 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
404 using ReadMode =
typename Cache::ReadMode;
406 LruCacheWrapper(std::shared_ptr<Cache> cache,
typename Cache::UpdateValueFunc update_func)
407 : cache_(std::move(cache)), update_func_(std::move(update_func)) {}
410 Value
Get(
const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
411 return cache_->Get(key, update_func_, read_mode);
415 std::optional<Value>
GetOptional(
const Key& key) {
return cache_->GetOptional(key, update_func_); }
417 void InvalidateByKey(
const Key& key) { cache_->InvalidateByKey(key); }
419 template <
typename Predicate>
420 void InvalidateByKeyIf(
const Key& key, Predicate pred) {
421 cache_->InvalidateByKeyIf(key, pred);
428 std::shared_ptr<Cache>
GetCache() {
return cache_; }
431 std::shared_ptr<Cache> cache_;
432 typename Cache::UpdateValueFunc update_func_;
435template <
typename Key,
typename Value,
typename Hash,
typename Equal>
436void ExpirableLruCache<Key, Value, Hash, Equal>::Write(
dump::
Writer& writer)
const {
437 utils::impl::UpdateGlobalTime();
441template <
typename Key,
typename Value,
typename Hash,
typename Equal>
442void ExpirableLruCache<Key, Value, Hash, Equal>::Read(
dump::
Reader& reader) {
443 utils::impl::UpdateGlobalTime();
447template <
typename Key,
typename Value,
typename Hash,
typename Equal>
448void ExpirableLruCache<Key, Value, Hash, Equal>::
SetDumper(std::shared_ptr<
dump::Dumper> dumper) {
449 lru_.SetDumper(std::move(dumper));
452template <
typename Key,
typename Value,
typename Hash,
typename Equal>
453void DumpMetric(
utils::statistics::Writer& writer,
const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
454 writer
["current-documents-count"] = cache.GetSizeApproximate();
455 writer = cache.GetStatistics();