LCOV - differential code coverage report
Current view: top level - proxy/src - cache.rs (source / functions) Coverage Total Hit UBC CBC
Current: cd44433dd675caa99df17a61b18949c8387e2242.info Lines: 45.3 % 86 39 47 39
Current Date: 2024-01-09 02:06:09 Functions: 26.0 % 50 13 37 13
Baseline: 66c52a629a0f4a503e193045e0df4c77139e344b.info
Baseline Date: 2024-01-08 15:34:46

           TLA  Line data    Source code
       1                 : use std::{
       2                 :     borrow::Borrow,
       3                 :     hash::Hash,
       4                 :     ops::{Deref, DerefMut},
       5                 :     time::{Duration, Instant},
       6                 : };
       7                 : use tracing::debug;
       8                 : 
       9                 : // This seems to make more sense than `lru` or `cached`:
      10                 : //
      11                 : // * `near/nearcore` ditched `cached` in favor of `lru`
      12                 : //   (https://github.com/near/nearcore/issues?q=is%3Aissue+lru+is%3Aclosed).
      13                 : //
      14                 : // * `lru` methods use an obscure `KeyRef` type in their contraints (which is deliberately excluded from docs).
      15                 : //   This severely hinders its usage both in terms of creating wrappers and supported key types.
      16                 : //
      17                 : // On the other hand, `hashlink` has good download stats and appears to be maintained.
      18                 : use hashlink::{linked_hash_map::RawEntryMut, LruCache};
      19                 : 
      20                 : /// A generic trait which exposes types of cache's key and value,
      21                 : /// as well as the notion of cache entry invalidation.
      22                 : /// This is useful for [`timed_lru::Cached`].
      23                 : pub trait Cache {
      24                 :     /// Entry's key.
      25                 :     type Key;
      26                 : 
      27                 :     /// Entry's value.
      28                 :     type Value;
      29                 : 
      30                 :     /// Used for entry invalidation.
      31                 :     type LookupInfo<Key>;
      32                 : 
      33                 :     /// Invalidate an entry using a lookup info.
      34                 :     /// We don't have an empty default impl because it's error-prone.
      35                 :     fn invalidate(&self, _: &Self::LookupInfo<Self::Key>);
      36                 : }
      37                 : 
      38                 : impl<C: Cache> Cache for &C {
      39                 :     type Key = C::Key;
      40                 :     type Value = C::Value;
      41                 :     type LookupInfo<Key> = C::LookupInfo<Key>;
      42                 : 
      43 UBC           0 :     fn invalidate(&self, info: &Self::LookupInfo<Self::Key>) {
      44               0 :         C::invalidate(self, info)
      45               0 :     }
      46                 : }
      47                 : 
      48                 : pub use timed_lru::TimedLru;
      49                 : pub mod timed_lru {
      50                 :     use super::*;
      51                 : 
      52                 :     /// An implementation of timed LRU cache with fixed capacity.
      53                 :     /// Key properties:
      54                 :     ///
      55                 :     /// * Whenever a new entry is inserted, the least recently accessed one is evicted.
      56                 :     ///   The cache also keeps track of entry's insertion time (`created_at`) and TTL (`expires_at`).
      57                 :     ///
      58                 :     /// * If `update_ttl_on_retrieval` is `true`. When the entry is about to be retrieved, we check its expiration timestamp.
      59                 :     ///   If the entry has expired, we remove it from the cache; Otherwise we bump the
      60                 :     ///   expiration timestamp (e.g. +5mins) and change its place in LRU list to prolong
      61                 :     ///   its existence.
      62                 :     ///
      63                 :     /// * There's an API for immediate invalidation (removal) of a cache entry;
      64                 :     ///   It's useful in case we know for sure that the entry is no longer correct.
      65                 :     ///   See [`timed_lru::LookupInfo`] & [`timed_lru::Cached`] for more information.
      66                 :     ///
      67                 :     /// * Expired entries are kept in the cache, until they are evicted by the LRU policy,
      68                 :     ///   or by a successful lookup (i.e. the entry hasn't expired yet).
      69                 :     ///   There is no background job to reap the expired records.
      70                 :     ///
      71                 :     /// * It's possible for an entry that has not yet expired entry to be evicted
      72                 :     ///   before expired items. That's a bit wasteful, but probably fine in practice.
      73                 :     pub struct TimedLru<K, V> {
      74                 :         /// Cache's name for tracing.
      75                 :         name: &'static str,
      76                 : 
      77                 :         /// The underlying cache implementation.
      78                 :         cache: parking_lot::Mutex<LruCache<K, Entry<V>>>,
      79                 : 
      80                 :         /// Default time-to-live of a single entry.
      81                 :         ttl: Duration,
      82                 : 
      83                 :         update_ttl_on_retrieval: bool,
      84                 :     }
      85                 : 
      86                 :     impl<K: Hash + Eq, V> Cache for TimedLru<K, V> {
      87                 :         type Key = K;
      88                 :         type Value = V;
      89                 :         type LookupInfo<Key> = LookupInfo<Key>;
      90                 : 
      91               0 :         fn invalidate(&self, info: &Self::LookupInfo<K>) {
      92               0 :             self.invalidate_raw(info)
      93               0 :         }
      94                 :     }
      95                 : 
      96                 :     struct Entry<T> {
      97                 :         created_at: Instant,
      98                 :         expires_at: Instant,
      99                 :         value: T,
     100                 :     }
     101                 : 
     102                 :     impl<K: Hash + Eq, V> TimedLru<K, V> {
     103                 :         /// Construct a new LRU cache with timed entries.
     104 CBC           3 :         pub fn new(
     105               3 :             name: &'static str,
     106               3 :             capacity: usize,
     107               3 :             ttl: Duration,
     108               3 :             update_ttl_on_retrieval: bool,
     109               3 :         ) -> Self {
     110               3 :             Self {
     111               3 :                 name,
     112               3 :                 cache: LruCache::new(capacity).into(),
     113               3 :                 ttl,
     114               3 :                 update_ttl_on_retrieval,
     115               3 :             }
     116               3 :         }
     117                 : 
     118                 :         /// Drop an entry from the cache if it's outdated.
     119 UBC           0 :         #[tracing::instrument(level = "debug", fields(cache = self.name), skip_all)]
     120                 :         fn invalidate_raw(&self, info: &LookupInfo<K>) {
     121                 :             let now = Instant::now();
     122                 : 
     123                 :             // Do costly things before taking the lock.
     124                 :             let mut cache = self.cache.lock();
     125                 :             let raw_entry = match cache.raw_entry_mut().from_key(&info.key) {
     126                 :                 RawEntryMut::Vacant(_) => return,
     127                 :                 RawEntryMut::Occupied(x) => x,
     128                 :             };
     129                 : 
     130                 :             // Remove the entry if it was created prior to lookup timestamp.
     131                 :             let entry = raw_entry.get();
     132                 :             let (created_at, expires_at) = (entry.created_at, entry.expires_at);
     133                 :             let should_remove = created_at <= info.created_at || expires_at <= now;
     134                 : 
     135                 :             if should_remove {
     136                 :                 raw_entry.remove();
     137                 :             }
     138                 : 
     139                 :             drop(cache); // drop lock before logging
     140               0 :             debug!(
     141               0 :                 created_at = format_args!("{created_at:?}"),
     142               0 :                 expires_at = format_args!("{expires_at:?}"),
     143               0 :                 entry_removed = should_remove,
     144               0 :                 "processed a cache entry invalidation event"
     145               0 :             );
     146                 :         }
     147                 : 
     148                 :         /// Try retrieving an entry by its key, then execute `extract` if it exists.
     149 CBC           8 :         #[tracing::instrument(level = "debug", fields(cache = self.name), skip_all)]
     150                 :         fn get_raw<Q, R>(&self, key: &Q, extract: impl FnOnce(&K, &Entry<V>) -> R) -> Option<R>
     151                 :         where
     152                 :             K: Borrow<Q>,
     153                 :             Q: Hash + Eq + ?Sized,
     154                 :         {
     155                 :             let now = Instant::now();
     156                 :             let deadline = now.checked_add(self.ttl).expect("time overflow");
     157                 : 
     158                 :             // Do costly things before taking the lock.
     159                 :             let mut cache = self.cache.lock();
     160                 :             let mut raw_entry = match cache.raw_entry_mut().from_key(key) {
     161                 :                 RawEntryMut::Vacant(_) => return None,
     162                 :                 RawEntryMut::Occupied(x) => x,
     163                 :             };
     164                 : 
     165                 :             // Immeditely drop the entry if it has expired.
     166                 :             let entry = raw_entry.get();
     167                 :             if entry.expires_at <= now {
     168                 :                 raw_entry.remove();
     169                 :                 return None;
     170                 :             }
     171                 : 
     172                 :             let value = extract(raw_entry.key(), entry);
     173                 :             let (created_at, expires_at) = (entry.created_at, entry.expires_at);
     174                 : 
     175                 :             // Update the deadline and the entry's position in the LRU list.
     176                 :             if self.update_ttl_on_retrieval {
     177                 :                 raw_entry.get_mut().expires_at = deadline;
     178                 :             }
     179                 :             raw_entry.to_back();
     180                 : 
     181                 :             drop(cache); // drop lock before logging
     182 UBC           0 :             debug!(
     183               0 :                 created_at = format_args!("{created_at:?}"),
     184               0 :                 old_expires_at = format_args!("{expires_at:?}"),
     185               0 :                 new_expires_at = format_args!("{deadline:?}"),
     186               0 :                 "accessed a cache entry"
     187               0 :             );
     188                 : 
     189                 :             Some(value)
     190                 :         }
     191                 : 
     192                 :         /// Insert an entry to the cache. If an entry with the same key already
     193                 :         /// existed, return the previous value and its creation timestamp.
     194               0 :         #[tracing::instrument(level = "debug", fields(cache = self.name), skip_all)]
     195                 :         fn insert_raw(&self, key: K, value: V) -> (Instant, Option<V>) {
     196                 :             let created_at = Instant::now();
     197                 :             let expires_at = created_at.checked_add(self.ttl).expect("time overflow");
     198                 : 
     199                 :             let entry = Entry {
     200                 :                 created_at,
     201                 :                 expires_at,
     202                 :                 value,
     203                 :             };
     204                 : 
     205                 :             // Do costly things before taking the lock.
     206                 :             let old = self
     207                 :                 .cache
     208                 :                 .lock()
     209                 :                 .insert(key, entry)
     210               0 :                 .map(|entry| entry.value);
     211                 : 
     212               0 :             debug!(
     213               0 :                 created_at = format_args!("{created_at:?}"),
     214               0 :                 expires_at = format_args!("{expires_at:?}"),
     215               0 :                 replaced = old.is_some(),
     216               0 :                 "created a cache entry"
     217               0 :             );
     218                 : 
     219                 :             (created_at, old)
     220                 :         }
     221                 :     }
     222                 : 
     223                 :     impl<K: Hash + Eq + Clone, V: Clone> TimedLru<K, V> {
     224               0 :         pub fn insert(&self, key: K, value: V) -> (Option<V>, Cached<&Self>) {
     225               0 :             let (created_at, old) = self.insert_raw(key.clone(), value.clone());
     226               0 : 
     227               0 :             let cached = Cached {
     228               0 :                 token: Some((self, LookupInfo { created_at, key })),
     229               0 :                 value,
     230               0 :             };
     231               0 : 
     232               0 :             (old, cached)
     233               0 :         }
     234                 :     }
     235                 : 
     236                 :     impl<K: Hash + Eq, V: Clone> TimedLru<K, V> {
     237                 :         /// Retrieve a cached entry in convenient wrapper.
     238 CBC           4 :         pub fn get<Q>(&self, key: &Q) -> Option<timed_lru::Cached<&Self>>
     239               4 :         where
     240               4 :             K: Borrow<Q> + Clone,
     241               4 :             Q: Hash + Eq + ?Sized,
     242               4 :         {
     243               4 :             self.get_raw(key, |key, entry| {
     244 UBC           0 :                 let info = LookupInfo {
     245               0 :                     created_at: entry.created_at,
     246               0 :                     key: key.clone(),
     247               0 :                 };
     248               0 : 
     249               0 :                 Cached {
     250               0 :                     token: Some((self, info)),
     251               0 :                     value: entry.value.clone(),
     252               0 :                 }
     253 CBC           4 :             })
     254               4 :         }
     255                 :     }
     256                 : 
     257                 :     /// Lookup information for key invalidation.
     258                 :     pub struct LookupInfo<K> {
     259                 :         /// Time of creation of a cache [`Entry`].
     260                 :         /// We use this during invalidation lookups to prevent eviction of a newer
     261                 :         /// entry sharing the same key (it might've been inserted by a different
     262                 :         /// task after we got the entry we're trying to invalidate now).
     263                 :         created_at: Instant,
     264                 : 
     265                 :         /// Search by this key.
     266                 :         key: K,
     267                 :     }
     268                 : 
     269                 :     /// Wrapper for convenient entry invalidation.
     270                 :     pub struct Cached<C: Cache> {
     271                 :         /// Cache + lookup info.
     272                 :         token: Option<(C, C::LookupInfo<C::Key>)>,
     273                 : 
     274                 :         /// The value itself.
     275                 :         value: C::Value,
     276                 :     }
     277                 : 
     278                 :     impl<C: Cache> Cached<C> {
     279                 :         /// Place any entry into this wrapper; invalidation will be a no-op.
     280             130 :         pub fn new_uncached(value: C::Value) -> Self {
     281             130 :             Self { token: None, value }
     282             130 :         }
     283                 : 
     284                 :         /// Drop this entry from a cache if it's still there.
     285              11 :         pub fn invalidate(self) -> C::Value {
     286              11 :             if let Some((cache, info)) = &self.token {
     287 UBC           0 :                 cache.invalidate(info);
     288 CBC          11 :             }
     289              11 :             self.value
     290              11 :         }
     291                 : 
     292                 :         /// Tell if this entry is actually cached.
     293               8 :         pub fn cached(&self) -> bool {
     294               8 :             self.token.is_some()
     295               8 :         }
     296                 :     }
     297                 : 
     298                 :     impl<C: Cache> Deref for Cached<C> {
     299                 :         type Target = C::Value;
     300                 : 
     301             345 :         fn deref(&self) -> &Self::Target {
     302             345 :             &self.value
     303             345 :         }
     304                 :     }
     305                 : 
     306                 :     impl<C: Cache> DerefMut for Cached<C> {
     307             172 :         fn deref_mut(&mut self) -> &mut Self::Target {
     308             172 :             &mut self.value
     309             172 :         }
     310                 :     }
     311                 : }
        

Generated by: LCOV version 2.1-beta