dyn_cache/
definition.rs

1macro_rules! doc_comment {
2    ($($contents:expr)+ => $($item:tt)+) => {
3        doc_comment! {@ concat!($($contents),+), $($item)+ }
4    };
5    (@ $contents:expr, $($item:tt)+) => {
6        #[doc = $contents]
7        $($item)+
8    };
9}
10
11macro_rules! impl_common_traits_for_type_with_addr {
12    ($type_:ty) => {
13        impl Hash for $type_ {
14            fn hash<H>(&self, hasher: &mut H)
15            where
16                H: Hasher,
17            {
18                self.addr().hash(hasher);
19            }
20        }
21
22        impl PartialEq for $type_ {
23            fn eq(&self, other: &Self) -> bool {
24                self.addr().eq(&other.addr())
25            }
26        }
27        impl Eq for $type_ {}
28
29        impl PartialOrd for $type_ {
30            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
31                self.addr().partial_cmp(&other.addr())
32            }
33        }
34
35        impl Ord for $type_ {
36            fn cmp(&self, other: &Self) -> Ordering {
37                self.addr().cmp(&other.addr())
38            }
39        }
40    };
41}
42
43macro_rules! define_cache {
44    ($module:ident, $cache:ident $(: $bound:ident)?, $($rest:tt)*) => {
45paste::item! {
46    define_cache! {@
47        $module,
48        $cache $(: $bound)?,
49        [<$cache:snake _tests>],
50        [<Shared $cache>],
51        $($rest)*
52    }
53}
54    };
55    (@
56        $module:ident,
57        $cache:ident $(: $bound:ident)?,
58        $test_mod:ident,
59        $shared:ident,
60        $refct:ident,
61        $lock:ident :: $acquire:ident
62    ) => {
63use crate::{dep_node::Dependent, *};
64use hash_hasher::HashBuildHasher;
65use hashbrown::HashMap;
66use std::{any::TypeId, borrow::Borrow, cmp::{Eq, Ordering}, hash::{Hash, Hasher}};
67
68doc_comment! {"
69Holds arbitrary query results which are namespaced by arbitrary scope types. Usually used
70through [`" stringify!($shared) "::cache_with`] and [`" stringify!($shared) "::gc`].
71
72# Query types
73
74> Note: the types referenced in this documentation are only visible on individual methods, as
75> `" stringify!($cache) "` is not itself a generic type.
76
77Storage is sharded by the type of the query. The type of a query has three parts:
78
79The query scope is the value which indexes the storage for a particular query type, it has the
80bound `Scope: 'static + Eq + Hash" $(" + " stringify!($bound))? "`.
81
82Each `Scope` corresponds to at most a single `Input: 'static" $(" + " stringify!($bound))? "`
83and a single `Output: 'static" $(" + " stringify!($bound))? "` value at any given time.
84
85# Reading stored values
86
87See [`" stringify!($cache) "::get`] which accepts borrowed forms of `Scope`
88and `Input`: `Key` and `Arg` respectively. `Arg` must satisfy `PartialEq<Input>` to determine
89whether to return a stored output.
90
91# Garbage Collection
92
93Each time [`" stringify!($cache) "::gc`] is called it removes any values which haven't been
94referenced since the prior call.
95
96After each GC, all values still in the cache are marked garbage. They are marked live again when
97inserted with [`" stringify!($cache) "::store`] or read with
98[`" stringify!($cache) "::get`].
99"=>
100#[derive(Debug, Default)]
101pub struct $cache {
102    /// We use a [`hash_hasher::HashBuildHasher`] here because we know that `TypeId`s
103    /// are globally unique and pre-hashed courtesy of rustc.
104    inner: HashMap<TypeId, Box<dyn Storage $(+ $bound)?>, HashBuildHasher>,
105    revision: u64,
106}}
107
108impl $cache {
109doc_comment! {"
110Return a reference to a query's stored output if a result is stored *and* `arg` equals the
111previously-stored `Input`. If a reference is returned, the stored input/output
112is marked as a root and will not be GC'd the next call.
113
114If no reference is found, a [`CacheMiss`] is returned. Call [`CacheMiss::init`] to get
115a [`CacheEntry`] to pass to [`" stringify!($cache) "::store`].
116"=>
117    pub fn get<'k, Key, Scope, Arg, Input, Output>(
118        &self,
119        key: &'k Key,
120        arg: &Arg,
121    ) -> Result<&Output, CacheMiss<'k, Key, Scope, Input, Output>>
122    where
123        Key: Eq + Hash + ToOwned<Owned = Scope> + ?Sized,
124        Scope: 'static + Borrow<Key> + Eq + Hash,
125        Arg: PartialEq<Input> + ToOwned<Owned=Input> + ?Sized,
126        Input: 'static + Borrow<Arg>,
127        Output: 'static,
128    {
129        let dependent = Dependent::incoming();
130        let query = Query::new(self.inner.hasher());
131
132        if let Some(ns) = self.get_namespace(&query) {
133            ns.get(key, arg, dependent, self.revision)
134                .map_err(|key_miss| CacheMiss { query, key_miss })
135        } else {
136            let key_miss = KeyMiss::just_key(key, arg.to_owned(), dependent, self.revision);
137            Err(CacheMiss { query, key_miss, })
138        }
139    }}
140
141doc_comment! {"
142Stores a fresh [`CacheEntry`] whose input/output will not be GC'd at the next call.
143Call [`" stringify!($cache) "::get`] to get a [`CacheMiss`] and [`CacheMiss::init`] to get a
144[`CacheEntry`].
145    "=>
146    pub fn store<Key, Scope, Input, Output>(
147        &mut self,
148        entry: CacheEntry<'_, Key, Scope, Input, Output>,
149    ) where
150        Key: Eq + Hash + ToOwned<Owned = Scope> + ?Sized,
151        Scope: 'static + Borrow<Key> + Eq + Hash $(+ $bound)?,
152        Input: 'static $(+ $bound)?,
153        Output: 'static $(+ $bound)?,
154    {
155        let revision = self.revision; // avoid double-borrowing self
156        let CacheEntry {
157            miss: CacheMiss { query, key_miss },
158            output,
159        } = entry;
160        self.get_namespace_mut(&query).store(key_miss, output, revision);
161    }}
162
163    fn get_namespace<Scope, Input, Output>(
164        &self,
165        query: &Query<Scope, Input, Output>,
166    ) -> Option<&Namespace<Scope, Input, Output>>
167    where
168        Scope: 'static,
169        Input: 'static,
170        Output: 'static,
171    {
172        let gc: &dyn Storage = &**self
173            .inner
174            .raw_entry()
175            .from_hash(query.hash(), |t| t == &query.ty())?.1;
176        Some(gc.as_any().downcast_ref().unwrap())
177    }
178
179    fn get_namespace_mut<Scope, Input, Output>(
180        &mut self,
181        query: &Query<Scope, Input, Output>,
182    ) -> &mut Namespace<Scope, Input, Output>
183    where
184        Scope: 'static + Eq + Hash $(+ $bound)?,
185        Input: 'static $(+ $bound)?,
186        Output: 'static $(+ $bound)?,
187    {
188        let gc: &mut dyn Storage = &mut **self
189            .inner
190            .raw_entry_mut()
191            .from_hash(query.hash(), |t| t == &query.ty())
192            .or_insert_with(|| {
193                (query.ty(), query.make_namespace())
194            }).1;
195        gc.as_any_mut().downcast_mut().unwrap()
196    }
197
198    /// Drop any values which have not been marked alive since the last call to this method.
199    pub fn gc(&mut self) {
200        let prev = self.revision; // avoid double-borrowing self
201        self.inner.values_mut().for_each(|ns| ns.mark(prev));
202        self.inner.values_mut().for_each(|namespace| namespace.sweep());
203        self.revision += 1;
204    }
205}
206
207impl std::panic::UnwindSafe for $cache {}
208impl std::panic::RefUnwindSafe for $cache {}
209
210doc_comment! {"
211Provides shared, synchronized access to a [`" stringify!($cache) "`] and a function-memoization
212API in [`" stringify!($shared) "::cache_with`].
213
214For convenience wrappers around [`" stringify!($shared) "::cache_with`] see
215[`" stringify!($shared) "::cache`] for returned types that implement
216`Clone` and [`" stringify!($shared) "::hold`] for values that just need to be stored
217without returning a reference.
218
219# Example
220
221```
222let storage = dyn_cache::" stringify!($module) "::" stringify!($shared) r#"::default();
223let call_count = std::cell::Cell::new(0);
224let increment_count = |&to_add: &i32| {
225    let new_count = call_count.get() + to_add;
226    call_count.set(new_count);
227    new_count
228};
229
230assert_eq!(call_count.get(), 0, "not called yet");
231
232let with_one = storage.cache_with(&'a', &1, &increment_count, Clone::clone);
233assert_eq!(call_count.get(), 1, "called only once");
234assert_eq!(call_count.get(), with_one);
235
236let with_one_again = storage.cache_with(&'a', &1, &increment_count, Clone::clone);
237assert_eq!(call_count.get(), 1, "still called only once, previous value cached");
238assert_eq!(call_count.get(), with_one_again);
239
240let with_two = storage.cache_with(&'a', &2, &increment_count, Clone::clone);
241assert_eq!(call_count.get(), 3, "called again with a new, larger increment");
242assert_eq!(call_count.get(), with_two);
243
244let with_other_query = storage.cache_with(&'b', &1, &increment_count, Clone::clone);
245assert_eq!(call_count.get(), 4, "called again with the same increment, different scope");
246assert_eq!(call_count.get(), with_other_query);
247
248let with_two_again = storage.cache_with(&'a', &2, &increment_count, Clone::clone);
249assert_eq!(call_count.get(), 4, "cell still has last mutation's value");
250assert_eq!(with_two_again, with_two, "cache should still have previous value");
251
252storage.gc(); // won't drop any values, but sets all of the cached values to be dropped
253call_count.set(0);
254
255// re-run 'b', marking it live
256let reran_other_query = storage.cache_with(&'b', &1, &increment_count, Clone::clone);
257assert_eq!(reran_other_query , 4, "returns the cached value");
258assert_eq!(call_count.get(), 0, "without running increment_count");
259
260storage.gc(); // query 'a' will be dropped
261
262// re-run 'b', observing cached value
263let reran_other_query = storage.cache_with(&'b', &1, &increment_count, Clone::clone);
264assert_eq!(reran_other_query , 4, "still returns the cached value");
265assert_eq!(call_count.get(), 0, "still without running increment_count");
266
267// run 'a' again, observe no cached value
268let with_one_again = storage.cache_with(&'a', &1, &increment_count, Clone::clone);
269assert_eq!(call_count.get(), 1, "called without caching");
270assert_eq!(call_count.get(), with_one_again);
271```
272"#=>
273#[derive(Clone, Debug, Default)]
274pub struct $shared {
275    inner: $refct<$lock<$cache>>,
276}}
277
278impl $shared {
279doc_comment!{r"
280Caches the result of `init(arg)` once per `key`, re-running it when `arg` changes. Always
281runs `with` on the stored `Output` before returning the result.
282
283See [`" stringify!($shared) "::cache`] for an ergonomic wrapper that requires `Output: Clone`.
284"=>
285    pub fn cache_with<Key, Scope, Arg, Input, Output, Ret>(
286        &self,
287        key: &Key,
288        arg: &Arg,
289        init: impl FnOnce(&Input) -> Output,
290        with: impl FnOnce(&Output) -> Ret,
291    ) -> Ret
292    where
293        Key: Eq + Hash + ToOwned<Owned = Scope> + ?Sized,
294        Scope: 'static + Borrow<Key> + Eq + Hash $(+ $bound)?,
295        Arg: PartialEq<Input> + ToOwned<Owned=Input> + ?Sized,
296        Input: 'static + Borrow<Arg> $(+ $bound)?,
297        Output: 'static $(+ $bound)?,
298        Ret: 'static $(+ $bound)?,
299    {
300        let miss = match { self.inner.$acquire().get(key, arg) } {
301            Ok(stored) => return with(stored),
302            Err(m) => m,
303        };
304
305        let (to_store, to_return) = miss.init(|arg| {
306            let store = init(arg);
307            let ret = with(&store);
308            (store, ret)
309        });
310
311        self.inner.$acquire().store(to_store);
312        to_return
313    }}
314
315doc_comment!{r"
316Caches the result of `init(arg)` once per `key`, re-running it when `arg` changes. Clones
317the cached output before returning the result.
318
319See [`" stringify!($shared) "::cache_with`] for a lower-level version which does not require
320`Output: Clone`.
321"=>
322    pub fn cache<Key, Scope, Arg, Input, Output>(
323        &self,
324        key: &Key,
325        arg: &Arg,
326        init: impl FnOnce(&Input) -> Output,
327    ) -> Output
328    where
329        Key: Eq + Hash + ToOwned<Owned = Scope> + ?Sized,
330        Scope: 'static + Borrow<Key> + Eq + Hash $(+ $bound)?,
331        Arg: PartialEq<Input> + ToOwned<Owned=Input> + ?Sized,
332        Input: 'static + Borrow<Arg> $(+ $bound)?,
333        Output: 'static + Clone $(+ $bound)?,
334    {
335        self.cache_with(key, arg, init, Clone::clone)
336    }}
337
338doc_comment!{r"
339Caches the result of `init(arg)` once per `key`, re-running it when `arg` changes.
340
341Does not return any reference to the cached value. See [`" stringify!($shared) "::cache`]
342for similar functionality that returns a copy of `Output` or
343[`" stringify!($shared) "::cache_with`] which allows specifying other pre-return functions.
344"=>
345    pub fn hold<Key, Scope, Arg, Input, Output>(
346        &self,
347        key: &Key,
348        arg: &Arg,
349        init: impl FnOnce(&Input) -> Output,
350    )
351    where
352        Key: Eq + Hash + ToOwned<Owned = Scope> + ?Sized,
353        Scope: 'static + Borrow<Key> + Eq + Hash $(+ $bound)?,
354        Arg: PartialEq<Input> + ToOwned<Owned=Input> + ?Sized,
355        Input: 'static + Borrow<Arg> $(+ $bound)?,
356        Output: 'static $(+ $bound)?,
357    {
358        self.cache_with(key, arg, init, |_| {})
359    }}
360
361doc_comment!{"
362Forwards to [`" stringify!($cache) "::gc`].
363"=>
364    pub fn gc(&self) {
365        self.inner.$acquire().gc();
366    }}
367
368    fn addr(&self) -> usize {
369        $refct::as_ptr(&self.inner) as *const _ as _
370    }
371}
372
373impl_common_traits_for_type_with_addr!($shared);
374
375impl From<$cache> for $shared {
376    fn from(inner: $cache) -> Self {
377        Self { inner: $refct::new($lock::new(inner)) }
378    }
379}
380
381impl std::panic::UnwindSafe for $shared {}
382impl std::panic::RefUnwindSafe for $shared {}
383
384#[cfg(test)]
385mod $test_mod {
386    use super::*;
387    use std::sync::{
388        atomic::{AtomicU32, Ordering},
389        Arc,
390    };
391    use parking_lot::Mutex;
392
393    #[test]
394    fn single_query_with_gc() {
395        let storage = $shared::default();
396        let call_count = std::cell::Cell::new(0);
397        let increment_count = |&to_add: &i32| {
398            let new_count = call_count.get() + to_add;
399            call_count.set(new_count);
400            new_count
401        };
402
403        assert_eq!(call_count.get(), 0);
404
405        let with_b = storage.cache_with(&'b', &1, &increment_count, Clone::clone);
406        assert_eq!(call_count.get(), 1);
407        assert_eq!(call_count.get(), with_b);
408
409        storage.gc(); // won't drop any values, but sets all of the cached values to be dropped
410        call_count.set(0);
411
412        let rerun_b = storage.cache_with(&'b', &1, &increment_count, Clone::clone);
413        assert_eq!(rerun_b , 1);
414        assert_eq!(call_count.get(), 0);
415
416        storage.gc();
417        // 'b' is not refreshed before we call gc again
418        storage.gc();
419
420        let again = storage.cache_with(&'b', &1, &increment_count, Clone::clone);
421        assert_eq!(again, 1);
422        assert_eq!(call_count.get(), 1);
423    }
424
425    #[test]
426    fn distinct_scopes_distinct_storage() {
427        let storage = $shared::default();
428        let call_count = std::cell::Cell::new(0);
429        let increment_count = |&to_add: &i32| {
430            let new_count = call_count.get() + to_add;
431            call_count.set(new_count);
432            new_count
433        };
434
435        assert_eq!(call_count.get(), 0);
436
437        let a_with_1 = storage.cache_with(&'a', &1, &increment_count, Clone::clone);
438        assert_eq!(call_count.get(), 1);
439        assert_eq!(call_count.get(), a_with_1);
440
441        let b_with_1 = storage.cache_with(&'b', &1, &increment_count, Clone::clone);
442        assert_eq!(call_count.get(), 2);
443        assert_eq!(call_count.get(), b_with_1);
444
445        let a_with_1_again = storage.cache_with(&'a', &1, &increment_count, Clone::clone);
446        assert_eq!(call_count.get(), 2, "untouched");
447        assert_eq!(a_with_1_again, a_with_1, "cached");
448
449        let with_a_2 = storage.cache_with(&'a', &2, &increment_count, Clone::clone);
450        assert_eq!(call_count.get(), 4);
451        assert_eq!(call_count.get(), with_a_2);
452
453        let with_a_2_again = storage.cache_with(&'a', &2, &increment_count, Clone::clone);
454        assert_eq!(call_count.get(), 4);
455        assert_eq!(with_a_2_again, with_a_2);
456    }
457
458    #[test]
459    fn hold_retains_across_gcs() {
460        let storage = $shared::default();
461
462        let guard_count_inc = Arc::new(Mutex::new(0));
463        let drop_count_inc = Arc::new(Mutex::new(0));
464        let (guard_count, drop_count) = (guard_count_inc.clone(), drop_count_inc.clone());
465
466        macro_rules! assert_counts {
467            ($guard:expr, $drop:expr) => {{
468                assert_eq!($guard, *guard_count.lock());
469                assert_eq!($drop, *drop_count.lock());
470            }};
471        }
472
473        let make_guard = || {
474            let (guard_count_inc, drop_count_inc) = (
475                guard_count_inc.clone(),
476                drop_count_inc.clone(),
477            );
478            storage.hold(
479                &'a',
480                &(),
481                move |&()| {
482                    *guard_count_inc.lock() += 1;
483                    scopeguard::guard((), move |()| *drop_count_inc.lock() += 1)
484                },
485            );
486        };
487
488        assert_counts!(0, 0);
489        make_guard();
490        assert_counts!(1, 0);
491        storage.gc();
492        assert_counts!(1, 0);
493        make_guard();
494        assert_counts!(1, 0);
495        storage.gc();
496        assert_counts!(1, 0);
497        storage.gc();
498        assert_counts!(1, 1);
499        make_guard();
500        assert_counts!(2, 1);
501    }
502
503    #[test]
504    fn nested_hold_retains_across_gcs() {
505        let storage = $shared::default();
506
507        let guard_count_inc = Arc::new(Mutex::new(0));
508        let drop_count_inc = Arc::new(Mutex::new(0));
509        let (guard_count, drop_count) = (guard_count_inc.clone(), drop_count_inc.clone());
510
511        macro_rules! assert_counts {
512            ($guard:expr, $drop:expr) => {{
513                assert_eq!($guard, *guard_count.lock(), "guard count incorrect");
514                assert_eq!($drop, *drop_count.lock(), "drop count incorrect");
515            }};
516        }
517
518        let make_guard = || {
519            let (guard_count_inc, drop_count_inc) = (
520                guard_count_inc.clone(),
521                drop_count_inc.clone(),
522            );
523            storage.hold(
524                &'a',
525                &(),
526                |&()| {
527                    *guard_count_inc.lock() += 1;
528                    scopeguard::guard((), move |()| *drop_count_inc.lock() += 1)
529                },
530            );
531        };
532
533        let memo_make_guard = || {
534            storage.hold("foo", "bar", |_| make_guard());
535        };
536
537        let memo_memo_make_guard = || {
538            storage.hold("baz", "quux", |_| memo_make_guard());
539        };
540
541        assert_counts!(0, 0);
542        memo_memo_make_guard();
543        assert_counts!(1, 0);
544        storage.gc();
545        assert_counts!(1, 0);
546        memo_memo_make_guard();
547        assert_counts!(1, 0);
548        storage.gc();
549        assert_counts!(1, 0);
550        storage.gc();
551        assert_counts!(1, 1); // prior GC had no accesses, should be dropped
552    }
553
554    struct CountDrops {
555        num_drops: Arc<AtomicU32>,
556    }
557
558    impl Drop for CountDrops {
559        fn drop(&mut self) {
560            self.num_drops.fetch_add(1, Ordering::SeqCst);
561        }
562    }
563
564    #[test]
565    fn issue_238_cache_call_incorrectly_preserves_child() {
566        // counts the number of times the once() call below is made
567        let once_calls = Arc::new(AtomicU32::new(0));
568
569        // counts the number of times the value returned from once() below is GC'd
570        let once_drops = Arc::new(AtomicU32::new(0));
571
572        let mut storage = $shared::default();
573
574        let mut i = 0; // a unique value for the cache
575        let i = &mut i;
576        let adder = once_calls.clone();
577        let drop_adder = once_drops.clone();
578        let mut tick = move |should_hold, storage: &mut $shared| {
579            storage.cache_with(&(), &*i, |_| {
580                if should_hold {
581                    storage.hold(&(), &(), |_| {
582                        adder.fetch_add(1, Ordering::SeqCst);
583                        Arc::new(CountDrops { num_drops: drop_adder.clone() })
584                    });
585                }
586            }, |_| {});
587            storage.gc();
588            *i += 1;
589        };
590
591        tick(false, &mut storage);
592        assert_eq!(once_calls.load(Ordering::SeqCst), 0);
593        assert_eq!(once_drops.load(Ordering::SeqCst), 0);
594
595        tick(true, &mut storage);
596        assert_eq!(once_calls.load(Ordering::SeqCst), 1);
597        assert_eq!(once_drops.load(Ordering::SeqCst), 0);
598
599        tick(false, &mut storage);
600        assert_eq!(once_calls.load(Ordering::SeqCst), 1);
601        assert_eq!(once_drops.load(Ordering::SeqCst), 1);
602    }
603}
604    };
605}