feat(causal): wall-anchored send_after — user-facing timer opt-out (RFC 007)
send_after_wall / send_after_named_wall (+ Timers::insert_send_wall) arm a
message-delivery timer that opts out of the RFC 007 virtual-time shift and
fires at its raw deadline regardless of injected delay — the Send-reason
sibling of sleep_wall, closing the jar item whose substrate efbc254 landed.
For deadlines that reflect the outside world (protocol timeouts, wall-clock
schedules) rather than workload pacing. cancel_timer is anchor-agnostic and
unchanged; without the feature the API exists and is identical to send_after.
The gen_server timer layer (send_after_to, RFC 015 §5) deliberately stays
virtual-only — an opt-out there means new options on the gen_server/statem
timeout API, out of scope for now.
Tests: wall send fires at raw deadline while a virtual sibling shifts;
cancel on a wall send with debt outstanding; featureless delivery/cancel
smokes through the public named API. 15/15 causal, 34/34 binaries both
feature configs, lib clippy clean both.
This commit is contained in:
@@ -405,3 +405,60 @@ fn send_after_to_dead_typed_pid_is_silent() {
|
||||
assert_eq!(report_rx.try_recv(), Ok(None));
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Wall-anchored send_after (RFC 007 user-facing opt-out). The API exists in
|
||||
// both feature configs; featureless it is behaviourally identical to
|
||||
// `send_after` — these tests pin exactly that.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn armed_wall_send_timer_is_returned_and_fires() {
|
||||
let mut t = Timers::new();
|
||||
let now = Instant::now();
|
||||
let fired = Arc::new(AtomicBool::new(false));
|
||||
let f = fired.clone();
|
||||
let _id = t.insert_send_wall(
|
||||
now + Duration::from_millis(10),
|
||||
Pid::new(0, 0),
|
||||
Box::new(move || f.store(true, Ordering::SeqCst)),
|
||||
);
|
||||
|
||||
let mut due = t.pop_due(now + Duration::from_millis(20));
|
||||
assert_eq!(due.len(), 1, "an armed wall send timer should pop when due");
|
||||
run_fire(due.pop().unwrap());
|
||||
assert!(fired.load(Ordering::SeqCst), "running the thunk delivers");
|
||||
assert!(t.is_empty());
|
||||
}
|
||||
|
||||
use smarm::send_after_named_wall;
|
||||
|
||||
#[test]
|
||||
fn send_after_named_wall_delivers_after_the_delay() {
|
||||
const WPING: Name<u64> = Name::new("send_after_wall_ping");
|
||||
run(|| {
|
||||
let (tx, rx) = channel::<u64>();
|
||||
register(WPING, tx).unwrap();
|
||||
let t0 = Instant::now();
|
||||
let _id = send_after_named_wall(Duration::from_millis(30), WPING, 99);
|
||||
assert_eq!(rx.recv().unwrap(), 99);
|
||||
assert!(
|
||||
t0.elapsed() >= Duration::from_millis(25),
|
||||
"delivered too early: {:?}",
|
||||
t0.elapsed()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_after_named_wall_cancels() {
|
||||
const WC: Name<u64> = Name::new("send_after_wall_cancel");
|
||||
run(|| {
|
||||
let (tx, rx) = channel::<u64>();
|
||||
register(WC, tx).unwrap();
|
||||
let id = send_after_named_wall(Duration::from_millis(50), WC, 7);
|
||||
assert!(cancel_timer(id), "cancel before fire returns true");
|
||||
sleep(Duration::from_millis(90));
|
||||
assert_eq!(rx.try_recv(), Ok(None), "cancelled wall timer delivered");
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user