gen_server: TimerHandle arm_after/cancel + Sys::Timer dispatch (RFC 015 §4.3, §5)
This commit is contained in:
@@ -443,3 +443,95 @@ fn unused_ctx_closes_control_arm_silently() {
|
||||
});
|
||||
assert_eq!(*got.lock().unwrap(), 42);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RFC 015 — gen_server timers. A server that arms one-shot timers from a cast
|
||||
// and records each fire's payload, plus the cancel race signal.
|
||||
// ---------------------------------------------------------------------------
|
||||
use smarm::gen_server::{ServerCtx, TimerHandle};
|
||||
use smarm::TimerId;
|
||||
|
||||
enum TkCast {
|
||||
Arm(Duration),
|
||||
CancelLast,
|
||||
}
|
||||
|
||||
struct Timed {
|
||||
timer: Option<TimerHandle<Self>>,
|
||||
fired: Arc<Mutex<Vec<u32>>>,
|
||||
cancel_won: Arc<Mutex<Option<bool>>>,
|
||||
last: Option<TimerId>,
|
||||
}
|
||||
|
||||
impl GenServer for Timed {
|
||||
type Call = ();
|
||||
type Reply = usize; // count of fires so far (a sync read point)
|
||||
type Cast = TkCast;
|
||||
type Info = ();
|
||||
type Timer = u32;
|
||||
|
||||
fn init(&mut self, ctx: &ServerCtx<Self>) {
|
||||
self.timer = Some(ctx.timer());
|
||||
}
|
||||
|
||||
fn handle_call(&mut self, _: ()) -> usize {
|
||||
self.fired.lock().unwrap().len()
|
||||
}
|
||||
|
||||
fn handle_cast(&mut self, c: TkCast) {
|
||||
let t = self.timer.as_ref().expect("init ran first");
|
||||
match c {
|
||||
TkCast::Arm(d) => self.last = Some(t.arm_after(d, 7)),
|
||||
TkCast::CancelLast => {
|
||||
let id = self.last.take().expect("a timer was armed");
|
||||
*self.cancel_won.lock().unwrap() = Some(t.cancel(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_timer(&mut self, msg: u32) {
|
||||
self.fired.lock().unwrap().push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn timed(fired: Arc<Mutex<Vec<u32>>>, cancel_won: Arc<Mutex<Option<bool>>>) -> Timed {
|
||||
Timed { timer: None, fired, cancel_won, last: None }
|
||||
}
|
||||
|
||||
// A one-shot armed from a handler fires into handle_timer with its payload.
|
||||
#[test]
|
||||
fn arm_after_fires_into_handle_timer() {
|
||||
let fired = Arc::new(Mutex::new(Vec::new()));
|
||||
let f2 = fired.clone();
|
||||
run(move || {
|
||||
let cw = Arc::new(Mutex::new(None));
|
||||
let server = start(timed(f2, cw));
|
||||
server.cast(TkCast::Arm(Duration::from_millis(10))).unwrap();
|
||||
let _ = server.call(()).unwrap(); // sync: arm done
|
||||
smarm::sleep(Duration::from_millis(40)); // let the timer fire
|
||||
let count = server.call(()).unwrap(); // timer arm outranks this inbox call
|
||||
assert_eq!(count, 1, "the one-shot should have fired exactly once");
|
||||
});
|
||||
assert_eq!(*fired.lock().unwrap(), vec![7]);
|
||||
}
|
||||
|
||||
// cancel before the deadline wins the race (returns true) and suppresses the
|
||||
// fire entirely.
|
||||
#[test]
|
||||
fn cancel_before_fire_suppresses_it() {
|
||||
let fired = Arc::new(Mutex::new(Vec::new()));
|
||||
let cancel_won = Arc::new(Mutex::new(None));
|
||||
let f2 = fired.clone();
|
||||
let c2 = cancel_won.clone();
|
||||
run(move || {
|
||||
let server = start(timed(f2, c2));
|
||||
server.cast(TkCast::Arm(Duration::from_millis(50))).unwrap();
|
||||
server.cast(TkCast::CancelLast).unwrap();
|
||||
let _ = server.call(()).unwrap(); // sync: arm + cancel both handled
|
||||
smarm::sleep(Duration::from_millis(80)); // past the original deadline
|
||||
let count = server.call(()).unwrap();
|
||||
assert_eq!(count, 0, "cancelled timer must not fire");
|
||||
});
|
||||
assert_eq!(*cancel_won.lock().unwrap(), Some(true), "cancel beat the fire");
|
||||
assert!(fired.lock().unwrap().is_empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user