moq_net/runtime.rs
1//! Private deadlines driven by the owning driver's supplied clock.
2
3use std::task::Poll;
4
5/// The instant type used for deadlines and annotations.
6///
7/// [`std::time::Instant`] on native. The browser has no monotonic std clock, so
8/// wasm substitutes an equivalent backed by `performance.now()`.
9#[cfg(not(target_family = "wasm"))]
10pub type Instant = std::time::Instant;
11/// The instant type used for deadlines and annotations (wasm shim).
12#[cfg(target_family = "wasm")]
13pub type Instant = web_async::time::Instant;
14
15/// A single re-armable timer registration, produced by [`Timers::timer`].
16///
17/// This is the primitive [`Deadline`] wraps; implement it, use `Deadline`.
18/// Arming is synchronous and in-memory: no I/O submission, no async
19/// cancellation. Implementations typically keep a slot in the runtime's timer
20/// wheel (or wrap a tokio `Sleep`).
21pub trait Timer {
22 /// Arm, re-arm, or disarm (`None`) the timer.
23 ///
24 /// Re-arming an elapsed timer for a later instant makes it pend again;
25 /// re-arming for an instant already in the past leaves it elapsed.
26 fn set(&mut self, at: Option<Instant>);
27
28 /// Ready once the armed instant has passed, registering `waiter` otherwise.
29 ///
30 /// Fused: an elapsed timer keeps reporting `Ready` until re-armed. A
31 /// disarmed timer never fires.
32 fn poll(&mut self, waiter: &kio::Waiter) -> Poll<()>;
33}
34
35/// The timer half of a runtime: mint [`Timer`]s and read the clock they follow.
36///
37/// Shared by session and origin drivers, independently of their transport or
38/// executor. Clones must be cheap (a ZST or a reference count).
39pub trait Timers: Clone {
40 /// The timer registration this runtime hands out.
41 type Timer: Timer;
42
43 /// A new, disarmed timer.
44 fn timer(&self) -> Self::Timer;
45
46 /// The latest instant supplied by the owner.
47 fn now(&self) -> Instant;
48}
49
50/// A wall-clock deadline: the ergonomic layer over [`Timer`].
51///
52/// Arm it with an [`Instant`], poll it from a `poll_*` function, re-arm or
53/// disarm as the deadline moves. Re-setting the instant it already holds does
54/// nothing, so a poll loop can recompute its deadline every turn without
55/// restarting the countdown.
56pub struct Deadline<R: Timers> {
57 at: Option<Instant>,
58 timer: R::Timer,
59}
60
61impl<R: Timers> Deadline<R> {
62 /// A disarmed deadline, which never fires until [`set`](Self::set) arms it.
63 pub fn new(runtime: &R) -> Self {
64 Self {
65 at: None,
66 timer: runtime.timer(),
67 }
68 }
69
70 /// A deadline armed for `duration` past the runtime's [`now`](Timers::now).
71 ///
72 /// A duration the clock cannot represent (e.g. [`std::time::Duration::MAX`])
73 /// leaves the deadline disarmed, so it never fires rather than panicking on
74 /// the overflow.
75 pub fn after(runtime: &R, duration: std::time::Duration) -> Self {
76 let mut deadline = Self::new(runtime);
77 deadline.set(runtime.now().checked_add(duration));
78 deadline
79 }
80
81 /// Arm, re-arm, or disarm (`None`) the deadline.
82 pub fn set(&mut self, at: Option<Instant>) {
83 if self.at == at {
84 return;
85 }
86 self.at = at;
87 self.timer.set(at);
88 }
89
90 /// The instant this fires at, or `None` while disarmed.
91 #[cfg(test)]
92 pub fn deadline(&self) -> Option<Instant> {
93 self.at
94 }
95
96 /// Poll the deadline, registering `waiter` so the poll re-fires once it
97 /// elapses. `Ready` once the instant has passed, `Pending` before then and
98 /// while disarmed.
99 pub fn poll(&mut self, waiter: &kio::Waiter) -> Poll<()> {
100 if self.at.is_none() {
101 return Poll::Pending;
102 }
103 self.timer.poll(waiter)
104 }
105}
106
107impl<R: Timers> std::fmt::Debug for Deadline<R> {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("Deadline").field("at", &self.at).finish()
110 }
111}
112
113#[cfg(test)]
114mod test;
115#[cfg(test)]
116pub use test::Test;
117
118/// A tokio-backed runtime for this crate's own unit tests, so the existing
119/// `tokio::time::pause`/`advance` tests keep their semantics: `now` reads
120/// tokio's (pausable) clock and timers are tokio sleeps, which paused tests
121/// auto-advance. Production adapters live outside this crate; this one is
122/// compiled only into the test harness (integration tests carry their own copy
123/// in `tests/support`).
124#[cfg(all(test, not(target_family = "wasm")))]
125pub(crate) mod tokio_test {
126 use std::{pin::Pin, task::Poll};
127
128 use super::{Instant, Timer};
129
130 #[derive(Clone, Default)]
131 pub(crate) struct Tokio;
132
133 impl Tokio {
134 pub fn new() -> Self {
135 Self
136 }
137 }
138
139 impl super::Timers for Tokio {
140 type Timer = TokioTimer;
141
142 fn timer(&self) -> Self::Timer {
143 TokioTimer { at: None, sleep: None }
144 }
145
146 fn now(&self) -> Instant {
147 tokio::time::Instant::now().into_std()
148 }
149 }
150
151 pub(crate) struct TokioTimer {
152 at: Option<Instant>,
153 // Allocated on the first poll after arming, then re-armed in place via
154 // `Sleep::reset`. Construction is deferred because it panics without a
155 // live tokio time driver, and only the poll is guaranteed to run inside
156 // the runtime.
157 sleep: Option<Pin<Box<tokio::time::Sleep>>>,
158 }
159
160 impl Timer for TokioTimer {
161 fn set(&mut self, at: Option<Instant>) {
162 self.at = at;
163 // Reuse the allocation when there is one; `reset` also clears
164 // `is_elapsed`.
165 if let (Some(at), Some(sleep)) = (at, &mut self.sleep) {
166 sleep.as_mut().reset(tokio::time::Instant::from_std(at));
167 }
168 }
169
170 fn poll(&mut self, waiter: &kio::Waiter) -> Poll<()> {
171 let Some(at) = self.at else { return Poll::Pending };
172 let sleep = self
173 .sleep
174 .get_or_insert_with(|| Box::pin(tokio::time::sleep_until(tokio::time::Instant::from_std(at))));
175 if sleep.is_elapsed() {
176 return Poll::Ready(());
177 }
178 waiter.poll_future(sleep.as_mut())
179 }
180 }
181}