use core::cell::Cell;
use core::task::Waker;
use std::sync::Arc as StdArc;
use std::sync::atomic::{AtomicUsize as StdAtomicUsize, Ordering as StdOrdering};
use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use loom::sync::{Arc, Notify};
use loom::thread;
use super::contend::{self, Contend, Lock, MAX_BYPASS};
use super::queue::{Arrivals, HANDOFF, Policy, QUEUED, Step, WaitQueue};
use super::raw_semaphore::RawSemaphore;
use super::waiter::{Wait, WakeList};
const MOST_PASSES: usize = 3;
const BARGES: usize = 2;
fn explore(
name: &'static str,
preemptions: usize,
min_interleavings: usize,
min_witnessed: usize,
model: impl Fn(&StdAtomicUsize) + Sync + Send + 'static,
) {
let runs = StdArc::new(StdAtomicUsize::new(0));
let witnessed = StdArc::new(StdAtomicUsize::new(0));
let counted = {
let (runs, witnessed) = (StdArc::clone(&runs), StdArc::clone(&witnessed));
move || {
model(&witnessed);
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);
let reached = witnessed.load(StdOrdering::Relaxed);
println!("loom model {name}: {explored} interleavings explored, {reached} witnessed");
assert!(
explored >= min_interleavings,
"{name} explored only {explored} interleavings, below the {min_interleavings} floor"
);
assert!(
reached >= min_witnessed,
"{name} reached its interesting state {reached} times, below the {min_witnessed} floor"
);
}
struct Counted<'a> {
sem: &'a RawSemaphore,
passes: Cell<usize>,
most: Cell<usize>,
}
impl Counted<'_> {
fn next_drain(&self) {
self.most.set(self.most.get().max(self.passes.get()));
self.passes.set(0);
}
fn take_most(&self) -> usize {
self.next_drain();
self.most.replace(0)
}
}
impl Policy for Counted<'_> {
fn queue(&self) -> &WaitQueue {
self.sem.queue()
}
fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, wake_front: bool) -> usize {
self.passes.set(self.passes.get() + 1);
self.sem.pass(arrivals, woken, wake_front)
}
fn enqueue(&self, wait: &mut Wait, payload: usize, waker: &Waker) {
wait.enqueue(self.queue(), payload, waker);
self.next_drain();
self.transition(|state| Step::Drain(state | QUEUED));
self.next_drain();
}
}
impl Lock for Counted<'_> {
fn admit(&self, state: usize, need: usize) -> Option<usize> {
self.sem.admit(state, need)
}
fn give_back(&self, need: usize) {
self.sem.give_back(need);
}
fn try_take(&self, need: usize) -> Result<(), usize> {
self.next_drain();
contend::take(self, need, false)
}
fn take_or_owe(&self, wait: &Wait, need: usize) -> Result<(), usize> {
self.next_drain();
let attempt = self.owe_or_take(
wait,
|state| self.admit(state, need),
|| self.give_back(need),
);
self.next_drain();
attempt
}
}
struct Unpark(Notify);
impl std::task::Wake for Unpark {
fn wake(self: StdArc<Self>) {
self.0.notify();
}
fn wake_by_ref(self: &StdArc<Self>) {
self.0.notify();
}
}
pub fn a_waiter_serving_the_queue_is_served_while_the_holder_barges() {
explore(
"a_waiter_serving_the_queue_is_served_while_the_holder_barges",
3,
100,
1,
|witnessed| {
let sem = Arc::new(RawSemaphore::new(1));
assert!(sem.try_acquire(1), "free at the start");
let holder = {
let sem = Arc::clone(&sem);
thread::spawn(move || {
sem.release(1);
for _ in 0..BARGES {
thread::yield_now();
if sem.try_acquire(1) {
sem.release(1);
}
}
})
};
let counted = Counted {
sem: &sem,
passes: Cell::new(0),
most: Cell::new(0),
};
let park = StdArc::new(Unpark(Notify::new()));
let waker = Waker::from(StdArc::clone(&park));
let mut contend = Contend::new();
loop {
let polled = contend.poll(&counted, 1, &waker);
let passes = counted.take_most();
if passes > 1 {
witnessed.fetch_add(1, StdOrdering::Relaxed);
}
assert!(
passes <= MOST_PASSES,
"the drain ran {passes} passes for the waiter that holds it"
);
if polled.is_ready() {
break;
}
park.0.wait();
}
holder.join().expect("holder");
sem.release(1);
assert_eq!(sem.available(), 1, "a permit was lost or made up");
},
);
}
pub fn calibration_a_marker_lowered_on_a_pending_nudge_keeps_the_drain_passing() {
const DRAINING: usize = 1;
const DIRTY: usize = 1 << 1;
const NUDGE_OUT: usize = 1 << 2;
const FREE: usize = 1 << 3;
explore(
"calibration_a_marker_lowered_on_a_pending_nudge_keeps_the_drain_passing",
3,
0,
0,
|_witnessed| {
let state = Arc::new(AtomicUsize::new(DRAINING));
let nudged = Arc::new(AtomicBool::new(false));
let holder = {
let state = Arc::clone(&state);
thread::spawn(move || {
for _ in 0..=BARGES {
let before = state.fetch_or(FREE, Ordering::AcqRel);
if before & NUDGE_OUT == 0 {
state.fetch_or(DIRTY, Ordering::AcqRel);
}
state.fetch_and(!FREE, Ordering::AcqRel);
thread::yield_now();
}
})
};
let mut passes = 0;
loop {
state.fetch_and(!DIRTY, Ordering::AcqRel);
passes += 1;
state.fetch_or(NUDGE_OUT, Ordering::AcqRel);
let already = nudged.swap(true, Ordering::AcqRel);
if already {
state.fetch_and(!NUDGE_OUT, Ordering::AcqRel);
}
let seen = state.load(Ordering::Acquire);
if seen & DIRTY == 0
&& state
.compare_exchange(
seen,
seen & !DRAINING,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
break;
}
}
holder.join().expect("holder");
assert!(
passes <= MOST_PASSES,
"the drain ran {passes} passes for the waiter that holds it"
);
},
);
}
const RELEASES: usize = 3;
pub fn a_drain_that_found_nobody_queued_ends_while_another_thread_releases() {
explore(
"a_drain_that_found_nobody_queued_ends_while_another_thread_releases",
3,
100,
1,
|witnessed| {
let sem = Arc::new(RawSemaphore::new(2));
assert!(sem.try_acquire(1), "the other thread's permit");
let counted = Counted {
sem: &sem,
passes: Cell::new(0),
most: Cell::new(0),
};
let mut contend = Contend::new();
assert!(
contend.poll(&counted, 2, Waker::noop()).is_pending(),
"one permit is out"
);
let other = {
let sem = Arc::clone(&sem);
thread::spawn(move || {
for _ in 0..RELEASES {
sem.release(1);
thread::yield_now();
assert!(sem.try_acquire(1), "its permit is free again");
}
})
};
counted.take_most();
contend.cancel(&counted, 2);
let passes = counted.take_most();
if passes == 1 {
witnessed.fetch_add(1, StdOrdering::Relaxed);
}
assert!(
passes <= 1,
"the drain ran {passes} passes over a queue with nobody in it"
);
other.join().expect("other");
sem.release(1);
assert_eq!(sem.available(), 2, "a permit was lost or made up");
},
);
}
pub fn calibration_a_release_that_dirties_keeps_an_empty_drain_passing() {
const DRAINING: usize = 1;
const DIRTY: usize = 1 << 1;
const QUEUED: usize = 1 << 2;
explore(
"calibration_a_release_that_dirties_keeps_an_empty_drain_passing",
3,
0,
0,
|_witnessed| {
let state = Arc::new(AtomicUsize::new(DRAINING | QUEUED));
let other = {
let state = Arc::clone(&state);
thread::spawn(move || {
for _ in 0..RELEASES {
if state.load(Ordering::Acquire) & QUEUED != 0 {
state.fetch_or(DIRTY, Ordering::AcqRel);
}
thread::yield_now();
}
})
};
let mut passes = 0;
loop {
state.fetch_and(!DIRTY, Ordering::AcqRel);
passes += 1;
let seen = state.load(Ordering::Acquire);
if seen & DIRTY == 0
&& state
.compare_exchange(
seen,
seen & !(DRAINING | QUEUED),
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
break;
}
}
other.join().expect("other");
assert!(
passes <= 1,
"the drain ran {passes} passes over a queue with nobody in it"
);
},
);
}
fn acquire_parking(sem: &RawSemaphore) {
let park = StdArc::new(Unpark(Notify::new()));
let waker = Waker::from(StdArc::clone(&park));
let mut contend = Contend::new();
while contend.poll(sem, 1, &waker).is_pending() {
park.0.wait();
}
}
pub fn the_first_attempt_refuses_a_lock_owed_to_a_waiter() {
owed_handoff_model(
"the_first_attempt_refuses_a_lock_owed_to_a_waiter",
RawSemaphore::take_one,
);
}
pub fn calibration_a_first_attempt_that_ignores_handoff_barges_past_an_owed_waiter() {
owed_handoff_model(
"calibration_a_first_attempt_that_ignores_handoff_barges_past_an_owed_waiter",
|sem| {
use super::raw_semaphore::ONE;
let state = &sem.queue().state;
let seen = state.load(Ordering::Relaxed);
if seen >= ONE {
state
.compare_exchange(seen, seen - ONE, Ordering::Acquire, Ordering::Relaxed)
.map(drop)
} else {
Err(seen)
}
},
);
}
fn owed_handoff_model(name: &'static str, attempt: fn(&RawSemaphore) -> Result<(), usize>) {
explore(name, 1, 10, 1, move |witnessed| {
let sem = Arc::new(RawSemaphore::new(1));
assert!(sem.try_acquire(1), "free at the start");
let waiter = {
let sem = Arc::clone(&sem);
thread::spawn(move || acquire_parking(&sem))
};
thread::yield_now();
let mut held = true;
for _ in 0..=MAX_BYPASS + 1 {
if held {
sem.release(1);
}
let owed = sem.queue().state.load(Ordering::Acquire) & HANDOFF != 0;
held = attempt(&sem).is_ok();
if owed {
witnessed.fetch_add(1, StdOrdering::Relaxed);
assert!(!held, "the holder took a lock owed to the queued waiter");
}
thread::yield_now();
}
if held {
sem.release(1);
}
waiter.join().expect("waiter");
sem.release(1);
assert_eq!(sem.available(), 1, "a permit was lost or made up");
});
}
pub fn a_nudge_while_the_waiter_spins_is_never_lost() {
node_spin_model(
"a_nudge_while_the_waiter_spins_is_never_lost",
[100, 10, 10],
);
}
pub fn calibration_a_node_spin_that_skips_its_look_sleeps_beside_the_mark() {
super::waiter::spin_probe::SKIP_LOOK.with(|skip| skip.set(true));
node_spin_model(
"calibration_a_node_spin_that_skips_its_look_sleeps_beside_the_mark",
[0, 0, 0],
);
}
fn node_spin_model(name: &'static str, floors: [usize; 3]) {
super::waiter::spin_probe::take();
explore(name, 2, 10, 1, |witnessed| {
let sem = Arc::new(RawSemaphore::new(1));
assert!(sem.try_acquire(1), "free at the start");
let waiter = {
let sem = Arc::clone(&sem);
thread::spawn(move || acquire_parking(&sem))
};
while sem.queue().state.load(Ordering::Acquire) & QUEUED == 0 {
thread::yield_now();
}
if sem.queue().spin.rounds() == 0 {
sem.queue().spin.won(0);
}
sem.release(1);
if sem.take_one().is_ok() {
witnessed.fetch_add(1, StdOrdering::Relaxed);
thread::yield_now();
sem.release(1);
}
waiter.join().expect("waiter");
sem.release(1);
assert_eq!(sem.available(), 1, "a permit was lost or made up");
});
let [spins, seen, late] = super::waiter::spin_probe::take();
println!(
"loom model {name}: {spins} node spins, {seen} marks seen by the spin, {late} only by the look"
);
assert!(
spins >= floors[0],
"{name}: {spins} node spins, below the floor {}",
floors[0]
);
assert!(
seen >= floors[1],
"{name}: {seen} marks seen by a spin, below the floor {}",
floors[1]
);
assert!(
late >= floors[2],
"{name}: {late} marks seen only by the look, below the floor {}",
floors[2]
);
}
pub fn fair_handoff_counts() -> [usize; 3] {
super::fair_semaphore::probe::take()
}
pub fn fair_drop_pass_on(on: bool) {
super::fair_semaphore::probe::drop_pass_on(on);
}
std::thread_local! {
static BUSY_LOOKS: Cell<usize> = const { Cell::new(0) };
}
pub(super) fn busy_look() {
BUSY_LOOKS.with(|n| n.set(n.get() + 1));
}
pub fn mpsc_busy_looks() -> usize {
BUSY_LOOKS.with(|n| n.replace(0))
}