feat(causal): wall-anchored timers — controller windows keep fixed wall length (RFC 007)
New timer anchor: insert_sleep_wall / scheduler::sleep_wall (exported) opt a Sleep entry out of the RFC 007 virtual-time shift, so it fires at its raw deadline regardless of injected delay. Featureless config is unchanged (the API exists but is identical to sleep). The causal controller's window/cooldown sleeps and the tsc_hz calibration sleep use it on the actor path (the OS-thread path was already wall). This fixes the controller's own sleeps dilating under its own injection — experiment windows stretched ~2x at 50% speedup (337ms -> 646ms injected/ window). Deltas were rate-normalized so results were unbiased; this fixes sweep cost, not bias. The general wall-anchored-timer-semantics jar item (user-facing opt-out) remains open; this lands the substrate. Test: wall_timer_ignores_injected_delay — wall entry fires at raw deadline while a virtual sibling in the same heap shifts. 13/13 causal, 34/34 binaries both feature configs.
This commit is contained in:
@@ -461,6 +461,34 @@ fn timer_zero_debt_pops_at_raw_deadline() {
|
||||
assert!(t.is_empty());
|
||||
}
|
||||
|
||||
/// A wall-anchored timer (RFC 007 controller fix) fires at its raw deadline
|
||||
/// regardless of injected delay, while a virtual sibling in the same heap
|
||||
/// still shifts. This is what keeps the causal controller's experiment
|
||||
/// windows a fixed wall length even under heavy injection.
|
||||
#[test]
|
||||
fn wall_timer_ignores_injected_delay() {
|
||||
let _s = serial();
|
||||
use smarm::pid::Pid;
|
||||
use smarm::timer::Timers;
|
||||
use std::time::Instant;
|
||||
|
||||
let hz = smarm::causal::tsc_hz();
|
||||
let mut t = Timers::new();
|
||||
let now = Instant::now();
|
||||
t.insert_sleep_wall(now + Duration::from_millis(50), Pid::new(0, 0), 1);
|
||||
t.insert_sleep(now + Duration::from_millis(50), Pid::new(1, 0), 1);
|
||||
|
||||
// 100ms of debt lands while both are pending.
|
||||
smarm::causal::inject_delay_cycles_for_test((hz * 0.100) as u64);
|
||||
|
||||
// Just past the raw deadline: the wall entry fires, the virtual one is
|
||||
// re-queued at its shifted deadline.
|
||||
let due = t.pop_due(now + Duration::from_millis(60));
|
||||
assert_eq!(due.len(), 1, "exactly the wall entry must fire at raw deadline");
|
||||
assert_eq!(due[0].pid, Pid::new(0, 0));
|
||||
assert!(!t.is_empty(), "virtual sibling must remain queued, shifted");
|
||||
}
|
||||
|
||||
/// Re-queueing a shifted entry must preserve its identity: a `send_after`
|
||||
/// cancelled *after* being shifted past its raw deadline must still cancel
|
||||
/// (return true) and must never deliver.
|
||||
|
||||
Reference in New Issue
Block a user