use alloc::{boxed::Box, vec::Vec};
use bytes::Bytes;
use core::{
cell::UnsafeCell,
sync::atomic::{AtomicUsize, Ordering},
};
type RecLen = u16;
pub const LEN_SUFFIX: usize = core::mem::size_of::<RecLen>();
pub const MAX_RECORD: usize = RecLen::MAX as usize;
#[repr(align(128))]
struct CacheAligned(AtomicUsize);
pub struct Ring {
buf: Box<[UnsafeCell<u8>]>,
mask: usize,
offset: CacheAligned,
}
unsafe impl Send for Ring {}
unsafe impl Sync for Ring {}
impl Ring {
pub fn new(requested: usize) -> Self {
assert!(requested > 0, "Ring capacity must be non-zero");
let capacity = requested.next_power_of_two();
let mut buf = Vec::with_capacity(capacity);
buf.resize_with(capacity, || UnsafeCell::new(0u8));
Self {
buf: buf.into_boxed_slice(),
mask: capacity - 1,
offset: CacheAligned(AtomicUsize::new(0)),
}
}
#[inline]
pub fn capacity(&self) -> usize {
self.mask + 1
}
#[inline]
pub fn head(&self) -> usize {
self.offset.0.load(Ordering::Acquire)
}
pub fn push(&self, payload: &[u8]) -> usize {
assert!(
payload.len() <= MAX_RECORD,
"record payload exceeds MAX_RECORD"
);
let total = payload.len() + LEN_SUFFIX;
assert!(
total <= self.capacity(),
"record size exceeds ring capacity"
);
let start = self.offset.0.fetch_add(total, Ordering::Relaxed);
self.write_wrapping(start, payload);
self.write_wrapping(
start + payload.len(),
&(payload.len() as RecLen).to_le_bytes(),
);
start
}
pub fn push_event<E: crate::event::Event>(&self, event: &E) -> usize {
let id = E::ID.get().to_le_bytes();
self.push_parts(&[&id, event.as_bytes()])
}
pub fn push_parts(&self, parts: &[&[u8]]) -> usize {
let payload_len: usize = parts.iter().map(|p| p.len()).sum();
assert!(
payload_len <= MAX_RECORD,
"record payload exceeds MAX_RECORD"
);
let total = payload_len + LEN_SUFFIX;
assert!(
total <= self.capacity(),
"record size exceeds ring capacity"
);
let start = self.offset.0.fetch_add(total, Ordering::Relaxed);
let mut at = start;
for part in parts {
self.write_wrapping(at, part);
at += part.len();
}
self.write_wrapping(at, &(payload_len as RecLen).to_le_bytes());
start
}
#[inline]
fn write_wrapping(&self, abs: usize, src: &[u8]) {
let begin = abs & self.mask;
let first = (self.capacity() - begin).min(src.len());
unsafe {
let base = self.buf.as_ptr() as *mut u8;
core::ptr::copy_nonoverlapping(src.as_ptr(), base.add(begin), first);
if first < src.len() {
core::ptr::copy_nonoverlapping(src.as_ptr().add(first), base, src.len() - first);
}
}
}
pub fn snapshot_into(&self, out: &mut [u8]) -> usize {
assert_eq!(
out.len(),
self.capacity(),
"snapshot buffer must be capacity()"
);
let head = self.head();
unsafe {
core::ptr::copy_nonoverlapping(
self.buf.as_ptr() as *const u8,
out.as_mut_ptr(),
self.capacity(),
);
}
head
}
}
pub fn walk(region: &Bytes, head: usize, capacity: usize, mut f: impl FnMut(Bytes) -> bool) {
if region.len() != capacity || !capacity.is_power_of_two() {
return;
}
let mask = capacity - 1;
let valid_low = head.saturating_sub(capacity);
let read_wrapping = |abs: usize, len: usize, out: &mut [u8]| {
let begin = abs & mask;
let first = (capacity - begin).min(len);
out[..first].copy_from_slice(®ion[begin..begin + first]);
if first < len {
out[first..len].copy_from_slice(®ion[..len - first]);
}
};
let mut abs = head;
while abs >= valid_low + LEN_SUFFIX {
let suffix_start = abs - LEN_SUFFIX;
let mut len_bytes = [0u8; LEN_SUFFIX];
read_wrapping(suffix_start, LEN_SUFFIX, &mut len_bytes);
let payload_len = u16::from_le_bytes(len_bytes) as usize;
let rec_total = payload_len + LEN_SUFFIX;
if rec_total > abs || abs - rec_total < valid_low {
abs -= 1;
continue;
}
let payload_start = abs - rec_total;
let begin = payload_start & mask;
let payload = if begin + payload_len <= capacity {
region.slice(begin..begin + payload_len)
} else {
let mut buf = alloc::vec![0u8; payload_len];
read_wrapping(payload_start, payload_len, &mut buf);
Bytes::from(buf)
};
if f(payload) {
abs = payload_start;
} else {
abs -= 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_newest_first() {
let ring = Ring::new(4096);
for i in 0u32..5 {
ring.push(&i.to_le_bytes());
}
let mut region = alloc::vec![0u8; ring.capacity()];
let head = ring.snapshot_into(&mut region);
let region = Bytes::from(region);
let mut got = Vec::new();
walk(®ion, head, ring.capacity(), |p| {
if p.len() != 4 {
return false;
}
got.push(u32::from_le_bytes(p[..].try_into().unwrap()));
true
});
assert_eq!(got, [4, 3, 2, 1, 0]);
}
#[test]
fn capacity_rounds_up_to_pow2() {
assert_eq!(Ring::new(1000).capacity(), 1024);
assert_eq!(Ring::new(4096).capacity(), 4096);
}
#[test]
fn wrapping_keeps_newest_records() {
let ring = Ring::new(64);
for i in 0u64..100 {
ring.push(&i.to_le_bytes());
}
let mut region = alloc::vec![0u8; ring.capacity()];
let head = ring.snapshot_into(&mut region);
let region = Bytes::from(region);
let mut got = Vec::new();
walk(®ion, head, ring.capacity(), |p| {
if p.len() != 8 {
return false;
}
got.push(u64::from_le_bytes(p[..].try_into().unwrap()));
true
});
assert!(!got.is_empty());
assert_eq!(got[0], 99);
for w in got.windows(2) {
assert_eq!(w[0], w[1] + 1);
}
}
#[test]
fn resyncs_past_a_torn_head_record() {
let ring = Ring::new(4096);
for i in 0u32..6 {
let mut p = [0xEDu8; 8];
p[4..].copy_from_slice(&i.to_le_bytes());
ring.push(&p);
}
let mut region = alloc::vec![0u8; ring.capacity()];
let head = ring.snapshot_into(&mut region);
let begin = head.saturating_sub(8 + LEN_SUFFIX);
for b in &mut region[begin..head] {
*b = 0x77;
}
let region = Bytes::from(region);
let mut got = Vec::new();
walk(®ion, head, ring.capacity(), |p| {
if p.len() != 8 || p[..4] != [0xED; 4] {
return false;
}
got.push(u32::from_le_bytes(p[4..].try_into().unwrap()));
true
});
assert_eq!(got, [4, 3, 2, 1, 0]);
}
#[test]
fn recovers_a_record_that_wraps_the_boundary() {
let ring = Ring::new(64);
for i in 0u64..20 {
ring.push(&i.to_le_bytes());
}
let mut region = alloc::vec![0u8; ring.capacity()];
let head = ring.snapshot_into(&mut region);
let region = Bytes::from(region);
let mut got = Vec::new();
walk(®ion, head, ring.capacity(), |p| {
if p.len() != 8 {
return false;
}
got.push(u64::from_le_bytes(p[..].try_into().unwrap()));
true
});
assert_eq!(got[0], 19);
for w in got.windows(2) {
assert_eq!(w[0], w[1] + 1);
}
}
}