use super::error::IpcError;
use super::memory::SharedMemory;
use core::mem;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
pub struct SharedQueue<T> {
#[allow(dead_code)]
memory: SharedMemory,
meta: *mut QueueMetadata,
buffer: *mut T,
capacity: usize,
}
unsafe impl<T: Send> Send for SharedQueue<T> {}
const HEADER_ALIGN: usize = 64;
#[repr(C, align(64))]
struct QueueMetadata {
head: AtomicUsize,
capacity: AtomicUsize,
_pad1: [u8; 48],
tail: AtomicUsize,
_pad2: [u8; 56],
closed: AtomicBool,
_pad3: [u8; 63],
}
fn layout_for<T>(capacity: usize) -> Result<usize, IpcError> {
if capacity == 0 || mem::align_of::<T>() > HEADER_ALIGN {
return Err(IpcError::InvalidArgument);
}
let meta_size = mem::size_of::<QueueMetadata>();
capacity
.checked_mul(mem::size_of::<T>())
.and_then(|data_size| meta_size.checked_add(data_size))
.ok_or(IpcError::InvalidArgument)
}
impl<T: bytemuck::Pod> SharedQueue<T> {
pub fn create(name: &str, capacity: usize) -> Result<Self, IpcError> {
let meta_size = mem::size_of::<QueueMetadata>();
let total_size = layout_for::<T>(capacity)?;
let memory = SharedMemory::create(name, total_size)?;
unsafe {
#[allow(clippy::cast_ptr_alignment)]
let meta = memory.ptr as *mut QueueMetadata;
(*meta).head = AtomicUsize::new(0);
(*meta).tail = AtomicUsize::new(0);
(*meta).capacity = AtomicUsize::new(capacity);
(*meta).closed = AtomicBool::new(false);
let buffer = memory.ptr.add(meta_size) as *mut T;
Ok(Self {
memory,
meta,
buffer,
capacity,
})
}
}
pub fn open(name: &str, capacity: usize) -> Result<Self, IpcError> {
let meta_size = mem::size_of::<QueueMetadata>();
let total_size = layout_for::<T>(capacity)?;
let memory = SharedMemory::open(name, total_size)?;
unsafe {
#[allow(clippy::cast_ptr_alignment)]
let meta = memory.ptr as *mut QueueMetadata;
if (*meta).capacity.load(Ordering::Acquire) != capacity {
return Err(IpcError::InvalidArgument);
}
let buffer = memory.ptr.add(meta_size) as *mut T;
Ok(Self {
memory,
meta,
buffer,
capacity,
})
}
}
pub fn send(&mut self, value: T) -> Result<(), T> {
unsafe {
if (*self.meta).closed.load(Ordering::Relaxed) {
return Err(value);
}
let head = (*self.meta).head.load(Ordering::Relaxed);
let tail = (*self.meta).tail.load(Ordering::Acquire);
if head.wrapping_sub(tail) >= self.capacity {
return Err(value);
}
core::ptr::write(self.buffer.add(head % self.capacity), value);
(*self.meta)
.head
.store(head.wrapping_add(1), Ordering::Release);
Ok(())
}
}
pub fn recv(&mut self) -> Option<T> {
unsafe {
let tail = (*self.meta).tail.load(Ordering::Relaxed);
let head = (*self.meta).head.load(Ordering::Acquire);
if tail == head {
return None;
}
let value = core::ptr::read(self.buffer.add(tail % self.capacity));
(*self.meta)
.tail
.store(tail.wrapping_add(1), Ordering::Release);
Some(value)
}
}
}