Skip to main content

sva_engine/cache/
memory.rs

1// Concern: the memory tier: every resident value and node under one cap, what it evicts and writes back, the misses it keeps | Non-concern: the disk | IO: (key) -> held, answered; offers -> kept
2
3use std::collections::HashMap;
4use std::sync::{Arc, Mutex, MutexGuard};
5
6use sva_formula::Hash;
7use sva_samples::{Buffer, Extent, Label};
8
9use super::stored::{Header, Samples};
10use super::{Entry, Expected, Payload, Stored, joined};
11
12pub const DEFAULT_CACHE_BYTES: u64 = 2 << 30;
13
14/// Samples between two states a run keeps, so a reader resumes from one at most this far back.
15pub const DEFAULT_MARK_EVERY: usize = 16_384;
16
17/// Which computed values memory keeps besides those a render offers as nodes; the rest are
18/// computed again when asked.
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20pub enum CachePolicy {
21    #[default]
22    All,
23    /// The values two or more nodes read, and the render target.
24    Forks,
25    Target,
26}
27
28impl CachePolicy {
29    pub const ALL: [CachePolicy; 3] = [CachePolicy::All, CachePolicy::Forks, CachePolicy::Target];
30
31    pub fn name(self) -> &'static str {
32        match self {
33            CachePolicy::All => "all",
34            CachePolicy::Forks => "forks",
35            CachePolicy::Target => "target",
36        }
37    }
38
39    pub fn named(name: &str) -> Option<CachePolicy> {
40        CachePolicy::ALL.into_iter().find(|p| p.name() == name)
41    }
42
43    fn keeps(self, fork: bool, target: bool) -> bool {
44        match self {
45            CachePolicy::All => true,
46            CachePolicy::Forks => fork || target,
47            CachePolicy::Target => target,
48        }
49    }
50}
51
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
53pub enum PrunePolicy {
54    /// Every entry the newest render neither stored nor read.
55    #[default]
56    Oldest,
57    /// Every entry whose node fewer than two nodes read.
58    Forks,
59}
60
61impl PrunePolicy {
62    pub const ALL: [PrunePolicy; 2] = [PrunePolicy::Oldest, PrunePolicy::Forks];
63
64    pub fn name(self) -> &'static str {
65        match self {
66            PrunePolicy::Oldest => "oldest",
67            PrunePolicy::Forks => "forks",
68        }
69    }
70
71    pub fn named(name: &str) -> Option<PrunePolicy> {
72        PrunePolicy::ALL.into_iter().find(|p| p.name() == name)
73    }
74}
75
76/// What passed between memory and the disk beneath it.
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
78pub struct Counters {
79    pub disk_lookups: u64,
80    pub disk_reads: u64,
81    pub disk_read_bytes: u64,
82    /// Disk answers made resident: a header looked up, or samples read.
83    pub promotions: u64,
84    pub writebacks: u64,
85    pub evictions: u64,
86}
87
88impl Counters {
89    pub fn since(self, then: Counters) -> Counters {
90        Counters {
91            disk_lookups: self.disk_lookups - then.disk_lookups,
92            disk_reads: self.disk_reads - then.disk_reads,
93            disk_read_bytes: self.disk_read_bytes - then.disk_read_bytes,
94            promotions: self.promotions - then.promotions,
95            writebacks: self.writebacks - then.writebacks,
96            evictions: self.evictions - then.evictions,
97        }
98    }
99}
100
101#[derive(Clone, Copy, Debug)]
102pub(crate) struct Stamp {
103    pub tree: u64,
104    pub fork: bool,
105    /// A volatile node's value replaces the last one stored under the same slot.
106    pub slot: Option<Hash>,
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110pub(crate) enum Kept {
111    Held,
112    Replaced,
113    Refused,
114}
115
116/// What memory answers a node's key with: a miss holds for the round it was met in and later
117/// ones begun before it, so another holder's commit is found once a new round begins.
118pub(crate) enum Known {
119    Hit(Arc<Stored>),
120    Miss,
121    Unknown,
122}
123
124/// Where an offered node's sample `n` is: sample `n + by` of the values memory holds, a value's
125/// segments or a run's, each under its key from its start on, or of another node.
126#[derive(Clone, Debug, PartialEq)]
127pub(crate) enum Offered {
128    Values {
129        keys: Vec<(i64, Hash)>,
130        by: i64,
131    },
132    Moves {
133        of: Hash,
134        by: i64,
135    },
136    /// Samples no value holds, the node's own.
137    Held(Vec<Arc<Buffer>>),
138}
139
140pub(crate) struct Writeback {
141    pub(crate) key: Hash,
142    pub(crate) head: Header,
143    pub(crate) parts: Vec<Arc<Buffer>>,
144}
145
146enum Item {
147    Value {
148        payload: Payload,
149        label: Option<Label>,
150        slot: Option<Hash>,
151    },
152    Node {
153        source: Source,
154        dirty: bool,
155        slot: Option<Hash>,
156    },
157}
158
159enum Source {
160    Disk {
161        head: Box<Header>,
162        chunks: Vec<Arc<Buffer>>,
163    },
164    Offered {
165        stored: Box<Stored>,
166        offered: Offered,
167    },
168}
169
170impl Source {
171    fn stored(&self) -> &Stored {
172        match self {
173            Source::Disk { head, .. } => head.stored(),
174            Source::Offered { stored, .. } => stored,
175        }
176    }
177}
178
179struct Held {
180    item: Item,
181    read: u64,
182    tree: u64,
183    fork: bool,
184}
185
186fn planes(b: &Buffer) -> u64 {
187    (b.len() * b.width * size_of::<f64>()) as u64
188}
189
190impl Held {
191    fn bytes(&self) -> u64 {
192        match &self.item {
193            Item::Value { payload, .. } => payload.bytes() as u64,
194            Item::Node {
195                source:
196                    Source::Disk { chunks, .. }
197                    | Source::Offered {
198                        offered: Offered::Held(chunks),
199                        ..
200                    },
201                ..
202            } => chunks.iter().map(|c| planes(c)).sum(),
203            Item::Node { .. } => 0,
204        }
205    }
206
207    fn slot(&self) -> Option<Hash> {
208        match &self.item {
209            Item::Value { slot, .. } | Item::Node { slot, .. } => *slot,
210        }
211    }
212}
213
214#[derive(Default)]
215struct State {
216    entries: HashMap<Hash, Held>,
217    slots: HashMap<Hash, Hash>,
218    misses: HashMap<Hash, u64>,
219    pending: Vec<Writeback>,
220    bytes: u64,
221    max_bytes: u64,
222    policy: CachePolicy,
223    prune: PrunePolicy,
224    clock: u64,
225    tree: u64,
226    round: u64,
227    mark_every: usize,
228    counters: Counters,
229    disk: bool,
230    /// Writes that failed, and why the latest did: none is tried again until a persist.
231    failed: (u64, Option<String>),
232}
233
234impl State {
235    fn tick(&mut self) -> u64 {
236        self.clock += 1;
237        self.clock
238    }
239
240    fn stands(&self, key: Hash) -> bool {
241        let doomed = self.doomed(key);
242        doomed.iter().skip(1).any(|at| {
243            matches!(
244                self.entries.get(at),
245                Some(Held {
246                    item: Item::Node { dirty: true, .. },
247                    ..
248                })
249            )
250        })
251    }
252
253    /// The values and nodes reading `key`'s samples, and theirs, `key` first.
254    fn doomed(&self, key: Hash) -> Vec<Hash> {
255        let mut out = vec![key];
256        let mut k = 0;
257        while k < out.len() {
258            let gone = out[k];
259            for (at, held) in &self.entries {
260                let Item::Node {
261                    source: Source::Offered { offered, .. },
262                    ..
263                } = &held.item
264                else {
265                    continue;
266                };
267                let reads = match offered {
268                    Offered::Values { keys, .. } => keys.iter().any(|(_, key)| *key == gone),
269                    Offered::Moves { of, .. } => *of == gone,
270                    Offered::Held(_) => false,
271                };
272                if reads && !out.contains(at) {
273                    out.push(*at);
274                }
275            }
276            k += 1;
277        }
278        out
279    }
280
281    /// `key` and everything reading its samples gone, each node not yet on the disk sent
282    /// there first.
283    fn remove(&mut self, key: Hash) -> bool {
284        if !self.entries.contains_key(&key) {
285            return false;
286        }
287        let doomed = self.doomed(key);
288        for at in &doomed {
289            self.flush(*at);
290        }
291        for at in doomed {
292            self.discard(at);
293        }
294        true
295    }
296
297    fn discard(&mut self, key: Hash) {
298        let Some(gone) = self.entries.remove(&key) else {
299            return;
300        };
301        self.bytes -= gone.bytes();
302        let value = matches!(gone.item, Item::Value { .. });
303        if let Some(slot) = gone.slot().filter(|_| value)
304            && self.slots.get(&slot) == Some(&key)
305        {
306            self.slots.remove(&slot);
307        }
308    }
309
310    fn flush(&mut self, key: Hash) {
311        let dirty = matches!(
312            self.entries.get(&key),
313            Some(Held {
314                item: Item::Node { dirty: true, .. },
315                ..
316            })
317        );
318        if !dirty {
319            return;
320        }
321        if let Some(back) = self.written(key) {
322            self.pending.push(back);
323        }
324        if let Some(Held {
325            item: Item::Node { dirty, .. },
326            ..
327        }) = self.entries.get_mut(&key)
328        {
329            *dirty = false;
330        }
331    }
332
333    fn written(&self, key: Hash) -> Option<Writeback> {
334        let Item::Node {
335            source: Source::Offered { stored, offered },
336            ..
337        } = &self.entries.get(&key)?.item
338        else {
339            return None;
340        };
341        let head = |samples| Header::new((**stored).clone(), samples);
342        Some(match offered {
343            Offered::Moves { of, by } => {
344                let (of, by) = self.referred(*of, *by)?;
345                Writeback {
346                    key,
347                    head: head(Samples::Of { key: of, by }),
348                    parts: Vec::new(),
349                }
350            }
351            _ => Writeback {
352                key,
353                head: head(Samples::None),
354                parts: self.offered_parts(offered, Extent::EVERYWHERE)?,
355            },
356        })
357    }
358
359    fn referred(&self, of: Hash, by: i64) -> Option<(Hash, i64)> {
360        match &self.entries.get(&of)?.item {
361            Item::Node {
362                source: Source::Disk { head, .. },
363                ..
364            } => match head.samples() {
365                Samples::Entry { file, shift, .. } => Some((*file, by - shift)),
366                _ => None,
367            },
368            Item::Node {
369                source:
370                    Source::Offered {
371                        offered: Offered::Moves { of, by: more },
372                        ..
373                    },
374                ..
375            } => self.referred(*of, by + more),
376            Item::Node { .. } => Some((of, by)),
377            Item::Value { .. } => None,
378        }
379    }
380
381    /// Each part the values under `keys` hold, with the stretch of it that is the node's.
382    fn shares(&self, keys: &[(i64, Hash)]) -> Option<Vec<(Arc<Buffer>, Extent)>> {
383        let mut out = Vec::new();
384        for (k, (start, key)) in keys.iter().enumerate() {
385            let end = keys.get(k + 1).map_or(i64::MAX, |(next, _)| *next);
386            let within = Extent::new(*start, end);
387            let parts = match &self.entries.get(key).map(|held| &held.item) {
388                Some(Item::Value {
389                    payload: Payload::Segments(parts),
390                    ..
391                }) => parts.clone(),
392                Some(Item::Value {
393                    payload: Payload::Run(run),
394                    ..
395                }) => vec![Arc::clone(&run.samples)],
396                _ => Vec::new(),
397            };
398            let met = |part: Arc<Buffer>| {
399                let met = part.extent().intersect(within);
400                (!met.is_empty()).then_some((part, met))
401            };
402            out.extend(parts.into_iter().filter_map(met));
403        }
404        let held = keys.iter().any(|(_, key)| self.entries.contains_key(key));
405        held.then_some(out)
406    }
407
408    fn offered_parts(&self, offered: &Offered, over: Extent) -> Option<Vec<Arc<Buffer>>> {
409        let (parts, by): (Vec<Arc<Buffer>>, i64) = match offered {
410            Offered::Values { keys, by } => {
411                let within = over.shifted(*by);
412                let met = self.shares(keys)?.into_iter();
413                let met = met.filter(|(_, held)| !held.intersect(within).is_empty());
414                (met.map(|(part, held)| clipped(&part, held)).collect(), *by)
415            }
416            Offered::Moves { of, by } => (self.resident(*of, over.shifted(*by))?.0, *by),
417            Offered::Held(parts) => (parts.clone(), 0),
418        };
419        let over = over.shifted(by);
420        let meets = |b: &&Arc<Buffer>| !b.extent().intersect(over).is_empty();
421        Some(parts.iter().filter(meets).map(|p| moved(p, -by)).collect())
422    }
423
424    /// What memory holds of `key` meeting `over`, and for a node off the disk, what of `over`
425    /// the disk holds that memory lacks, with its layout.
426    fn resident(&self, key: Hash, over: Extent) -> Option<Resident> {
427        let Item::Node { source, .. } = &self.entries.get(&key)?.item else {
428            return None;
429        };
430        match source {
431            Source::Offered { offered, .. } => Some((self.offered_parts(offered, over)?, None)),
432            Source::Disk { head, chunks } => {
433                let meets = |b: &&Arc<Buffer>| !b.extent().intersect(over).is_empty();
434                let parts: Vec<Arc<Buffer>> = chunks.iter().filter(meets).cloned().collect();
435                let mut lacks = Vec::new();
436                for held in head.stored().extents() {
437                    let mut asked = vec![held.intersect(over)];
438                    for part in chunks {
439                        asked = asked
440                            .into_iter()
441                            .flat_map(|a| minus(a, part.extent()))
442                            .collect();
443                    }
444                    lacks.extend(asked.into_iter().filter(|a| !a.is_empty()));
445                }
446                let hull = lacks.iter().fold(Extent::NOWHERE, |h, e| h.hull(*e));
447                Some((parts, (!hull.is_empty()).then(|| ((**head).clone(), hull))))
448            }
449        }
450    }
451
452    fn coverage(&self, key: Hash, depth: usize) -> Option<Vec<Extent>> {
453        let Item::Node { source, .. } = &self.entries.get(&key)?.item else {
454            return None;
455        };
456        let offered = match source {
457            Source::Disk { head, .. } => return Some(head.stored().extents().to_vec()),
458            Source::Offered { offered, .. } => offered,
459        };
460        match offered {
461            Offered::Moves { of, by } if depth < 64 => {
462                let held = self.coverage(*of, depth + 1)?;
463                Some(held.into_iter().map(|e| e.shifted(-by)).collect())
464            }
465            Offered::Moves { .. } => None,
466            Offered::Values { keys, by } => {
467                let shares = self.shares(keys)?.into_iter();
468                Some(shares.map(|(_, held)| held.shifted(-by)).collect())
469            }
470            Offered::Held(parts) => Some(parts.iter().map(|p| p.extent()).collect()),
471        }
472    }
473
474    fn evict(&mut self, key: Hash) {
475        let disk = match self.entries.get_mut(&key) {
476            Some(Held {
477                item:
478                    Item::Node {
479                        source: Source::Disk { chunks, .. },
480                        ..
481                    },
482                ..
483            }) => Some(std::mem::take(chunks)),
484            _ => None,
485        };
486        match disk {
487            Some(chunks) => {
488                self.bytes -= chunks.iter().map(|c| planes(c)).sum::<u64>();
489                self.counters.evictions += 1;
490            }
491            None => {
492                if self.remove(key) {
493                    self.counters.evictions += 1;
494                }
495            }
496        }
497    }
498
499    /// Every entry `policy` names, oldest-read first, until `bytes` is at most `to`; then whole
500    /// trees oldest-first, until the cap holds. A node off the disk keeps its header; one that
501    /// holds no bytes goes only where everything named goes.
502    fn prune(&mut self, policy: PrunePolicy, to: u64) {
503        let newest = self.tree;
504        let mut named: Vec<(u64, Hash)> = self
505            .entries
506            .iter()
507            .filter(|(_, held)| match policy {
508                PrunePolicy::Oldest => held.tree != newest,
509                PrunePolicy::Forks => !held.fork,
510            })
511            .filter(|(_, held)| to == 0 || held.bytes() > 0)
512            .map(|(key, held)| (held.read, *key))
513            .collect();
514        named.sort_unstable();
515        for (_, key) in named {
516            if self.bytes <= to && to > 0 {
517                break;
518            }
519            self.evict(key);
520        }
521        let mut trees: Vec<u64> = self.entries.values().map(|held| held.tree).collect();
522        trees.sort_unstable();
523        trees.dedup();
524        for tree in trees {
525            if self.bytes <= self.max_bytes {
526                break;
527            }
528            let whole: Vec<Hash> = self
529                .entries
530                .iter()
531                .filter(|(_, held)| held.tree == tree && held.bytes() > 0)
532                .map(|(key, _)| *key)
533                .collect();
534            for key in whole {
535                self.evict(key);
536            }
537        }
538    }
539
540    fn bounded(&mut self) {
541        if self.bytes > self.max_bytes {
542            self.prune(self.prune, self.max_bytes);
543        }
544    }
545}
546
547type Resident = (Vec<Arc<Buffer>>, Option<(Header, Extent)>);
548
549fn minus(e: Extent, cut: Extent) -> Vec<Extent> {
550    let met = e.intersect(cut);
551    if met.is_empty() {
552        return vec![e];
553    }
554    vec![Extent::new(e.start, met.start), Extent::new(met.end, e.end)]
555}
556
557fn clipped(part: &Arc<Buffer>, met: Extent) -> Arc<Buffer> {
558    let held = part.extent();
559    match met == held {
560        true => Arc::clone(part),
561        false => Arc::new(part.over(met, held)),
562    }
563}
564
565fn moved(part: &Arc<Buffer>, by: i64) -> Arc<Buffer> {
566    if by == 0 {
567        return Arc::clone(part);
568    }
569    let mut out = (**part).clone();
570    out.start += by;
571    Arc::new(out)
572}
573
574/// The memory tier: the one owner of every value and node held resident, and of what memory
575/// knows a disk beneath it holds or lacks. A clone is a handle on the same memory.
576#[derive(Clone)]
577pub(crate) struct Memory {
578    state: Arc<Mutex<State>>,
579}
580
581impl Default for Memory {
582    fn default() -> Memory {
583        Memory::holding(DEFAULT_CACHE_BYTES)
584    }
585}
586
587impl Memory {
588    pub(crate) fn holding(max_bytes: u64) -> Memory {
589        Memory {
590            state: Arc::new(Mutex::new(State {
591                max_bytes,
592                mark_every: DEFAULT_MARK_EVERY,
593                ..State::default()
594            })),
595        }
596    }
597
598    /// Over a disk: each node a render offers is written back before it goes.
599    pub(crate) fn over_disk(max_bytes: u64) -> Memory {
600        let memory = Memory::holding(max_bytes);
601        memory.locked().disk = true;
602        memory
603    }
604
605    fn locked(&self) -> MutexGuard<'_, State> {
606        self.state.lock().unwrap_or_else(|poisoned| {
607            let mut state = poisoned.into_inner();
608            state.entries.clear();
609            state.slots.clear();
610            state.bytes = 0;
611            self.state.clear_poison();
612            state
613        })
614    }
615
616    pub(crate) fn max_bytes(&self) -> u64 {
617        self.locked().max_bytes
618    }
619
620    pub(crate) fn set_max_bytes(&self, max_bytes: u64) {
621        let mut state = self.locked();
622        state.max_bytes = max_bytes;
623        state.bounded();
624    }
625
626    pub(crate) fn policy(&self) -> CachePolicy {
627        self.locked().policy
628    }
629
630    pub(crate) fn set_policy(&self, policy: CachePolicy) {
631        self.locked().policy = policy;
632    }
633
634    pub(crate) fn prune_policy(&self) -> PrunePolicy {
635        self.locked().prune
636    }
637
638    pub(crate) fn set_prune_policy(&self, policy: PrunePolicy) {
639        self.locked().prune = policy;
640    }
641
642    /// Evicts every entry `policy` names, and more where the cap still needs it.
643    pub(crate) fn prune(&self, policy: PrunePolicy) {
644        self.locked().prune(policy, 0);
645    }
646
647    /// Everything gone, each node not yet on the disk sent there first.
648    pub(crate) fn clear(&self) {
649        let mut state = self.locked();
650        let keys: Vec<Hash> = state.entries.keys().copied().collect();
651        for key in &keys {
652            state.flush(*key);
653        }
654        for key in keys {
655            state.discard(key);
656        }
657        state.misses.clear();
658    }
659
660    pub(crate) fn bytes(&self) -> u64 {
661        self.locked().bytes
662    }
663
664    pub(crate) fn entries(&self) -> usize {
665        self.locked().entries.len()
666    }
667
668    pub(crate) fn holds(&self, key: Hash) -> bool {
669        self.locked().entries.contains_key(&key)
670    }
671
672    pub(crate) fn counters(&self) -> Counters {
673        self.locked().counters
674    }
675
676    pub(crate) fn count(&self, by: impl FnOnce(&mut Counters)) {
677        by(&mut self.locked().counters);
678    }
679
680    pub(crate) fn mark_every(&self) -> usize {
681        self.locked().mark_every
682    }
683
684    pub(crate) fn set_mark_every(&self, samples: usize) {
685        self.locked().mark_every = samples.max(1);
686    }
687
688    /// Whether memory takes a value computed: as its policy says, and one offered as a node
689    /// whatever it says while a disk lies beneath, to write it there; a memory of no bytes
690    /// takes none.
691    pub(crate) fn keeps(&self, fork: bool, target: bool, offered: bool) -> bool {
692        let state = self.locked();
693        state.max_bytes > 0 && ((offered && state.disk) || state.policy.keeps(fork, target))
694    }
695
696    pub(crate) fn begin_tree(&self) -> u64 {
697        let mut state = self.locked();
698        state.tree += 1;
699        state.tree
700    }
701
702    /// A new round of lookups: what any earlier round missed is asked again.
703    pub(crate) fn begin(&self) -> u64 {
704        let mut state = self.locked();
705        state.round += 1;
706        let round = state.round;
707        state.misses.retain(|_, met| *met + 1 >= round);
708        round
709    }
710
711    pub(crate) fn answer(&self, key: Hash, round: u64) -> Known {
712        let mut state = self.locked();
713        let covered = state.coverage(key, 0);
714        if let Some(held) = covered.clone().filter(|held| !held.is_empty()) {
715            let tick = state.tick();
716            let found = state.entries.get_mut(&key).expect("a node it covers");
717            found.read = tick;
718            let Item::Node { source, .. } = &found.item else {
719                unreachable!("only a node covers");
720            };
721            return Known::Hit(Arc::new(source.stored().holding(held)));
722        }
723        let node = matches!(
724            state.entries.get(&key),
725            Some(Held {
726                item: Item::Node { .. },
727                ..
728            })
729        );
730        match (node, covered) {
731            (true, None) => {
732                state.remove(key);
733            }
734            (true, Some(_)) => return Known::Miss,
735            (false, _) => {}
736        }
737        match state.misses.get(&key) {
738            Some(met) if *met >= round => Known::Miss,
739            _ => Known::Unknown,
740        }
741    }
742
743    pub(crate) fn miss(&self, key: Hash, round: u64) {
744        let mut state = self.locked();
745        let met = state.misses.entry(key).or_insert(round);
746        *met = (*met).max(round);
747    }
748
749    /// A header the disk answered, resident from now on, unless memory took the node meanwhile.
750    pub(crate) fn promote(&self, head: Header) {
751        let mut state = self.locked();
752        let (key, read, tree) = (head.stored().key, state.tick(), state.tree);
753        if state.entries.contains_key(&key) {
754            return;
755        }
756        state.misses.remove(&key);
757        state.counters.promotions += 1;
758        let item = Item::Node {
759            source: Source::Disk {
760                head: Box::new(head),
761                chunks: Vec::new(),
762            },
763            dirty: false,
764            slot: None,
765        };
766        let held = Held {
767            item,
768            read,
769            tree,
770            fork: false,
771        };
772        state.entries.insert(key, held);
773    }
774
775    pub(crate) fn promote_samples(&self, key: Hash, read: Vec<Buffer>) -> Vec<Arc<Buffer>> {
776        let read: Vec<Arc<Buffer>> = read.into_iter().map(Arc::new).collect();
777        let mut state = self.locked();
778        let tick = state.tick();
779        let Some(held) = state.entries.get_mut(&key) else {
780            return read;
781        };
782        let Item::Node {
783            source: Source::Disk { chunks, .. },
784            ..
785        } = &mut held.item
786        else {
787            return read;
788        };
789        let mut added = 0;
790        for part in &read {
791            let covered = chunks
792                .iter()
793                .any(|c| c.extent().intersect(part.extent()) == part.extent());
794            if !covered {
795                added += planes(part);
796                chunks.push(Arc::clone(part));
797            }
798        }
799        chunks.sort_by_key(|c| c.start);
800        held.read = tick;
801        state.bytes += added;
802        state.counters.promotions += 1;
803        state.bounded();
804        read
805    }
806
807    /// What memory holds of `key` over `over`, and what it lacks there that the disk holds.
808    pub(crate) fn resident(&self, key: Hash, over: Extent) -> Resident {
809        let mut state = self.locked();
810        let tick = state.tick();
811        if let Some(held) = state.entries.get_mut(&key) {
812            held.read = tick;
813        }
814        state.resident(key, over).unwrap_or_default()
815    }
816
817    pub(crate) fn forget(&self, key: Hash) {
818        self.locked().remove(key);
819    }
820
821    /// A node a render computes, standing on samples memory holds, in place of the last one
822    /// offered under `slot`. Until `settled` it holds what is computed so far, and a write
823    /// back sends that much; settled, it goes where memory holds none of its samples and has
824    /// no disk to name it to.
825    pub(crate) fn offer(
826        &self,
827        stored: Stored,
828        offered: Offered,
829        (slot, settled): (Option<Hash>, bool),
830    ) {
831        let mut state = self.locked();
832        let key = stored.key;
833        state.discard(key);
834        let (read, tree, dirty) = (state.tick(), state.tree, state.disk);
835        let slot = slot.map(|slot| super::mixed(slot, &[0x6e_6f_64_65]));
836        let item = Item::Node {
837            source: Source::Offered {
838                stored: Box::new(stored),
839                offered,
840            },
841            dirty,
842            slot,
843        };
844        let held = Held {
845            item,
846            read,
847            tree,
848            fork: false,
849        };
850        state.bytes += held.bytes();
851        state.entries.insert(key, held);
852        state.misses.remove(&key);
853        let covered = state.coverage(key, 0).is_some_and(|held| !held.is_empty());
854        if settled && !covered && !dirty {
855            state.discard(key);
856            return;
857        }
858        if let Some(last) = slot.and_then(|slot| state.slots.insert(slot, key))
859            && last != key
860        {
861            state.remove(last);
862        }
863        state.bounded();
864    }
865
866    /// Every node not yet on the disk, on its way there.
867    pub(crate) fn flush(&self) {
868        let mut state = self.locked();
869        let keys: Vec<Hash> = state.entries.keys().copied().collect();
870        for key in keys {
871            state.flush(key);
872        }
873        state.failed.1 = None;
874    }
875
876    /// The nodes on their way to the disk; none while a write that failed waits for a persist.
877    pub(crate) fn pending(&self) -> Vec<Writeback> {
878        let mut state = self.locked();
879        match state.failed.1 {
880            Some(_) => Vec::new(),
881            None => std::mem::take(&mut state.pending),
882        }
883    }
884
885    /// `left` still on its way, after a write failed for `why`.
886    pub(crate) fn failed(&self, why: String, left: Vec<Writeback>) {
887        let mut state = self.locked();
888        state.failed.0 += 1;
889        state.failed.1 = Some(why);
890        let mut more = std::mem::take(&mut state.pending);
891        state.pending = left;
892        state.pending.append(&mut more);
893    }
894
895    pub(crate) fn written(&self) {
896        self.locked().counters.writebacks += 1;
897    }
898
899    pub(crate) fn failures(&self) -> (u64, Option<String>) {
900        self.locked().failed.clone()
901    }
902
903    pub(crate) fn blocked(&self) -> bool {
904        self.locked().failed.1.is_some()
905    }
906
907    /// The disk committed what was staged: a header that named staged samples names nothing
908    /// now.
909    pub(crate) fn committed(&self) {
910        let mut state = self.locked();
911        let staged: Vec<Hash> = state
912            .entries
913            .iter()
914            .filter(|(_, held)| {
915                let Item::Node {
916                    source: Source::Disk { head, .. },
917                    ..
918                } = &held.item
919                else {
920                    return false;
921                };
922                matches!(head.samples(), Samples::Staged { .. })
923            })
924            .map(|(key, _)| *key)
925            .collect();
926        for key in staged {
927            state.discard(key);
928        }
929    }
930
931    /// What `key` holds, shared, never copied.
932    pub(crate) fn load(&self, key: Hash, expected: Expected, stamp: Stamp) -> Option<Entry> {
933        let mut state = self.locked();
934        let tick = state.tick();
935        let held = state.entries.get_mut(&key)?;
936        let Item::Value { payload, label, .. } = &held.item else {
937            return None;
938        };
939        if !payload.answers(expected) {
940            state.remove(key);
941            return None;
942        }
943        let entry = Entry {
944            payload: payload.clone(),
945            label: label.clone(),
946        };
947        held.read = tick;
948        held.tree = stamp.tree;
949        held.fork = stamp.fork;
950        Some(entry)
951    }
952
953    /// A value's segments join those held under `key`, and a run continuing the one held
954    /// there extends it, each in place; anything else replaces what `key` held.
955    pub(crate) fn merge(
956        &self,
957        key: Hash,
958        payload: Payload,
959        label: Option<&Label>,
960        stamp: Stamp,
961    ) -> Kept {
962        let mut state = self.locked();
963        let tick = state.tick();
964        let joined = match (state.entries.get_mut(&key), payload) {
965            (Some(held), payload) if held.slot() == stamp.slot => {
966                let before = held.bytes();
967                let Item::Value { payload: had, .. } = &mut held.item else {
968                    unreachable!("a value key holds a value");
969                };
970                let payload = match (had, payload) {
971                    (Payload::Segments(parts), Payload::Segments(more)) => {
972                        joined(parts, more);
973                        None
974                    }
975                    (Payload::Run(run), Payload::Run(more)) if overlaps(run, &more) => {
976                        let from = (run.end() - more.samples.start).max(0) as usize;
977                        let run = Arc::make_mut(run);
978                        let samples = Arc::make_mut(&mut run.samples);
979                        for (held, more) in samples.planes.iter_mut().zip(&more.samples.planes) {
980                            held.extend_from_slice(&more[from.min(more.len())..]);
981                        }
982                        run.marks
983                            .extend(more.marks.iter().map(|(at, m)| (*at, m.clone())));
984                        None
985                    }
986                    (_, payload) => Some(payload),
987                };
988                match payload {
989                    None => {
990                        held.read = tick;
991                        held.tree = stamp.tree;
992                        held.fork = stamp.fork;
993                        let after = held.bytes();
994                        Ok((before, after))
995                    }
996                    Some(payload) => Err(payload),
997                }
998            }
999            (_, payload) => Err(payload),
1000        };
1001        match joined {
1002            Ok((before, after)) => {
1003                state.bytes = state.bytes - before + after;
1004                state.bounded();
1005                Kept::Held
1006            }
1007            Err(payload) => {
1008                drop(state);
1009                self.store(key, payload, label, stamp)
1010            }
1011        }
1012    }
1013
1014    /// A value too large to stay is refused, unless a node not yet on the disk stands on it:
1015    /// then it is kept only to be evicted, and so written back.
1016    pub(crate) fn store(
1017        &self,
1018        key: Hash,
1019        payload: Payload,
1020        label: Option<&Label>,
1021        stamp: Stamp,
1022    ) -> Kept {
1023        let mut state = self.locked();
1024        let bytes = payload.bytes() as u64;
1025        if bytes > state.max_bytes && !state.stands(key) {
1026            return Kept::Refused;
1027        }
1028        let read = state.tick();
1029        let replaced = match stamp.slot.and_then(|slot| state.slots.insert(slot, key)) {
1030            Some(last) if last != key => state.remove(last),
1031            _ => false,
1032        };
1033        let held = Held {
1034            item: Item::Value {
1035                payload,
1036                label: label.cloned(),
1037                slot: stamp.slot,
1038            },
1039            read,
1040            tree: stamp.tree,
1041            fork: stamp.fork,
1042        };
1043        if let Some(old) = state.entries.insert(key, held) {
1044            state.bytes -= old.bytes();
1045        }
1046        state.bytes += bytes;
1047        state.bounded();
1048        match replaced {
1049            true => Kept::Replaced,
1050            false => Kept::Held,
1051        }
1052    }
1053}
1054
1055/// A run that starts inside or at the end of the one held continues it: what it holds past
1056/// that one's end is laid on, the samples both hold being the same.
1057fn overlaps(held: &super::Run, more: &super::Run) -> bool {
1058    let (a, b) = (held.samples.start, held.end());
1059    a <= more.samples.start && more.samples.start <= b
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064    use super::*;
1065
1066    /// Only a colliding key reaches this, so no render can: the entry is a miss, and goes.
1067    #[test]
1068    fn an_entry_that_does_not_answer_what_was_asked_is_a_miss_and_goes() {
1069        let memory = Memory::default();
1070        let key = Hash(7, 11);
1071        let stamp = Stamp {
1072            tree: memory.begin_tree(),
1073            fork: false,
1074            slot: None,
1075        };
1076        let four = Payload::Segments(vec![Arc::new(Buffer::mono(8_000, vec![0.25; 4]))]);
1077        for (rate, width) in [(48_000, 1), (8_000, 2)] {
1078            memory.store(key, four.clone(), None, stamp);
1079            let asked = Expected::Segments { rate, width };
1080            assert!(memory.load(key, asked, stamp).is_none());
1081            assert!(!memory.holds(key));
1082            assert_eq!(memory.bytes(), 0);
1083        }
1084    }
1085
1086    #[test]
1087    fn a_load_shares_the_samples_it_holds() {
1088        let memory = Memory::default();
1089        let key = Hash(3, 5);
1090        let stamp = Stamp {
1091            tree: memory.begin_tree(),
1092            fork: false,
1093            slot: None,
1094        };
1095        let part = Arc::new(Buffer::mono(8_000, vec![0.5; 64]));
1096        memory.store(key, Payload::Segments(vec![Arc::clone(&part)]), None, stamp);
1097        let asked = Expected::Segments {
1098            rate: 8_000,
1099            width: 1,
1100        };
1101        for _ in 0..2 {
1102            let loaded = memory.load(key, asked, stamp).expect("a hit");
1103            let Payload::Segments(parts) = loaded.payload else {
1104                panic!("segments were stored");
1105            };
1106            assert!(Arc::ptr_eq(&parts[0], &part), "the stored part itself");
1107        }
1108    }
1109}