Skip to main content

nuclei/time/
precise.rs

1//! [`SleepPrecise`]: a sleep that ends at its deadline to the microsecond,
2//! off the millisecond wheel.
3//!
4//! A precise timer is armed in its shard's microsecond wheel (the same wheel
5//! as the millisecond one, counting microseconds), and the shard's wait ends
6//! at the earliest deadline of either wheel through the backend's
7//! high-resolution timer: the wait's own timeout on io_uring (its hrtimer has
8//! no slack) and on Windows (a high-resolution waitable timer), a timerfd on
9//! epoll (whose own timeout counts milliseconds), and an `EVFILT_TIMER` in
10//! nanoseconds on kqueue. A paused clock moves to a precise deadline exactly.
11
12use core::fmt;
13use core::future::Future;
14use core::pin::Pin;
15use core::task::{Context, Poll};
16use std::time::Duration;
17
18use super::Instant;
19use super::sleep::{Fine, Timer};
20
21/// Waits until `duration` has passed, to the microsecond.
22///
23/// The wait is at least `duration`, rounded up to the microsecond, and ends
24/// as soon after that as the system's timer wakes the shard; a busy shard
25/// may let it run later by the time its current task takes to yield. Use it
26/// where the millisecond of [`sleep`](super::sleep) is too coarse: a pacer, a
27/// retry spaced in microseconds, a deadline a protocol counts in
28/// microseconds. Each precise deadline that is the shard's earliest costs
29/// the shard a timer of its own in the kernel (one system call to arm it on
30/// epoll), so a coarse sleep stays the cheaper choice where a millisecond is
31/// fine.
32///
33/// ```
34/// use std::time::Duration;
35/// use nuclei::time::{self, Instant};
36///
37/// # nuclei::Builder::new().worker_threads(0).build().unwrap().block_on(async {
38/// time::pause();
39/// let start = Instant::now();
40/// time::sleep_precise(Duration::from_micros(250)).await;
41/// // A paused clock moves to the deadline itself: not a millisecond later.
42/// assert_eq!(start.elapsed(), Duration::from_micros(250));
43/// # });
44/// ```
45#[inline]
46pub fn sleep_precise(duration: Duration) -> SleepPrecise {
47    SleepPrecise {
48        timer: Timer::after(duration),
49    }
50}
51
52/// Waits until `deadline`, to the microsecond (see [`sleep_precise`]).
53pub fn sleep_until_precise(deadline: impl Into<Instant>) -> SleepPrecise {
54    SleepPrecise {
55        timer: Timer::new(deadline.into()),
56    }
57}
58
59/// A future that completes at its deadline, to the microsecond. Made by
60/// [`sleep_precise`] and [`sleep_until_precise`].
61///
62/// Polled on one of nuclei's shards, it arms a timer in that shard's
63/// microsecond wheel; polled anywhere else (another runtime's thread), it
64/// asks a shard of the current runtime (or the default one) to fire it.
65/// Dropping it cancels the timer.
66#[must_use = "a sleep does nothing unless awaited"]
67pub struct SleepPrecise {
68    timer: Timer<Fine>,
69}
70
71impl SleepPrecise {
72    /// The deadline.
73    pub fn deadline(&self) -> Instant {
74        self.timer.deadline()
75    }
76
77    /// Whether the deadline has passed.
78    pub fn is_elapsed(&self) -> bool {
79        self.timer.is_elapsed()
80    }
81
82    /// Moves the deadline; the sleep is pending again until it.
83    pub fn reset(self: Pin<&mut Self>, deadline: impl Into<Instant>) {
84        self.get_mut().timer.reset(deadline.into());
85    }
86}
87
88impl Future for SleepPrecise {
89    type Output = ();
90
91    #[inline(always)]
92    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
93        self.get_mut().timer.poll(cx)
94    }
95}
96
97impl fmt::Debug for SleepPrecise {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        f.debug_struct("SleepPrecise")
100            .field("deadline", &self.deadline())
101            .field("elapsed", &self.is_elapsed())
102            .finish()
103    }
104}