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&)>;
74 const Equal& equal = Equal());
82 std::chrono::milliseconds GetMaxLifetime()
const noexcept;
84 void SetMaxLifetime(std::chrono::milliseconds max_lifetime);
87
88
89
90
94
95
96
97
98 Value
Get(
const Key& key,
const UpdateValueFunc& update_func,
102
103
104
105
106
108 const UpdateValueFunc& update_func);
111
112
113
114
118
119
120
121
123 const Key& key,
const UpdateValueFunc& update_func);
126
127
130 void Put(
const Key& key,
const Value& value);
132 void Put(
const Key& key, Value&& value);
134 const impl::ExpirableLruCacheStatistics& GetStatistics()
const;
136 size_t GetSizeApproximate()
const;
156 bool IsExpired(std::chrono::steady_clock::time_point update_time,
157 std::chrono::steady_clock::time_point now)
const;
159 bool ShouldUpdate(std::chrono::steady_clock::time_point update_time,
160 std::chrono::steady_clock::time_point now)
const;
162 cache::NWayLRU<Key,
impl::ExpirableValue<Value>, Hash, Equal> lru_;
163 std::atomic<std::chrono::milliseconds> max_lifetime_{
164 std::chrono::milliseconds(0)};
165 std::atomic<BackgroundUpdateMode> background_update_mode_{
166 BackgroundUpdateMode::kDisabled};
167 impl::ExpirableLruCacheStatistics stats_;
168 concurrent::MutexSet<Key, Hash, Equal> mutex_set_;
169 utils::impl::WaitTokenStorage wait_token_storage_;
172template <
typename Key,
typename Value,
typename Hash,
typename Equal>
174 size_t ways, size_t way_size,
const Hash& hash,
const Equal& 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
190ExpirableLruCache<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(
196 std::chrono::milliseconds max_lifetime) {
197 max_lifetime_ = max_lifetime;
200template <
typename Key,
typename Value,
typename Hash,
typename Equal>
202 BackgroundUpdateMode background_update) {
203 background_update_mode_ = background_update;
206template <
typename Key,
typename Value,
typename Hash,
typename Equal>
207Value ExpirableLruCache<Key, Value, Hash, Equal>::
Get(
208 const Key& key,
const UpdateValueFunc& update_func,
ReadMode read_mode) {
212 return std::move(*opt_old_value);
215 auto mutex = mutex_set_.GetMutexForKey(key);
216 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>
232std::optional<Value> ExpirableLruCache<Key, Value, Hash, Equal>::
GetOptional(
233 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>
259 auto old_value = lru_.Get(key);
262 impl::CacheHit(stats_);
263 return old_value->value;
265 impl::CacheMiss(stats_);
270template <
typename Key,
typename Value,
typename Hash,
typename Equal>
273 const Key& key,
const UpdateValueFunc& update_func) {
275 auto old_value = lru_.Get(key);
278 impl::CacheHit(stats_);
280 if (ShouldUpdate(old_value->update_time, now)) {
284 return old_value->value;
286 impl::CacheMiss(stats_);
291template <
typename Key,
typename Value,
typename Hash,
typename Equal>
296 auto old_value = lru_.Get(key);
299 if (!IsExpired(old_value->update_time, now)) {
300 impl::CacheHit(stats_);
302 return old_value->value;
304 impl::CacheStale(stats_);
307 impl::CacheMiss(stats_);
312template <
typename Key,
typename Value,
typename Hash,
typename Equal>
313void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key,
314 const Value& value) {
318template <
typename Key,
typename Value,
typename Hash,
typename Equal>
319void ExpirableLruCache<Key, Value, Hash, Equal>::Put(
const Key& key,
324template <
typename Key,
typename Value,
typename Hash,
typename Equal>
325const impl::ExpirableLruCacheStatistics&
326ExpirableLruCache<Key, Value, Hash, Equal>::GetStatistics()
const {
330template <
typename Key,
typename Value,
typename Hash,
typename Equal>
331size_t ExpirableLruCache<Key, Value, Hash, Equal>::GetSizeApproximate()
const {
332 return lru_.GetSize();
335template <
typename Key,
typename Value,
typename Hash,
typename Equal>
336void ExpirableLruCache<Key, Value, Hash, Equal>::
Invalidate() {
340template <
typename Key,
typename Value,
typename Hash,
typename Equal>
343 lru_.InvalidateByKey(key);
346template <
typename Key,
typename Value,
typename Hash,
typename Equal>
348 const Key& key, UpdateValueFunc update_func) {
349 stats_.total.background_updates++;
350 stats_.recent.GetCurrentCounter().background_updates++;
353 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(),
this, key,
354 update_func = std::move(update_func)] {
355 auto mutex = mutex_set_.GetMutexForKey(key);
356 std::unique_lock lock(mutex, std::try_to_lock);
363 auto value = update_func(key);
364 lru_.Put(key, {value, now});
368template <
typename Key,
typename Value,
typename Hash,
typename Equal>
369bool ExpirableLruCache<Key, Value, Hash, Equal>::IsExpired(
370 std::chrono::steady_clock::time_point update_time,
371 std::chrono::steady_clock::time_point now)
const {
372 auto max_lifetime = max_lifetime_.load();
373 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
376template <
typename Key,
typename Value,
typename Hash,
typename Equal>
377bool ExpirableLruCache<Key, Value, Hash, Equal>::ShouldUpdate(
378 std::chrono::steady_clock::time_point update_time,
379 std::chrono::steady_clock::time_point now)
const {
380 auto max_lifetime = max_lifetime_.load();
381 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) &&
382 max_lifetime.count() != 0 && update_time + max_lifetime / 2 < now;
385template <
typename Key,
typename Value,
typename Hash = std::hash<Key>,
386 typename Equal = std::equal_to<Key>>
387class LruCacheWrapper final {
389 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
390 using ReadMode =
typename Cache::ReadMode;
392 LruCacheWrapper(std::shared_ptr<Cache> cache,
393 typename Cache::UpdateValueFunc update_func)
394 : cache_(std::move(cache)), update_func_(std::move(update_func)) {}
397 Value
Get(
const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
398 return cache_->Get(key, update_func_, read_mode);
403 return cache_->GetOptional(key, update_func_);
406 void InvalidateByKey(
const Key& key) { cache_->InvalidateByKey(key); }
410 cache_->UpdateInBackground(key, update_func_);
414 std::shared_ptr<Cache>
GetCache() {
return cache_; }
417 std::shared_ptr<Cache> cache_;
418 typename Cache::UpdateValueFunc update_func_;
421template <
typename Key,
typename Value,
typename Hash,
typename Equal>
422void ExpirableLruCache<Key, Value, Hash, Equal>::Write(
424 utils::impl::UpdateGlobalTime();
428template <
typename Key,
typename Value,
typename Hash,
typename Equal>
429void ExpirableLruCache<Key, Value, Hash, Equal>::Read(
dump::
Reader& reader) {
430 utils::impl::UpdateGlobalTime();
434template <
typename Key,
typename Value,
typename Hash,
typename Equal>
435void ExpirableLruCache<Key, Value, Hash, Equal>::
SetDumper(
436 std::shared_ptr<
dump::Dumper> dumper) {
437 lru_.SetDumper(std::move(dumper));
440template <
typename Key,
typename Value,
typename Hash,
typename Equal>
441void DumpMetric(utils::statistics::Writer& writer,
442 const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
443 writer[
"current-documents-count"] = cache.GetSizeApproximate();
444 writer = cache.GetStatistics();