OXYGENHUB
# 2fb70d019851 oxygenhub / crates / hub / src / repos / cache.rs
3.0 KiB · Rust · last changed State cache keyed by heads; history and graph through oxy-core's lanes and log window 1 month ago source blame history raw
1
//! Loaded states cached by their heads (hub spec §16.3). A state is a pure
2
//! function of its heads, so entries never go stale; the cache is only
3
//! bounded. Shared across threads because `oxy_core::state::State` is
4
//! `Send + Sync`.
5
6
use oxy_core::hash::ChangeHash;
7
use oxy_core::repo::Repo;
8
use oxy_core::state::{Snapshot, State};
9
use std::collections::HashMap;
10
use std::sync::{Arc, Mutex};
11
12
/// A state and its materialised snapshot.
13
pub struct Loaded {
14
    pub heads: Vec<ChangeHash>,
15
    pub state: State,
16
    pub snapshot: Snapshot,
17
}
18
19
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
20
struct Key {
21
    repo: [u8; 16],
22
    heads: Vec<ChangeHash>,
23
}
24
25
struct Entry {
26
    loaded: Arc<Loaded>,
27
    last_used: u64,
28
}
29
30
/// A bounded least-recently-used cache of loaded states.
31
pub struct StateCache {
32
    capacity: usize,
33
    inner: Mutex<(HashMap<Key, Entry>, u64)>,
34
}
35
36
impl StateCache {
37
    pub fn new(capacity: usize) -> StateCache {
38
        StateCache { capacity: capacity.max(1), inner: Mutex::new((HashMap::new(), 0)) }
39
    }
40
41
    /// The state with these heads, loaded once per hub process.
42
    pub fn load(&self, repo: &Repo, heads: &[ChangeHash]) -> Result<Arc<Loaded>, oxy_core::repo::RepoError> {
43
        let mut sorted = heads.to_vec();
44
        sorted.sort();
45
        sorted.dedup();
46
        let key = Key { repo: repo.id()?, heads: sorted.clone() };
47
        {
48
            let mut guard = self.inner.lock().unwrap();
49
            let (map, tick) = &mut *guard;
50
            *tick += 1;
51
            if let Some(e) = map.get_mut(&key) {
52
                e.last_used = *tick;
53
                return Ok(e.loaded.clone());
54
            }
55
        }
56
        let state = repo.load_state(&sorted)?;
57
        let snapshot = state.materialize();
58
        let loaded = Arc::new(Loaded { heads: sorted, state, snapshot });
59
        let mut guard = self.inner.lock().unwrap();
60
        let (map, tick) = &mut *guard;
61
        if map.len() >= self.capacity {
62
            if let Some(oldest) = map.iter().min_by_key(|(_, e)| e.last_used).map(|(k, _)| k.clone()) {
63
                map.remove(&oldest);
64
            }
65
        }
66
        map.insert(key, Entry { loaded: loaded.clone(), last_used: *tick });
67
        Ok(loaded)
68
    }
69
70
    pub fn len(&self) -> usize {
71
        self.inner.lock().unwrap().0.len()
72
    }
73
74
    pub fn is_empty(&self) -> bool {
75
        self.len() == 0
76
    }
77
}
78
79
#[cfg(test)]
80
mod tests {
81
    use super::*;
82
83
    #[test]
84
    fn caches_by_heads_and_evicts_the_oldest() {
85
        let dir = tempfile::tempdir().unwrap();
86
        let repo = Repo::init_bare(dir.path()).unwrap();
87
        let cache = StateCache::new(2);
88
        let a = cache.load(&repo, &[]).unwrap();
89
        let b = cache.load(&repo, &[]).unwrap();
90
        assert!(Arc::ptr_eq(&a, &b), "same heads, same entry");
91
        assert_eq!(cache.len(), 1);
92
        let h1 = oxy_core::hash::ObjectRef([1; 32]);
93
        let h2 = oxy_core::hash::ObjectRef([2; 32]);
94
        // Unknown heads fail to load and are not cached.
95
        assert!(cache.load(&repo, &[h1]).is_err());
96
        assert_eq!(cache.len(), 1);
97
        let _ = (h2, dir);
98
    }
99
}