use std::future::Future;
use std::sync::atomic::{AtomicUsize as StdAtomicUsize, Ordering as StdOrdering};
use std::task::{Context, Poll};
use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering, fence};
use loom::sync::{Arc, Notify as Park};
use loom::thread;
use crate::sync::CancellationToken;
use crate::sync::atomic_waker::AtomicWaker;
use crate::sync::oneshot::{self, error::TryRecvError};
use crate::sync::watch;
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"
);
}
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 Counting(AtomicUsize);
impl std::task::Wake for Counting {
fn wake(self: std::sync::Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
fn wake_by_ref(self: &std::sync::Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
fn counting() -> (std::sync::Arc<Counting>, std::task::Waker) {
let counter = std::sync::Arc::new(Counting(AtomicUsize::new(0)));
let waker = std::task::Waker::from(std::sync::Arc::clone(&counter));
(counter, waker)
}
struct Counted(std::sync::Arc<StdAtomicUsize>);
impl Drop for Counted {
fn drop(&mut self) {
self.0.fetch_add(1, StdOrdering::SeqCst);
}
}
fn counted() -> (std::sync::Arc<StdAtomicUsize>, Counted) {
let drops = std::sync::Arc::new(StdAtomicUsize::new(0));
(std::sync::Arc::clone(&drops), Counted(drops))
}
#[test]
fn a_register_racing_a_wake_loses_no_wakeup() {
explore("a_register_racing_a_wake_loses_no_wakeup", 3, 10, || {
let cell = Arc::new(AtomicWaker::new());
let ready = Arc::new(AtomicBool::new(false));
let waking = {
let (cell, ready) = (Arc::clone(&cell), Arc::clone(&ready));
thread::spawn(move || {
ready.store(true, Ordering::Release);
cell.wake();
})
};
block_on(std::future::poll_fn(|cx| {
cell.register(cx.waker());
if ready.load(Ordering::Acquire) {
Poll::Ready(())
} else {
Poll::Pending
}
}));
waking.join().expect("waking thread");
});
}
#[test]
fn registrations_repeated_against_a_wake_lose_no_wakeup() {
explore(
"registrations_repeated_against_a_wake_lose_no_wakeup",
3,
10,
|| {
let cell = Arc::new(AtomicWaker::new());
let step = Arc::new(AtomicUsize::new(0));
let waking = {
let (cell, step) = (Arc::clone(&cell), Arc::clone(&step));
thread::spawn(move || {
step.fetch_add(1, Ordering::AcqRel);
cell.wake();
step.fetch_add(1, Ordering::AcqRel);
cell.wake();
})
};
block_on(std::future::poll_fn(|cx| {
cell.register(cx.waker());
if step.load(Ordering::Acquire) == 2 {
Poll::Ready(())
} else {
Poll::Pending
}
}));
waking.join().expect("waking thread");
},
);
}
#[test]
fn a_wake_before_the_registration_is_neither_lost_nor_doubled() {
explore(
"a_wake_before_the_registration_is_neither_lost_nor_doubled",
3,
5,
|| {
let cell = Arc::new(AtomicWaker::new());
let (counter, waker) = counting();
let waking = {
let cell = Arc::clone(&cell);
thread::spawn(move || cell.wake())
};
cell.register(&waker);
waking.join().expect("waking thread");
cell.wake();
assert_eq!(counter.0.load(Ordering::SeqCst), 1);
},
);
}
#[test]
fn a_new_registration_replaces_the_old_one_under_a_wake() {
explore(
"a_new_registration_replaces_the_old_one_under_a_wake",
3,
10,
|| {
let cell = Arc::new(AtomicWaker::new());
let (first, first_waker) = counting();
let (second, second_waker) = counting();
let waking = {
let cell = Arc::clone(&cell);
thread::spawn(move || cell.wake())
};
cell.register(&first_waker);
cell.register(&second_waker);
waking.join().expect("waking thread");
cell.wake();
assert_eq!(second.0.load(Ordering::SeqCst), 1);
assert!(first.0.load(Ordering::SeqCst) <= 1);
},
);
}
#[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 ready = std::sync::Arc::new(AtomicBool::new(false));
let waking = {
let (cell, ready) = (std::sync::Arc::clone(&cell), std::sync::Arc::clone(&ready));
thread::spawn(move || {
ready.store(true, Ordering::Release);
cell.wake();
})
};
let mut looked = false;
block_on(std::future::poll_fn(|cx| {
if !looked {
looked = true;
if ready.load(Ordering::Acquire) {
return Poll::Ready(());
}
cell.register(cx.waker());
return Poll::Pending;
}
Poll::Ready(())
}));
waking.join().expect("waking thread");
},
);
}
#[test]
fn a_send_racing_the_receivers_wait_is_delivered_once() {
explore(
"a_send_racing_the_receivers_wait_is_delivered_once",
3,
10,
|| {
let (drops, value) = counted();
let (tx, rx) = oneshot::channel();
let sender = thread::spawn(move || tx.send(value).is_ok());
let got = block_on(rx).expect("the sender sends");
assert!(sender.join().expect("sender"));
drop(got);
assert_eq!(drops.load(StdOrdering::SeqCst), 1);
},
);
}
#[test]
fn a_send_racing_the_receiver_drop_has_one_owner() {
explore(
"a_send_racing_the_receiver_drop_has_one_owner",
3,
10,
|| {
let (drops, value) = counted();
let (tx, rx) = oneshot::channel();
let sender = thread::spawn(move || tx.send(value).err());
drop(rx);
let back = sender.join().expect("sender");
let returned = usize::from(back.is_some());
assert_eq!(drops.load(StdOrdering::SeqCst), 1 - returned);
drop(back);
assert_eq!(drops.load(StdOrdering::SeqCst), 1);
},
);
}
#[test]
fn a_send_racing_a_close_then_try_recv_has_one_owner() {
explore(
"a_send_racing_a_close_then_try_recv_has_one_owner",
3,
10,
|| {
let (drops, value) = counted();
let (tx, mut rx) = oneshot::channel();
let sender = thread::spawn(move || tx.send(value).err());
rx.close();
let received = rx.try_recv();
let back = sender.join().expect("sender");
match (received, back) {
(Ok(value), None) => drop(value),
(Err(TryRecvError::Closed), Some(value)) => drop(value),
(received, back) => panic!(
"not one owner: received {}, returned {}",
received.is_ok(),
back.is_some()
),
}
drop(rx);
assert_eq!(drops.load(StdOrdering::SeqCst), 1);
},
);
}
#[test]
fn a_send_racing_a_close_then_drop_has_one_owner() {
explore(
"a_send_racing_a_close_then_drop_has_one_owner",
3,
10,
|| {
let (drops, value) = counted();
let (tx, mut rx) = oneshot::channel();
let sender = thread::spawn(move || tx.send(value).err());
rx.close();
drop(rx);
let back = sender.join().expect("sender");
assert_eq!(
drops.load(StdOrdering::SeqCst),
1 - usize::from(back.is_some())
);
drop(back);
assert_eq!(drops.load(StdOrdering::SeqCst), 1);
},
);
}
#[test]
fn a_sender_drop_racing_the_receivers_wait_wakes_it() {
explore(
"a_sender_drop_racing_the_receivers_wait_wakes_it",
3,
5,
|| {
let (tx, rx) = oneshot::channel::<u8>();
let dropper = thread::spawn(move || drop(tx));
assert!(block_on(rx).is_err());
dropper.join().expect("dropper");
},
);
}
#[test]
fn a_sender_drop_racing_a_close_leaves_nothing() {
explore("a_sender_drop_racing_a_close_leaves_nothing", 3, 2, || {
let (tx, mut rx) = oneshot::channel::<u8>();
let dropper = thread::spawn(move || drop(tx));
rx.close();
dropper.join().expect("dropper");
assert_eq!(rx.try_recv(), Err(TryRecvError::Closed));
});
}
#[test]
fn a_close_racing_the_senders_closed_wait_wakes_it() {
explore(
"a_close_racing_the_senders_closed_wait_wakes_it",
3,
10,
|| {
let (drops, value) = counted();
let (mut tx, mut rx) = oneshot::channel();
let sender = thread::spawn(move || {
block_on(tx.closed());
tx.send(value).err()
});
rx.close();
let back = sender.join().expect("sender");
assert!(back.is_some(), "the send comes after the close");
drop(back);
drop(rx);
assert_eq!(drops.load(StdOrdering::SeqCst), 1);
},
);
}
#[test]
fn a_receiver_drop_racing_the_senders_closed_wait_wakes_it() {
explore(
"a_receiver_drop_racing_the_senders_closed_wait_wakes_it",
3,
5,
|| {
let (mut tx, rx) = oneshot::channel::<u8>();
let dropper = thread::spawn(move || drop(rx));
block_on(tx.closed());
assert!(tx.is_closed());
dropper.join().expect("dropper");
},
);
}
const BIG_STACK: usize = 4 * 1024 * 1024;
fn spawn_big<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> thread::JoinHandle<T> {
thread::Builder::new()
.stack_size(BIG_STACK)
.spawn(f)
.expect("spawn a loom thread")
}
fn on_big_stack(body: impl FnOnce() + Send + 'static) {
spawn_big(body).join().expect("the body of the model");
}
#[test]
fn a_send_racing_a_changed_wait_loses_no_wakeup() {
explore(
"a_send_racing_a_changed_wait_loses_no_wakeup",
3,
10,
|| {
on_big_stack(|| {
let (tx, mut rx) = watch::channel(0u32);
let sender = spawn_big(move || {
tx.send(1).expect("the receiver is alive");
tx
});
block_on(rx.changed()).expect("the sender is alive");
assert_eq!(*rx.borrow(), 1);
drop(sender.join().expect("sender"));
});
},
);
}
#[test]
fn the_last_sender_drop_racing_a_changed_wait_wakes_it() {
explore(
"the_last_sender_drop_racing_a_changed_wait_wakes_it",
3,
5,
|| {
on_big_stack(|| {
let (tx, mut rx) = watch::channel(0u32);
let dropper = spawn_big(move || drop(tx));
block_on(rx.changed()).expect_err("the only sender is gone");
dropper.join().expect("dropper");
});
},
);
}
#[test]
fn a_send_then_a_drop_is_seen_in_that_order() {
explore("a_send_then_a_drop_is_seen_in_that_order", 2, 10, || {
on_big_stack(|| {
let (tx, mut rx) = watch::channel(0u32);
let sender = spawn_big(move || {
tx.send(1).expect("the receiver is alive");
});
block_on(rx.changed()).expect("the send comes first");
assert_eq!(*rx.borrow_and_update(), 1);
block_on(rx.changed()).expect_err("then the close");
sender.join().expect("sender");
});
});
}
fn two_receivers_model() {
on_big_stack(|| {
let (tx, mut rx1) = watch::channel(0u32);
let mut rx2 = rx1.clone();
let first = spawn_big(move || {
block_on(rx1.changed()).expect("the sender is alive");
*rx1.borrow()
});
let second = spawn_big(move || {
block_on(rx2.changed()).expect("the sender is alive");
*rx2.borrow()
});
tx.send(7).expect("receivers alive");
assert_eq!(first.join().expect("first receiver"), 7);
assert_eq!(second.join().expect("second receiver"), 7);
drop(tx);
});
}
#[test]
fn two_receivers_waiting_are_both_woken_by_a_send() {
explore(
"two_receivers_waiting_are_both_woken_by_a_send",
1,
100,
two_receivers_model,
);
}
#[test]
#[ignore = "preemption bound 2 takes over ten minutes: run it with --ignored"]
fn two_receivers_waiting_are_both_woken_by_a_send_at_bound_2() {
explore(
"two_receivers_waiting_are_both_woken_by_a_send_at_bound_2",
2,
100,
two_receivers_model,
);
}
#[test]
fn a_wait_for_racing_two_sends_ends_on_the_value_it_waits_for() {
explore(
"a_wait_for_racing_two_sends_ends_on_the_value_it_waits_for",
3,
10,
|| {
on_big_stack(|| {
let (tx, mut rx) = watch::channel(0u32);
let sender = spawn_big(move || {
tx.send(1).expect("alive");
tx.send(2).expect("alive");
tx
});
let seen = *block_on(rx.wait_for(|v| *v == 2)).expect("the sender sends 2");
assert_eq!(seen, 2);
drop(sender.join().expect("sender"));
});
},
);
}
#[test]
fn a_receiver_drop_racing_the_producers_closed_wait_wakes_it() {
explore(
"a_receiver_drop_racing_the_producers_closed_wait_wakes_it",
3,
5,
|| {
on_big_stack(|| {
let (tx, rx) = watch::channel(());
let dropper = spawn_big(move || drop(rx));
block_on(tx.closed());
assert!(tx.is_closed());
dropper.join().expect("dropper");
});
},
);
}
#[test]
fn a_subscribe_racing_the_last_receiver_drop_never_hangs_the_producer() {
explore(
"a_subscribe_racing_the_last_receiver_drop_never_hangs_the_producer",
3,
10,
|| {
on_big_stack(|| {
let (tx, rx) = watch::channel(());
let tx = Arc::new(tx);
let reopener = {
let tx = Arc::clone(&tx);
spawn_big(move || drop(tx.subscribe()))
};
drop(rx);
reopener.join().expect("reopener");
block_on(tx.closed());
});
},
);
}
fn dekker(fenced: bool) {
let registered = std::sync::Arc::new(AtomicBool::new(false));
let cancelled = std::sync::Arc::new(AtomicBool::new(false));
let canceller = {
let (registered, cancelled) = (
std::sync::Arc::clone(®istered),
std::sync::Arc::clone(&cancelled),
);
thread::spawn(move || {
cancelled.store(true, Ordering::Release);
if fenced {
fence(Ordering::SeqCst);
}
registered.load(Ordering::Acquire)
})
};
registered.store(true, Ordering::Release);
if fenced {
fence(Ordering::SeqCst);
}
let child_sees_cancelled = cancelled.load(Ordering::Acquire);
let canceller_sees_child = canceller.join().expect("canceller");
assert!(
child_sees_cancelled || canceller_sees_child,
"child left running: it missed the cancel and the cancel missed it"
);
}
#[test]
fn the_fences_of_child_token_and_cancel_make_one_side_see_the_other() {
explore(
"the_fences_of_child_token_and_cancel_make_one_side_see_the_other",
3,
2,
|| dekker(true),
);
}
#[test]
#[should_panic(expected = "child left running")]
fn calibration_a_handshake_without_fences_leaves_a_child_running() {
explore(
"calibration_a_handshake_without_fences_leaves_a_child_running",
3,
1,
|| dekker(false),
);
}
#[test]
fn a_cancel_racing_child_token_leaves_the_child_cancelled() {
explore(
"a_cancel_racing_child_token_leaves_the_child_cancelled",
3,
10,
|| {
let root = CancellationToken::new();
let canceller = {
let root = root.clone();
thread::spawn(move || root.cancel())
};
let child = root.child_token();
let grandchild = child.child_token();
canceller.join().expect("canceller");
assert!(root.is_cancelled());
assert!(child.is_cancelled());
assert!(grandchild.is_cancelled());
},
);
}
#[test]
fn a_cancel_racing_two_child_tokens_cancels_both() {
explore(
"a_cancel_racing_two_child_tokens_cancels_both",
2,
10,
|| {
let root = CancellationToken::new();
let creator = {
let root = root.clone();
thread::spawn(move || root.child_token())
};
let canceller = {
let root = root.clone();
thread::spawn(move || root.cancel())
};
let mine = root.child_token();
let theirs = creator.join().expect("creator");
canceller.join().expect("canceller");
assert!(mine.is_cancelled() && theirs.is_cancelled());
},
);
}
#[test]
fn a_cancel_racing_the_drop_of_a_child_is_safe() {
explore("a_cancel_racing_the_drop_of_a_child_is_safe", 3, 4, || {
let root = CancellationToken::new();
let child = root.child_token();
let kept = child.child_token();
let canceller = {
let root = root.clone();
thread::spawn(move || root.cancel())
};
drop(child);
canceller.join().expect("canceller");
assert!(
kept.is_cancelled(),
"the grandchild outlives the dropped child"
);
assert!(root.child_token().is_cancelled());
});
}
#[test]
fn dropping_the_parent_handle_racing_a_grandparents_cancel_still_reaches_the_child() {
explore(
"dropping_the_parent_handle_racing_a_grandparents_cancel_still_reaches_the_child",
3,
4,
|| {
let grandparent = CancellationToken::new();
let parent = grandparent.child_token();
let child = parent.child_token();
let canceller = {
let grandparent = grandparent.clone();
thread::spawn(move || grandparent.cancel())
};
drop(parent);
canceller.join().expect("canceller");
assert!(child.is_cancelled(), "the relay passed the cancel on");
},
);
}
#[test]
fn dropping_parent_and_child_racing_a_cancel_frees_every_node() {
explore(
"dropping_parent_and_child_racing_a_cancel_frees_every_node",
2,
10,
|| {
let root = CancellationToken::new();
let parent = root.child_token();
let child = parent.child_token();
let canceller = {
let root = root.clone();
thread::spawn(move || root.cancel())
};
let dropper = thread::spawn(move || {
drop(parent);
drop(child);
});
canceller.join().expect("canceller");
dropper.join().expect("dropper");
assert!(root.is_cancelled());
},
);
}
#[test]
fn two_cancels_racing_on_a_parent_each_return_with_the_subtree_cancelled() {
explore(
"two_cancels_racing_on_a_parent_each_return_with_the_subtree_cancelled",
3,
10,
|| {
let root = CancellationToken::new();
let child = root.child_token();
let grandchild = child.child_token();
let other = {
let (root, child, grandchild) = (root.clone(), child.clone(), grandchild.clone());
thread::spawn(move || {
root.cancel();
assert!(
root.is_cancelled() && child.is_cancelled() && grandchild.is_cancelled(),
"a cancel returned with part of its subtree running"
);
})
};
root.cancel();
assert!(
root.is_cancelled() && child.is_cancelled() && grandchild.is_cancelled(),
"a cancel returned with part of its subtree running"
);
other.join().expect("other canceller");
},
);
}
#[test]
fn a_cancel_returns_with_the_grandchild_cancelled_whoever_marked_the_child() {
explore(
"a_cancel_returns_with_the_grandchild_cancelled_whoever_marked_the_child",
3,
10,
|| {
let root = CancellationToken::new();
let child = root.child_token();
let grandchild = child.child_token();
let lower = {
let child = child.clone();
thread::spawn(move || child.cancel())
};
root.cancel();
assert!(
child.is_cancelled() && grandchild.is_cancelled(),
"the root's cancel returned with the subtree below it running"
);
lower.join().expect("lower canceller");
},
);
}
fn walk_chain(descend_marked: bool) {
let flags: std::sync::Arc<[AtomicBool; 3]> = std::sync::Arc::new([
AtomicBool::new(false),
AtomicBool::new(false),
AtomicBool::new(false),
]);
let walk = move |flags: &[AtomicBool; 3], from: usize| {
flags[from].store(true, Ordering::Release);
for next in from + 1..3 {
let was_set = flags[next].swap(true, Ordering::AcqRel);
if was_set && !descend_marked {
return;
}
}
};
let lower = {
let flags = std::sync::Arc::clone(&flags);
thread::spawn(move || walk(&flags, 1))
};
walk(&flags, 0);
assert!(
flags.iter().all(|f| f.load(Ordering::Acquire)),
"the walk returned early: part of the chain below it was still running"
);
lower.join().expect("lower walker");
}
#[test]
fn a_walk_that_descends_into_marked_nodes_returns_with_the_chain_marked() {
explore(
"a_walk_that_descends_into_marked_nodes_returns_with_the_chain_marked",
3,
2,
|| walk_chain(true),
);
}
#[test]
#[should_panic(expected = "the walk returned early")]
fn calibration_a_walk_that_skips_marked_nodes_returns_early() {
explore(
"calibration_a_walk_that_skips_marked_nodes_returns_early",
3,
1,
|| walk_chain(false),
);
}
#[test]
fn a_cancel_racing_a_child_cancel_cancels_each_once() {
explore(
"a_cancel_racing_a_child_cancel_cancels_each_once",
3,
10,
|| {
let root = CancellationToken::new();
let child = root.child_token();
let grandchild = child.child_token();
let a = {
let root = root.clone();
thread::spawn(move || root.cancel())
};
let b = {
let child = child.clone();
thread::spawn(move || child.cancel())
};
a.join().expect("root canceller");
b.join().expect("child canceller");
assert!(root.is_cancelled() && child.is_cancelled() && grandchild.is_cancelled());
},
);
}
#[test]
fn a_cancel_wakes_a_waiter_on_a_child_whatever_it_races() {
explore(
"a_cancel_wakes_a_waiter_on_a_child_whatever_it_races",
3,
10,
|| {
let root = CancellationToken::new();
let child = root.child_token();
let waiter = thread::spawn(move || block_on(child.cancelled()));
root.cancel();
waiter.join().expect("waiter");
},
);
}
#[test]
fn a_cancel_wakes_an_owned_waiter_on_a_grandchild_whatever_it_races() {
explore(
"a_cancel_wakes_an_owned_waiter_on_a_grandchild_whatever_it_races",
3,
10,
|| {
let root = CancellationToken::new();
let grandchild = root.child_token().child_token();
let waiter = thread::spawn(move || block_on(grandchild.cancelled_owned()));
root.cancel();
waiter.join().expect("waiter");
},
);
}