Skip to main content

vane_core/
slab.rs

1//! Thread-local generational slab for connection sessions (`MM-01`).
2//!
3//! Session slots are pre-reserved up front and reused via free list; the
4//! 16-bit generation counter guards against ABA when a completion for a
5//! closed session races the slot's reuse. Single-threaded by design: the
6//! worker that owns a slab is the only thread that touches it.
7
8/// Maximum sessions a slab tracks (24-bit slot index in [`crate::token::Token`]).
9pub const MAX_SLOTS: usize = 1 << 24;
10
11/// Errors from [`SessionSlab::new`].
12#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
13pub enum SlabError {
14    /// Capacity is zero or exceeds [`MAX_SLOTS`].
15    #[error("capacity {0} out of range 1..={MAX_SLOTS}")]
16    Capacity(usize),
17    /// Slab is exhausted.
18    #[error("session slab exhausted")]
19    Exhausted,
20}
21
22struct Entry<T> {
23    value: Option<T>,
24    generation: u16,
25    next_free: u32,
26    occupied: bool,
27}
28
29/// Generational slab of sessions.
30pub struct SessionSlab<T> {
31    entries: Box<[Entry<T>]>,
32    free_head: u32,
33    free_count: usize,
34}
35
36impl<T> SessionSlab<T> {
37    /// Pre-allocates `capacity` slots (no `T` constructed yet).
38    ///
39    /// # Errors
40    /// [`SlabError::Capacity`] when `capacity` is 0 or exceeds `MAX_SLOTS`.
41    pub fn new(capacity: usize) -> Result<Self, SlabError> {
42        if capacity == 0 || capacity > MAX_SLOTS {
43            return Err(SlabError::Capacity(capacity));
44        }
45        let mut entries = Vec::with_capacity(capacity);
46        for i in 0..capacity {
47            entries.push(Entry {
48                value: None,
49                generation: 0,
50                next_free: (i + 1) as u32,
51                occupied: false,
52            });
53        }
54        Ok(Self {
55            entries: entries.into_boxed_slice(),
56            free_head: 0,
57            free_count: capacity,
58        })
59    }
60
61    /// Inserts a value, returning `(slot, generation)`.
62    ///
63    /// # Errors
64    /// [`SlabError::Exhausted`] when every slot is live.
65    pub fn insert(&mut self, value: T) -> Result<(u32, u16), SlabError> {
66        if self.free_count == 0 {
67            return Err(SlabError::Exhausted);
68        }
69        let slot = self.free_head as usize;
70        let entry = &mut self.entries[slot];
71        self.free_head = entry.next_free;
72        entry.occupied = true;
73        entry.value = Some(value);
74        self.free_count -= 1;
75        Ok((slot as u32, entry.generation))
76    }
77
78    /// Removes a value. The slot's generation increments immediately so any
79    /// in-flight token for the old generation becomes stale on reuse.
80    pub fn remove(&mut self, slot: u32) -> Option<T> {
81        let entry = self.entries.get_mut(slot as usize)?;
82        if !entry.occupied {
83            return None;
84        }
85        entry.occupied = false;
86        entry.generation = entry.generation.wrapping_add(1);
87        entry.next_free = self.free_head;
88        self.free_head = slot;
89        self.free_count += 1;
90        entry.value.take()
91    }
92
93    /// Immutable access.
94    #[must_use]
95    pub fn get(&self, slot: u32) -> Option<&T> {
96        let e = self.entries.get(slot as usize)?;
97        if e.occupied { e.value.as_ref() } else { None }
98    }
99
100    /// Mutable access.
101    pub fn get_mut(&mut self, slot: u32) -> Option<&mut T> {
102        let e = self.entries.get_mut(slot as usize)?;
103        if e.occupied { e.value.as_mut() } else { None }
104    }
105
106    /// Generation currently assigned to a slot.
107    #[must_use]
108    pub fn generation(&self, slot: u32) -> u16 {
109        self.entries.get(slot as usize).map_or(0, |e| e.generation)
110    }
111
112    /// Number of live sessions.
113    #[must_use]
114    pub fn len(&self) -> usize {
115        self.entries.len() - self.free_count
116    }
117
118    /// `true` when no sessions are live.
119    #[must_use]
120    pub fn is_empty(&self) -> bool {
121        self.len() == 0
122    }
123
124    /// True when the slot is live with the given generation (token check).
125    #[must_use]
126    pub fn matches(&self, slot: u32, generation: u16) -> bool {
127        self.entries
128            .get(slot as usize)
129            .is_some_and(|e| e.occupied && e.generation == generation)
130    }
131
132    /// Iterates all live `(slot, generation)` pairs.
133    #[must_use]
134    pub fn live(&self) -> Vec<(u32, u16)> {
135        self.entries
136            .iter()
137            .enumerate()
138            .filter(|(_, e)| e.occupied)
139            .map(|(i, e)| (i as u32, e.generation))
140            .collect()
141    }
142}
143
144/// Bumps a slot's generation (called by the worker right before reuse).
145pub trait GenerationBump<T> {
146    /// Increments the slot generation, invalidating stale tokens.
147    fn bump_generation(&mut self, slot: u32);
148}
149
150impl<T> GenerationBump<T> for SessionSlab<T> {
151    fn bump_generation(&mut self, slot: u32) {
152        if let Some(e) = self.entries.get_mut(slot as usize) {
153            e.generation = e.generation.wrapping_add(1);
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn insert_remove_reuse() {
164        let mut slab: SessionSlab<&str> = SessionSlab::new(4).expect("ok");
165        let (a, gen_a) = slab.insert("a").expect("fits");
166        let (b, _) = slab.insert("b").expect("fits");
167        assert_eq!(slab.get(a), Some(&"a"));
168        let old = slab.remove(a);
169        assert_eq!(old, Some("a"));
170        let (c, gen_c) = slab.insert("c").expect("reuses slot a");
171        assert_eq!(c, a);
172        assert_ne!(gen_c, gen_a);
173        assert!(slab.matches(a, gen_c));
174        assert!(!slab.matches(a, gen_a));
175        assert_eq!(slab.remove(b), Some("b"));
176        assert_eq!(slab.remove(c), Some("c"));
177        assert!(slab.is_empty());
178    }
179
180    #[test]
181    fn exhaustion() {
182        let mut slab: SessionSlab<u8> = SessionSlab::new(2).expect("ok");
183        slab.insert(1).expect("1");
184        slab.insert(2).expect("2");
185        assert_eq!(slab.insert(3), Err(SlabError::Exhausted));
186    }
187}