use crate::sync::{AtomicBool, AtomicU32, Ordering, fence};
use core::cell::{Cell, UnsafeCell};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
#[cfg(test)]
use core::sync::atomic::AtomicUsize;
#[cfg(not(loom))]
const fn atomic_u32_array<const N: usize>() -> [AtomicU32; N] {
[const { AtomicU32::new(0) }; N]
}
#[cfg(loom)]
fn atomic_u32_array<const N: usize>() -> [AtomicU32; N] {
core::array::from_fn(|_| AtomicU32::new(0))
}
#[cfg(not(loom))]
const fn unsafe_cell_array<T, const N: usize>() -> [UnsafeCell<MaybeUninit<T>>; N] {
[const { UnsafeCell::new(MaybeUninit::uninit()) }; N]
}
#[cfg(loom)]
fn unsafe_cell_array<T, const N: usize>() -> [UnsafeCell<MaybeUninit<T>>; N] {
core::array::from_fn(|_| UnsafeCell::new(MaybeUninit::uninit()))
}
#[cfg(test)]
static TEST_AFTER_READ_TARGET: AtomicUsize = AtomicUsize::new(0);
#[cfg(test)]
static TEST_AFTER_READ_SEQ: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
#[must_use]
#[derive(Copy, Clone, Debug)]
pub struct PollStats {
pub read: usize,
pub dropped: usize,
pub newest: u32,
}
pub struct SeqRing<T: Copy, const N: usize> {
next_seq: AtomicU32,
published_seq: AtomicU32,
slot_seq: [AtomicU32; N],
slots: [UnsafeCell<MaybeUninit<T>>; N],
producer_taken: AtomicBool,
consumer_taken: AtomicBool,
}
unsafe impl<T: Copy + Send, const N: usize> Sync for SeqRing<T, N> {}
impl<T: Copy, const N: usize> SeqRing<T, N> {
#[cfg(not(loom))]
pub const fn new() -> Self {
const {
assert!(N > 0, "SeqRing capacity N must be > 0");
}
Self {
next_seq: AtomicU32::new(0),
published_seq: AtomicU32::new(0),
slot_seq: atomic_u32_array::<N>(),
slots: unsafe_cell_array::<T, N>(),
producer_taken: AtomicBool::new(false),
consumer_taken: AtomicBool::new(false),
}
}
#[cfg(loom)]
pub fn new() -> Self {
assert!(N > 0, "SeqRing capacity N must be > 0");
Self {
next_seq: AtomicU32::new(0),
published_seq: AtomicU32::new(0),
slot_seq: atomic_u32_array::<N>(),
slots: unsafe_cell_array::<T, N>(),
producer_taken: AtomicBool::new(false),
consumer_taken: AtomicBool::new(false),
}
}
#[inline]
pub const fn capacity(&self) -> usize {
N
}
#[inline(always)]
const fn idx_for(seq: u32) -> usize {
((seq.wrapping_sub(1)) as usize) % N
}
#[inline]
pub fn try_producer(&self) -> Option<Producer<'_, T, N>> {
if self.producer_taken.swap(true, Ordering::AcqRel) {
None
} else {
Some(Producer {
ring: self,
_not_sync: PhantomData,
})
}
}
#[inline]
pub fn try_consumer(&self) -> Option<Consumer<'_, T, N>> {
if self.consumer_taken.swap(true, Ordering::AcqRel) {
None
} else {
Some(Consumer {
ring: self,
last_seq: 0,
dropped_accum: 0,
_not_sync: PhantomData,
})
}
}
#[inline]
fn newest_seq(&self) -> u32 {
self.published_seq.load(Ordering::Acquire)
}
#[inline]
fn push_inner(&self, value: T) -> u32 {
let mut seq = self
.next_seq
.fetch_add(1, Ordering::Relaxed)
.wrapping_add(1);
if seq == 0 {
seq = 1;
self.next_seq.store(1, Ordering::Relaxed);
}
let idx = Self::idx_for(seq);
self.slot_seq[idx].store(0, Ordering::Relaxed);
fence(Ordering::Release);
unsafe { core::ptr::write_volatile(self.slots[idx].get(), MaybeUninit::new(value)) };
self.slot_seq[idx].store(seq, Ordering::Release);
self.published_seq.store(seq, Ordering::Release);
seq
}
#[inline(always)]
const fn next_after(seq: u32) -> u32 {
match seq.wrapping_add(1) {
0 => 1,
n => n,
}
}
#[inline(always)]
const fn seq_distance(from: u32, to: u32) -> u32 {
let raw = to.wrapping_sub(from);
if to < from { raw - 1 } else { raw }
}
#[inline]
fn read_seq_inner(&self, seq: u32) -> Option<T> {
let idx = Self::idx_for(seq);
let s1 = self.slot_seq[idx].load(Ordering::Acquire);
if s1 != seq {
return None;
}
let v: MaybeUninit<T> = unsafe { core::ptr::read_volatile(self.slots[idx].get()) };
#[cfg(test)]
self.test_after_read_hook(idx);
fence(Ordering::Acquire);
let s2 = self.slot_seq[idx].load(Ordering::Relaxed);
if s2 != seq {
return None;
}
Some(unsafe { v.assume_init() })
}
#[cfg(test)]
fn test_after_read_hook(&self, idx: usize) {
let target = TEST_AFTER_READ_TARGET.load(Ordering::Acquire);
if target == self as *const _ as usize {
let seq = TEST_AFTER_READ_SEQ.load(Ordering::Relaxed);
self.slot_seq[idx].store(seq, Ordering::Release);
TEST_AFTER_READ_TARGET.store(0, Ordering::Release);
}
}
}
impl<T: Copy, const N: usize> Default for SeqRing<T, N> {
fn default() -> Self {
Self::new()
}
}
impl<T: Copy, const N: usize> core::fmt::Debug for SeqRing<T, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SeqRing")
.field("capacity", &N)
.field("published_seq", &self.published_seq.load(Ordering::Relaxed))
.finish()
}
}
pub struct Producer<'a, T: Copy, const N: usize> {
ring: &'a SeqRing<T, N>,
_not_sync: PhantomData<Cell<()>>,
}
impl<'a, T: Copy, const N: usize> Producer<'a, T, N> {
#[inline]
pub fn push(&self, value: T) -> u32 {
self.ring.push_inner(value)
}
}
impl<'a, T: Copy, const N: usize> Drop for Producer<'a, T, N> {
fn drop(&mut self) {
self.ring.producer_taken.store(false, Ordering::Release);
}
}
impl<T: Copy, const N: usize> core::fmt::Debug for Producer<'_, T, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("seq_ring::Producer")
.field("capacity", &N)
.finish()
}
}
pub struct Consumer<'a, T: Copy, const N: usize> {
ring: &'a SeqRing<T, N>,
last_seq: u32,
dropped_accum: usize,
_not_sync: PhantomData<Cell<()>>,
}
impl<'a, T: Copy, const N: usize> Consumer<'a, T, N> {
#[inline]
pub fn dropped(&self) -> usize {
self.dropped_accum
}
#[inline]
pub fn reset_dropped(&mut self) {
self.dropped_accum = 0;
}
#[inline]
pub fn poll_one(&mut self, hook: impl FnOnce(u32, &T)) -> bool {
let mut hook = Some(hook);
let stats = self.poll_up_to(1, |seq, v| {
if let Some(hook) = hook.take() {
hook(seq, v);
}
});
stats.read == 1
}
#[inline]
pub fn poll_one_value(&mut self) -> Option<(u32, T)> {
let mut result = None;
self.poll_one(|seq, v| result = Some((seq, *v)));
result
}
pub fn poll_up_to(&mut self, max: usize, mut hook: impl FnMut(u32, &T)) -> PollStats {
if max == 0 {
return PollStats {
read: 0,
dropped: 0,
newest: self.ring.newest_seq(),
};
}
let newest = self.ring.newest_seq();
if newest == 0 || newest == self.last_seq {
return PollStats {
read: 0,
dropped: 0,
newest,
};
}
let mut read = 0usize;
let mut dropped = 0usize;
let lag = SeqRing::<T, N>::seq_distance(self.last_seq, newest) as usize;
if lag > N {
let keep_from = newest.wrapping_sub((N - 1) as u32);
let resume_after = keep_from.wrapping_sub(1);
let jumped = SeqRing::<T, N>::seq_distance(self.last_seq, resume_after) as usize;
dropped = dropped.saturating_add(jumped);
self.last_seq = resume_after;
}
while read < max && self.last_seq != newest {
let next = SeqRing::<T, N>::next_after(self.last_seq);
match self.ring.read_seq_inner(next) {
Some(v) => {
hook(next, &v);
self.last_seq = next;
read += 1;
}
None => {
self.last_seq = next;
dropped = dropped.saturating_add(1);
}
}
}
self.dropped_accum = self.dropped_accum.saturating_add(dropped);
PollStats {
read,
dropped,
newest,
}
}
#[inline]
pub fn latest(&self, hook: impl FnOnce(u32, &T)) -> bool {
let newest = self.ring.newest_seq();
if newest == 0 {
return false;
}
if let Some(v) = self.ring.read_seq_inner(newest) {
hook(newest, &v);
true
} else {
false
}
}
#[inline]
pub fn latest_value(&self) -> Option<(u32, T)> {
let mut result = None;
self.latest(|seq, v| result = Some((seq, *v)));
result
}
#[inline]
pub fn skip_to_latest(&mut self) {
let newest = self.ring.newest_seq();
if newest != 0 {
self.last_seq = newest.wrapping_sub(1);
}
}
}
impl<'a, T: Copy, const N: usize> Drop for Consumer<'a, T, N> {
fn drop(&mut self) {
self.ring.consumer_taken.store(false, Ordering::Release);
}
}
impl<T: Copy, const N: usize> core::fmt::Debug for Consumer<'_, T, N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("seq_ring::Consumer")
.field("capacity", &N)
.field("last_seq", &self.last_seq)
.field("dropped", &self.dropped_accum)
.finish()
}
}
impl<T: Copy, const N: usize> crate::traits::Sink<T> for Producer<'_, T, N> {
type Error = core::convert::Infallible;
#[inline]
fn try_push(&mut self, val: T) -> Result<(), core::convert::Infallible> {
self.push(val);
Ok(())
}
}
impl<T: Copy, const N: usize> crate::traits::Source<T> for Consumer<'_, T, N> {
#[inline]
fn try_pop(&mut self) -> Option<T> {
self.poll_one_value().map(|(_, v)| v)
}
}
#[cfg(test)]
mod tests {
use super::{SeqRing, TEST_AFTER_READ_SEQ, TEST_AFTER_READ_TARGET};
use core::sync::atomic::Ordering;
use std::vec::Vec;
#[test]
fn poll_one_empty_returns_false() {
let ring = SeqRing::<u32, 4>::new();
let mut consumer = ring.try_consumer().unwrap();
let ok = consumer.poll_one(|_, _| {});
assert!(!ok);
}
#[test]
fn polls_in_order() {
let ring = SeqRing::<u32, 8>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
producer.push(10);
producer.push(11);
producer.push(12);
let mut seen = Vec::new();
let stats = consumer.poll_up_to(10, |seq, v| seen.push((seq, *v)));
assert_eq!(stats.read, 3);
assert_eq!(stats.dropped, 0);
assert_eq!(stats.newest, 3);
assert_eq!(&seen[..], &[(1, 10), (2, 11), (3, 12)]);
}
#[test]
fn drops_when_consumer_lags() {
let ring = SeqRing::<u32, 4>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
for i in 0..10 {
producer.push(i);
}
let mut seen = Vec::new();
let stats = consumer.poll_up_to(10, |seq, v| seen.push((seq, *v)));
assert_eq!(stats.read, 4);
assert_eq!(stats.dropped, 6);
assert_eq!(stats.newest, 10);
assert_eq!(&seen[..], &[(7, 6), (8, 7), (9, 8), (10, 9)]);
}
#[test]
fn latest_reads_newest() {
let ring = SeqRing::<u32, 8>::new();
let producer = ring.try_producer().unwrap();
let consumer = ring.try_consumer().unwrap();
producer.push(1);
producer.push(2);
let mut got = None;
let ok = consumer.latest(|seq, v| got = Some((seq, *v)));
assert!(ok);
assert_eq!(got, Some((2, 2)));
}
#[test]
fn skip_to_latest_makes_next_poll_latest() {
let ring = SeqRing::<u32, 8>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
producer.push(10);
producer.push(11);
producer.push(12);
consumer.skip_to_latest();
let mut got = None;
let ok = consumer.poll_one(|seq, v| got = Some((seq, *v)));
assert!(ok);
assert_eq!(got, Some((3, 12)));
}
#[test]
fn poll_up_to_zero_returns_newest_only() {
let ring = SeqRing::<u32, 4>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
producer.push(42);
let stats = consumer.poll_up_to(0, |_, _| panic!("hook should not run"));
assert_eq!(stats.read, 0);
assert_eq!(stats.dropped, 0);
assert_eq!(stats.newest, 1);
}
#[test]
fn dropped_counter_can_reset() {
let ring = SeqRing::<u32, 2>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
for i in 0..5 {
producer.push(i);
}
let stats = consumer.poll_up_to(10, |_, _| {});
assert_eq!(consumer.dropped(), stats.dropped);
consumer.reset_dropped();
assert_eq!(consumer.dropped(), 0);
}
#[test]
fn latest_empty_returns_false() {
let ring = SeqRing::<u32, 4>::new();
let consumer = ring.try_consumer().unwrap();
let ok = consumer.latest(|_, _| {});
assert!(!ok);
}
#[test]
fn latest_returns_false_when_slot_missing() {
let ring = SeqRing::<u32, 4>::new();
let consumer = ring.try_consumer().unwrap();
ring.published_seq.store(1, Ordering::Release);
let ok = consumer.latest(|_, _| {});
assert!(!ok);
}
#[test]
fn poll_up_to_counts_dropped_when_slot_missing() {
let ring = SeqRing::<u32, 4>::new();
let mut consumer = ring.try_consumer().unwrap();
ring.published_seq.store(1, Ordering::Release);
let stats = consumer.poll_up_to(1, |_, _| panic!("hook should not run"));
assert_eq!(stats.read, 0);
assert_eq!(stats.dropped, 1);
assert_eq!(consumer.dropped(), 1);
}
#[test]
fn read_seq_inner_detects_overwrite_during_read() {
let ring = SeqRing::<u32, 4>::new();
let producer = ring.try_producer().unwrap();
let seq = producer.push(7);
TEST_AFTER_READ_SEQ.store(seq.wrapping_add(1), Ordering::Relaxed);
TEST_AFTER_READ_TARGET.store(&ring as *const _ as usize, Ordering::Release);
let got = ring.read_seq_inner(seq);
TEST_AFTER_READ_TARGET.store(0, Ordering::Release);
assert!(got.is_none());
}
#[test]
fn push_wraps_seq_from_zero_to_one() {
let ring = SeqRing::<u32, 4>::new();
ring.next_seq.store(u32::MAX, Ordering::Relaxed);
let seq = ring.try_producer().unwrap().push(1);
assert_eq!(seq, 1);
assert_eq!(ring.next_seq.load(Ordering::Relaxed), 1);
}
#[test]
fn read_seq_inner_rejects_invalidated_slot() {
let ring = SeqRing::<u32, 4>::new();
let producer = ring.try_producer().unwrap();
let seq = producer.push(7);
ring.slot_seq[SeqRing::<u32, 4>::idx_for(seq)].store(0, Ordering::Release);
assert!(ring.read_seq_inner(seq).is_none());
}
#[test]
fn consumer_skips_reserved_seq_zero_on_wrap() {
let ring = SeqRing::<u32, 4>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
ring.next_seq.store(u32::MAX - 1, Ordering::Relaxed);
assert_eq!(producer.push(10), u32::MAX);
consumer.skip_to_latest();
let mut got = None;
assert!(consumer.poll_one(|s, v| got = Some((s, *v))));
assert_eq!(got, Some((u32::MAX, 10)));
assert_eq!(producer.push(20), 1);
let mut got = None;
let stats = consumer.poll_up_to(4, |s, v| got = Some((s, *v)));
assert_eq!(stats.read, 1);
assert_eq!(stats.dropped, 0);
assert_eq!(got, Some((1, 20)));
}
#[test]
fn poll_window_is_frozen_at_entry() {
let ring = SeqRing::<u32, 4>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
producer.push(10);
producer.push(20);
let mut seen = std::vec::Vec::new();
let stats = consumer.poll_up_to(4, |seq, v| {
if seq == 1 {
producer.push(30);
}
seen.push((seq, *v));
});
assert_eq!(stats.read, 2);
assert_eq!(stats.dropped, 0);
assert_eq!(stats.newest, 2);
assert_eq!(seen, [(1, 10), (2, 20)]);
let stats = consumer.poll_up_to(4, |seq, v| assert_eq!((seq, *v), (3, 30)));
assert_eq!(stats.read, 1);
assert_eq!(stats.dropped, 0);
assert_eq!(stats.newest, 3);
}
#[test]
fn lag_across_wrap_counts_drops_exactly() {
let ring = SeqRing::<u32, 4>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
ring.next_seq.store(u32::MAX - 6, Ordering::Relaxed);
assert_eq!(producer.push(100), u32::MAX - 5);
let mut got = None;
assert!(consumer.poll_one(|s, v| got = Some((s, *v))));
assert_eq!(got, Some((u32::MAX - 5, 100)));
consumer.reset_dropped();
let pushed: Vec<u32> = (0..15u32).map(|i| producer.push(i)).collect();
assert_eq!(pushed.last().copied(), Some(10));
let mut seen = Vec::new();
let stats = consumer.poll_up_to(16, |seq, v| seen.push((seq, *v)));
assert_eq!(stats.read, 4);
assert_eq!(stats.dropped, 11);
assert_eq!(stats.read + stats.dropped, pushed.len());
let seqs: Vec<u32> = seen.iter().map(|(s, _)| *s).collect();
assert_eq!(&seqs[..], &[7, 8, 9, 10]);
}
#[test]
fn dropped_accum_saturates_instead_of_overflowing() {
let ring = SeqRing::<u32, 4>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
ring.next_seq.store(u32::MAX - 2, Ordering::Relaxed);
producer.push(1);
let _ = consumer.poll_up_to(4, |_, _| {});
let after_first = consumer.dropped();
assert!(after_first > 0);
for _ in 0..8 {
producer.push(2);
let _ = consumer.poll_up_to(4, |_, _| {});
}
assert!(
consumer.dropped() >= after_first,
"dropped counter went backwards — it wrapped instead of saturating"
);
}
#[test]
fn seq_distance_skips_the_reserved_zero() {
type R = SeqRing<u32, 4>;
assert_eq!(R::seq_distance(0, 0), 0);
assert_eq!(R::seq_distance(0, 5), 5);
assert_eq!(R::seq_distance(5, 9), 4);
assert_eq!(R::seq_distance(u32::MAX, 1), 1);
assert_eq!(R::seq_distance(u32::MAX - 5, 6), 11);
assert_eq!(R::seq_distance(u32::MAX, u32::MAX), 0);
}
#[test]
fn concurrent_overwrite_never_yields_a_mismatched_value() {
use core::sync::atomic::AtomicBool;
let ring = SeqRing::<[u32; 4], 2>::new();
let total = crate::test_support::iterations(20_000);
let done = AtomicBool::new(false);
std::thread::scope(|scope| {
scope.spawn(|| {
let producer = ring.try_producer().unwrap();
for i in 0..total {
producer.push([i; 4]);
}
done.store(true, Ordering::Release);
});
scope.spawn(|| {
let mut consumer = ring.try_consumer().unwrap();
let mut last_seq = 0u32;
let mut read_total = 0usize;
loop {
let finished = done.load(Ordering::Acquire);
let mut batch_last = last_seq;
let stats = consumer.poll_up_to(8, |seq, v| {
assert!(
seq > batch_last,
"sequence went backwards: {seq} after {batch_last}"
);
batch_last = seq;
let expected = seq - 1;
assert_eq!(
*v, [expected; 4],
"sequence {seq} carried a stale or torn payload"
);
});
last_seq = batch_last;
read_total += stats.read;
if finished && stats.read == 0 && stats.dropped == 0 {
break;
}
}
assert_eq!(last_seq, total, "consumer stopped short of the tail");
assert_eq!(
read_total + consumer.dropped(),
total as usize,
"read + dropped must account for every published item"
);
});
});
}
#[test]
fn capacity_returns_n() {
let ring = SeqRing::<u32, 8>::new();
assert_eq!(ring.capacity(), 8);
}
#[test]
fn try_producer_and_try_consumer() {
let ring = SeqRing::<u32, 4>::new();
let p = ring.try_producer().expect("first producer");
assert!(ring.try_producer().is_none());
let mut c = ring.try_consumer().expect("first consumer");
assert!(ring.try_consumer().is_none());
p.push(7);
let mut got = None;
assert!(c.poll_one(|seq, v| got = Some((seq, *v))));
assert_eq!(got, Some((1, 7)));
drop(p);
drop(c);
assert!(ring.try_producer().is_some());
assert!(ring.try_consumer().is_some());
}
#[test]
fn poll_one_value_and_latest_value() {
let ring = SeqRing::<u32, 8>::new();
let producer = ring.try_producer().unwrap();
let mut consumer = ring.try_consumer().unwrap();
assert_eq!(consumer.poll_one_value(), None);
assert_eq!(consumer.latest_value(), None);
producer.push(10);
producer.push(20);
assert_eq!(consumer.latest_value(), Some((2, 20)));
assert_eq!(consumer.poll_one_value(), Some((1, 10)));
assert_eq!(consumer.poll_one_value(), Some((2, 20)));
assert_eq!(consumer.poll_one_value(), None);
assert_eq!(consumer.latest_value(), Some((2, 20)));
}
#[cfg(not(loom))]
#[test]
fn const_new_works_in_const_context() {
static RING: SeqRing<u32, 4> = SeqRing::new();
assert_eq!(RING.capacity(), 4);
}
#[cfg(not(loom))]
#[test]
fn static_ring_yields_static_sendable_handles() {
static RING: SeqRing<u32, 4> = SeqRing::new();
fn producer_for_isr() -> super::Producer<'static, u32, 4> {
RING.try_producer().unwrap()
}
fn consumer_for_task() -> super::Consumer<'static, u32, 4> {
RING.try_consumer().unwrap()
}
fn assert_send<T: Send>(_: &T) {}
let p = producer_for_isr();
let mut c = consumer_for_task();
assert_send(&p);
assert_send(&c);
p.push(9);
assert_eq!(c.poll_one_value(), Some((1, 9)));
}
}