use crate::sync::atomic::{AtomicU8, Ordering};
#[cfg(not(loom))]
use std::time::Duration;
#[cfg(not(loom))]
use tokio::time::Instant;
use super::{TS_EXITED, TS_INACTIVITY_TIMED_OUT, TS_PENDING, TS_TIMED_OUT};
#[cfg(not(loom))]
pub(crate) async fn wait_deadline_and_claim(
started: Instant,
limit: Duration,
flag: &AtomicU8,
) -> bool {
let remaining = limit
.checked_sub(started.elapsed())
.unwrap_or(Duration::ZERO);
tokio::time::sleep(remaining).await;
claim_timed_out(flag)
}
pub(super) fn claim_timed_out(flag: &AtomicU8) -> bool {
flag.compare_exchange(
TS_PENDING,
TS_TIMED_OUT,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
}
pub(super) fn claim_inactivity_timed_out(flag: &AtomicU8) -> bool {
flag.compare_exchange(
TS_PENDING,
TS_INACTIVITY_TIMED_OUT,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
}
pub(crate) fn claim_exited(flag: &AtomicU8) -> bool {
flag.compare_exchange(TS_PENDING, TS_EXITED, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
}
#[cfg(all(test, loom))]
mod loom_model {
use super::{
TS_EXITED, TS_INACTIVITY_TIMED_OUT, TS_PENDING, TS_TIMED_OUT, claim_exited,
claim_inactivity_timed_out, claim_timed_out,
};
use crate::sync::atomic::{AtomicU8, Ordering};
use loom::sync::Arc;
#[test]
fn a_fired_deadline_and_a_natural_reap_never_both_claim() {
loom::model(|| {
let flag = Arc::new(AtomicU8::new(TS_PENDING));
let reaper = {
let flag = flag.clone();
loom::thread::spawn(move || claim_exited(&flag))
};
let deadline_won = claim_timed_out(&flag);
let exit_won = reaper.join().unwrap();
assert!(
deadline_won ^ exit_won,
"exactly one of the deadline / reap claims must win the arbiter \
(won: deadline={deadline_won}, exit={exit_won})"
);
let final_state = flag.load(Ordering::Acquire);
let expected = if deadline_won {
TS_TIMED_OUT
} else {
TS_EXITED
};
assert_eq!(
final_state, expected,
"the arbiter must settle on the winner's state"
);
});
}
#[test]
fn a_fired_deadline_claims_when_no_reap_competes() {
loom::model(|| {
let flag = AtomicU8::new(TS_PENDING);
assert!(
claim_timed_out(&flag),
"an uncontested deadline claim must win and enforce the timeout"
);
assert_eq!(flag.load(Ordering::Acquire), TS_TIMED_OUT);
});
}
#[test]
fn both_watchdogs_and_reap_have_exactly_one_winner() {
loom::model(|| {
let flag = Arc::new(AtomicU8::new(TS_PENDING));
let deadline = {
let flag = flag.clone();
loom::thread::spawn(move || claim_timed_out(&flag))
};
let inactivity = {
let flag = flag.clone();
loom::thread::spawn(move || claim_inactivity_timed_out(&flag))
};
let exit_won = claim_exited(&flag);
let deadline_won = deadline.join().unwrap();
let inactivity_won = inactivity.join().unwrap();
assert_eq!(
usize::from(deadline_won) + usize::from(inactivity_won) + usize::from(exit_won),
1,
"exactly one terminal cause must claim the arbiter"
);
let expected = if deadline_won {
TS_TIMED_OUT
} else if inactivity_won {
TS_INACTIVITY_TIMED_OUT
} else {
TS_EXITED
};
assert_eq!(flag.load(Ordering::Acquire), expected);
});
}
#[test]
fn fired_inactivity_claims_when_uncontested() {
loom::model(|| {
let flag = AtomicU8::new(TS_PENDING);
assert!(claim_inactivity_timed_out(&flag));
assert_eq!(flag.load(Ordering::Acquire), TS_INACTIVITY_TIMED_OUT);
});
}
}