Skip to main content

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 `at`.
71	pub fn at(runtime: &R, at: Instant) -> Self {
72		let mut deadline = Self::new(runtime);
73		deadline.set(Some(at));
74		deadline
75	}
76
77	/// A deadline armed for `duration` past the runtime's [`now`](Timers::now).
78	///
79	/// A duration the clock cannot represent (e.g. [`std::time::Duration::MAX`])
80	/// leaves the deadline disarmed, so it never fires rather than panicking on
81	/// the overflow.
82	pub fn after(runtime: &R, duration: std::time::Duration) -> Self {
83		let mut deadline = Self::new(runtime);
84		deadline.set(runtime.now().checked_add(duration));
85		deadline
86	}
87
88	/// Arm, re-arm, or disarm (`None`) the deadline.
89	pub fn set(&mut self, at: Option<Instant>) {
90		if self.at == at {
91			return;
92		}
93		self.at = at;
94		self.timer.set(at);
95	}
96
97	/// The instant this fires at, or `None` while disarmed.
98	pub fn deadline(&self) -> Option<Instant> {
99		self.at
100	}
101
102	/// Poll the deadline, registering `waiter` so the poll re-fires once it
103	/// elapses. `Ready` once the instant has passed, `Pending` before then and
104	/// while disarmed.
105	pub fn poll(&mut self, waiter: &kio::Waiter) -> Poll<()> {
106		if self.at.is_none() {
107			return Poll::Pending;
108		}
109		self.timer.poll(waiter)
110	}
111}
112
113impl<R: Timers> std::fmt::Debug for Deadline<R> {
114	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115		f.debug_struct("Deadline").field("at", &self.at).finish()
116	}
117}
118
119#[cfg(test)]
120mod test;
121#[cfg(test)]
122pub use test::Test;
123
124/// A tokio-backed runtime for this crate's own unit tests, so the existing
125/// `tokio::time::pause`/`advance` tests keep their semantics: `now` reads
126/// tokio's (pausable) clock and timers are tokio sleeps, which paused tests
127/// auto-advance. Production adapters live outside this crate; this one is
128/// compiled only into the test harness (integration tests carry their own copy
129/// in `tests/support`).
130#[cfg(all(test, not(target_family = "wasm")))]
131pub(crate) mod tokio_test {
132	use std::{pin::Pin, task::Poll};
133
134	use super::{Instant, Timer};
135
136	#[derive(Clone, Default)]
137	pub(crate) struct Tokio;
138
139	impl Tokio {
140		pub fn new() -> Self {
141			Self
142		}
143	}
144
145	impl super::Timers for Tokio {
146		type Timer = TokioTimer;
147
148		fn timer(&self) -> Self::Timer {
149			TokioTimer { at: None, sleep: None }
150		}
151
152		fn now(&self) -> Instant {
153			tokio::time::Instant::now().into_std()
154		}
155	}
156
157	pub(crate) struct TokioTimer {
158		at: Option<Instant>,
159		// Allocated on the first poll after arming, then re-armed in place via
160		// `Sleep::reset`. Construction is deferred because it panics without a
161		// live tokio time driver, and only the poll is guaranteed to run inside
162		// the runtime.
163		sleep: Option<Pin<Box<tokio::time::Sleep>>>,
164	}
165
166	impl Timer for TokioTimer {
167		fn set(&mut self, at: Option<Instant>) {
168			self.at = at;
169			// Reuse the allocation when there is one; `reset` also clears
170			// `is_elapsed`.
171			if let (Some(at), Some(sleep)) = (at, &mut self.sleep) {
172				sleep.as_mut().reset(tokio::time::Instant::from_std(at));
173			}
174		}
175
176		fn poll(&mut self, waiter: &kio::Waiter) -> Poll<()> {
177			let Some(at) = self.at else { return Poll::Pending };
178			let sleep = self
179				.sleep
180				.get_or_insert_with(|| Box::pin(tokio::time::sleep_until(tokio::time::Instant::from_std(at))));
181			if sleep.is_elapsed() {
182				return Poll::Ready(());
183			}
184			waiter.poll_future(sleep.as_mut())
185		}
186	}
187}