use core::cell::RefCell;
use core::time::Duration;
use std::time::Instant;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum Fault {
Wait,
Spawn,
Thread,
Lock,
}
#[must_use = "the fault is disarmed as soon as this is dropped"]
pub(super) fn arm(fault: Fault) -> Armed {
ripe_at(fault, Instant::now())
}
#[must_use = "the fault is disarmed as soon as this is dropped"]
pub(super) fn arm_late(fault: Fault, after: Duration) -> Armed {
ripe_at(fault, Instant::now() + after)
}
fn ripe_at(fault: Fault, ripe: Instant) -> Armed {
ARMED.with_borrow_mut(|armed| armed.push((fault, ripe)));
Armed(fault)
}
pub(super) fn fired(fault: Fault) -> bool {
let now = Instant::now();
ARMED.with_borrow_mut(|armed| {
armed
.iter()
.position(|&(candidate, ripe)| candidate == fault && ripe <= now)
.map(|at| armed.remove(at))
.is_some()
})
}
fn disarm(fault: Fault) {
ARMED.with_borrow_mut(|armed| {
if let Some(at) = armed.iter().position(|&(candidate, _ripe)| candidate == fault) {
let _spent = armed.remove(at);
}
});
}
#[derive(Debug)]
pub(super) struct Armed(Fault);
impl Drop for Armed {
fn drop(&mut self) {
disarm(self.0);
}
}
thread_local! {
static ARMED: RefCell<Vec<(Fault, Instant)>> = const { RefCell::new(Vec::new()) };
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fault_fires_once() {
let armed = arm(Fault::Wait);
assert!(fired(Fault::Wait), "the fault was armed");
assert!(!fired(Fault::Wait), "and firing it must have taken it away");
drop(armed);
}
#[test]
fn nothing_fires_unasked() {
assert!(!fired(Fault::Wait));
assert!(!fired(Fault::Spawn));
}
#[test]
fn one_fault_does_not_stand_in_for_another() {
let _armed = arm(Fault::Wait);
assert!(!fired(Fault::Spawn), "arming one fault must not arm the others");
assert!(!fired(Fault::Thread));
assert!(fired(Fault::Wait));
}
#[test]
fn an_unfired_fault_does_not_outlive_its_guard() {
drop(arm(Fault::Spawn));
assert!(!fired(Fault::Spawn), "the guard must disarm what it armed");
}
#[test]
fn faults_do_not_displace_each_other() {
let _spawn = arm(Fault::Spawn);
let _wait = arm(Fault::Wait);
assert!(fired(Fault::Wait));
assert!(fired(Fault::Spawn));
}
#[test]
fn a_fault_does_not_reach_another_thread() {
let _armed = arm(Fault::Spawn);
let elsewhere = std::thread::spawn(|| fired(Fault::Spawn)).join().expect("the probe thread");
assert!(!elsewhere, "a fault must not escape the thread that armed it");
assert!(fired(Fault::Spawn), "and must still be waiting on the thread that did");
}
#[test]
fn a_late_fault_waits_for_its_moment() {
let _armed = arm_late(Fault::Wait, Duration::from_millis(50));
assert!(!fired(Fault::Wait), "a fault that is not due yet must not fire");
std::thread::sleep(Duration::from_millis(75));
assert!(fired(Fault::Wait), "and must fire at the first check after it is");
assert!(!fired(Fault::Wait), "then be spent like any other");
}
#[test]
fn a_late_fault_that_never_fired_does_not_outlive_its_guard() {
drop(arm_late(Fault::Spawn, Duration::from_millis(1)));
std::thread::sleep(Duration::from_millis(5));
assert!(!fired(Fault::Spawn), "an undue fault must not be left behind for the next test");
}
}