use std::cell::Cell;
use std::sync::atomic::{AtomicUsize, Ordering, fence};
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use tokio::sync::mpsc::{self, error::TrySendError};
thread_local! {
static CONSUMER: Cell<bool> = const { Cell::new(false) };
}
pub(super) fn mark_consumer() {
CONSUMER.with(|c| c.set(true));
}
#[derive(Default)]
struct Room {
waiting: AtomicUsize,
lock: Mutex<()>,
freed: Condvar,
}
impl Room {
fn wake(&self) {
fence(Ordering::SeqCst);
if self.waiting.load(Ordering::SeqCst) > 0 {
let _guard = self.lock.lock().unwrap_or_else(|e| e.into_inner());
self.freed.notify_all();
}
}
}
pub(super) struct Sender<T> {
tx: mpsc::Sender<T>,
room: Arc<Room>,
}
pub(super) struct Receiver<T> {
rx: mpsc::Receiver<T>,
room: Arc<Room>,
}
pub(super) fn bounded<T>(capacity: usize) -> (Sender<T>, Receiver<T>) {
let (tx, rx) = mpsc::channel(capacity);
let room = Arc::new(Room::default());
(
Sender {
tx,
room: room.clone(),
},
Receiver { rx, room },
)
}
impl<T> Sender<T> {
pub(super) fn send(&self, value: T) -> Result<(), T> {
match self.tx.try_send(value) {
Ok(()) => Ok(()),
Err(TrySendError::Closed(value)) => Err(value),
Err(TrySendError::Full(value)) if CONSUMER.with(Cell::get) => Err(value),
Err(TrySendError::Full(value)) => self.wait(value),
}
}
#[cold]
fn wait(&self, mut value: T) -> Result<(), T> {
self.room.waiting.fetch_add(1, Ordering::SeqCst);
let mut guard = self.room.lock.lock().unwrap_or_else(|e| e.into_inner());
let result = loop {
fence(Ordering::SeqCst);
match self.tx.try_send(value) {
Ok(()) => break Ok(()),
Err(TrySendError::Closed(v)) => break Err(v),
Err(TrySendError::Full(v)) => value = v,
}
guard = self
.room
.freed
.wait_timeout(guard, Duration::from_millis(1))
.unwrap_or_else(|e| e.into_inner())
.0;
};
drop(guard);
self.room.waiting.fetch_sub(1, Ordering::SeqCst);
result
}
pub(super) async fn send_async(&self, value: T) -> Result<(), T> {
self.tx.send(value).await.map_err(|e| e.0)
}
pub(super) fn is_closed(&self) -> bool {
self.tx.is_closed()
}
pub(super) fn is_empty(&self) -> bool {
self.tx.capacity() == self.tx.max_capacity()
}
}
impl<T> Receiver<T> {
pub(super) async fn recv(&mut self) -> Option<T> {
let value = self.rx.recv().await;
self.room.wake();
value
}
pub(super) fn is_empty(&self) -> bool {
self.rx.is_empty()
}
pub(super) fn try_recv(&mut self) -> Option<T> {
let value = self.rx.try_recv().ok();
if value.is_some() {
self.room.wake();
}
value
}
pub(super) fn close(&mut self) {
self.rx.close();
self.room.wake();
}
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
self.close();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicBool;
use std::time::Instant;
#[test]
fn a_burst_waits_for_room_and_arrives_whole_and_in_order() {
const CAPACITY: usize = 8;
const PRODUCERS: u64 = 6;
const EACH: u64 = 500;
let (tx, mut rx) = bounded::<(u64, u64)>(CAPACITY);
let tx = Arc::new(tx);
let sent = Arc::new(AtomicUsize::new(0));
let producers: Vec<_> = (0..PRODUCERS)
.map(|p| {
let tx = tx.clone();
let sent = sent.clone();
std::thread::spawn(move || {
for i in 0..EACH {
assert!(tx.send((p, i)).is_ok());
sent.fetch_add(1, Ordering::SeqCst);
}
})
})
.collect();
let deadline = Instant::now() + Duration::from_secs(10);
while sent.load(Ordering::SeqCst) < CAPACITY {
assert!(
Instant::now() < deadline,
"producers never filled the queue"
);
std::thread::sleep(Duration::from_millis(1));
}
std::thread::sleep(Duration::from_millis(50));
assert_eq!(sent.load(Ordering::SeqCst), CAPACITY);
let mut next = vec![0; PRODUCERS as usize];
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
runtime.block_on(async {
for _ in 0..PRODUCERS * EACH {
let (p, i) = if let Some(v) = rx.try_recv() {
v
} else {
rx.recv().await.unwrap()
};
assert_eq!(next[p as usize], i, "producer {p} reordered");
next[p as usize] += 1;
}
});
for p in producers {
p.join().unwrap();
}
assert!(next.iter().all(|&n| n == EACH));
assert!(rx.try_recv().is_none());
}
#[test]
fn stopping_the_consumer_releases_blocked_producers() {
let (tx, rx) = bounded::<u32>(1);
tx.send(0).unwrap();
let blocked = Arc::new(AtomicBool::new(true));
let producer = std::thread::spawn({
let blocked = blocked.clone();
move || {
let result = tx.send(1);
blocked.store(false, Ordering::SeqCst);
result
}
});
std::thread::sleep(Duration::from_millis(30));
assert!(blocked.load(Ordering::SeqCst));
drop(rx);
assert_eq!(producer.join().unwrap(), Err(1));
}
#[test]
fn the_protocol_thread_never_waits_for_its_own_queue() {
let (tx, mut rx) = bounded::<u32>(1);
std::thread::spawn(move || {
mark_consumer();
tx.send(0).unwrap();
let started = Instant::now();
assert_eq!(tx.send(1), Err(1));
assert!(started.elapsed() < Duration::from_millis(500));
})
.join()
.unwrap();
assert_eq!(rx.try_recv(), Some(0));
}
}