1
use std::sync::Mutex;
2

            
3
/// A lazily populated cache whose entries are valid for one external generation.
4
#[derive(Debug)]
5
pub(crate) struct VersionedCache<T> {
6
    value: Mutex<Option<(u64, T)>>,
7
}
8

            
9
impl<T> VersionedCache<T> {
10
93975901832
    pub(crate) fn new() -> Self {
11
93975901832
        Self {
12
93975901832
            value: Mutex::new(None),
13
93975901832
        }
14
93975901832
    }
15

            
16
3206054961
    pub(crate) fn clear(&self) {
17
3206054961
        *self.value.lock().unwrap_or_else(|e| e.into_inner()) = None;
18
3206054961
    }
19
}
20

            
21
impl<T: Clone> VersionedCache<T> {
22
    /// Returns the cached value for the current generation, computing it on a cache miss.
23
    ///
24
    /// If the generation changes during computation, the result is discarded and recomputed so
25
    /// a value is never cached against a generation it may not represent.
26
487436467
    pub(crate) fn get_or_init(
27
487436467
        &self,
28
487436467
        generation: impl Fn() -> u64,
29
487436467
        mut compute: impl FnMut(u64) -> T,
30
487436467
    ) -> T {
31
        loop {
32
487436468
            let generation_before = generation();
33
487436468
            let mut cached = self.value.lock().unwrap_or_else(|e| e.into_inner());
34

            
35
487436468
            if let Some((cached_generation, value)) = cached.as_ref()
36
179961602
                && *cached_generation == generation_before
37
            {
38
173888387
                return value.clone();
39
313548081
            }
40

            
41
313548081
            let computed = compute(generation_before);
42
313548081
            let generation_after = generation();
43
313548081
            if generation_before == generation_after {
44
313548080
                *cached = Some((generation_after, computed.clone()));
45
313548080
                return computed;
46
1
            }
47
        }
48
487436467
    }
49
}
50

            
51
impl<T> Default for VersionedCache<T> {
52
    fn default() -> Self {
53
        Self::new()
54
    }
55
}
56

            
57
#[cfg(test)]
58
mod tests {
59
    use std::cell::Cell;
60

            
61
    use super::*;
62

            
63
    #[test]
64
1
    fn retries_when_generation_changes_during_computation() {
65
1
        let generation = Cell::new(1);
66
1
        let computations = Cell::new(0);
67
1
        let cache = VersionedCache::new();
68

            
69
1
        let value = cache.get_or_init(
70
4
            || generation.get(),
71
2
            |_| {
72
2
                let attempt = computations.get() + 1;
73
2
                computations.set(attempt);
74
2
                if attempt == 1 {
75
1
                    generation.set(2);
76
1
                }
77
2
                attempt
78
2
            },
79
        );
80

            
81
1
        assert_eq!(value, 2);
82
1
        assert_eq!(computations.get(), 2);
83
1
        assert_eq!(cache.get_or_init(|| generation.get(), |_| 3), 2);
84
1
    }
85
}