use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize as StdAtomicUsize, Ordering as StdOrdering};
use std::task::{Context, Poll, Waker};
use loom::sync::atomic::{AtomicBool, Ordering};
use loom::sync::{Arc, Mutex};
use loom::thread;
use super::{Runnable, Schedule, ScheduleInfo, spawn};
#[derive(Clone)]
struct Count(std::sync::Arc<StdAtomicUsize>);
impl Count {
fn new() -> Self {
Self(std::sync::Arc::new(StdAtomicUsize::new(0)))
}
fn get(&self) -> usize {
self.0.load(StdOrdering::SeqCst)
}
fn token(&self) -> Token {
Token(self.clone())
}
}
struct Token(Count);
impl Drop for Token {
fn drop(&mut self) {
self.0.0.fetch_add(1, StdOrdering::SeqCst);
}
}
struct Sched {
queue: Arc<Mutex<Vec<Runnable>>>,
_token: Token,
lost: Option<Arc<Mutex<Vec<Runnable>>>>,
kept: AtomicBool,
}
impl Sched {
fn new(queue: &Arc<Mutex<Vec<Runnable>>>, count: &Count) -> Self {
Self {
queue: queue.clone(),
_token: count.token(),
lost: None,
kept: AtomicBool::new(false),
}
}
}
unsafe impl Schedule for Sched {
fn schedule(&self, runnable: Runnable, _info: ScheduleInfo) {
match &self.lost {
Some(lost) if self.kept.swap(true, Ordering::AcqRel) => {
lost.lock().unwrap().push(runnable)
}
_ => self.queue.lock().unwrap().push(runnable),
}
}
}
fn pop(queue: &Arc<Mutex<Vec<Runnable>>>) -> Option<Runnable> {
queue.lock().unwrap().pop()
}
struct Flagged {
flag: Arc<AtomicBool>,
slot: Arc<Mutex<Option<Waker>>>,
_token: Token,
}
impl Future for Flagged {
type Output = Token;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Token> {
*self.slot.lock().unwrap() = Some(cx.waker().clone());
if self.flag.load(Ordering::Acquire) {
Poll::Ready(self._token.0.token())
} else {
Poll::Pending
}
}
}
fn block_on<F: Future>(future: F) -> F::Output {
struct Unpark(loom::sync::Notify);
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();
}
}
let park = std::sync::Arc::new(Unpark(loom::sync::Notify::new()));
let waker = 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(out) = future.as_mut().poll(&mut cx) {
return out;
}
park.0.wait();
}
}
fn model(name: &str, floor: usize, f: impl Fn() + Sync + Send + 'static) {
let runs = std::sync::Arc::new(StdAtomicUsize::new(0));
let counted = {
let runs = runs.clone();
move || {
f();
runs.fetch_add(1, StdOrdering::Relaxed);
}
};
let mut builder = loom::model::Builder::new();
builder.preemption_bound = builder.preemption_bound.or(Some(3));
builder.check(counted);
let explored = runs.load(StdOrdering::Relaxed);
println!("loom model {name}: {explored} interleavings explored");
assert!(
explored >= floor,
"{name}: only {explored} interleavings, below {floor}"
);
}
fn wake_racing_poll(lose_wakes: bool) {
let (futures, scheds) = (Count::new(), Count::new());
let queue = Arc::new(Mutex::new(Vec::new()));
let lost = Arc::new(Mutex::new(Vec::new()));
let flag = Arc::new(AtomicBool::new(false));
let slot = Arc::new(Mutex::new(None::<Waker>));
let future = Flagged {
flag: flag.clone(),
slot: slot.clone(),
_token: futures.token(),
};
let mut sched = Sched::new(&queue, &scheds);
if lose_wakes {
sched.lost = Some(lost.clone());
}
let (runnable, task) = spawn(future, sched);
runnable.schedule();
let waker_thread = {
let (flag, slot) = (flag.clone(), slot.clone());
thread::spawn(move || {
flag.store(true, Ordering::Release);
if let Some(waker) = slot.lock().unwrap().take() {
waker.wake();
}
})
};
while let Some(runnable) = pop(&queue) {
runnable.run();
}
waker_thread.join().unwrap();
while let Some(runnable) = pop(&queue) {
runnable.run();
}
let finished = task.is_finished();
drop(task);
lost.lock().unwrap().clear();
slot.lock().unwrap().take();
assert!(finished, "the wake after the flag was lost");
assert_eq!(futures.get(), 2, "the future, and the output token it made");
assert_eq!(scheds.get(), 1, "the memory went once");
}
#[test]
fn a_wake_racing_the_poll_is_never_lost() {
model("a_wake_racing_the_poll_is_never_lost", 200, || {
wake_racing_poll(false)
});
}
#[test]
#[should_panic(expected = "the wake after the flag was lost")]
fn calibration_a_schedule_that_loses_wakes_is_caught() {
model(
"calibration_a_schedule_that_loses_wakes_is_caught",
1,
|| wake_racing_poll(true),
);
}
#[test]
fn a_cancel_racing_the_run_drops_the_future_once() {
model("a_cancel_racing_the_run_drops_the_future_once", 70, || {
let (futures, scheds) = (Count::new(), Count::new());
let queue = Arc::new(Mutex::new(Vec::new()));
let guard = futures.token();
let (runnable, task) = spawn(
async move {
let _guard = guard;
std::future::pending::<()>().await
},
Sched::new(&queue, &scheds),
);
let canceller = thread::spawn(move || drop(task));
runnable.run();
canceller.join().unwrap();
while let Some(runnable) = pop(&queue) {
runnable.run();
}
assert_eq!(futures.get(), 1);
assert_eq!(scheds.get(), 1);
});
}
#[test]
fn an_awaiter_racing_the_completion_is_woken() {
model("an_awaiter_racing_the_completion_is_woken", 3000, || {
let scheds = Count::new();
let queue = Arc::new(Mutex::new(Vec::new()));
let (runnable, task) = spawn(async { 7 }, Sched::new(&queue, &scheds));
let runner = thread::spawn(move || {
runnable.run();
});
assert_eq!(block_on(task), 7);
runner.join().unwrap();
assert_eq!(scheds.get(), 1);
});
}
#[test]
fn a_detach_racing_the_completion_drops_the_output_once() {
model(
"a_detach_racing_the_completion_drops_the_output_once",
35,
|| {
let (outputs, scheds) = (Count::new(), Count::new());
let queue = Arc::new(Mutex::new(Vec::new()));
let out = outputs.token();
let (runnable, task) = spawn(async move { out }, Sched::new(&queue, &scheds));
let detacher = thread::spawn(move || task.detach());
runnable.run();
detacher.join().unwrap();
assert_eq!(outputs.get(), 1);
assert_eq!(scheds.get(), 1);
},
);
}
#[test]
fn the_last_waker_racing_a_detach_drops_the_future_once() {
model(
"the_last_waker_racing_a_detach_drops_the_future_once",
8,
|| {
let (futures, scheds) = (Count::new(), Count::new());
let queue = Arc::new(Mutex::new(Vec::new()));
let slot = Arc::new(Mutex::new(None::<Waker>));
let flag = Arc::new(AtomicBool::new(false));
let (runnable, task) = spawn(
Flagged {
flag,
slot: slot.clone(),
_token: futures.token(),
},
Sched::new(&queue, &scheds),
);
runnable.run();
let waker = slot
.lock()
.unwrap()
.take()
.expect("the poll left its waker");
let dropper = thread::spawn(move || drop(waker));
task.detach();
dropper.join().unwrap();
while let Some(runnable) = pop(&queue) {
runnable.run();
}
assert_eq!(futures.get(), 1);
assert_eq!(scheds.get(), 1);
},
);
}
#[test]
fn a_wake_racing_a_cancel_drops_the_future_once() {
model("a_wake_racing_a_cancel_drops_the_future_once", 40, || {
let (futures, scheds) = (Count::new(), Count::new());
let queue = Arc::new(Mutex::new(Vec::new()));
let slot = Arc::new(Mutex::new(None::<Waker>));
let flag = Arc::new(AtomicBool::new(false));
let (runnable, task) = spawn(
Flagged {
flag,
slot: slot.clone(),
_token: futures.token(),
},
Sched::new(&queue, &scheds),
);
runnable.run();
let waker = slot
.lock()
.unwrap()
.take()
.expect("the poll left its waker");
let waking = thread::spawn(move || waker.wake_by_ref());
drop(task);
waking.join().unwrap();
while let Some(runnable) = pop(&queue) {
runnable.run();
}
assert_eq!(futures.get(), 1);
assert_eq!(scheds.get(), 1);
});
}
#[test]
fn the_last_waker_dropped_during_the_poll_drops_the_future_once() {
model(
"the_last_waker_dropped_during_the_poll_drops_the_future_once",
25,
|| {
let (futures, scheds) = (Count::new(), Count::new());
let queue = Arc::new(Mutex::new(Vec::new()));
let slot = Arc::new(Mutex::new(None::<Waker>));
let flag = Arc::new(AtomicBool::new(false));
let (runnable, task) = spawn(
Flagged {
flag,
slot: slot.clone(),
_token: futures.token(),
},
Sched::new(&queue, &scheds),
);
task.detach();
let dropper = {
let slot = slot.clone();
thread::spawn(move || drop(slot.lock().unwrap().take()))
};
runnable.run();
dropper.join().unwrap();
drop(slot.lock().unwrap().take());
while let Some(runnable) = pop(&queue) {
runnable.run();
}
assert_eq!(futures.get(), 1, "the future was dropped once");
assert_eq!(scheds.get(), 1, "the memory went once");
},
);
}