use std::future::Future;
use std::sync::atomic::{AtomicUsize as StdAtomicUsize, Ordering as StdOrdering};
use std::task::{Context, Poll};
use loom::sync::Notify as Park;
use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use loom::thread;
use crate::sync::atomic_waker::AtomicWaker;
use crate::sync::loom_model::{fair_drop_pass_on, mpsc_busy_looks};
use crate::sync::mpsc::{
self,
error::{SendError, TrySendError},
};
fn explore(
name: &str,
preemptions: usize,
min_interleavings: usize,
model: impl Fn() + Sync + Send + 'static,
) {
let runs = std::sync::Arc::new(StdAtomicUsize::new(0));
let counted = {
let runs = std::sync::Arc::clone(&runs);
move || {
model();
runs.fetch_add(1, StdOrdering::Relaxed);
}
};
let mut builder = loom::model::Builder::new();
builder.preemption_bound = builder.preemption_bound.or(Some(preemptions));
builder.check(counted);
let explored = runs.load(StdOrdering::Relaxed);
println!("loom model {name}: {explored} interleavings explored");
assert!(
explored >= min_interleavings,
"{name} explored only {explored} interleavings, below the {min_interleavings} floor"
);
}
fn explore_busy(
name: &str,
preemptions: usize,
min_interleavings: usize,
model: impl Fn() + Sync + Send + 'static,
) {
mpsc_busy_looks();
explore(name, preemptions, min_interleavings, model);
let busy = mpsc_busy_looks();
println!("loom model {name}: {busy} looks found the next value still being written");
assert!(
busy > 0,
"{name}: the search never found a value still being written"
);
}
struct Unpark(Park);
impl std::task::Wake for Unpark {
fn wake(self: std::sync::Arc<Self>) {
self.0.notify();
}
fn wake_by_ref(self: &std::sync::Arc<Self>) {
self.0.notify();
}
}
fn block_on<F: Future>(future: F) -> F::Output {
let park = std::sync::Arc::new(Unpark(Park::new()));
let waker = std::task::Waker::from(std::sync::Arc::clone(&park));
let mut cx = Context::from_waker(&waker);
let mut future = std::pin::pin!(future);
loop {
if let Poll::Ready(output) = future.as_mut().poll(&mut cx) {
return output;
}
thread::yield_now();
park.0.wait();
}
}
struct Counted(std::sync::Arc<StdAtomicUsize>);
impl Drop for Counted {
fn drop(&mut self) {
self.0.fetch_add(1, StdOrdering::SeqCst);
}
}
struct Drops(std::sync::Arc<StdAtomicUsize>);
impl Drops {
fn new() -> Self {
Self(std::sync::Arc::new(StdAtomicUsize::new(0)))
}
fn value(&self) -> Counted {
Counted(std::sync::Arc::clone(&self.0))
}
fn count(&self) -> usize {
self.0.load(StdOrdering::SeqCst)
}
}
#[test]
fn a_bounded_send_racing_the_receivers_wait_is_delivered_once() {
explore_busy(
"a_bounded_send_racing_the_receivers_wait_is_delivered_once",
3,
100,
|| {
let drops = Drops::new();
let (tx, mut rx) = mpsc::channel::<Counted>(1);
let value = drops.value();
let sender = thread::spawn(move || block_on(tx.send(value)).is_ok());
let got = block_on(rx.recv()).expect("the sender sends");
assert!(sender.join().expect("sender"));
drop(got);
assert_eq!(drops.count(), 1);
},
);
}
#[test]
fn an_unbounded_send_racing_the_receivers_wait_is_delivered_once() {
explore_busy(
"an_unbounded_send_racing_the_receivers_wait_is_delivered_once",
3,
100,
|| {
let drops = Drops::new();
let (tx, mut rx) = mpsc::unbounded_channel::<Counted>();
let value = drops.value();
let sender = thread::spawn(move || tx.send(value).is_ok());
let got = block_on(rx.recv()).expect("the sender sends");
assert!(sender.join().expect("sender"));
drop(got);
assert_eq!(drops.count(), 1);
},
);
}
#[test]
fn a_bounded_send_wakes_the_receiver_while_another_sender_lives() {
explore(
"a_bounded_send_wakes_the_receiver_while_another_sender_lives",
3,
100,
|| {
let (tx, mut rx) = mpsc::channel::<u32>(1);
let keep = tx.clone();
let sender = thread::spawn(move || block_on(tx.send(7)).is_ok());
assert_eq!(block_on(rx.recv()), Some(7));
assert!(sender.join().expect("sender"));
drop(keep);
},
);
}
#[test]
fn an_unbounded_send_wakes_the_receiver_while_another_sender_lives() {
explore(
"an_unbounded_send_wakes_the_receiver_while_another_sender_lives",
3,
100,
|| {
let (tx, mut rx) = mpsc::unbounded_channel::<u32>();
let keep = tx.clone();
let sender = thread::spawn(move || tx.send(7).is_ok());
assert_eq!(block_on(rx.recv()), Some(7));
assert!(sender.join().expect("sender"));
drop(keep);
},
);
}
#[test]
fn two_senders_racing_one_waiting_receiver_both_arrive() {
explore_busy(
"two_senders_racing_one_waiting_receiver_both_arrive",
2,
3000,
|| {
let drops = Drops::new();
let (tx, mut rx) = mpsc::unbounded_channel::<Counted>();
let tx2 = tx.clone();
let (a, b) = (drops.value(), drops.value());
let first = thread::spawn(move || tx.send(a).is_ok());
let second = thread::spawn(move || tx2.send(b).is_ok());
let one = block_on(rx.recv());
let two = block_on(rx.recv());
assert!(one.is_some() && two.is_some());
assert!(first.join().expect("first") && second.join().expect("second"));
assert_eq!(block_on(rx.recv()).map(drop), None);
drop((one, two));
assert_eq!(drops.count(), 2);
},
);
}
#[test]
fn recv_many_racing_a_send_takes_the_value_in_one_batch() {
explore(
"recv_many_racing_a_send_takes_the_value_in_one_batch",
3,
800,
|| {
let drops = Drops::new();
let (tx, mut rx) = mpsc::channel::<Counted>(2);
let (a, b) = (drops.value(), drops.value());
let sender = thread::spawn(move || {
tx.try_send(a).map_err(|_| ()).expect("room for the first");
tx.try_send(b).map_err(|_| ()).expect("room for the second");
});
let mut batch = Vec::new();
let mut total = 0;
while total < 2 {
let got = block_on(rx.recv_many(&mut batch, 2));
assert!(got >= 1, "recv_many returns zero only at the end");
total += got;
}
sender.join().expect("sender");
assert_eq!(batch.len(), 2);
drop(batch);
assert_eq!(drops.count(), 2);
},
);
}
#[test]
fn the_last_sender_dropping_racing_a_waiting_recv_ends_it() {
explore(
"the_last_sender_dropping_racing_a_waiting_recv_ends_it",
3,
500,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
let dropper = thread::spawn(move || drop(tx));
assert_eq!(block_on(rx.recv()), None);
dropper.join().expect("dropper");
let (tx, mut rx) = mpsc::unbounded_channel::<u8>();
let dropper = thread::spawn(move || drop(tx));
assert_eq!(block_on(rx.recv()), None);
dropper.join().expect("dropper");
},
);
}
#[test]
fn a_value_sent_before_the_last_sender_drops_is_received_before_the_end() {
explore(
"a_value_sent_before_the_last_sender_drops_is_received_before_the_end",
3,
250,
|| {
let drops = Drops::new();
let (tx, mut rx) = mpsc::unbounded_channel::<Counted>();
let value = drops.value();
let sender = thread::spawn(move || {
tx.send(value).map_err(|_| ()).expect("receiver is alive");
});
let got = block_on(rx.recv());
assert!(got.is_some(), "the value comes before the end");
assert!(block_on(rx.recv()).is_none());
sender.join().expect("sender");
drop(got);
assert_eq!(drops.count(), 1);
},
);
}
#[test]
fn the_last_of_two_senders_dropping_ends_the_channel_exactly_once() {
explore(
"the_last_of_two_senders_dropping_ends_the_channel_exactly_once",
3,
2000,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
let tx2 = tx.clone();
let first = thread::spawn(move || drop(tx));
let second = thread::spawn(move || drop(tx2));
assert_eq!(block_on(rx.recv()), None);
first.join().expect("first");
second.join().expect("second");
assert_eq!(rx.sender_strong_count(), 0);
},
);
}
#[test]
fn a_receiver_drop_fails_a_waiting_bounded_send_and_returns_its_value() {
explore(
"a_receiver_drop_fails_a_waiting_bounded_send_and_returns_its_value",
3,
4000,
|| {
let drops = Drops::new();
let (tx, rx) = mpsc::channel::<Counted>(1);
tx.try_send(drops.value())
.map_err(|_| ())
.expect("the first fits");
let second = drops.value();
let sender = thread::spawn(move || block_on(tx.send(second)));
drop(rx);
let SendError(back) = sender
.join()
.expect("sender")
.expect_err("the channel is full and then closed");
assert!(
drops.count() <= 1,
"the queued value is dropped by the receiver, the refused one is still held"
);
drop(back);
assert_eq!(drops.count(), 2, "both values dropped exactly once");
},
);
}
#[test]
fn an_unbounded_send_racing_the_receiver_drop_is_refused_or_dropped_with_the_channel() {
explore(
"an_unbounded_send_racing_the_receiver_drop_is_refused_or_dropped_with_the_channel",
3,
30,
|| {
let drops = Drops::new();
let (tx, rx) = mpsc::unbounded_channel::<Counted>();
let value = drops.value();
let sender = thread::spawn(move || tx.send(value).map_err(|e| e.0));
drop(rx);
let outcome = sender.join().expect("sender");
drop(outcome);
assert_eq!(drops.count(), 1);
},
);
}
#[test]
fn a_waiting_closed_wait_is_woken_by_the_receiver_drop() {
explore(
"a_waiting_closed_wait_is_woken_by_the_receiver_drop",
3,
1500,
|| {
let (tx, rx) = mpsc::channel::<u8>(1);
let waiter = thread::spawn(move || block_on(tx.closed()));
drop(rx);
waiter.join().expect("waiter");
},
);
}
#[test]
fn a_permit_dropping_wakes_a_closed_receiver_waiting_for_idle() {
explore(
"a_permit_dropping_wakes_a_closed_receiver_waiting_for_idle",
3,
100,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
let permit = tx.clone().try_reserve_owned().expect("the slot is free");
rx.close();
let dropper = thread::spawn(move || drop(permit));
assert_eq!(block_on(rx.recv()), None);
dropper.join().expect("dropper");
drop(tx);
},
);
}
#[test]
fn a_permit_sending_wakes_a_closed_receiver_which_gets_the_value() {
explore(
"a_permit_sending_wakes_a_closed_receiver_which_gets_the_value",
3,
100,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
let permit = tx.try_reserve_owned().expect("the slot is free");
rx.close();
let sender = thread::spawn(move || drop(permit.send(7)));
assert_eq!(block_on(rx.recv()), Some(7));
assert_eq!(block_on(rx.recv()), None);
sender.join().expect("sender");
},
);
}
#[test]
fn a_permit_dropping_racing_a_waiting_sender_hands_it_the_slot() {
explore(
"a_permit_dropping_racing_a_waiting_sender_hands_it_the_slot",
3,
5000,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
let permit = tx.clone().try_reserve_owned().expect("the slot is free");
let waiter = thread::spawn(move || block_on(tx.send(5)).is_ok());
drop(permit);
assert_eq!(block_on(rx.recv()), Some(5));
assert!(waiter.join().expect("waiter"));
},
);
}
#[test]
fn a_try_send_racing_close_is_received_or_refused_never_lost() {
explore(
"a_try_send_racing_close_is_received_or_refused_never_lost",
3,
150,
|| {
let drops = Drops::new();
let (tx, mut rx) = mpsc::channel::<Counted>(2);
let value = drops.value();
let sender = thread::spawn(move || tx.try_send(value));
rx.close();
let mut received = 0;
while let Some(got) = block_on(rx.recv()) {
received += 1;
drop(got);
}
match sender.join().expect("sender") {
Ok(()) => assert_eq!(received, 1, "an accepted value is received"),
Err(TrySendError::Closed(back)) => {
assert_eq!(received, 0, "a refused value is not also received");
drop(back);
}
Err(TrySendError::Full(_)) => panic!("the channel has room"),
}
assert_eq!(drops.count(), 1, "exactly once");
},
);
}
#[test]
fn an_unbounded_send_racing_close_is_received_or_refused_never_lost() {
explore(
"an_unbounded_send_racing_close_is_received_or_refused_never_lost",
3,
60,
|| {
let drops = Drops::new();
let (tx, mut rx) = mpsc::unbounded_channel::<Counted>();
let value = drops.value();
let sender = thread::spawn(move || tx.send(value).map_err(|e| e.0));
rx.close();
let mut received = 0;
while let Some(got) = block_on(rx.recv()) {
received += 1;
drop(got);
}
match sender.join().expect("sender") {
Ok(()) => assert_eq!(received, 1, "an accepted value is received"),
Err(back) => {
assert_eq!(received, 0);
drop(back);
}
}
assert_eq!(drops.count(), 1, "exactly once");
},
);
}
#[test]
fn an_unbounded_send_refused_by_the_close_wakes_a_receiver_waiting_for_idle() {
explore(
"an_unbounded_send_refused_by_the_close_wakes_a_receiver_waiting_for_idle",
3,
60,
|| {
let (tx, mut rx) = mpsc::unbounded_channel::<u8>();
let keep = tx.clone();
rx.close();
let sender = thread::spawn(move || tx.send(5).is_err());
assert_eq!(block_on(rx.recv()), None, "the closed, idle channel ends");
assert!(sender.join().expect("sender"), "refused: closed before it");
drop(keep);
},
);
}
#[test]
fn a_reservation_racing_close_sends_through_or_fails() {
explore(
"a_reservation_racing_close_sends_through_or_fails",
3,
500,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
let reserver = thread::spawn(move || match block_on(tx.reserve()) {
Ok(permit) => {
permit.send(3);
true
}
Err(_) => false,
});
rx.close();
let mut received = Vec::new();
while let Some(got) = block_on(rx.recv()) {
received.push(got);
}
let sent = reserver.join().expect("reserver");
assert_eq!(received, if sent { vec![3] } else { vec![] });
},
);
}
#[test]
fn a_waiting_reservation_racing_close_and_a_recv_ends_cleanly() {
explore(
"a_waiting_reservation_racing_close_and_a_recv_ends_cleanly",
3,
25000,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
tx.try_send(1).expect("the first fits");
let waiting = thread::spawn(move || block_on(tx.send(2)).is_ok());
assert_eq!(block_on(rx.recv()), Some(1));
rx.close();
let mut rest = Vec::new();
while let Some(got) = block_on(rx.recv()) {
rest.push(got);
}
let sent = waiting.join().expect("waiting sender");
assert_eq!(rest, if sent { vec![2] } else { vec![] });
},
);
}
#[test]
fn an_upgrade_racing_the_last_strong_drop_wins_cleanly_or_finds_the_end() {
explore(
"an_upgrade_racing_the_last_strong_drop_wins_cleanly_or_finds_the_end",
3,
50,
|| {
let (tx, mut rx) = mpsc::unbounded_channel::<u8>();
let weak = tx.downgrade();
let upgrader = thread::spawn(move || {
let upgraded = weak.upgrade();
if let Some(sender) = &upgraded {
sender.send(1).expect("receiver is alive");
}
upgraded.is_some()
});
drop(tx);
let mut got = Vec::new();
while let Some(value) = block_on(rx.recv()) {
got.push(value);
}
let upgraded = upgrader.join().expect("upgrader");
assert_eq!(got, if upgraded { vec![1] } else { vec![] });
assert_eq!(rx.sender_strong_count(), 0);
},
);
}
mod fair;
use fair::poll_once;
#[test]
#[should_panic(expected = "deadlock")]
fn calibration_a_poll_that_skips_the_recheck_loses_a_wakeup() {
explore(
"calibration_a_poll_that_skips_the_recheck_loses_a_wakeup",
3,
1,
|| {
let cell = std::sync::Arc::new(AtomicWaker::new());
let queued = std::sync::Arc::new(AtomicUsize::new(0));
let sender = {
let (cell, queued) = (std::sync::Arc::clone(&cell), std::sync::Arc::clone(&queued));
thread::spawn(move || {
queued.fetch_add(1, Ordering::AcqRel);
cell.wake();
})
};
let mut looked = false;
block_on(std::future::poll_fn(|cx| {
if !looked {
looked = true;
if queued.load(Ordering::Acquire) > 0 {
return Poll::Ready(());
}
cell.register(cx.waker());
return Poll::Pending;
}
Poll::Ready(())
}));
sender.join().expect("sender");
},
);
}
#[test]
#[should_panic(expected = "the close came first")]
fn calibration_the_close_can_win_against_a_try_send() {
explore(
"calibration_the_close_can_win_against_a_try_send",
3,
1,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
let outcome = std::sync::Arc::new(AtomicBool::new(true));
let sender = {
let outcome = std::sync::Arc::clone(&outcome);
thread::spawn(move || outcome.store(tx.try_send(1).is_ok(), Ordering::Release))
};
rx.close();
sender.join().expect("sender");
drop(rx);
assert!(outcome.load(Ordering::Acquire), "the close came first");
},
);
}
#[test]
#[should_panic(expected = "the send came first")]
fn calibration_the_try_send_can_win_against_the_close() {
explore(
"calibration_the_try_send_can_win_against_the_close",
3,
1,
|| {
let (tx, mut rx) = mpsc::channel::<u8>(1);
let outcome = std::sync::Arc::new(AtomicBool::new(false));
let sender = {
let outcome = std::sync::Arc::clone(&outcome);
thread::spawn(move || outcome.store(tx.try_send(1).is_ok(), Ordering::Release))
};
rx.close();
sender.join().expect("sender");
drop(rx);
assert!(!outcome.load(Ordering::Acquire), "the send came first");
},
);
}
#[test]
#[should_panic(expected = "the share of a dropped reservation was lost")]
fn calibration_a_share_that_is_not_handed_on_is_lost() {
explore(
"calibration_a_share_that_is_not_handed_on_is_lost",
3,
1,
|| {
fair_drop_pass_on(true);
let (tx, rx) = mpsc::channel::<u8>(2);
let held = tx
.clone()
.try_reserve_owned()
.expect("the first slot is free");
let last = tx
.clone()
.try_reserve_owned()
.expect("the second slot is free");
let mut many = Box::pin(tx.reserve_many(2));
let mut one = Box::pin(tx.reserve());
assert!(poll_once(&mut many).is_pending());
assert!(poll_once(&mut one).is_pending());
drop(held);
let releaser = thread::spawn(move || drop(last));
drop(many);
releaser.join().expect("releaser");
let served = poll_once(&mut one).is_ready();
drop(one);
let capacity = tx.capacity();
drop((tx, rx));
fair_drop_pass_on(false);
assert!(
served && capacity == 2,
"the share of a dropped reservation was lost"
);
},
);
}
#[test]
#[should_panic(expected = "the close kept a set-aside slot")]
fn calibration_a_close_that_does_not_return_the_set_aside_loses_it() {
explore(
"calibration_a_close_that_does_not_return_the_set_aside_loses_it",
3,
1,
|| {
fair_drop_pass_on(true);
let (tx, mut rx) = mpsc::channel::<u8>(2);
let held = tx
.clone()
.try_reserve_owned()
.expect("the first slot is free");
let last = tx
.clone()
.try_reserve_owned()
.expect("the second slot is free");
let mut many = Box::pin(tx.reserve_many(2));
assert!(poll_once(&mut many).is_pending());
drop(held);
let releaser = thread::spawn(move || drop(last));
rx.close();
releaser.join().expect("releaser");
drop(many);
let capacity = tx.capacity();
drop((tx, rx));
fair_drop_pass_on(false);
assert_eq!(capacity, 2, "the close kept a set-aside slot");
},
);
}