Skip to main content

liminal_server/tap/
ring.rs

1//! A bounded queue that drops its oldest entry, with a count, when full.
2//!
3//! A slow reader can never grow server memory beyond `capacity` entries, and
4//! what it lost is a number it is told, never a silence: the evictions since
5//! the reader last took from the ring travel with what it takes.
6
7use std::collections::VecDeque;
8
9/// A bounded first-in first-out queue that evicts its oldest entry on a push
10/// into a full queue and counts every eviction.
11#[derive(Debug)]
12pub struct BoundedRing<T> {
13    entries: VecDeque<T>,
14    capacity: usize,
15    dropped: u64,
16    dropped_since_take: u64,
17}
18
19impl<T> BoundedRing<T> {
20    /// An empty ring holding at most `capacity` entries (at least one).
21    #[must_use]
22    pub fn new(capacity: usize) -> Self {
23        let capacity = capacity.max(1);
24        Self {
25            entries: VecDeque::with_capacity(capacity),
26            capacity,
27            dropped: 0,
28            dropped_since_take: 0,
29        }
30    }
31
32    /// Appends `entry`, evicting and counting the oldest entry when full.
33    /// Answers whether an entry was evicted.
34    pub fn push(&mut self, entry: T) -> bool {
35        let evicted = self.entries.len() >= self.capacity;
36        if evicted {
37            self.entries.pop_front();
38            self.dropped = self.dropped.saturating_add(1);
39            self.dropped_since_take = self.dropped_since_take.saturating_add(1);
40        }
41        self.entries.push_back(entry);
42        evicted
43    }
44
45    /// Removes every held entry, oldest first, with the evictions since the
46    /// previous take; the next take counts from zero again.
47    pub fn take(&mut self) -> (std::collections::vec_deque::Drain<'_, T>, u64) {
48        let dropped = std::mem::take(&mut self.dropped_since_take);
49        (self.entries.drain(..), dropped)
50    }
51
52    /// Entries currently held.
53    #[must_use]
54    pub fn len(&self) -> usize {
55        self.entries.len()
56    }
57
58    /// Whether the ring holds nothing.
59    #[must_use]
60    pub fn is_empty(&self) -> bool {
61        self.entries.is_empty()
62    }
63
64    /// The bound.
65    #[must_use]
66    pub const fn capacity(&self) -> usize {
67        self.capacity
68    }
69
70    /// Entries evicted since the ring was made (monotonic).
71    #[must_use]
72    pub const fn dropped(&self) -> u64 {
73        self.dropped
74    }
75
76    /// Entries evicted since the previous take.
77    #[must_use]
78    pub const fn dropped_since_take(&self) -> u64 {
79        self.dropped_since_take
80    }
81}