use crate::sync::internal::{AtomicBool, Ordering, seq_cst_handshake};
pub(crate) struct ParkState {
parked: AtomicBool,
}
impl ParkState {
pub(crate) fn new() -> Self {
Self {
parked: AtomicBool::new(false),
}
}
pub(crate) fn begin_park(&self, has_work: impl FnOnce() -> bool) -> bool {
self.parked.store(true, Ordering::SeqCst);
seq_cst_handshake();
if has_work() {
self.parked.store(false, Ordering::SeqCst);
false
} else {
true
}
}
pub(crate) fn is_parked(&self) -> bool {
self.parked.load(Ordering::SeqCst)
}
pub(crate) fn end_park(&self) {
self.parked.store(false, Ordering::SeqCst);
}
pub(crate) fn wake(&self, notify: impl FnOnce()) {
seq_cst_handshake();
if self.parked.load(Ordering::SeqCst) && self.parked.swap(false, Ordering::SeqCst) {
notify();
}
}
}
#[cfg(all(test, loom))]
mod loom_tests {
use loom::sync::Arc;
use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use loom::thread;
use super::ParkState;
struct Model {
state: ParkState,
work: AtomicBool,
notified: AtomicUsize,
}
#[test]
fn a_wake_racing_the_park_is_never_lost() {
loom::model(|| {
let m = Arc::new(Model {
state: ParkState::new(),
work: AtomicBool::new(false),
notified: AtomicUsize::new(0),
});
let waker = {
let m = m.clone();
thread::spawn(move || {
m.work.store(true, Ordering::SeqCst);
m.state.wake(|| {
m.notified.fetch_add(1, Ordering::SeqCst);
});
})
};
let sleeps = m.state.begin_park(|| m.work.load(Ordering::SeqCst));
waker.join().unwrap();
if sleeps {
assert_eq!(m.notified.load(Ordering::SeqCst), 1, "a wake was lost");
m.state.end_park();
}
assert!(m.work.load(Ordering::SeqCst));
});
}
fn broken_park(recheck: bool, ordering: Ordering) {
let parked = Arc::new(AtomicBool::new(false));
let work = Arc::new(AtomicBool::new(false));
let notified = Arc::new(AtomicUsize::new(0));
let waker = {
let (parked, work, notified) = (parked.clone(), work.clone(), notified.clone());
thread::spawn(move || {
work.store(true, ordering);
if ordering == Ordering::SeqCst {
loom::sync::atomic::fence(Ordering::SeqCst);
}
if parked.load(ordering) {
notified.fetch_add(1, Ordering::SeqCst);
}
})
};
parked.store(true, ordering);
if ordering == Ordering::SeqCst {
loom::sync::atomic::fence(Ordering::SeqCst);
}
let sleeps = !(recheck && work.load(ordering));
waker.join().unwrap();
if sleeps {
assert_eq!(notified.load(Ordering::SeqCst), 1, "a wake was lost");
}
}
#[test]
#[should_panic(expected = "a wake was lost")]
fn calibration_a_park_without_its_look_loses_a_wake() {
loom::model(|| broken_park(false, Ordering::SeqCst));
}
#[test]
#[should_panic(expected = "a wake was lost")]
fn calibration_a_relaxed_pair_without_a_fence_loses_a_wake() {
loom::model(|| broken_park(true, Ordering::Relaxed));
}
#[test]
fn calibration_the_sequentially_consistent_pair_keeps_every_wake() {
loom::model(|| broken_park(true, Ordering::SeqCst));
}
#[test]
fn a_burst_of_wakes_notifies_once() {
loom::model(|| {
let m = Arc::new(Model {
state: ParkState::new(),
work: AtomicBool::new(false),
notified: AtomicUsize::new(0),
});
let wakers: Vec<_> = (0..2)
.map(|_| {
let m = m.clone();
thread::spawn(move || {
m.work.store(true, Ordering::SeqCst);
m.state.wake(|| {
m.notified.fetch_add(1, Ordering::SeqCst);
});
})
})
.collect();
let sleeps = m.state.begin_park(|| m.work.load(Ordering::SeqCst));
for w in wakers {
w.join().unwrap();
}
let notified = m.notified.load(Ordering::SeqCst);
assert!(notified <= 1, "{notified} notifications for one park");
if sleeps {
assert_eq!(notified, 1, "a wake was lost");
}
});
}
}