use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr;
use kovan::CachePadded;
use crate::sync::internal::{AtomicPtr, AtomicU64, Ordering};
const MAX_SEGMENT: u64 = 32;
pub(super) const MAX_CELLS: u64 = 1 << 16;
struct Cell<T> {
stamp: AtomicU64,
value: UnsafeCell<MaybeUninit<T>>,
}
const CHUNK: usize = 16;
#[repr(align(128))]
struct Chunk<T>([AtomicPtr<Cell<T>>; CHUNK]);
struct Head<T> {
pos: AtomicU64,
last: UnsafeCell<(u64, *mut Cell<T>)>,
}
pub(in crate::sync::mpsc) struct Ring<T> {
tail: CachePadded<AtomicU64>,
head: CachePadded<Head<T>>,
segments: Box<[Chunk<T>]>,
cell_bits: u32,
cell_mask: u64,
segment_mask: u64,
_values: PhantomData<T>,
}
unsafe impl<T: Send> Send for Ring<T> {}
unsafe impl<T: Send> Sync for Ring<T> {}
impl<T> Ring<T> {
pub(in crate::sync::mpsc) fn new(capacity: usize) -> Self {
let cells = (capacity as u64).max(1).next_power_of_two();
debug_assert!(cells <= MAX_CELLS, "a ring holds at most MAX_CELLS cells");
let per_segment = cells.min(MAX_SEGMENT);
let segments = (cells / per_segment) as usize;
Self {
tail: CachePadded::new(AtomicU64::new(0)),
head: CachePadded::new(Head {
pos: AtomicU64::new(0),
last: UnsafeCell::new((u64::MAX, ptr::null_mut())),
}),
segments: (0..segments.div_ceil(CHUNK))
.map(|_| Chunk(core::array::from_fn(|_| AtomicPtr::new(ptr::null_mut()))))
.collect(),
cell_bits: per_segment.trailing_zeros(),
cell_mask: per_segment - 1,
segment_mask: segments as u64 - 1,
_values: PhantomData,
}
}
fn per_segment(&self) -> usize {
1 << self.cell_bits
}
fn slot(&self, number: u64) -> &AtomicPtr<Cell<T>> {
let number = number as usize;
&self.segments[number / CHUNK].0[number % CHUNK]
}
fn cell(&self, pos: u64, make: bool) -> *const Cell<T> {
let slot = self.slot((pos >> self.cell_bits) & self.segment_mask);
let mut segment = slot.load(Ordering::SeqCst);
if segment.is_null() {
if !make {
return ptr::null();
}
segment = self.make_segment(slot);
}
unsafe { segment.add((pos & self.cell_mask) as usize) }
}
#[cold]
#[inline(never)]
fn make_segment(&self, slot: &AtomicPtr<Cell<T>>) -> *mut Cell<T> {
let fresh = Self::alloc_segment(self.per_segment());
match slot.compare_exchange(ptr::null_mut(), fresh, Ordering::SeqCst, Ordering::SeqCst) {
Ok(_) => fresh,
Err(made) => {
unsafe { Self::free_segment(fresh, self.per_segment()) };
made
}
}
}
fn alloc_segment(cells: usize) -> *mut Cell<T> {
let segment: Box<[Cell<T>]> = (0..cells)
.map(|_| Cell {
stamp: AtomicU64::new(0),
value: UnsafeCell::new(MaybeUninit::uninit()),
})
.collect();
Box::into_raw(segment).cast::<Cell<T>>()
}
unsafe fn free_segment(segment: *mut Cell<T>, cells: usize) {
drop(unsafe { Box::from_raw(ptr::slice_from_raw_parts_mut(segment, cells)) });
}
#[inline]
pub(in crate::sync::mpsc) fn push(&self, value: T) {
let pos = self.tail.fetch_add(1, Ordering::AcqRel);
let cell = unsafe { &*self.cell(pos, true) };
unsafe { cell.value.get().write(MaybeUninit::new(value)) };
cell.stamp.store(pos + 1, Ordering::SeqCst);
}
#[inline]
pub(in crate::sync::mpsc) unsafe fn try_pop(&self) -> Option<T> {
let pos = self.head.pos.load(Ordering::Relaxed);
let number = (pos >> self.cell_bits) & self.segment_mask;
let last = unsafe { &mut *self.head.last.get() };
if last.0 != number {
let segment = self.slot(number).load(Ordering::SeqCst);
if segment.is_null() {
return None;
}
*last = (number, segment);
}
let cell = unsafe { &*last.1.add((pos & self.cell_mask) as usize) };
if cell.stamp.load(Ordering::SeqCst) != pos + 1 {
return None;
}
let value = unsafe { cell.value.get().read().assume_init() };
self.head.pos.store(pos + 1, Ordering::Relaxed);
Some(value)
}
pub(in crate::sync::mpsc) fn len(&self) -> usize {
let front = self.head.pos.load(Ordering::Acquire);
let back = self.tail.load(Ordering::Acquire);
usize::try_from(back.saturating_sub(front)).unwrap_or(usize::MAX)
}
}
impl<T> Drop for Ring<T> {
fn drop(&mut self) {
let head = *self.head.pos.get_mut();
let tail = *self.tail.get_mut();
for pos in head..tail {
let cell = self.cell(pos, false).cast_mut();
if cell.is_null() {
continue;
}
let cell = unsafe { &mut *cell };
if *cell.stamp.get_mut() == pos + 1 {
unsafe { cell.value.get_mut().assume_init_drop() };
}
}
let cells = self.per_segment();
for slot in self
.segments
.iter_mut()
.flat_map(|chunk| chunk.0.iter_mut())
{
let segment = *slot.get_mut();
if !segment.is_null() {
unsafe { Self::free_segment(segment, cells) };
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
fn pop<T>(ring: &Ring<T>) -> Option<T> {
unsafe { ring.try_pop() }
}
#[test]
fn a_ring_is_a_power_of_two_in_segments_of_at_most_32() {
for (capacity, cells, segments) in [
(1, 1, 1),
(3, 4, 1),
(32, 32, 1),
(33, 32, 2),
(1000, 32, 32),
] {
let ring = Ring::<u8>::new(capacity);
assert_eq!(ring.per_segment(), cells, "capacity {capacity}");
assert_eq!(ring.segment_mask + 1, segments, "capacity {capacity}");
}
}
#[test]
fn values_come_out_in_order_lap_after_lap() {
let ring = Ring::new(5);
let mut next = 0u64;
for lap in 0..40u64 {
let n = 1 + lap % 5;
for i in 0..n {
ring.push(next + i);
}
for i in 0..n {
assert_eq!(pop(&ring), Some(next + i));
}
assert_eq!(
pop(&ring),
None,
"a stamp of an earlier lap is not this one's"
);
next += n;
}
assert_eq!(ring.len(), 0);
}
#[test]
fn a_value_left_in_the_ring_is_dropped_once_with_it() {
struct Counted(Arc<AtomicUsize>);
impl Drop for Counted {
fn drop(&mut self) {
self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
let drops = Arc::new(AtomicUsize::new(0));
let ring = Ring::new(64);
for _ in 0..50 {
ring.push(Counted(Arc::clone(&drops)));
}
for _ in 0..20 {
assert!(pop(&ring).is_some());
}
for _ in 0..30 {
ring.push(Counted(Arc::clone(&drops)));
}
assert_eq!(drops.load(std::sync::atomic::Ordering::SeqCst), 20);
drop(ring);
assert_eq!(drops.load(std::sync::atomic::Ordering::SeqCst), 80);
}
#[test]
fn values_that_borrow_go_through() {
let words = [String::from("a"), String::from("b")];
let ring = Ring::new(8);
for _ in 0..20 {
ring.push(&words[0]);
ring.push(&words[1]);
assert_eq!(pop(&ring).map(String::as_str), Some("a"));
assert_eq!(pop(&ring).map(String::as_str), Some("b"));
}
}
#[test]
fn concurrent_pushes_within_the_capacity_each_arrive_once_and_in_order() {
const PUSHERS: u64 = 4;
const CAPACITY: usize = 6;
let each: u64 = if cfg!(miri) { 40 } else { 20_000 };
let ring = Arc::new(Ring::<(u64, u64)>::new(CAPACITY));
let free = Arc::new(AtomicUsize::new(CAPACITY));
let pushers: Vec<_> = (0..PUSHERS)
.map(|p| {
let (ring, free) = (Arc::clone(&ring), Arc::clone(&free));
std::thread::spawn(move || {
for i in 0..each {
loop {
let n = free.load(std::sync::atomic::Ordering::Acquire);
if n > 0
&& free
.compare_exchange(
n,
n - 1,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
)
.is_ok()
{
break;
}
std::hint::spin_loop();
}
ring.push((p, i));
}
})
})
.collect();
let mut next = [0u64; PUSHERS as usize];
let mut got = 0;
while got < PUSHERS * each {
match pop(&ring) {
Some((p, i)) => {
assert_eq!(i, next[p as usize], "pusher {p} out of order");
next[p as usize] += 1;
got += 1;
free.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
}
None => std::hint::spin_loop(),
}
}
for p in pushers {
p.join().unwrap();
}
assert_eq!(pop(&ring), None);
}
}