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