use crate::channel::error::{Channel, ChannelError, Result};
use crate::communication::RingBuffer;
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, Ordering};
const SPSC_BLOCK_SPINS: usize = 6;
pub(crate) struct SpscChannel<T> {
ring: RingBuffer<T>,
pub(super) closed: AtomicBool,
}
unsafe impl<T: Send> Send for SpscChannel<T> {}
unsafe impl<T: Send> Sync for SpscChannel<T> {}
impl<T> SpscChannel<T> {
pub fn new(capacity: usize) -> Self {
Self {
ring: RingBuffer::new(capacity.next_power_of_two().max(2)),
closed: AtomicBool::new(false),
}
}
pub(super) fn indices(&self) -> (usize, usize) {
self.ring.indices()
}
}
#[inline]
fn back_off(spin: &mut usize) {
if *spin < SPSC_BLOCK_SPINS {
for _ in 0..(1 << *spin) {
std::hint::spin_loop();
}
*spin += 1;
} else {
std::thread::yield_now();
}
}
pub(super) fn blocking<F, R>(mut attempt: F) -> Result<R>
where
F: FnMut() -> Result<R>,
{
let mut spin = 0;
loop {
match attempt() {
Err(ChannelError::Full | ChannelError::Empty) => back_off(&mut spin),
other => return other,
}
}
}
impl<T: Send> SpscChannel<T> {
pub(super) fn try_send_cached(&self, value: T, cached_tail: &Cell<usize>) -> Result<()> {
if self.closed.load(Ordering::Acquire) {
return Err(ChannelError::Closed);
}
let head = self.ring.producer_relaxed();
if !self.ring.has_room(head, cached_tail) {
return Err(ChannelError::Full);
}
unsafe { self.ring.produce_at(head, value) };
Ok(())
}
pub(super) fn try_recv_cached(&self, cached_head: &Cell<usize>) -> Result<T> {
let tail = self.ring.consumer_relaxed();
if !self.ring.has_value(tail, cached_head) {
if self.closed.load(Ordering::Acquire) {
let published = self.ring.producer_acquire();
cached_head.set(published);
if tail == published {
return Err(ChannelError::Closed);
}
} else {
return Err(ChannelError::Empty);
}
}
Ok(unsafe { self.ring.consume_at(tail) })
}
pub(super) fn send_cached(&self, value: T, cached_tail: &Cell<usize>) -> Result<()> {
let mut spin = 0;
loop {
if self.closed.load(Ordering::Acquire) {
return Err(ChannelError::Closed);
}
let head = self.ring.producer_relaxed();
if self.ring.has_room(head, cached_tail) {
unsafe { self.ring.produce_at(head, value) };
return Ok(());
}
back_off(&mut spin);
}
}
}
impl<T: Send> Channel<T> for SpscChannel<T> {
fn send(&self, value: T) -> Result<()> {
let cached_tail = Cell::new(self.ring.consumer_acquire());
self.send_cached(value, &cached_tail)
}
fn try_send(&self, value: T) -> Result<()> {
let cached_tail = Cell::new(self.ring.consumer_acquire());
self.try_send_cached(value, &cached_tail)
}
fn recv(&self) -> Result<T> {
let cached_head = Cell::new(self.ring.producer_acquire());
blocking(|| self.try_recv_cached(&cached_head))
}
fn try_recv(&self) -> Result<T> {
let cached_head = Cell::new(self.ring.producer_acquire());
self.try_recv_cached(&cached_head)
}
fn is_empty(&self) -> bool {
self.ring.is_empty()
}
fn is_full(&self) -> bool {
self.ring.is_full()
}
fn capacity(&self) -> Option<usize> {
Some(self.ring.capacity())
}
}