use std::{
future::Future,
panic::Location,
sync::{
Arc, Mutex, MutexGuard, PoisonError,
atomic::{AtomicUsize, Ordering},
mpsc,
},
task::{Context, Wake, Waker},
thread,
time::Instant as RealInstant,
};
use super::{
Duration, Instant, advance, ambient_scope, enter_dynamic, flash_enabled, participate, reset,
system::{FlashInner, credit, sched},
yield_now,
};
use crate::sync::Notify;
static GUARD: Mutex<()> = Mutex::new(());
fn guard() -> MutexGuard<'static, ()> {
GUARD.lock().unwrap_or_else(PoisonError::into_inner)
}
const NANOS_PER_SEC: u64 = 1_000_000_000;
fn assert_fast(start: RealInstant) {
assert!(
start.elapsed() < Duration::from_secs(2),
"engine wait consumed real wall-clock time (likely a stalled advance)"
);
}
fn bracketed_on<F: FnOnce()>(flash: &FlashInner, body: F) {
credit::reset_credit();
flash.pre_count_dedicated();
credit::mark_dedicated();
body();
flash.on_participant_exit();
}
fn run_parked_waiters_on(flash: &Arc<FlashInner>, secs: &[u64]) {
let coordinator = flash.test_hold();
let handles: Vec<_> = secs
.iter()
.copied()
.map(|s| {
let flash = Arc::clone(flash);
thread::spawn(move || bracketed_on(&flash, || flash.park_for(Duration::from_secs(s))))
})
.collect();
while flash.timed_count() != secs.len() {
thread::yield_now();
}
drop(coordinator);
for h in handles {
h.join().expect("waiter thread panicked");
}
}
struct NoopWake;
impl Wake for NoopWake {
fn wake(self: Arc<Self>) {}
fn wake_by_ref(self: &Arc<Self>) {}
}
#[test]
fn flash_active_defaults_real_and_nests() {
let _g = guard();
assert!(!flash_enabled(), "default must be real (`active` flag off)");
{
let _a = ambient_scope(true); let _g2 = enter_dynamic(true);
assert!(
flash_enabled(),
"inside an ambient+dynamic scope flash is active"
);
{
let _real = enter_dynamic(false);
assert!(!flash_enabled(), "flash(false) carves real");
}
assert!(flash_enabled(), "restored on carve drop");
}
assert!(!flash_enabled(), "restored on scope drop");
}
#[test]
fn dynamic_is_noop_without_ambient() {
let _g = guard();
let _d = enter_dynamic(true);
assert!(!flash_enabled(), "dynamic flash without ambient stays real");
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "non-LIFO mode-scope drop")]
fn non_lifo_mode_scope_drop_is_caught() {
let outer = ambient_scope(true);
let _inner = ambient_scope(false);
drop(outer);
}
#[test]
fn woken_async_task_stays_counted_until_repolled() {
let _g = guard();
reset();
let notify = Notify::default();
let mut task = Box::pin(participate(
async {
notify.notified().await;
},
Location::caller(),
));
let waker = Waker::from(Arc::new(NoopWake));
let mut cx = Context::from_waker(&waker);
assert!(task.as_mut().poll(&mut cx).is_pending());
assert_eq!(sched::async_active_count(), 0, "parked task is uncounted");
notify.notify_one();
assert_eq!(
sched::async_active_count(),
1,
"a woken-but-not-yet-repolled task must keep the engine non-quiescent"
);
assert!(task.as_mut().poll(&mut cx).is_ready());
assert_eq!(
sched::async_active_count(),
0,
"a completed task releases its slot exactly once"
);
}
#[test]
fn now_advances_only_on_advance() {
let _g = guard();
reset();
let _a = ambient_scope(true);
let _f = enter_dynamic(true);
let t0 = Instant::now();
assert_eq!(Instant::now(), t0, "clock frozen between advances");
advance(Duration::from_millis(10));
let t1 = Instant::now();
assert_eq!(t1.duration_since(t0), Duration::from_millis(10));
assert!(t1 > t0);
}
#[test]
fn elapsed_tracks_virtual_clock() {
let _g = guard();
reset();
let _a = ambient_scope(true);
let _f = enter_dynamic(true);
let start = Instant::now();
assert_eq!(start.elapsed(), Duration::ZERO);
advance(Duration::from_secs(5));
assert_eq!(start.elapsed(), Duration::from_secs(5));
}
#[test]
fn arithmetic_saturates_and_orders() {
let now = Instant::now();
let later = now + Duration::from_secs(1);
let earlier = now - Duration::from_secs(1);
assert!(earlier < now && now < later);
assert_eq!(later - now, Duration::from_secs(1));
assert_eq!(later.duration_since(now), Duration::from_secs(1));
assert_eq!(now.saturating_duration_since(later), Duration::ZERO);
}
#[test]
fn base_keeps_backward_offset_positive() {
let now = Instant::now();
let earlier = now - Duration::from_secs(3600);
assert_eq!(now.duration_since(earlier), Duration::from_secs(3600));
}
#[test]
fn quiescence_advances_to_max_deadline_fast() {
let flash = FlashInner::new_arc();
let base = flash.clock.now_nanos();
let real_start = RealInstant::now();
run_parked_waiters_on(&flash, &[10, 3]);
assert_fast(real_start);
assert_eq!(
flash.clock.now_nanos(),
base + 10 * NANOS_PER_SEC,
"clock lands on the max registered deadline"
);
assert_eq!(
flash.advance_log(),
vec![base + 3 * NANOS_PER_SEC, base + 10 * NANOS_PER_SEC],
"advance jumps to the min deadline first, then the next, regardless of start order"
);
}
#[test]
fn equal_deadlines_wake_in_one_step() {
let flash = FlashInner::new_arc();
let base = flash.clock.now_nanos();
let real_start = RealInstant::now();
run_parked_waiters_on(&flash, &[5, 5, 8]);
assert_fast(real_start);
assert_eq!(
flash.advance_log(),
vec![base + 5 * NANOS_PER_SEC, base + 8 * NANOS_PER_SEC],
"three waiters, two advance steps: the two equal deadlines wake in one batch"
);
assert_eq!(flash.clock.now_nanos(), base + 8 * NANOS_PER_SEC);
}
#[test]
fn sequence_is_deterministic_across_runs() {
let flash = FlashInner::new_arc();
let run = || {
flash.reset();
let base = flash.clock.now_nanos();
run_parked_waiters_on(&flash, &[7, 2, 11, 4]);
(base, flash.advance_log())
};
let real_start = RealInstant::now();
let (base_a, log_a) = run();
let (base_b, log_b) = run();
assert_fast(real_start);
assert_eq!(
base_a, base_b,
"reset restores the same timeline origin each run"
);
assert_eq!(log_a, log_b, "advance sequence is identical run-to-run");
assert_eq!(
log_a,
vec![
base_a + 2 * NANOS_PER_SEC,
base_a + 4 * NANOS_PER_SEC,
base_a + 7 * NANOS_PER_SEC,
base_a + 11 * NANOS_PER_SEC,
],
"distinct deadlines advance in sorted order"
);
}
#[test]
fn first_park_bootstraps_and_self_advances() {
let flash = FlashInner::new_arc();
let base = flash.clock.now_nanos();
let real_start = RealInstant::now();
let waiter = {
let flash = Arc::clone(&flash);
thread::spawn(move || bracketed_on(&flash, || flash.park_for(Duration::from_secs(4))))
};
waiter.join().expect("waiter thread panicked");
assert_fast(real_start);
assert_eq!(
flash.clock.now_nanos(),
base + 4 * NANOS_PER_SEC,
"first-park bootstrap self-advanced the lone waiter"
);
assert_eq!(flash.advance_log(), vec![base + 4 * NANOS_PER_SEC]);
assert_eq!(flash.active_count(), 0, "bootstrap balanced on exit");
}
#[test]
fn real_io_defers_advance_past_real_pace() {
let flash = FlashInner::new_arc();
let t0 = flash.clock.now_nanos();
flash.real_io_enter();
let waiter = {
let flash = Arc::clone(&flash);
thread::spawn(move || bracketed_on(&flash, || flash.park_for(Duration::from_secs(10))))
};
while flash.timed_count() != 1 {
thread::yield_now();
}
thread::sleep(Duration::from_millis(50));
assert_eq!(
flash.timed_count(),
1,
"a virtual deadline beyond the real pace must stay parked while real I/O is in flight"
);
assert_eq!(
flash.clock.now_nanos(),
t0,
"the clock must not advance past the real pace while real I/O is in flight"
);
let real_start = RealInstant::now();
flash.real_io_exit();
waiter.join().expect("waiter thread panicked");
assert_fast(real_start);
assert_eq!(
flash.clock.now_nanos(),
t0 + 10 * NANOS_PER_SEC,
"full-speed collapse resumes the instant the last real I/O op completes"
);
}
#[test]
fn real_io_paces_deadline_to_real_time_not_pin() {
let flash = FlashInner::new_arc();
let t0 = flash.clock.now_nanos();
flash.real_io_enter();
let real_start = RealInstant::now();
let waiter = {
let flash = Arc::clone(&flash);
thread::spawn(move || bracketed_on(&flash, || flash.park_for(Duration::from_millis(20))))
};
waiter.join().expect("waiter thread panicked");
let waited = real_start.elapsed();
flash.real_io_exit();
assert!(
waited >= Duration::from_millis(15),
"deadline fired ahead of its real-time pace: {waited:?}"
);
assert!(
waited < Duration::from_secs(2),
"pace must release the deadline at ~20ms real, not pin it: {waited:?}"
);
assert_eq!(flash.clock.now_nanos(), t0 + 20_000_000);
}
#[test]
fn real_io_nests_overlapping_ops() {
let flash = FlashInner::new_arc();
flash.real_io_enter();
flash.real_io_enter();
let waiter = {
let flash = Arc::clone(&flash);
thread::spawn(move || bracketed_on(&flash, || flash.park_for(Duration::from_secs(10))))
};
while flash.timed_count() != 1 {
thread::yield_now();
}
flash.real_io_exit();
thread::sleep(Duration::from_millis(30));
assert_eq!(
flash.timed_count(),
1,
"the deadline must stay deferred while ANY real I/O op is still in flight"
);
let real_start = RealInstant::now();
flash.real_io_exit();
waiter.join().expect("waiter thread panicked");
assert_fast(real_start);
}
fn xs(state: &mut u64) -> u64 {
let mut x = *state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*state = x;
x
}
#[test]
fn stress_mixed_waits_no_underflow_no_lost_wakeup() {
const ROUNDS: u64 = 16;
const K: u64 = 5;
const SPIN_WAITS: u64 = 3;
let flash = FlashInner::new_arc();
let real_start = RealInstant::now();
for round in 0..ROUNDS {
flash.reset();
let coordinator = flash.test_hold();
let mut untimed_cvids: Vec<super::ids::CvId> = Vec::new();
let untimed_left = Arc::new(AtomicUsize::new(0));
let spin = {
let flash = Arc::clone(&flash);
thread::spawn(move || {
bracketed_on(&flash, || {
for _ in 0..64 {
thread::yield_now();
}
});
})
};
let builder = {
let mut seed = round.wrapping_mul(2_654_435_761).wrapping_add(7);
let flash = Arc::clone(&flash);
thread::spawn(move || {
bracketed_on(&flash, || {
for _ in 0..SPIN_WAITS {
let cvid = flash.next_condvar_id();
let deadline = flash.clock.now_nanos() + NANOS_PER_SEC;
let (token, adv, wait) = flash.register_condvar_timed(deadline, cvid);
let racer = {
let flash = Arc::clone(&flash);
thread::spawn(move || {
if xs(&mut seed) & 1 == 0 {
thread::yield_now();
}
flash.signal_condvar(cvid, true);
})
};
adv.fire();
token.wait();
wait.resume();
racer.join().expect("builder signal racer panicked");
}
});
})
};
let scheduler = {
let mut seed = round.wrapping_mul(40_503).wrapping_add(13);
let flash = Arc::clone(&flash);
thread::spawn(move || {
bracketed_on(&flash, || {
let me = super::ids::ThreadKey::of(thread::current().id());
for _ in 0..SPIN_WAITS {
let racer = {
let flash = Arc::clone(&flash);
thread::spawn(move || {
if xs(&mut seed) & 1 == 0 {
thread::yield_now();
}
flash.unpark(me);
})
};
flash.park_timed_unparkable(Duration::from_secs(2), me);
racer.join().expect("scheduler unpark racer panicked");
}
});
})
};
let handles: Vec<_> = (0..K)
.map(|idx| {
let kind = (round.wrapping_mul(31).wrapping_add(idx)) % 3;
let cvid = flash.next_condvar_id();
if kind == 2 {
untimed_cvids.push(cvid);
untimed_left.fetch_add(1, Ordering::Relaxed);
}
let mut seed = round.wrapping_mul(1_000_003).wrapping_add(idx + 1);
let left = Arc::clone(&untimed_left);
let flash = Arc::clone(&flash);
thread::spawn(move || {
bracketed_on(&flash, || match kind {
0 => {
let me = super::ids::ThreadKey::of(thread::current().id());
let racer = {
let flash = Arc::clone(&flash);
thread::spawn(move || {
if xs(&mut seed) & 1 == 0 {
thread::yield_now();
}
flash.unpark(me);
})
};
flash.park_timed_unparkable(Duration::from_secs(1 + (idx % 4)), me);
racer.join().expect("unpark racer panicked");
}
1 => {
let deadline =
flash.clock.now_nanos() + (1 + (idx % 4)) * NANOS_PER_SEC;
let (token, adv, wait) = flash.register_condvar_timed(deadline, cvid);
adv.fire();
let racer = {
let flash = Arc::clone(&flash);
thread::spawn(move || {
if xs(&mut seed) & 1 == 0 {
thread::yield_now();
}
flash.signal_condvar(cvid, true);
})
};
token.wait();
wait.resume();
racer.join().expect("signal racer panicked");
}
_ => {
let (token, adv, wait) = flash.register_condvar_untimed(cvid);
adv.fire();
token.wait();
wait.resume();
left.fetch_sub(1, Ordering::Relaxed);
}
});
})
})
.collect();
drop(coordinator);
while untimed_left.load(Ordering::Relaxed) != 0 {
for cvid in &untimed_cvids {
flash.signal_condvar(*cvid, true);
}
thread::yield_now();
}
spin.join().expect("busy-spin worker panicked");
builder.join().expect("decoder-build-like worker panicked");
scheduler.join().expect("scheduler-like worker panicked");
for h in handles {
h.join().expect("stress worker panicked");
}
assert_eq!(flash.active_count(), 0, "round {round}: active leaked");
assert_eq!(flash.timed_count(), 0, "round {round}: timed waiter leaked");
assert_eq!(flash.indef_count(), 0, "round {round}: indef waiter leaked");
}
assert!(
real_start.elapsed() < Duration::from_secs(30),
"stress mix stalled (a lost wakeup hangs a join far beyond thread-churn cost)"
);
}
#[test]
fn stress_advance_log_is_deterministic_across_runs() {
let flash = FlashInner::new_arc();
let secs = [6_u64, 2, 9, 2, 4, 9, 1];
let real_start = RealInstant::now();
let run = || {
flash.reset();
let base = flash.clock.now_nanos();
let coordinator = flash.test_hold();
let handles: Vec<_> = secs
.iter()
.copied()
.map(|s| {
let flash = Arc::clone(&flash);
thread::spawn(move || {
bracketed_on(&flash, || {
let cvid = flash.next_condvar_id();
let deadline = flash.clock.now_nanos() + s * NANOS_PER_SEC;
let (token, adv, wait) = flash.register_condvar_timed(deadline, cvid);
adv.fire();
token.wait();
wait.resume();
});
})
})
.collect();
while flash.timed_count() != secs.len() {
thread::yield_now();
}
drop(coordinator);
for h in handles {
h.join().expect("waiter panicked");
}
(base, flash.clock.now_nanos(), flash.advance_log())
};
let (base_a, end_a, log_a) = run();
let (base_b, end_b, log_b) = run();
assert_fast(real_start);
assert_eq!(base_a, base_b, "reset restores the same origin");
assert_eq!(log_a, log_b, "advance sequence identical run-to-run");
assert_eq!(
log_a,
vec![
base_a + NANOS_PER_SEC,
base_a + 2 * NANOS_PER_SEC,
base_a + 4 * NANOS_PER_SEC,
base_a + 6 * NANOS_PER_SEC,
base_a + 9 * NANOS_PER_SEC,
],
"min-jump over the deadline multiset",
);
assert_eq!(
end_a,
base_a + 9 * NANOS_PER_SEC,
"clock lands on max deadline"
);
assert_eq!(end_b, base_b + 9 * NANOS_PER_SEC);
}
#[test]
fn ambient_off_yield_now_is_real_passthrough() {
let _g = guard();
reset();
assert!(!flash_enabled());
let waker = Waker::from(Arc::new(NoopWake));
let mut cx = Context::from_waker(&waker);
let mut task = Box::pin(participate(
async {
yield_now().await;
},
Location::caller(),
));
assert!(task.as_mut().poll(&mut cx).is_pending());
assert_eq!(
sched::diag_yield_count(),
0,
"ambient-off yield must not register an engine yield-waiter"
);
assert!(
task.as_mut().poll(&mut cx).is_ready(),
"ambient-off yield must resolve on the next poll without an engine advance"
);
}
#[test]
fn ambient_on_yield_now_is_engine_backed() {
let _g = guard();
reset();
let _a = ambient_scope(true);
let waker = Waker::from(Arc::new(NoopWake));
let mut cx = Context::from_waker(&waker);
let mut task = Box::pin(participate(
async {
yield_now().await;
},
Location::caller(),
));
assert!(task.as_mut().poll(&mut cx).is_pending());
assert!(task.as_mut().poll(&mut cx).is_ready());
}
#[test]
fn ambient_blocking_closure_pins_virtual_clock() {
let _g = guard();
reset();
let _a = ambient_scope(true);
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.expect("build current-thread runtime");
let _rt = rt.enter();
let entered = Arc::new(AtomicUsize::new(0));
let release = Arc::new(AtomicUsize::new(0));
let entered_in = Arc::clone(&entered);
let release_in = Arc::clone(&release);
let handle = crate::tokio::task::spawn_blocking(move || {
entered_in.store(1, Ordering::Release);
while release_in.load(Ordering::Acquire) == 0 {
std::hint::spin_loop();
}
});
while entered.load(Ordering::Acquire) == 0 {
thread::yield_now();
}
let release_timer = Arc::clone(&release);
let releaser = thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
release_timer.store(1, Ordering::Release);
});
let start = RealInstant::now();
sched::park_for(Duration::from_millis(10));
let waited = start.elapsed();
releaser.join().expect("releaser thread");
rt.block_on(handle).expect("blocking closure joined");
assert!(
waited >= Duration::from_millis(40),
"virtual clock advanced past a 10ms deadline while an ambient blocking \
closure was still running (park returned after {waited:?} real)"
);
}
#[test]
fn unpark_from_flash_callstack_reaches_real_parked_thread() {
let _g = guard();
reset();
let start = RealInstant::now();
let (tx, rx) = mpsc::channel();
let target = thread::spawn(move || {
tx.send(thread::current()).expect("send park handle");
crate::thread::park_timeout(Duration::from_secs(5));
});
let handle = rx.recv().expect("recv park handle");
let _a = ambient_scope(true);
let _f = enter_dynamic(true);
crate::thread::unpark(&handle);
target.join().expect("real-parked target panicked");
assert_fast(start);
}
#[test]
fn park_for_keeps_firer_bump_unsettled() {
let flash = FlashInner::new_arc();
credit::reset_credit();
flash.park_for(Duration::from_millis(3));
assert_eq!(
flash.active_count(),
1,
"park_for must resume via bare mark_running; a resume() conversion settles the bump to 0"
);
credit::reset_credit();
}