#![cfg(loom)]
use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use loom::sync::Arc;
use loom::thread;
use threadteam::Parker;
const IDLE: usize = 1;
const RESERVED: usize = 3;
fn model(f: impl Fn() + Sync + Send + 'static) {
let mut b = loom::model::Builder::new();
b.preemption_bound = Some(3);
b.check(f);
}
fn member(
gen: &Arc<AtomicUsize>,
state: &Arc<AtomicUsize>,
parker: &Arc<Parker>,
) -> thread::JoinHandle<()> {
let (gen, state, parker) = (gen.clone(), state.clone(), parker.clone());
thread::spawn(move || {
parker.park_while(|| {
gen.load(Ordering::SeqCst) == 0 && state.load(Ordering::SeqCst) == RESERVED
});
})
}
#[test]
fn a_published_section_wakes_every_parking_member() {
model(|| {
let gen = Arc::new(AtomicUsize::new(0));
let state = Arc::new(AtomicUsize::new(RESERVED));
let parker = Arc::new(Parker::new());
let ms = [member(&gen, &state, &parker), member(&gen, &state, &parker)];
gen.fetch_add(1, Ordering::SeqCst);
parker.wake();
for m in ms {
m.join().unwrap();
}
});
}
#[test]
fn releasing_the_reservation_wakes_every_parking_member() {
model(|| {
let gen = Arc::new(AtomicUsize::new(0));
let state = Arc::new(AtomicUsize::new(RESERVED));
let parker = Arc::new(Parker::new());
let ms = [member(&gen, &state, &parker), member(&gen, &state, &parker)];
state.store(IDLE, Ordering::SeqCst);
parker.wake();
for m in ms {
m.join().unwrap();
}
});
}
#[test]
fn a_member_that_parks_after_the_first_wake_is_woken_by_the_second() {
model(|| {
let gen = Arc::new(AtomicUsize::new(0));
let state = Arc::new(AtomicUsize::new(RESERVED));
let parker = Arc::new(Parker::new());
let m = {
let (gen, state, parker) = (gen.clone(), state.clone(), parker.clone());
thread::spawn(move || {
for round in 1..=2 {
parker.park_while(|| {
gen.load(Ordering::SeqCst) < round
&& state.load(Ordering::SeqCst) == RESERVED
});
}
})
};
gen.fetch_add(1, Ordering::SeqCst);
parker.wake();
gen.fetch_add(1, Ordering::SeqCst);
parker.wake();
m.join().unwrap();
});
}
#[test]
fn a_wake_of_one_slot_leaves_the_other_asleep_until_its_own_change() {
model(|| {
let gens = [Arc::new(AtomicUsize::new(0)), Arc::new(AtomicUsize::new(0))];
let state = Arc::new(AtomicUsize::new(RESERVED));
let parkers = [Arc::new(Parker::new()), Arc::new(Parker::new())];
let ms: Vec<_> = (0..2)
.map(|i| {
let (gen, state, parker) = (gens[i].clone(), state.clone(), parkers[i].clone());
thread::spawn(move || {
parker.park_while(|| {
gen.load(Ordering::SeqCst) == 0 && state.load(Ordering::SeqCst) == RESERVED
});
assert!(
gen.load(Ordering::SeqCst) != 0 || state.load(Ordering::SeqCst) != RESERVED
);
})
})
.collect();
gens[0].fetch_add(1, Ordering::SeqCst);
parkers[0].wake();
state.store(IDLE, Ordering::SeqCst);
for p in &parkers {
p.wake();
}
for m in ms {
m.join().unwrap();
}
});
}
use threadteam::{Barrier, Completion, Waiter};
#[test]
fn the_last_member_wakes_a_leader_asleep_in_join() {
model(|| {
let done = Arc::new(Completion::new(2));
let sync = Arc::new(Parker::new());
let ms: Vec<_> = (0..2)
.map(|_| {
let (done, sync) = (done.clone(), sync.clone());
thread::spawn(move || unsafe {
Completion::finish(Arc::as_ptr(&done), Some(&sync))
})
})
.collect();
done.wait(Some(&sync), &mut Waiter::new(0));
assert!(done.is_done());
for m in ms {
m.join().unwrap();
}
});
}
#[test]
fn consecutive_barrier_rounds_wake_every_sleeper() {
model(|| {
let barrier = Arc::new(Barrier::new(2));
let cancelled = Arc::new(AtomicBool::new(false));
let sync = Arc::new(Parker::new());
let peer = {
let (barrier, cancelled, sync) = (barrier.clone(), cancelled.clone(), sync.clone());
thread::spawn(move || {
for _ in 0..2 {
assert!(barrier.arrive(&cancelled, Some(&sync), &mut Waiter::new(0)));
}
})
};
for _ in 0..2 {
assert!(barrier.arrive(&cancelled, Some(&sync), &mut Waiter::new(0)));
}
peer.join().unwrap();
});
}
#[test]
fn a_cancellation_wakes_a_participant_asleep_in_a_barrier() {
model(|| {
let barrier = Arc::new(Barrier::new(3));
let cancelled = Arc::new(AtomicBool::new(false));
let sync = Arc::new(Parker::new());
let waiter = {
let (barrier, cancelled, sync) = (barrier.clone(), cancelled.clone(), sync.clone());
thread::spawn(move || barrier.arrive(&cancelled, Some(&sync), &mut Waiter::new(0)))
};
cancelled.store(true, Ordering::SeqCst);
sync.wake();
assert!(!waiter.join().unwrap());
});
}