Skip to main content

hclient_core/unversioned/
timer.rs

1use core::time::Duration;
2use std::future::Future;
3use std::pin::Pin;
4use std::task::{Context, Poll};
5
6/// The one runtime capability the portable core needs: timeouts and
7/// backoff. Networking and spawning live in the transports.
8///
9/// Not `hyper::rt::Timer`: that one has `Sleep: Send + Sync` unconditionally,
10/// `sleep()` returns `Pin<Box<dyn Sleep>>` (an allocation per sleep), and
11/// `now()` is typed on `std::time::Instant`, which panics on
12/// `wasm32-unknown-unknown`.
13///
14/// # Why [`Timer::Sleep`] is an associated type and not `impl Future`
15///
16/// An RPITIT — `fn sleep(&self, d: Duration) -> impl Future<Output = ()>` —
17/// is more comfortable to write and costs two things:
18///
19/// - **A struct cannot hold a sleep.** It has no name to store, so a body
20///   wrapper can only check elapsed time on each `poll_frame`, which
21///   structurally cannot cut a response body that goes *completely* silent
22///   after the head: nothing wakes the wrapper, so nothing ever looks at
23///   the clock again. Measured with a counting waker and no executor
24///   running, that shape registers **zero** wakes; a stored sleep
25///   registers one. `hclient::body::Deadline` holds a
26///   `Pin<Box<Tm::Sleep>>` for exactly this reason.
27/// - **Generic code cannot spawn a background task.**
28///   `hclient_rt::Spawn<F>` takes the future as a type parameter, so a
29///   bound has to name it, and an anonymous future has no name. See
30///   `hclient-native`'s `pool` module doc.
31///
32/// It also hides a third thing, the one most likely to be mistaken for a
33/// bug: a backend whose native timer resolves to something other than
34/// `()`, which `async { t.await; }` discards silently. Naming the type
35/// makes that visible, and [`Discard`] is the adapter for it.
36///
37/// [`TcpConnect::Stream`](https://docs.rs/hclient-rt) is the same idea
38/// applied to a socket; this is not a new shape in the seam.
39pub trait Timer {
40    type Instant: Copy + PartialOrd;
41
42    /// The future [`Timer::sleep`] returns, **named**.
43    ///
44    /// `Send`ness is deliberately not required here, exactly as it is not
45    /// required of `Timer` itself: a caller that needs a `Send` sleep gets
46    /// it because its own clock's `Sleep` happens to be `Send`, inferred
47    /// rather than declared.
48    type Sleep: Future<Output = ()>;
49
50    fn sleep(&self, d: Duration) -> Self::Sleep;
51    fn now(&self) -> Self::Instant;
52    fn elapsed_since(&self, earlier: Self::Instant) -> Duration;
53}
54
55/// Adapts a future that resolves to *something* into one that resolves to
56/// `()`, for use as a [`Timer::Sleep`].
57///
58/// **This is not redundant, and it is not a mistake.** Two of this
59/// project's clocks have a native timer whose `Output` is not `()`:
60/// `async_io::Timer` resolves to the `std::time::Instant` at which it
61/// fired, and `hclient-fetch`'s `SendJsFuture` resolves to
62/// `Result<JsValue, JsValue>`. While [`Timer::sleep`] was an RPITIT both
63/// were discarded invisibly inside an `async` block; with a named
64/// associated type the discard has to be written down, and this is where
65/// it is written down once instead of twice.
66///
67/// `F: Unpin` rather than a pin projection: every timer this wraps is
68/// `Unpin` already, and this workspace forbids `unsafe`, so the safe
69/// projection is the only one available and the bound is honest about it.
70#[derive(Debug, Clone, Copy)]
71pub struct Discard<F>(pub F);
72
73impl<F: Future + Unpin> Future for Discard<F> {
74    type Output = ();
75
76    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
77        Pin::new(&mut self.0).poll(cx).map(|_| ())
78    }
79}