1use core::sync::atomic::{AtomicUsize, Ordering};
12
13use super::StaticRing;
14#[cfg(feature = "alloc")]
15use super::{Drainer, Ring};
16
17pub trait RingStorage {
23 type Item;
25
26 fn push(&self, item: Self::Item) -> Result<(), Self::Item>;
28 fn dequeue(&self) -> Option<Self::Item>;
30 fn len(&self) -> usize;
32 fn is_empty(&self) -> bool {
34 self.len() == 0
35 }
36 fn capacity(&self) -> usize;
38 fn drain_into(&self, out: &mut [Self::Item]) -> usize {
42 let mut count = 0;
43 while count < out.len() {
44 match self.dequeue() {
45 Some(item) => {
46 out[count] = item;
47 count += 1;
48 }
49 None => break,
50 }
51 }
52 count
53 }
54}
55
56impl<T, const N: usize> RingStorage for StaticRing<T, N> {
57 type Item = T;
58
59 fn push(&self, item: T) -> Result<(), T> {
60 StaticRing::push(self, item)
61 }
62 fn dequeue(&self) -> Option<T> {
63 StaticRing::dequeue(self)
64 }
65 fn len(&self) -> usize {
66 StaticRing::len(self)
67 }
68 fn capacity(&self) -> usize {
69 StaticRing::capacity(self)
70 }
71}
72
73#[cfg(feature = "alloc")]
74impl<T> RingStorage for Ring<T> {
75 type Item = T;
76
77 #[inline]
78 fn push(&self, item: T) -> Result<(), T> {
79 Ring::push(self, item)
80 }
81 #[inline]
82 fn dequeue(&self) -> Option<T> {
83 Ring::dequeue(self)
84 }
85 fn len(&self) -> usize {
86 Ring::len(self)
87 }
88 fn capacity(&self) -> usize {
89 Ring::cap(self)
90 }
91 fn drain_into(&self, out: &mut [T]) -> usize {
92 Drainer::new(self).drain_into(out)
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum FailMode {
99 DropOldest,
101 DropNewest,
103 FailClosed,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum EnqueueOutcome {
110 Enqueued,
112 DroppedOldest,
114 DroppedNewest,
116 Refused,
118}
119
120impl EnqueueOutcome {
121 #[must_use]
123 pub fn is_drop(self) -> bool {
124 !matches!(self, Self::Enqueued)
125 }
126}
127
128pub struct BoundedQueue<R: RingStorage> {
132 queue: R,
133 fail_mode: FailMode,
134 drops: AtomicUsize,
135}
136
137impl<R: RingStorage> BoundedQueue<R> {
138 pub const fn from_storage(queue: R, fail_mode: FailMode) -> Self {
140 Self {
141 queue,
142 fail_mode,
143 drops: AtomicUsize::new(0),
144 }
145 }
146
147 #[must_use]
148 pub fn capacity(&self) -> usize {
149 self.queue.capacity()
150 }
151
152 #[must_use]
153 pub fn len(&self) -> usize {
154 self.queue.len()
155 }
156
157 #[must_use]
158 pub fn is_empty(&self) -> bool {
159 self.queue.is_empty()
160 }
161
162 #[must_use]
164 pub fn dropped(&self) -> u64 {
165 self.drops.load(Ordering::Relaxed) as u64
168 }
169
170 pub fn note_drop(&self) {
177 self.drops.fetch_add(1, Ordering::Relaxed);
178 }
179
180 pub fn enqueue(&self, item: R::Item) -> EnqueueOutcome {
182 let outcome = match self.queue.push(item) {
183 Ok(()) => EnqueueOutcome::Enqueued,
184 Err(rejected) => match self.fail_mode {
185 FailMode::DropOldest => {
186 let _oldest = self.queue.dequeue();
190 match self.queue.push(rejected) {
191 Ok(()) => EnqueueOutcome::DroppedOldest,
192 Err(_lost) => EnqueueOutcome::DroppedNewest,
193 }
194 }
195 FailMode::DropNewest => EnqueueOutcome::DroppedNewest,
196 FailMode::FailClosed => EnqueueOutcome::Refused,
197 },
198 };
199 if outcome.is_drop() {
200 self.drops.fetch_add(1, Ordering::Relaxed);
201 }
202 outcome
203 }
204
205 #[inline]
211 pub fn try_enqueue(&self, item: R::Item) -> Result<(), R::Item> {
212 self.queue.push(item)
213 }
214
215 pub fn enqueue_assisting<F>(&self, mut item: R::Item, mut on_full: F) -> Result<(), R::Item>
225 where
226 F: FnMut() -> bool,
227 {
228 loop {
229 match self.queue.push(item) {
230 Ok(()) => return Ok(()),
231 Err(returned) => {
232 item = returned;
233 if !on_full() {
234 return Err(item);
235 }
236 }
237 }
238 }
239 }
240
241 #[must_use]
243 pub fn dequeue(&self) -> Option<R::Item> {
244 self.queue.dequeue()
245 }
246
247 pub fn drain_into(&self, out: &mut [R::Item]) -> usize {
250 self.queue.drain_into(out)
251 }
252}
253
254pub type StaticBoundedQueue<T, const N: usize> = BoundedQueue<StaticRing<T, N>>;
257
258impl<T, const N: usize> BoundedQueue<StaticRing<T, N>> {
259 #[must_use]
264 #[cfg(not(feature = "loom"))]
265 pub const fn new(fail_mode: FailMode) -> Self {
266 Self::from_storage(StaticRing::new(), fail_mode)
267 }
268
269 #[must_use]
270 #[cfg(feature = "loom")]
271 pub fn new(fail_mode: FailMode) -> Self {
272 Self::from_storage(StaticRing::new(), fail_mode)
273 }
274}
275
276#[cfg(feature = "alloc")]
278pub type HeapBoundedQueue<T> = BoundedQueue<Ring<T>>;
279
280#[cfg(feature = "alloc")]
281impl<T> BoundedQueue<Ring<T>> {
282 #[must_use]
285 pub fn new(capacity: usize, fail_mode: FailMode) -> Self {
286 Self::from_storage(Ring::with_capacity(capacity), fail_mode)
287 }
288}
289
290#[cfg(all(test, not(feature = "loom")))]
295#[allow(clippy::unwrap_used, clippy::expect_used)]
296mod tests {
297 use super::*;
298
299 fn drop_oldest_keeps_newest<R: RingStorage<Item = u32>>(queue: BoundedQueue<R>) {
302 assert_eq!(queue.enqueue(1), EnqueueOutcome::Enqueued);
303 assert_eq!(queue.enqueue(2), EnqueueOutcome::Enqueued);
304 assert_eq!(
305 queue.enqueue(3),
306 EnqueueOutcome::DroppedOldest,
307 "evicts the oldest"
308 );
309 assert_eq!(queue.dropped(), 1);
310 assert_eq!(
311 queue.dequeue(),
312 Some(2),
313 "oldest (1) evicted; 2 and 3 remain"
314 );
315 assert_eq!(queue.dequeue(), Some(3));
316 assert_eq!(queue.dequeue(), None);
317 }
318
319 fn fail_closed_refuses<R: RingStorage<Item = u32>>(queue: BoundedQueue<R>) {
320 assert_eq!(queue.enqueue(1), EnqueueOutcome::Enqueued);
321 assert_eq!(queue.enqueue(2), EnqueueOutcome::Enqueued);
322 assert_eq!(queue.enqueue(3), EnqueueOutcome::Refused);
323 assert_eq!(queue.dropped(), 1);
324 }
325
326 fn try_enqueue_hands_back_without_dropping<R: RingStorage<Item = u32>>(queue: BoundedQueue<R>) {
327 assert!(queue.try_enqueue(1).is_ok());
328 assert!(queue.try_enqueue(2).is_ok());
329 assert_eq!(queue.try_enqueue(3), Err(3), "handed back, not dropped");
330 assert_eq!(queue.dropped(), 0, "try_enqueue never counts a drop");
331 }
332
333 #[test]
334 fn static_tier_matches() {
335 drop_oldest_keeps_newest(StaticBoundedQueue::<u32, 2>::new(FailMode::DropOldest));
336 fail_closed_refuses(StaticBoundedQueue::<u32, 2>::new(FailMode::FailClosed));
337 try_enqueue_hands_back_without_dropping(StaticBoundedQueue::<u32, 2>::new(
338 FailMode::FailClosed,
339 ));
340 }
341
342 #[cfg(feature = "alloc")]
343 #[test]
344 fn heap_tier_matches() {
345 drop_oldest_keeps_newest(HeapBoundedQueue::<u32>::new(2, FailMode::DropOldest));
346 fail_closed_refuses(HeapBoundedQueue::<u32>::new(2, FailMode::FailClosed));
347 try_enqueue_hands_back_without_dropping(HeapBoundedQueue::<u32>::new(
348 2,
349 FailMode::FailClosed,
350 ));
351 }
352
353 #[cfg(feature = "alloc")]
354 #[test]
355 fn heap_capacity_rounds_up_to_power_of_two() {
356 assert_eq!(
357 HeapBoundedQueue::<u32>::new(3, FailMode::DropNewest).capacity(),
358 4
359 );
360 assert_eq!(
361 HeapBoundedQueue::<u32>::new(1, FailMode::DropNewest).capacity(),
362 2
363 );
364 }
365
366 #[test]
367 fn enqueue_assisting_makes_room_and_lands() {
368 let queue = StaticBoundedQueue::<u32, 2>::new(FailMode::FailClosed);
369 queue.enqueue(1);
370 queue.enqueue(2);
371 let landed = queue.enqueue_assisting(3, || queue.dequeue().is_some());
373 assert!(landed.is_ok());
374 assert_eq!(queue.dequeue(), Some(2), "1 was freed to make room");
375 assert_eq!(queue.dequeue(), Some(3));
376 }
377
378 #[test]
379 fn enqueue_assisting_hands_back_on_give_up() {
380 let queue = StaticBoundedQueue::<u32, 2>::new(FailMode::FailClosed);
381 queue.enqueue(1);
382 queue.enqueue(2);
383 assert_eq!(
384 queue.enqueue_assisting(3, || false),
385 Err(3),
386 "gave up, item handed back"
387 );
388 }
389
390 #[test]
391 fn drain_into_batch_pops_fifo_both_tiers() {
392 let queue = StaticBoundedQueue::<u32, 4>::new(FailMode::DropNewest);
393 for value in 1..=3 {
394 queue.enqueue(value);
395 }
396 let mut out = [0u32; 8];
397 assert_eq!(
398 queue.drain_into(&mut out),
399 3,
400 "drains exactly what is queued"
401 );
402 assert_eq!(&out[..3], &[1, 2, 3]);
403 assert!(queue.dequeue().is_none());
404 }
405
406 #[test]
407 fn drop_newest_leaves_queue_untouched() {
408 let queue = StaticBoundedQueue::<u32, 2>::new(FailMode::DropNewest);
409 queue.enqueue(1);
410 queue.enqueue(2);
411 assert_eq!(queue.enqueue(3), EnqueueOutcome::DroppedNewest);
412 assert_eq!(queue.dropped(), 1);
413 assert_eq!(queue.dequeue(), Some(1), "queue untouched");
414 assert_eq!(queue.dequeue(), Some(2));
415 }
416}