Skip to main content

bb_runtime/framework/
hold_table.rs

1//! `HoldTable` - slot-named value buffer for `Hold.Stash` /
2//! `Hold.Flush` syscalls.
3//!
4//! bounded by max-distinct-slot-names with a drop
5//! counter, so a producer that mints arbitrary slot names cannot
6//! grow the table unbounded. When the table is at capacity and an
7//! attempt to stash a fresh slot name arrives, the new entry is
8//! dropped and the drop counter ticks. Existing slots' values
9//! can always be overwritten (stash on an existing key never
10//! evicts).
11
12use std::collections::HashMap;
13
14/// Named-slot value buffer with bounded distinct-slot count.
15pub struct HoldTable {
16    slots: HashMap<String, Vec<u8>>,
17    max_slots: usize,
18    drops: u64,
19}
20
21impl Default for HoldTable {
22    fn default() -> Self {
23        Self {
24            slots: HashMap::new(),
25            max_slots: Self::DEFAULT_MAX_SLOTS,
26            drops: 0,
27        }
28    }
29}
30
31impl HoldTable {
32    /// Default cap on distinct slot names. 1024 covers any
33    /// reasonable user workload while bounding the worst-case
34    /// memory footprint of an unknown-key flood.
35    pub const DEFAULT_MAX_SLOTS: usize = 1024;
36
37    /// Construct a fresh empty table with the default cap.
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Construct with a custom max distinct-slot cap.
43    pub fn with_max_slots(max_slots: usize) -> Self {
44        Self {
45            slots: HashMap::new(),
46            max_slots,
47            drops: 0,
48        }
49    }
50
51    /// Stash bytes into the named slot. Overwrites any previous
52    /// value (existing slot names ALWAYS replace cleanly). New
53    /// slot names that would exceed the table's configured cap are dropped
54    /// and the drop counter ticks.
55    pub fn stash(&mut self, slot: &str, bytes: Vec<u8>) {
56        if self.slots.contains_key(slot) || self.slots.len() < self.max_slots {
57            self.slots.insert(slot.to_string(), bytes);
58        } else {
59            self.drops += 1;
60        }
61    }
62
63    /// Take the stashed value from the named slot, if present.
64    pub fn flush(&mut self, slot: &str) -> Option<Vec<u8>> {
65        self.slots.remove(slot)
66    }
67
68    /// Cumulative drop count for new-slot-on-full attempts.
69    pub fn drops(&self) -> u64 {
70        self.drops
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    #[test]
78    fn stash_flush_roundtrip() {
79        let mut t = HoldTable::new();
80        t.stash("a", vec![1, 2, 3]);
81        assert_eq!(t.flush("a"), Some(vec![1, 2, 3]));
82        assert_eq!(t.flush("a"), None);
83    }
84
85    #[test]
86    fn stash_overflow_drops_new_slot_and_ticks_counter() {
87        let mut t = HoldTable::with_max_slots(2);
88        t.stash("a", vec![1]);
89        t.stash("b", vec![2]);
90        assert_eq!(t.drops(), 0);
91        // Third distinct slot at cap: dropped.
92        t.stash("c", vec![3]);
93        assert_eq!(t.drops(), 1);
94        assert_eq!(t.flush("c"), None);
95        // Existing slot overwrite still works at cap.
96        t.stash("a", vec![9]);
97        assert_eq!(t.flush("a"), Some(vec![9]));
98    }
99}