#![allow(unsafe_code)]
use crate::cell::UnsafeCell;
use crossbeam_utils::{Backoff, CachePadded};
use std::sync::Arc;
#[cfg(loom)]
use loom::sync::atomic::AtomicUsize;
#[cfg(not(loom))]
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
struct Slot<T> {
stamp: AtomicUsize,
value: UnsafeCell<Option<T>>,
}
struct Inner<T> {
tail: CachePadded<AtomicUsize>,
slots: Box<[Slot<T>]>,
capacity: usize,
one_lap: usize,
}
unsafe impl<T: Send> Send for Inner<T> {}
unsafe impl<T: Send> Sync for Inner<T> {}
impl<T> Inner<T> {
#[inline]
fn index(&self, position: usize) -> usize {
position & (self.one_lap - 1)
}
#[inline]
fn advance(&self, position: usize) -> usize {
if self.index(position) + 1 < self.capacity {
position + 1
} else {
(position & !(self.one_lap - 1)).wrapping_add(self.one_lap)
}
}
}
const MAX_CAPACITY: usize = usize::MAX >> 1;
pub fn bounded<T>(capacity: usize) -> (Producer<T>, Consumer<T>) {
assert!(capacity > 0, "a ring needs capacity");
assert!(capacity <= MAX_CAPACITY, "a ring holds at most {MAX_CAPACITY} items");
let one_lap = (capacity + 1).next_power_of_two();
let inner = Arc::new(Inner {
tail: CachePadded::new(AtomicUsize::new(0)),
slots: (0..capacity)
.map(|index| Slot { stamp: AtomicUsize::new(index), value: UnsafeCell::new(None) })
.collect(),
capacity,
one_lap,
});
(Producer { inner: Arc::clone(&inner) }, Consumer { inner, head: 0 })
}
pub struct Producer<T> {
inner: Arc<Inner<T>>,
}
impl<T> Clone for Producer<T> {
fn clone(&self) -> Self {
Self { inner: Arc::clone(&self.inner) }
}
}
impl<T> Producer<T> {
#[inline]
pub fn capacity(&self) -> usize {
self.inner.capacity
}
pub fn try_push(&self, value: T) -> Result<(), T> {
let inner = &*self.inner;
let mut tail = inner.tail.load(Relaxed);
let backoff = Backoff::new();
loop {
let index = inner.index(tail);
debug_assert!(index < inner.slots.len(), "position outside the ring: {tail}");
let slot = unsafe { inner.slots.get_unchecked(index) };
let stamp = slot.stamp.load(Acquire);
if stamp == tail {
match inner.tail.compare_exchange_weak(tail, inner.advance(tail), Acquire, Relaxed)
{
Ok(_) => {
unsafe { slot.value.with_mut(|cell| cell.write(Some(value))) };
slot.stamp.store(tail.wrapping_add(1), Release);
return Ok(());
}
Err(current) => {
tail = current;
backoff.spin();
continue;
}
}
}
if stamp.wrapping_add(inner.one_lap) == tail.wrapping_add(1) {
return Err(value);
}
let current = inner.tail.load(Relaxed);
if current == tail {
return Err(value);
}
tail = current;
backoff.spin();
}
}
}
impl<T> std::fmt::Debug for Producer<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Producer").field("capacity", &self.capacity()).finish_non_exhaustive()
}
}
pub struct Consumer<T> {
inner: Arc<Inner<T>>,
head: usize,
}
impl<T> Consumer<T> {
#[inline]
pub fn capacity(&self) -> usize {
self.inner.capacity
}
pub fn pop(&mut self) -> Option<T> {
let inner = &*self.inner;
let index = inner.index(self.head);
debug_assert!(index < inner.slots.len(), "position outside the ring: {}", self.head);
let slot = unsafe { inner.slots.get_unchecked(index) };
if slot.stamp.load(Acquire) != self.head.wrapping_add(1) {
return None;
}
let value = unsafe { slot.value.with_mut(|cell| (*cell).take()) };
debug_assert!(value.is_some(), "a published slot always holds a value");
slot.stamp.store(self.head.wrapping_add(inner.one_lap), Release);
self.head = inner.advance(self.head);
value
}
#[inline]
pub fn is_empty(&self) -> bool {
let inner = &*self.inner;
let index = inner.index(self.head);
debug_assert!(index < inner.slots.len(), "position outside the ring: {}", self.head);
let slot = unsafe { inner.slots.get_unchecked(index) };
slot.stamp.load(Acquire) != self.head.wrapping_add(1)
}
}
impl<T> std::fmt::Debug for Consumer<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Consumer").field("capacity", &self.capacity()).finish_non_exhaustive()
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use std::collections::VecDeque;
use std::sync::atomic::AtomicUsize;
#[test]
fn values_come_out_in_the_order_one_producer_put_them_in() {
let (producer, mut consumer) = bounded(4);
for value in 0..4 {
producer.try_push(value).unwrap();
}
assert_eq!(
(0..4).map(|_| consumer.pop()).collect::<Vec<_>>(),
[Some(0), Some(1), Some(2), Some(3)]
);
assert_eq!(consumer.pop(), None);
}
#[test]
fn a_full_ring_hands_the_value_back_rather_than_dropping_it() {
let (producer, mut consumer) = bounded(2);
producer.try_push(1).unwrap();
producer.try_push(2).unwrap();
assert_eq!(producer.try_push(3), Err(3), "the caller gets its value back");
assert_eq!(consumer.pop(), Some(1));
producer.try_push(3).unwrap();
assert_eq!(consumer.pop(), Some(2));
assert_eq!(consumer.pop(), Some(3));
assert_eq!(consumer.pop(), None);
}
#[test]
fn capacity_is_exactly_what_was_asked_for_even_when_it_is_not_a_power_of_two() {
for capacity in 1..=9 {
let (producer, mut consumer) = bounded(capacity);
assert_eq!(producer.capacity(), capacity);
assert_eq!(consumer.capacity(), capacity);
for value in 0..capacity {
producer.try_push(value).unwrap_or_else(|_| panic!("{value} fits in {capacity}"));
}
assert_eq!(producer.try_push(usize::MAX), Err(usize::MAX), "capacity {capacity}");
for value in 0..capacity {
assert_eq!(consumer.pop(), Some(value));
}
assert_eq!(consumer.pop(), None);
}
}
#[test]
fn positions_wrap_through_many_laps_without_losing_their_ordering() {
let (producer, mut consumer) = bounded(3);
for round in 0..10_000 {
producer.try_push(round).unwrap();
producer.try_push(round + 1).unwrap();
assert_eq!(consumer.pop(), Some(round));
assert_eq!(consumer.pop(), Some(round + 1));
assert!(consumer.is_empty());
}
}
#[test]
fn an_empty_ring_reports_itself_empty_and_a_filled_one_does_not() {
let (producer, mut consumer) = bounded(2);
assert!(consumer.is_empty());
producer.try_push(1).unwrap();
assert!(!consumer.is_empty());
assert_eq!(consumer.pop(), Some(1));
assert!(consumer.is_empty());
}
struct Tracked<'a>(&'a AtomicUsize);
impl Drop for Tracked<'_> {
fn drop(&mut self) {
self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
#[test]
fn dropping_the_ring_drops_what_was_still_in_it_exactly_once() {
let drops = AtomicUsize::new(0);
{
let (producer, mut consumer) = bounded(4);
for _ in 0..4 {
assert!(producer.try_push(Tracked(&drops)).is_ok());
}
drop(consumer.pop().expect("a value was queued"));
assert_eq!(drops.load(std::sync::atomic::Ordering::Relaxed), 1);
}
assert_eq!(
drops.load(std::sync::atomic::Ordering::Relaxed),
4,
"values left in a dropped ring must be dropped, and only once"
);
}
#[test]
fn a_ring_outlives_the_handle_that_was_dropped_first() {
let (producer, mut consumer) = bounded(2);
producer.try_push(7).unwrap();
drop(producer);
assert_eq!(consumer.pop(), Some(7), "the queued value survives its producer");
let (producer, consumer) = bounded(2);
producer.try_push(8).unwrap();
drop(consumer);
assert_eq!(producer.try_push(9), Ok(()), "a departed consumer is not this layer's concern");
}
#[test]
#[should_panic(expected = "a ring needs capacity")]
fn a_zero_capacity_ring_is_refused() {
let _ = bounded::<u8>(0);
}
#[test]
#[should_panic(expected = "a ring holds at most")]
fn a_capacity_the_lap_encoding_cannot_address_is_refused() {
let _ = bounded::<u8>(MAX_CAPACITY + 1);
}
#[test]
fn concurrent_producers_lose_nothing_and_keep_their_own_order() {
let (producers, per_producer, capacity) =
if cfg!(miri) { (2, 16, 4) } else { (4, 4_000, 8) };
let total = producers * per_producer;
let (producer, mut consumer) = bounded::<(usize, usize)>(capacity);
let threads: Vec<_> = (0..producers)
.map(|id| {
let producer = producer.clone();
std::thread::spawn(move || {
for sequence in 0..per_producer {
let mut value = (id, sequence);
while let Err(returned) = producer.try_push(value) {
value = returned;
std::thread::yield_now();
}
}
})
})
.collect();
let mut seen: Vec<VecDeque<usize>> = (0..producers).map(|_| VecDeque::new()).collect();
let mut taken = 0;
while taken < total {
match consumer.pop() {
Some((id, sequence)) => {
seen[id].push_back(sequence);
taken += 1;
}
None => std::thread::yield_now(),
}
}
for thread in threads {
thread.join().unwrap();
}
assert_eq!(consumer.pop(), None, "everything pushed was accounted for");
for (id, sequences) in seen.iter().enumerate() {
assert_eq!(sequences.len(), per_producer, "producer {id} lost or duplicated values");
assert!(
sequences.iter().copied().eq(0..per_producer),
"producer {id}'s values were reordered"
);
}
}
}
#[cfg(all(test, loom))]
mod loom_tests {
use super::*;
#[test]
fn loom_concurrent_producers_and_a_consumer_lose_no_value() {
loom::model(|| {
let (producer, mut consumer) = bounded(2);
let left = {
let producer = producer.clone();
loom::thread::spawn(move || producer.try_push(1).is_ok())
};
let right = {
let producer = producer.clone();
loom::thread::spawn(move || producer.try_push(2).is_ok())
};
let mut taken = Vec::new();
for _ in 0..2 {
if let Some(value) = consumer.pop() {
taken.push(value);
}
}
assert!(left.join().unwrap(), "a ring with two free slots refused a push");
assert!(right.join().unwrap(), "a ring with two free slots refused a push");
while let Some(value) = consumer.pop() {
taken.push(value);
}
taken.sort_unstable();
assert_eq!(taken, [1, 2], "a value was lost or duplicated");
});
}
#[test]
fn loom_a_full_ring_admits_exactly_one_of_two_racing_producers() {
loom::model(|| {
let (producer, mut consumer) = bounded(1);
let left = {
let producer = producer.clone();
loom::thread::spawn(move || producer.try_push(1))
};
let right = {
let producer = producer.clone();
loom::thread::spawn(move || producer.try_push(2))
};
let left = left.join().unwrap();
let right = right.join().unwrap();
let admitted = usize::from(left.is_ok()) + usize::from(right.is_ok());
assert_eq!(admitted, 1, "a one-slot ring admitted {admitted} values");
let queued = consumer.pop().expect("the admitted value is queued");
let handed_back = left.err().or(right.err()).expect("the loser gets its value back");
assert_ne!(queued, handed_back, "the same value was both queued and rejected");
assert_eq!(consumer.pop(), None);
});
}
#[test]
fn loom_a_slot_released_by_the_consumer_is_safely_reclaimed() {
loom::model(|| {
let (producer, mut consumer) = bounded(1);
producer.try_push(1).expect("an empty ring accepts a value");
let refill = {
let producer = producer.clone();
loom::thread::spawn(move || producer.try_push(2).is_ok())
};
let first = consumer.pop();
let refilled = refill.join().unwrap();
assert_eq!(first, Some(1), "the queued value was overwritten or skipped");
let second = consumer.pop();
if refilled {
assert_eq!(second, Some(2), "an accepted value never arrived");
} else {
assert_eq!(second, None, "a rejected value arrived anyway");
}
});
}
}