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();