Skip to main content

lace_sans_io/core/
operand_cache.rs

1//! Peer operands held by hash, so a connected session can skip an operand
2//! Blob its peer already sent.
3
4use std::sync::{Arc, Mutex, PoisonError};
5
6use crate::logic::InterlaceModule;
7use crate::records::RecordHash;
8
9/// Peer operands received over connected sessions, most recently used first,
10/// at most `capacity` of them. Clones share one cache: a Lace hands its cache
11/// to every connected operation it runs. Entries are never persisted,
12/// admitted, observed, or exposed to policy.
13#[derive(Clone)]
14pub struct OperandCache(Arc<Mutex<Entries>>);
15
16struct Entries {
17    capacity: usize,
18    modules: Vec<(RecordHash, InterlaceModule)>,
19}
20
21impl OperandCache {
22    /// The capacity of a Lace's cache unless configured otherwise.
23    pub const DEFAULT_ENTRIES: usize = 32;
24
25    /// An empty cache of `capacity` entries; `0` caches nothing.
26    pub fn new(capacity: usize) -> Self {
27        Self(Arc::new(Mutex::new(Entries {
28            capacity,
29            modules: Vec::new(),
30        })))
31    }
32
33    /// Change the capacity, dropping the least recently used entries beyond it.
34    pub fn set_capacity(&self, capacity: usize) {
35        let mut entries = self.lock();
36        entries.capacity = capacity;
37        entries.modules.truncate(capacity);
38    }
39
40    /// The operand with this Blob hash, which becomes the most recently used.
41    pub(crate) fn get(&self, hash: &RecordHash) -> Option<InterlaceModule> {
42        let mut entries = self.lock();
43        let index = entries.modules.iter().position(|(id, _)| id == hash)?;
44        let entry = entries.modules.remove(index);
45        let module = entry.1.clone();
46        entries.modules.insert(0, entry);
47        Some(module)
48    }
49
50    /// Hold `module` as the most recently used entry.
51    pub(crate) fn insert(&self, hash: RecordHash, module: InterlaceModule) {
52        let mut entries = self.lock();
53        if entries.capacity == 0 {
54            return;
55        }
56        entries.modules.retain(|(id, _)| *id != hash);
57        entries.modules.insert(0, (hash, module));
58        let capacity = entries.capacity;
59        entries.modules.truncate(capacity);
60    }
61
62    fn lock(&self) -> std::sync::MutexGuard<'_, Entries> {
63        // Every mutation leaves the entries consistent.
64        self.0.lock().unwrap_or_else(PoisonError::into_inner)
65    }
66}
67
68impl Default for OperandCache {
69    fn default() -> Self {
70        Self::new(Self::DEFAULT_ENTRIES)
71    }
72}
73
74impl std::fmt::Debug for OperandCache {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        let entries = self.lock();
77        f.debug_struct("OperandCache")
78            .field("capacity", &entries.capacity)
79            .field("entries", &entries.modules.len())
80            .finish()
81    }
82}