use super::*;
use std::sync::{Arc, Barrier};
use std::time::Instant;
#[test]
fn cross_thread_with_state_lock_serializes() {
const THREADS: usize = 4;
let barrier = Arc::new(Barrier::new(THREADS));
let intervals = Arc::new(std::sync::Mutex::new(Vec::<(u64, u64)>::new()));
let epoch = Instant::now();
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let barrier = Arc::clone(&barrier);
let intervals = Arc::clone(&intervals);
std::thread::spawn(move || {
barrier.wait();
with_state_lock(|| {
let start = epoch.elapsed().as_nanos() as u64;
std::thread::sleep(std::time::Duration::from_millis(5));
let end = epoch.elapsed().as_nanos() as u64;
intervals.lock().unwrap().push((start, end));
Ok(())
})
.expect("with_state_lock failed");
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
let intervals = intervals.lock().unwrap();
assert_eq!(
intervals.len(),
THREADS,
"each thread must record one interval"
);
for i in 0..intervals.len() {
for j in (i + 1)..intervals.len() {
let (a_start, a_end) = intervals[i];
let (b_start, b_end) = intervals[j];
assert!(
a_end <= b_start || b_end <= a_start,
"intervals overlap: [{a_start}, {a_end}) and [{b_start}, {b_end})"
);
}
}
}
#[test]
fn same_thread_reentrancy_does_not_deadlock() {
let result = with_state_lock(|| with_state_lock(|| with_state_lock(|| Ok(42u32))));
assert_eq!(result.unwrap(), 42);
}
#[test]
fn poison_recovery_after_panicking_closure() {
use std::panic::{AssertUnwindSafe, catch_unwind};
let panicked = catch_unwind(AssertUnwindSafe(|| {
let _guard = StateLock::acquire().expect("acquire before panic");
panic!("closure blew up while holding the state lock");
}));
assert!(panicked.is_err(), "the inner closure must have panicked");
DEPTH.with(|d| assert_eq!(d.get(), 0, "depth must reset to 0 after unwind"));
let result = with_state_lock(|| Ok(7u32));
assert_eq!(result.unwrap(), 7, "lock must be reusable after a panic");
let again = with_state_lock(|| with_state_lock(|| Ok(8u32)));
assert_eq!(again.unwrap(), 8, "reentrancy still works post-recovery");
}