Skip to main content

rws_server/cache/
value.rs

1//! [`ValueCache`]: an in-memory cache of computed values with a
2//! `fetch_or` — Rails' `Rails.cache.fetch`, Spring's `@Cacheable` as a
3//! closure (18.35.0, #95).
4
5use std::collections::{BTreeMap, HashMap};
6use std::future::Future;
7use std::sync::{Arc, Mutex};
8use std::time::{Duration, Instant};
9
10struct Entry<V> {
11    value: V,
12    expires: Instant,
13    seq: u64,
14}
15
16struct Inner<V> {
17    entries: HashMap<String, Entry<V>>,
18    /// Insertion order, for evicting the oldest when full.
19    order: BTreeMap<u64, String>,
20    next_seq: u64,
21    hits: u64,
22    misses: u64,
23}
24
25/// Values by key, each with its own time to live, at most `capacity` of
26/// them (the oldest goes first when full, after anything expired). Cheap
27/// to clone; clones share the entries.
28///
29/// ```rust
30/// # extern crate rws_server as rust_web_server;
31/// use std::time::Duration;
32/// use rust_web_server::cache::ValueCache;
33///
34/// let cache: ValueCache<String> = ValueCache::new(1000);
35/// let a = cache.fetch_or("greeting:en", Duration::from_secs(60), || "Hello".to_string());
36/// let b = cache.fetch_or("greeting:en", Duration::from_secs(60), || unreachable!("cached"));
37/// assert_eq!(a, b);
38/// ```
39///
40/// The value is computed outside the lock: two threads missing the same key
41/// at once both compute it (the last one stored wins). Errors from
42/// [`try_fetch_or`](Self::try_fetch_or) aren't cached.
43pub struct ValueCache<V> {
44    inner: Arc<Mutex<Inner<V>>>,
45    capacity: usize,
46}
47
48impl<V> Clone for ValueCache<V> {
49    fn clone(&self) -> Self {
50        ValueCache { inner: Arc::clone(&self.inner), capacity: self.capacity }
51    }
52}
53
54impl<V: Clone> ValueCache<V> {
55    /// At most `capacity` entries (at least 1).
56    pub fn new(capacity: usize) -> Self {
57        ValueCache {
58            inner: Arc::new(Mutex::new(Inner { entries: HashMap::new(), order: BTreeMap::new(), next_seq: 0, hits: 0, misses: 0 })),
59            capacity: capacity.max(1),
60        }
61    }
62
63    fn lock(&self) -> std::sync::MutexGuard<'_, Inner<V>> {
64        self.inner.lock().unwrap_or_else(|e| e.into_inner())
65    }
66
67    /// The value under `key`, unless it's missing or expired.
68    pub fn get(&self, key: &str) -> Option<V> {
69        let mut inner = self.lock();
70        let now = Instant::now();
71        let found = match inner.entries.get(key) {
72            Some(entry) if entry.expires > now => Some(entry.value.clone()),
73            Some(_) => {
74                if let Some(entry) = inner.entries.remove(key) {
75                    inner.order.remove(&entry.seq);
76                }
77                None
78            }
79            None => None,
80        };
81        match found {
82            Some(value) => {
83                inner.hits += 1;
84                Some(value)
85            }
86            None => {
87                inner.misses += 1;
88                None
89            }
90        }
91    }
92
93    /// Store `value` under `key` for `ttl`.
94    pub fn put(&self, key: &str, value: V, ttl: Duration) {
95        let mut inner = self.lock();
96        let now = Instant::now();
97        if let Some(old) = inner.entries.remove(key) {
98            inner.order.remove(&old.seq);
99        }
100        if inner.entries.len() >= self.capacity {
101            let expired: Vec<String> = inner.entries.iter().filter(|(_, e)| e.expires <= now).map(|(k, _)| k.clone()).collect();
102            for k in expired {
103                if let Some(e) = inner.entries.remove(&k) {
104                    inner.order.remove(&e.seq);
105                }
106            }
107        }
108        while inner.entries.len() >= self.capacity {
109            let Some((&seq, _)) = inner.order.iter().next() else { break };
110            if let Some(oldest) = inner.order.remove(&seq) {
111                inner.entries.remove(&oldest);
112            }
113        }
114        let seq = inner.next_seq;
115        inner.next_seq += 1;
116        inner.order.insert(seq, key.to_string());
117        inner.entries.insert(key.to_string(), Entry { value, expires: now + ttl, seq });
118    }
119
120    /// The cached value, or `compute()` stored for `ttl` and returned.
121    pub fn fetch_or(&self, key: &str, ttl: Duration, compute: impl FnOnce() -> V) -> V {
122        if let Some(value) = self.get(key) {
123            return value;
124        }
125        let value = compute();
126        self.put(key, value.clone(), ttl);
127        value
128    }
129
130    /// [`fetch_or`](Self::fetch_or) for a computation that can fail: an
131    /// `Err` is returned and nothing is stored.
132    pub fn try_fetch_or<E>(&self, key: &str, ttl: Duration, compute: impl FnOnce() -> Result<V, E>) -> Result<V, E> {
133        if let Some(value) = self.get(key) {
134            return Ok(value);
135        }
136        let value = compute()?;
137        self.put(key, value.clone(), ttl);
138        Ok(value)
139    }
140
141    /// [`fetch_or`](Self::fetch_or) with an async computation (a query).
142    pub async fn fetch_or_async<F, Fut>(&self, key: &str, ttl: Duration, compute: F) -> V
143    where
144        F: FnOnce() -> Fut,
145        Fut: Future<Output = V>,
146    {
147        if let Some(value) = self.get(key) {
148            return value;
149        }
150        let value = compute().await;
151        self.put(key, value.clone(), ttl);
152        value
153    }
154
155    /// [`try_fetch_or`](Self::try_fetch_or) with an async computation.
156    pub async fn try_fetch_or_async<E, F, Fut>(&self, key: &str, ttl: Duration, compute: F) -> Result<V, E>
157    where
158        F: FnOnce() -> Fut,
159        Fut: Future<Output = Result<V, E>>,
160    {
161        if let Some(value) = self.get(key) {
162            return Ok(value);
163        }
164        let value = compute().await?;
165        self.put(key, value.clone(), ttl);
166        Ok(value)
167    }
168
169    /// Remove `key`; whether it was there.
170    pub fn invalidate(&self, key: &str) -> bool {
171        let mut inner = self.lock();
172        match inner.entries.remove(key) {
173            Some(entry) => {
174                inner.order.remove(&entry.seq);
175                true
176            }
177            None => false,
178        }
179    }
180
181    /// Remove every key starting with `prefix` (`"user:42:"`); how many.
182    pub fn invalidate_prefix(&self, prefix: &str) -> usize {
183        let mut inner = self.lock();
184        let keys: Vec<String> = inner.entries.keys().filter(|k| k.starts_with(prefix)).cloned().collect();
185        for k in &keys {
186            if let Some(entry) = inner.entries.remove(k) {
187                inner.order.remove(&entry.seq);
188            }
189        }
190        keys.len()
191    }
192
193    pub fn clear(&self) {
194        let mut inner = self.lock();
195        inner.entries.clear();
196        inner.order.clear();
197    }
198
199    /// Entries held (expired ones until they're noticed).
200    pub fn len(&self) -> usize {
201        self.lock().entries.len()
202    }
203
204    pub fn is_empty(&self) -> bool {
205        self.len() == 0
206    }
207
208    /// `(hits, misses)` of [`get`](Self::get) (and so of the `fetch_or`s).
209    pub fn stats(&self) -> (u64, u64) {
210        let inner = self.lock();
211        (inner.hits, inner.misses)
212    }
213}
214
215#[cfg(test)]
216mod tests;