#![cfg(loom)]
#![allow(clippy::unwrap_used, reason = "test scope")]
use loom::sync::atomic::{AtomicBool, Ordering};
use loom::sync::{Arc, Mutex};
use loom::thread;
const WAITERS: usize = 2;
struct Task {
completed: AtomicBool,
slot: Mutex<Vec<usize>>,
woken: [AtomicBool; WAITERS],
}
impl Task {
fn new() -> Self {
Self {
completed: AtomicBool::new(false),
slot: Mutex::new(Vec::new()),
woken: [AtomicBool::new(false), AtomicBool::new(false)],
}
}
fn take_and_wake(&self) {
let taken = std::mem::take(&mut *self.slot.lock().unwrap());
for waiter in taken {
self.woken[waiter].store(true, Ordering::Release);
}
}
fn register(&self, waiter: usize) -> bool {
self.slot.lock().unwrap().push(waiter);
let completed = self.completed.load(Ordering::Acquire);
if completed {
self.take_and_wake();
}
completed
}
fn complete(&self) {
self.completed.store(true, Ordering::Release);
self.take_and_wake();
}
}
#[test]
fn two_waiters_are_never_stranded_by_completion() {
loom::model(|| {
let task = Arc::new(Task::new());
let waiters: Vec<_> = (0..WAITERS)
.map(|waiter| {
let task = Arc::clone(&task);
thread::spawn(move || task.register(waiter))
})
.collect();
task.complete();
let outcomes: Vec<bool> = waiters
.into_iter()
.map(|handle| handle.join().unwrap())
.collect();
for (waiter, saw_completion) in outcomes.into_iter().enumerate() {
assert!(
saw_completion || task.woken[waiter].load(Ordering::Acquire),
"waiter {waiter} registered, saw a pending task, and was never woken"
);
}
});
}