Skip to main content

cached/stores/
expiring_value_cache.rs

1use super::{Cached, SizedCache, Status};
2use crate::CloneCached;
3use std::hash::Hash;
4
5/// The `CanExpire` trait defines a function for implementations to determine if
6/// the value has expired.
7pub trait CanExpire {
8    /// `is_expired` returns whether the value has expired.
9    fn is_expired(&self) -> bool;
10}
11
12/// Expiring Value Cache
13///
14/// Stores values that implement the `CanExpire` trait so that expiration
15/// is determined by the values themselves. This is useful for caching
16/// values which themselves contain an expiry timestamp.
17///
18/// Note: This cache is in-memory only.
19#[derive(Clone, Debug)]
20pub struct ExpiringValueCache<K: Hash + Eq, V: CanExpire> {
21    pub(super) store: SizedCache<K, V>,
22    pub(super) hits: u64,
23    pub(super) misses: u64,
24}
25
26impl<K: Clone + Hash + Eq, V: CanExpire> ExpiringValueCache<K, V> {
27    /// Creates a new `ExpiringValueCache` with a given size limit and
28    /// pre-allocated backing data.
29    #[must_use]
30    pub fn with_size(size: usize) -> ExpiringValueCache<K, V> {
31        ExpiringValueCache {
32            store: SizedCache::with_size(size),
33            hits: 0,
34            misses: 0,
35        }
36    }
37
38    fn status<Q>(&mut self, k: &Q) -> Status
39    where
40        K: std::borrow::Borrow<Q>,
41        Q: std::hash::Hash + Eq + ?Sized,
42    {
43        let v = self.store.cache_get(k);
44        match v {
45            Some(v) => {
46                if v.is_expired() {
47                    Status::Expired
48                } else {
49                    Status::Found
50                }
51            }
52            None => Status::NotFound,
53        }
54    }
55
56    /// Remove any expired values from the cache
57    pub fn flush(&mut self) {
58        self.store.retain(|_, v| !v.is_expired());
59    }
60}
61
62// https://docs.rs/cached/latest/cached/trait.Cached.html
63impl<K: Hash + Eq + Clone, V: CanExpire> Cached<K, V> for ExpiringValueCache<K, V> {
64    fn cache_get<Q>(&mut self, k: &Q) -> Option<&V>
65    where
66        K: std::borrow::Borrow<Q>,
67        Q: std::hash::Hash + Eq + ?Sized,
68    {
69        match self.status(k) {
70            Status::NotFound => {
71                self.misses += 1;
72                None
73            }
74            Status::Found => {
75                self.hits += 1;
76                self.store.cache_get(k)
77            }
78            Status::Expired => {
79                self.misses += 1;
80                self.store.cache_remove(k);
81                None
82            }
83        }
84    }
85
86    fn cache_get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
87    where
88        K: std::borrow::Borrow<Q>,
89        Q: std::hash::Hash + Eq + ?Sized,
90    {
91        match self.status(k) {
92            Status::NotFound => {
93                self.misses += 1;
94                None
95            }
96            Status::Found => {
97                self.hits += 1;
98                self.store.cache_get_mut(k)
99            }
100            Status::Expired => {
101                self.misses += 1;
102                self.store.cache_remove(k);
103                None
104            }
105        }
106    }
107
108    fn cache_get_or_set_with<F: FnOnce() -> V>(&mut self, k: K, f: F) -> &mut V {
109        // get_or_set_with_if will set the value in the cache if an existing
110        // value is not valid, which, in our case, is if the value has expired.
111        let (was_present, was_valid, v) = self.store.get_or_set_with_if(k, f, |v| !v.is_expired());
112        if was_present && was_valid {
113            self.hits += 1;
114        } else {
115            self.misses += 1;
116        }
117        v
118    }
119    fn cache_try_get_or_set_with<F: FnOnce() -> Result<V, E>, E>(
120        &mut self,
121        k: K,
122        f: F,
123    ) -> Result<&mut V, E> {
124        let (was_present, was_valid, v) = self
125            .store
126            .try_get_or_set_with_if(k, f, |v| !v.is_expired())?;
127        if was_present && was_valid {
128            self.hits += 1;
129        } else {
130            self.misses += 1;
131        }
132        Ok(v)
133    }
134    fn cache_set(&mut self, k: K, v: V) -> Option<V> {
135        self.store.cache_set(k, v)
136    }
137    fn cache_remove<Q>(&mut self, k: &Q) -> Option<V>
138    where
139        K: std::borrow::Borrow<Q>,
140        Q: std::hash::Hash + Eq + ?Sized,
141    {
142        self.store.cache_remove(k)
143    }
144    fn cache_clear(&mut self) {
145        self.store.cache_clear();
146    }
147    fn cache_reset(&mut self) {
148        self.store.cache_reset();
149    }
150    fn cache_size(&self) -> usize {
151        self.store.cache_size()
152    }
153    fn cache_hits(&self) -> Option<u64> {
154        Some(self.hits)
155    }
156    fn cache_misses(&self) -> Option<u64> {
157        Some(self.misses)
158    }
159    fn cache_reset_metrics(&mut self) {
160        self.hits = 0;
161        self.misses = 0;
162    }
163}
164
165impl<K: Hash + Eq + Clone, V: CanExpire + Clone> CloneCached<K, V> for ExpiringValueCache<K, V> {
166    fn cache_get_expired<Q>(&mut self, k: &Q) -> (Option<V>, bool)
167    where
168        K: std::borrow::Borrow<Q>,
169        Q: std::hash::Hash + Eq + ?Sized,
170    {
171        match self.status(k) {
172            Status::NotFound => {
173                self.misses += 1;
174                (None, false)
175            }
176            Status::Found => {
177                self.hits += 1;
178                (self.store.cache_get(k).cloned(), false)
179            }
180            Status::Expired => {
181                self.misses += 1;
182                (self.store.cache_remove(k), true)
183            }
184        }
185    }
186}
187
188#[cfg(test)]
189/// Expiring Value Cache tests
190mod tests {
191    use super::*;
192
193    type ExpiredU8 = u8;
194
195    impl CanExpire for ExpiredU8 {
196        fn is_expired(&self) -> bool {
197            *self > 10
198        }
199    }
200
201    #[test]
202    fn expiring_value_cache_get_miss() {
203        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
204
205        // Getting a non-existent cache key.
206        assert!(c.cache_get(&1).is_none());
207        assert_eq!(c.cache_hits(), Some(0));
208        assert_eq!(c.cache_misses(), Some(1));
209    }
210
211    #[test]
212    fn expiring_value_cache_get_hit() {
213        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
214
215        // Getting a cached value.
216        assert!(c.cache_set(1, 2).is_none());
217        assert_eq!(c.cache_get(&1), Some(&2));
218        assert_eq!(c.cache_hits(), Some(1));
219        assert_eq!(c.cache_misses(), Some(0));
220    }
221
222    #[test]
223    fn expiring_value_cache_get_expired() {
224        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
225
226        assert!(c.cache_set(2, 12).is_none());
227
228        assert!(c.cache_get(&2).is_none());
229        assert_eq!(c.cache_hits(), Some(0));
230        assert_eq!(c.cache_misses(), Some(1));
231    }
232
233    #[test]
234    fn expiring_value_cache_get_mut_miss() {
235        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
236
237        // Getting a non-existent cache key.
238        assert!(c.cache_get_mut(&1).is_none());
239        assert_eq!(c.cache_hits(), Some(0));
240        assert_eq!(c.cache_misses(), Some(1));
241    }
242
243    #[test]
244    fn expiring_value_cache_get_mut_hit() {
245        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
246
247        // Getting a cached value.
248        assert!(c.cache_set(1, 2).is_none());
249        assert_eq!(c.cache_get_mut(&1), Some(&mut 2));
250        assert_eq!(c.cache_hits(), Some(1));
251        assert_eq!(c.cache_misses(), Some(0));
252    }
253
254    #[test]
255    fn expiring_value_cache_get_mut_expired() {
256        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
257
258        assert!(c.cache_set(2, 12).is_none());
259
260        assert!(c.cache_get(&2).is_none());
261        assert_eq!(c.cache_hits(), Some(0));
262        assert_eq!(c.cache_misses(), Some(1));
263    }
264
265    #[test]
266    fn expiring_value_cache_get_or_set_with_missing() {
267        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
268
269        assert_eq!(c.cache_get_or_set_with(1, || 1), &1);
270        assert_eq!(c.cache_hits(), Some(0));
271        assert_eq!(c.cache_misses(), Some(1));
272    }
273
274    #[test]
275    fn expiring_value_cache_get_or_set_with_present() {
276        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
277        assert!(c.cache_set(1, 5).is_none());
278
279        // Existing value is returned rather than setting new value.
280        assert_eq!(c.cache_get_or_set_with(1, || 1), &5);
281        assert_eq!(c.cache_hits(), Some(1));
282        assert_eq!(c.cache_misses(), Some(0));
283    }
284
285    #[test]
286    fn expiring_value_cache_get_or_set_with_expired() {
287        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
288        assert!(c.cache_set(1, 11).is_none());
289
290        // New value is returned as existing had expired.
291        assert_eq!(c.cache_get_or_set_with(1, || 1), &1);
292        assert_eq!(c.cache_hits(), Some(0));
293        assert_eq!(c.cache_misses(), Some(1));
294    }
295
296    #[test]
297    fn expiring_value_cache_try_get_or_set_with_missing() {
298        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
299
300        assert_eq!(
301            c.cache_try_get_or_set_with(1, || Ok::<_, ()>(1)),
302            Ok(&mut 1)
303        );
304        assert_eq!(c.cache_hits(), Some(0));
305        assert_eq!(c.cache_misses(), Some(1));
306
307        assert_eq!(c.cache_try_get_or_set_with(1, || Err(())), Ok(&mut 1));
308        assert_eq!(c.cache_hits(), Some(1));
309        assert_eq!(c.cache_misses(), Some(1));
310
311        assert_eq!(
312            c.cache_try_get_or_set_with(2, || Ok::<_, ()>(2)),
313            Ok(&mut 2)
314        );
315        assert_eq!(c.cache_hits(), Some(1));
316        assert_eq!(c.cache_misses(), Some(2));
317    }
318
319    #[test]
320    fn flush_expired() {
321        let mut c: ExpiringValueCache<u8, ExpiredU8> = ExpiringValueCache::with_size(3);
322
323        assert_eq!(c.cache_set(1, 100), None);
324        assert_eq!(c.cache_set(1, 200), Some(100));
325        assert_eq!(c.cache_set(2, 1), None);
326        assert_eq!(c.cache_size(), 2);
327
328        // It should only flush n > 10
329        assert_eq!(2, c.cache_size());
330        c.flush();
331        assert_eq!(1, c.cache_size());
332    }
333}