Skip to main content

lychee_lib/
cache.rs

1//! Facility for cached asynchronous operations, with operations keyed by a
2//! key type and ensuring mutual exclusion of operations with the same key.
3
4use std::hash::Hash;
5use std::sync::Arc;
6use std::sync::atomic::AtomicUsize;
7use std::sync::atomic::Ordering;
8
9use dashmap::DashMap;
10use dashmap::mapref::entry::Entry;
11use tokio::sync::SetOnce;
12
13/// Cache for asynchronous operations. Each operation is associated with a key,
14/// and operations are cached, deduplicated and mutually exclusive with other
15/// operations on the same key, including in-progress operations.
16pub struct Cache<K, V> {
17    /// Internal map of keys to set-once values.
18    data: DashMap<K, Arc<SetOnce<V>>>,
19    /// Number of cache hits (including hits to in-progress values).
20    pub num_hits: AtomicUsize,
21    /// Number of cache misses.
22    pub num_misses: AtomicUsize,
23}
24
25/// A future returned on cache hits. [`CacheFut::wait`] returns a future which
26/// resolves when the cache value has been computed by another task.
27#[derive(Debug)]
28pub struct CacheFut<V>(Arc<SetOnce<V>>);
29
30impl<T> CacheFut<T> {
31    /// Returns a future which resolves when the cache value is computed
32    /// (by another task). If the value has already been computed and stored,
33    /// the future will be ready immediately.
34    pub async fn wait(&self) -> &T {
35        self.0.wait().await
36    }
37}
38
39/// A value returned on cache misses. The owner of this struct should compute
40/// the value, then call [`CacheSetter::set`] to write the value into the cache.
41///
42/// If this struct is dropped before being written to (including due to panic),
43/// the value will remain empty and associated [`CacheFut`]s will *never resolve*.
44/// This can be avoided by calling [`CacheSetter::with_fallback`] which will
45/// specify a fallback closure in case it is prematurely dropped.
46#[derive(Debug)]
47pub struct CacheSetter<T, Fn: FnOnce() -> T = fn() -> T>(Arc<SetOnce<T>>, Option<Fn>);
48
49impl<T, Fn: FnOnce() -> T> CacheSetter<T, Fn> {
50    /// Constructs a new [`CacheSetter`] writing into the given [`SetOnce`].
51    ///
52    /// By default, no fallback is configured.
53    #[must_use]
54    pub const fn new(arc: Arc<SetOnce<T>>) -> Self {
55        Self(arc, None)
56    }
57
58    /// Returns a new [`CacheSetter`] with the configured fallback closure and
59    /// writing into the same [`SetOnce`].
60    pub fn with_fallback<F: FnOnce() -> T>(mut self, default: F) -> CacheSetter<T, F> {
61        let arc = std::mem::take(&mut self.0);
62        self.1 = None;
63        CacheSetter(arc, Some(default))
64    }
65
66    /// Writes the given value into the cache, consuming this [`CacheSetter`].
67    pub fn set(self, value: T) {
68        let _ = self.0.set(value);
69    }
70
71    /// Returns a new dissociated [`CacheSetter`]. That is, a setter which is
72    /// not backed by any value within the cache. This can be useful to let
73    /// uncacheable entities use the same cache-handling logic.
74    pub fn dissociated() -> Self {
75        Self(Arc::default(), None)
76    }
77}
78
79/// Drop implementation that calls the stored [`CacheSetter::with_fallback`]
80/// closure, if it is configured and no value has been manually stored.
81impl<T, Fn: FnOnce() -> T> Drop for CacheSetter<T, Fn> {
82    fn drop(&mut self) {
83        if let Some(f) = self.1.take()
84            && !self.0.initialized()
85        {
86            let _ = self.0.set(f());
87        }
88    }
89}
90
91impl<K, V> Cache<K, V>
92where
93    K: Hash + Eq,
94{
95    /// Constructs a new empty [`Cache`].
96    #[must_use]
97    pub fn new() -> Self {
98        Self {
99            data: DashMap::new(),
100            num_hits: 0.into(),
101            num_misses: 0.into(),
102        }
103    }
104
105    /// Locks the cache entry with the given key, adding it to the cache if
106    /// it does not already exist. This function returns values which can be
107    /// used to write into or read from the cache.
108    ///
109    /// If this is the first task to lock this entry, [`Ok`] of [`CacheSetter`]
110    /// is returned so the call can compute and store the value. If the value is
111    /// already cached or another task is currently computing the value, [`Err`]
112    /// of [`CacheFut`] is returned which can be used to wait and retrieve the value
113    /// from the cache.
114    ///
115    /// The given key will only be cloned if the cache does not currently have
116    /// an entry for this key.
117    ///
118    /// # Errors
119    /// An [`Err`] means the cache key is already completed or in-progress, as
120    /// described above.
121    pub fn lock_entry(&self, key: &K) -> Result<CacheSetter<V>, CacheFut<V>>
122    where
123        K: Clone,
124    {
125        if let Some(entry) = self.data.get(key) {
126            return Err(CacheFut(entry.value().clone()));
127        }
128
129        match self.data.entry(key.clone()) {
130            Entry::Vacant(vac) => {
131                self.num_misses.fetch_add(1, Ordering::Relaxed);
132                let arc = vac.insert(Arc::default()).value().clone();
133                Ok(CacheSetter::new(arc))
134            }
135            Entry::Occupied(occ) => {
136                self.num_hits.fetch_add(1, Ordering::Relaxed);
137                Err(CacheFut(occ.get().clone()))
138            }
139        }
140    }
141
142    /// Consumes the cache and returns an iterator over the completed key
143    /// and value pairs.
144    ///
145    /// # Panics
146    ///
147    /// This function will panic if there are leftover [`CacheFut`] or [`CacheSetter`]
148    /// references pointing to the current cache.
149    pub fn into_completed_entries(self) -> impl Iterator<Item = (K, V)> {
150        self.data.into_iter().filter_map(|(k, v)| {
151            let cell = Arc::into_inner(v).expect("unresolved CacheFut or CacheSetter values exist");
152            cell.into_inner().map(|x| (k, x))
153        })
154    }
155}
156impl<K, V> Default for Cache<K, V>
157where
158    K: Hash + Eq,
159{
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165impl<K, V> FromIterator<(K, V)> for Cache<K, V>
166where
167    K: Hash + Eq,
168{
169    fn from_iter<It: IntoIterator<Item = (K, V)>>(iter: It) -> Self {
170        let cache = Self::new();
171        for (k, v) in iter {
172            cache.data.insert(k, Arc::new(v.into()));
173        }
174        cache
175    }
176}
177
178impl<K, V> std::fmt::Debug for Cache<K, V> {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        f.debug_struct("Cache").finish_non_exhaustive()
181    }
182}