use std::cell::UnsafeCell;
use std::fs::{File, OpenOptions};
use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
use memmap2::{MmapMut, MmapOptions};
use crate::shared_ring::RingError;
pub const FRAME_MAGIC: u64 = 0x4652_4d52_0000_0001;
pub const DESC_HEADER_BYTES: usize = 8;
pub const MIN_SLOT_SIZE: usize = DESC_HEADER_BYTES + 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FrameClass {
Inline = 0,
Offset = 1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LayoutHint {
#[default]
Auto,
ForceInline,
ForceOffset,
}
#[repr(C, align(64))]
struct FrameHeader {
magic: u64,
capacity: u64,
slot_size: u64,
region_bytes: u64,
inline_budget: u64,
_pad_meta: [u8; 64 - 40],
desc_head: AtomicU64,
_pad_dh: [u8; 64 - 8],
desc_tail: AtomicU64,
_pad_dt: [u8; 64 - 8],
region_head: AtomicU64,
_pad_rh: [u8; 64 - 8],
region_tail: AtomicU64,
_pad_rt: [u8; 64 - 8],
}
pub const fn frame_ring_file_size(
capacity: usize, slot_size: usize, region_bytes: usize,
) -> usize {
std::mem::size_of::<FrameHeader>() + capacity * slot_size + region_bytes
}
#[allow(dead_code)]
struct FrameCell(UnsafeCell<u8>);
#[allow(dead_code)]
enum FrameBacking {
Anon(MmapMut),
File(File, MmapMut),
Shm(crate::shm_file::ShmFile),
}
pub struct FrameRing {
_backing: FrameBacking,
raw_ptr: *mut u8,
capacity: usize,
slot_size: usize,
region_bytes: usize,
inline_budget: usize,
desc_base: usize,
region_base: usize,
}
unsafe impl Send for FrameRing {}
unsafe impl Sync for FrameRing {}
fn validate_params(capacity: usize, slot_size: usize, region_bytes: usize)
-> Result<(), RingError>
{
if !capacity.is_power_of_two() || capacity < 2 {
return Err(RingError::LayoutMismatch);
}
if slot_size < MIN_SLOT_SIZE {
return Err(RingError::LayoutMismatch);
}
if !region_bytes.is_power_of_two() || region_bytes < 2 {
return Err(RingError::LayoutMismatch);
}
Ok(())
}
unsafe fn init_frame_layout_raw(
ptr: *mut u8, capacity: usize, slot_size: usize, region_bytes: usize,
) {
let inline_budget = slot_size - DESC_HEADER_BYTES;
unsafe {
let desc_base = std::mem::size_of::<FrameHeader>();
std::ptr::write_bytes(ptr, 0, desc_base + capacity * slot_size);
let hdr = ptr as *mut FrameHeader;
(*hdr).capacity = capacity as u64;
(*hdr).slot_size = slot_size as u64;
(*hdr).region_bytes = region_bytes as u64;
(*hdr).inline_budget = inline_budget as u64;
std::ptr::write_volatile(&raw mut (*hdr).magic, FRAME_MAGIC);
}
}
impl FrameRing {
pub fn create_anon(
capacity: usize, slot_size: usize, region_bytes: usize,
) -> Result<Self, RingError> {
validate_params(capacity, slot_size, region_bytes)?;
let total = frame_ring_file_size(capacity, slot_size, region_bytes);
let mut mmap = MmapOptions::new().len(total).map_anon()?;
unsafe { init_frame_layout_raw(mmap.as_mut_ptr(), capacity, slot_size, region_bytes) };
let raw_ptr = mmap.as_mut_ptr();
Ok(Self::from_parts(
FrameBacking::Anon(mmap), raw_ptr, capacity, slot_size, region_bytes,
))
}
pub fn create(
path: impl AsRef<Path>, capacity: usize, slot_size: usize, region_bytes: usize,
) -> Result<Self, RingError> {
validate_params(capacity, slot_size, region_bytes)?;
let total = frame_ring_file_size(capacity, slot_size, region_bytes);
let (file, mut mmap) = crate::mmf_attach::create_or_attach(
path.as_ref(),
total,
|ptr| unsafe { init_frame_layout_raw(ptr, capacity, slot_size, region_bytes) },
|ptr| unsafe { (*(ptr as *const FrameHeader)).magic == FRAME_MAGIC },
)?;
Self::check_header(mmap.as_ptr(), capacity, slot_size, region_bytes)?;
let raw_ptr = mmap.as_mut_ptr();
Ok(Self::from_parts(
FrameBacking::File(file, mmap), raw_ptr, capacity, slot_size, region_bytes,
))
}
pub fn reset(
path: impl AsRef<Path>, capacity: usize, slot_size: usize, region_bytes: usize,
) -> Result<Self, RingError> {
validate_params(capacity, slot_size, region_bytes)?;
let total = frame_ring_file_size(capacity, slot_size, region_bytes);
let (file, mut mmap) = crate::mmf_attach::reset(
path.as_ref(),
total,
|ptr| unsafe { init_frame_layout_raw(ptr, capacity, slot_size, region_bytes) },
)?;
let raw_ptr = mmap.as_mut_ptr();
Ok(Self::from_parts(
FrameBacking::File(file, mmap), raw_ptr, capacity, slot_size, region_bytes,
))
}
pub fn open(
path: impl AsRef<Path>, capacity: usize, slot_size: usize, region_bytes: usize,
) -> Result<Self, RingError> {
validate_params(capacity, slot_size, region_bytes)?;
let total = frame_ring_file_size(capacity, slot_size, region_bytes);
let file = OpenOptions::new().read(true).write(true).open(path.as_ref())?;
if (file.metadata()?.len() as usize) < total {
return Err(RingError::LayoutMismatch);
}
let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
Self::check_header(mmap.as_ptr(), capacity, slot_size, region_bytes)?;
let raw_ptr = mmap.as_mut_ptr();
Ok(Self::from_parts(
FrameBacking::File(file, mmap), raw_ptr, capacity, slot_size, region_bytes,
))
}
pub fn create_from_shm(
mut shm: crate::shm_file::ShmFile,
capacity: usize, slot_size: usize, region_bytes: usize,
) -> Result<Self, RingError> {
validate_params(capacity, slot_size, region_bytes)?;
let total = frame_ring_file_size(capacity, slot_size, region_bytes);
if shm.len() < total {
return Err(RingError::LayoutMismatch);
}
let raw_ptr = shm.as_mut_slice().as_mut_ptr();
unsafe { init_frame_layout_raw(raw_ptr, capacity, slot_size, region_bytes) };
Ok(Self::from_parts(
FrameBacking::Shm(shm), raw_ptr, capacity, slot_size, region_bytes,
))
}
pub fn open_from_shm(
mut shm: crate::shm_file::ShmFile,
capacity: usize, slot_size: usize, region_bytes: usize,
) -> Result<Self, RingError> {
validate_params(capacity, slot_size, region_bytes)?;
let total = frame_ring_file_size(capacity, slot_size, region_bytes);
if shm.len() < total {
return Err(RingError::LayoutMismatch);
}
let raw_ptr = shm.as_mut_slice().as_mut_ptr();
Self::check_header(raw_ptr, capacity, slot_size, region_bytes)?;
Ok(Self::from_parts(
FrameBacking::Shm(shm), raw_ptr, capacity, slot_size, region_bytes,
))
}
fn from_parts(
backing: FrameBacking, raw_ptr: *mut u8,
capacity: usize, slot_size: usize, region_bytes: usize,
) -> Self {
let desc_base = std::mem::size_of::<FrameHeader>();
let region_base = desc_base + capacity * slot_size;
Self {
_backing: backing, raw_ptr, capacity, slot_size, region_bytes,
inline_budget: slot_size - DESC_HEADER_BYTES,
desc_base, region_base,
}
}
fn check_header(
ptr: *const u8, capacity: usize, slot_size: usize, region_bytes: usize,
) -> Result<(), RingError> {
let header = unsafe { &*(ptr as *const FrameHeader) };
if header.magic != FRAME_MAGIC
|| header.capacity != capacity as u64
|| header.slot_size != slot_size as u64
|| header.region_bytes != region_bytes as u64
{
return Err(RingError::LayoutMismatch);
}
Ok(())
}
pub fn capacity(&self) -> usize { self.capacity }
pub fn slot_size(&self) -> usize { self.slot_size }
pub fn inline_budget(&self) -> usize { self.inline_budget }
pub fn region_bytes(&self) -> usize { self.region_bytes }
pub fn max_payload(&self) -> usize { self.region_bytes / 2 }
fn header(&self) -> &FrameHeader {
unsafe { &*(self.raw_ptr as *const FrameHeader) }
}
fn desc_slot_ptr(&self, idx: u64) -> *mut u8 {
let masked = (idx as usize) & (self.capacity - 1);
unsafe { self.raw_ptr.add(self.desc_base + masked * self.slot_size) }
}
fn region_ptr(&self) -> *mut u8 {
unsafe { self.raw_ptr.add(self.region_base) }
}
pub fn approx_len(&self) -> usize {
let h = self.header();
h.desc_head.load(Ordering::Acquire)
.saturating_sub(h.desc_tail.load(Ordering::Acquire)) as usize
}
pub fn send(&self, payload: &[u8]) -> Result<FrameClass, RingError> {
self.send_as(payload, LayoutHint::Auto)
}
pub fn send_as(&self, payload: &[u8], hint: LayoutHint)
-> Result<FrameClass, RingError>
{
let h = self.header();
let head = h.desc_head.load(Ordering::Relaxed);
let tail = h.desc_tail.load(Ordering::Acquire);
if head.wrapping_sub(tail) >= self.capacity as u64 {
return Err(RingError::Full);
}
let inline = match hint {
LayoutHint::ForceInline => {
if payload.len() > self.inline_budget {
return Err(RingError::PayloadTooLarge);
}
true
}
LayoutHint::ForceOffset => false,
LayoutHint::Auto => payload.len() <= self.inline_budget,
};
let slot = self.desc_slot_ptr(head);
let len = payload.len() as u32;
let class = if inline {
unsafe {
slot.write(FrameClass::Inline as u8);
std::ptr::copy_nonoverlapping(
len.to_le_bytes().as_ptr(), slot.add(4), 4,
);
std::ptr::copy_nonoverlapping(
payload.as_ptr(), slot.add(DESC_HEADER_BYTES), payload.len(),
);
}
FrameClass::Inline
} else {
if payload.len() > self.max_payload() {
return Err(RingError::PayloadTooLarge);
}
let rh = h.region_head.load(Ordering::Relaxed);
let rt = h.region_tail.load(Ordering::Acquire);
let rb = self.region_bytes as u64;
let phys = rh % rb;
let start = if phys + payload.len() as u64 > rb {
rh + (rb - phys)
} else {
rh
};
if start.wrapping_add(payload.len() as u64).wrapping_sub(rt) > rb {
return Err(RingError::Full);
}
let pstart = (start % rb) as usize;
unsafe {
std::ptr::copy_nonoverlapping(
payload.as_ptr(), self.region_ptr().add(pstart), payload.len(),
);
}
h.region_head.store(start + payload.len() as u64, Ordering::Release);
unsafe {
slot.write(FrameClass::Offset as u8);
std::ptr::copy_nonoverlapping(
len.to_le_bytes().as_ptr(), slot.add(4), 4,
);
std::ptr::copy_nonoverlapping(
start.to_le_bytes().as_ptr(), slot.add(DESC_HEADER_BYTES), 8,
);
}
FrameClass::Offset
};
h.desc_head.store(head + 1, Ordering::Release);
crate::cache_ops::cldemote(slot as *const u8);
Ok(class)
}
pub fn recv_into(&self, out: &mut Vec<u8>) -> Result<FrameClass, RingError> {
let h = self.header();
let tail = h.desc_tail.load(Ordering::Relaxed);
let head = h.desc_head.load(Ordering::Acquire);
if tail == head {
return Err(RingError::Empty);
}
let slot = self.desc_slot_ptr(tail);
let class_byte = unsafe { slot.read() };
let len = unsafe {
let mut b = [0u8; 4];
std::ptr::copy_nonoverlapping(slot.add(4), b.as_mut_ptr(), 4);
u32::from_le_bytes(b) as usize
};
out.clear();
out.reserve(len);
let class = if class_byte == FrameClass::Inline as u8 {
unsafe {
std::ptr::copy_nonoverlapping(
slot.add(DESC_HEADER_BYTES),
out.spare_capacity_mut().as_mut_ptr() as *mut u8,
len,
);
out.set_len(len);
}
FrameClass::Inline
} else {
let off = unsafe {
let mut b = [0u8; 8];
std::ptr::copy_nonoverlapping(slot.add(DESC_HEADER_BYTES), b.as_mut_ptr(), 8);
u64::from_le_bytes(b)
};
let pstart = (off % self.region_bytes as u64) as usize;
unsafe {
std::ptr::copy_nonoverlapping(
self.region_ptr().add(pstart),
out.spare_capacity_mut().as_mut_ptr() as *mut u8,
len,
);
out.set_len(len);
}
h.region_tail.store(off + len as u64, Ordering::Release);
FrameClass::Offset
};
h.desc_tail.store(tail + 1, Ordering::Release);
crate::cache_ops::cldemote(slot as *const u8);
Ok(class)
}
pub fn recv(&self) -> Result<Vec<u8>, RingError> {
let mut out = Vec::new();
self.recv_into(&mut out)?;
Ok(out)
}
pub fn flush(&self) -> Result<(), RingError> {
if let FrameBacking::File(_, mmap) = &self._backing {
mmap.flush()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::thread;
fn ring() -> FrameRing {
FrameRing::create_anon(16, 64, 1 << 16).unwrap()
}
#[test]
fn inline_round_trip() {
let r = ring();
let payload = b"small payload under the inline budget";
assert_eq!(r.send(payload).unwrap(), FrameClass::Inline);
let got = r.recv().unwrap();
assert_eq!(got, payload);
}
#[test]
fn offset_round_trip_large() {
let r = ring();
let payload = vec![0xABu8; 4096]; assert_eq!(r.send(&payload).unwrap(), FrameClass::Offset);
let got = r.recv().unwrap();
assert_eq!(got, payload);
}
#[test]
fn boundary_inline_vs_offset() {
let r = ring();
let at = vec![1u8; r.inline_budget()];
assert_eq!(r.send(&at).unwrap(), FrameClass::Inline);
assert_eq!(r.recv().unwrap(), at);
let over = vec![2u8; r.inline_budget() + 1];
assert_eq!(r.send(&over).unwrap(), FrameClass::Offset);
assert_eq!(r.recv().unwrap(), over);
}
#[test]
fn empty_payload_round_trip() {
let r = ring();
assert_eq!(r.send(&[]).unwrap(), FrameClass::Inline);
assert_eq!(r.recv().unwrap(), Vec::<u8>::new());
}
#[test]
fn force_offset_overrides_small() {
let r = ring();
assert_eq!(r.send_as(b"tiny", LayoutHint::ForceOffset).unwrap(),
FrameClass::Offset);
assert_eq!(r.recv().unwrap(), b"tiny");
}
#[test]
fn force_inline_rejects_oversize() {
let r = ring();
let big = vec![0u8; r.inline_budget() + 1];
assert_eq!(r.send_as(&big, LayoutHint::ForceInline).unwrap_err(),
RingError::PayloadTooLarge);
}
#[test]
fn payload_over_region_cap_rejected() {
let r = ring();
let too_big = vec![0u8; r.max_payload() + 1];
assert_eq!(r.send(&too_big).unwrap_err(), RingError::PayloadTooLarge);
}
#[test]
fn descriptor_full_then_drains() {
let r = FrameRing::create_anon(4, 64, 1 << 16).unwrap();
for i in 0..4u8 {
r.send(&[i; 8]).unwrap();
}
assert_eq!(r.send(&[9u8; 8]).unwrap_err(), RingError::Full);
assert_eq!(r.recv().unwrap(), &[0u8; 8]);
r.send(&[9u8; 8]).unwrap();
}
#[test]
fn region_wraps_with_skip_pad() {
let region = 1usize << 12; let r = FrameRing::create_anon(8, 64, region).unwrap();
for i in 0..200u32 {
let len = 600 + (i as usize % 700); let payload: Vec<u8> = (0..len).map(|k| (k as u32 ^ i) as u8).collect();
assert_eq!(r.send(&payload).unwrap(), FrameClass::Offset);
let got = r.recv().unwrap();
assert_eq!(got, payload, "record {i} survived the region wrap");
}
}
#[test]
fn mixed_inline_and_offset_fifo_order() {
let r = FrameRing::create_anon(64, 64, 1 << 16).unwrap();
let mut expected = Vec::new();
for i in 0..40u32 {
let len = if i % 2 == 0 { 16 } else { 500 };
let p: Vec<u8> = (0..len).map(|k| (k as u32 + i) as u8).collect();
r.send(&p).unwrap();
expected.push(p);
}
for want in expected {
assert_eq!(r.recv().unwrap(), want);
}
assert_eq!(r.recv().unwrap_err(), RingError::Empty);
}
#[test]
fn two_thread_mixed_size_stream() {
let r = Arc::new(FrameRing::create_anon(256, 64, 1 << 20).unwrap());
let rp = r.clone();
let rc = r.clone();
const N: u32 = 50_000;
let producer = thread::spawn(move || {
for i in 0..N {
let len = (i as usize % 300) + 1;
let p: Vec<u8> =
(0..len).map(|k| i.wrapping_add(k as u32) as u8).collect();
while rp.send(&p).is_err() {
std::hint::spin_loop();
}
}
});
let consumer = thread::spawn(move || {
let mut buf = Vec::new();
let mut got = 0u32;
while got < N {
if rc.recv_into(&mut buf).is_ok() {
let len = (got as usize % 300) + 1;
assert_eq!(buf.len(), len, "item {got} length");
for (k, &b) in buf.iter().enumerate() {
assert_eq!(b, got.wrapping_add(k as u32) as u8,
"item {got} byte {k}");
}
got += 1;
} else {
std::hint::spin_loop();
}
}
});
producer.join().unwrap();
consumer.join().unwrap();
}
#[test]
fn shm_cross_handle_visibility() {
use crate::shm_file::ShmFile;
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let name = format!("frame_shm_{}_{}", std::process::id(), nonce);
let (cap, slot, region) = (16usize, 64usize, 1usize << 16);
let size = frame_ring_file_size(cap, slot, region);
let shm_a = ShmFile::create_or_open_named(&name, size).unwrap();
let producer = FrameRing::create_from_shm(shm_a, cap, slot, region).unwrap();
let shm_b = ShmFile::create_or_open_named(&name, size).unwrap();
let consumer = FrameRing::open_from_shm(shm_b, cap, slot, region).unwrap();
let small = b"inline across handles";
let large = vec![0x5Au8; 2000];
producer.send(small).unwrap();
producer.send(&large).unwrap();
assert_eq!(consumer.recv().unwrap(), small);
assert_eq!(consumer.recv().unwrap(), large);
}
#[test]
fn file_round_trips() {
let p = std::env::temp_dir().join(format!(
"subetha-frame-{}.bin", std::process::id(),
));
std::fs::remove_file(&p).ok();
let (cap, slot, region) = (16usize, 128usize, 1usize << 16);
{
let r = FrameRing::create(&p, cap, slot, region).unwrap();
r.send(b"persisted inline").unwrap();
r.send(&vec![7u8; 3000]).unwrap();
r.flush().unwrap();
}
let r2 = FrameRing::open(&p, cap, slot, region).unwrap();
assert_eq!(r2.recv().unwrap(), b"persisted inline");
assert_eq!(r2.recv().unwrap(), vec![7u8; 3000]);
std::fs::remove_file(&p).ok();
}
#[test]
fn rejects_bad_params() {
assert!(matches!(FrameRing::create_anon(3, 64, 1 << 16),
Err(RingError::LayoutMismatch))); assert!(matches!(FrameRing::create_anon(16, 8, 1 << 16),
Err(RingError::LayoutMismatch))); assert!(matches!(FrameRing::create_anon(16, 64, 1000),
Err(RingError::LayoutMismatch))); }
#[test]
fn second_create_attaches_and_keeps_frames() {
let p = std::env::temp_dir().join(format!(
"subetha-frame-attach-{}.bin", std::process::id(),
));
std::fs::remove_file(&p).ok();
let (cap, slot, region) = (16usize, 128usize, 1usize << 16);
let r = FrameRing::create(&p, cap, slot, region).unwrap();
r.send(b"inline survives attach").unwrap();
r.send(&vec![9u8; 3000]).unwrap();
let r2 = FrameRing::create(&p, cap, slot, region).unwrap();
assert_eq!(r2.recv().unwrap(), b"inline survives attach",
"attach lost a queued inline frame");
assert_eq!(r2.recv().unwrap(), vec![9u8; 3000],
"attach lost a queued region frame");
assert!(matches!(
FrameRing::create(&p, 8, slot, region),
Err(RingError::LayoutMismatch),
));
drop(r);
drop(r2);
let fresh = FrameRing::reset(&p, cap, slot, region).unwrap();
assert_eq!(fresh.approx_len(), 0, "reset kept a queued frame");
drop(fresh);
std::fs::remove_file(&p).ok();
}
}