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