Skip to main content

ntex_util/time/
mod.rs

1//! Utilities for tracking time.
2//!
3//! Use [`sleep`], [`deadline`] or [`interval`] for asynchronous waits. To run a
4//! short synchronous callback without polling a timer future, retain a
5//! [`CallbackTimer`].
6#![allow(
7    clippy::cast_possible_truncation,
8    clippy::cast_sign_loss,
9    clippy::cast_possible_wrap
10)]
11use std::{cmp, future::Future, future::poll_fn, pin::Pin, task, task::Poll};
12
13mod callbacks;
14mod types;
15mod wheel;
16
17pub use self::callbacks::CallbackTimer;
18pub use self::types::{Millis, Seconds};
19pub use self::wheel::{TimerHandle, now, system_time};
20
21/// Waits until `dur` has elapsed.
22///
23/// No work is performed while awaiting the returned [`Sleep`]. Timers have a
24/// granularity of approximately 16 milliseconds and are not suitable for
25/// high-resolution timing. A zero duration still waits for at least one timer
26/// tick.
27#[inline]
28pub fn sleep<T: Into<Millis>>(dur: T) -> Sleep {
29    Sleep::new(dur.into())
30}
31
32/// Waits until `dur` has elapsed.
33///
34/// Unlike [`sleep`], a zero-duration deadline never completes.
35#[inline]
36pub fn deadline<T: Into<Millis>>(dur: T) -> Deadline {
37    Deadline::new(dur.into())
38}
39
40/// Creates an [`Interval`] that ticks every `period`.
41///
42/// The first tick completes immediately, and each later tick completes
43/// `period` after the previous one was observed. An interval will tick
44/// indefinitely. At any time, the [`Interval`] value can be dropped. This
45/// cancels the interval.
46#[inline]
47pub fn interval<T: Into<Millis>>(period: T) -> Interval {
48    Interval::new(period.into())
49}
50
51/// Requires a future to complete before `dur` has elapsed.
52///
53/// If the future completes before the duration has elapsed, then the completed
54/// value is returned. Otherwise, an error is returned and the future is
55/// canceled. A zero duration still represents an active timeout of at least
56/// one timer tick; use [`timeout_checked`] to disable the timeout with zero. If
57/// the future and timer are both ready during the same poll, the future wins.
58#[inline]
59pub fn timeout<T, U>(dur: U, future: T) -> Timeout<T>
60where
61    T: Future,
62    U: Into<Millis>,
63{
64    Timeout::new_with_delay(future, Sleep::new(dur.into()))
65}
66
67/// Requires a future to complete before `dur` has elapsed.
68///
69/// If the future completes before the duration has elapsed, then the completed
70/// value is returned. Otherwise, an error is returned and the future is
71/// canceled. A zero duration disables the timeout.
72#[inline]
73pub fn timeout_checked<T, U>(dur: U, future: T) -> TimeoutChecked<T>
74where
75    T: Future,
76    U: Into<Millis>,
77{
78    TimeoutChecked::new_with_delay(future, dur.into())
79}
80
81/// Future returned by [`sleep`].
82///
83/// # Examples
84///
85/// Wait 100ms and print "100 ms have elapsed".
86///
87/// ```
88/// use ntex::time::{sleep, Millis};
89///
90/// #[ntex::main]
91/// async fn main() {
92///     sleep(Millis(100)).await;
93///     println!("100 ms have elapsed");
94/// }
95/// ```
96#[derive(Debug)]
97#[must_use = "futures do nothing unless you `.await` or poll them"]
98pub struct Sleep {
99    // The link between the `Sleep` instance and the timer that drives it.
100    hnd: TimerHandle,
101}
102
103impl Sleep {
104    /// Creates a new sleep future.
105    ///
106    /// A zero duration is rounded up to 1 ms.
107    #[inline]
108    pub fn new(duration: Millis) -> Sleep {
109        Sleep {
110            hnd: TimerHandle::new(u64::from(cmp::max(duration.0, 1))),
111        }
112    }
113
114    /// Returns `true` if `Sleep` has elapsed.
115    #[inline]
116    pub fn is_elapsed(&self) -> bool {
117        self.hnd.is_elapsed()
118    }
119
120    /// Completes the timer immediately.
121    #[inline]
122    pub fn elapse(&self) {
123        self.hnd.elapse();
124    }
125
126    /// Resets the `Sleep` instance to a new deadline.
127    ///
128    /// Calling this function allows changing the instant at which the `Sleep`
129    /// future completes without having to create new associated state.
130    ///
131    /// This function can be called both before and after the future has
132    /// completed. A zero duration is rounded up to 1 ms.
133    pub fn reset<T: Into<Millis>>(&self, millis: T) {
134        self.hnd.reset(u64::from(cmp::max(millis.into().0, 1)));
135    }
136
137    #[inline]
138    /// Waits until this timer has elapsed.
139    pub async fn wait(&self) {
140        poll_fn(|cx| self.hnd.poll_elapsed(cx)).await;
141    }
142
143    #[inline]
144    /// Polls until this timer has elapsed.
145    pub fn poll_elapsed(&self, cx: &mut task::Context<'_>) -> Poll<()> {
146        self.hnd.poll_elapsed(cx)
147    }
148}
149
150impl Future for Sleep {
151    type Output = ();
152
153    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
154        self.hnd.poll_elapsed(cx)
155    }
156}
157
158/// Future returned by [`deadline`].
159///
160/// # Examples
161///
162/// Wait 100ms and print "100 ms have elapsed".
163///
164/// ```
165/// use ntex::time::{deadline, Millis};
166///
167/// #[ntex::main]
168/// async fn main() {
169///     deadline(Millis(100)).await;
170///     println!("100 ms have elapsed");
171/// }
172/// ```
173#[derive(Debug)]
174#[must_use = "futures do nothing unless you `.await` or poll them"]
175pub struct Deadline {
176    hnd: Option<TimerHandle>,
177}
178
179impl Deadline {
180    /// Creates a new deadline future.
181    ///
182    /// A zero duration creates a deadline that never completes.
183    #[inline]
184    pub fn new(duration: Millis) -> Deadline {
185        if duration.0 != 0 {
186            Deadline {
187                hnd: Some(TimerHandle::new(u64::from(duration.0))),
188            }
189        } else {
190            Deadline { hnd: None }
191        }
192    }
193
194    #[inline]
195    /// Waits until this deadline has elapsed.
196    pub async fn wait(&self) {
197        poll_fn(|cx| self.poll_elapsed(cx)).await;
198    }
199
200    /// Resets the `Deadline` instance to a new deadline.
201    ///
202    /// Calling this function allows changing the instant at which the `Deadline`
203    /// future completes without having to create new associated state.
204    ///
205    /// This function can be called both before and after the future has
206    /// completed.
207    pub fn reset<T: Into<Millis>>(&mut self, millis: T) {
208        let millis = millis.into();
209        if millis.0 != 0 {
210            if let Some(ref mut hnd) = self.hnd {
211                hnd.reset(u64::from(millis.0));
212            } else {
213                self.hnd = Some(TimerHandle::new(u64::from(millis.0)));
214            }
215        } else {
216            let _ = self.hnd.take();
217        }
218    }
219
220    /// Returns `true` if `Deadline` has elapsed.
221    ///
222    /// A disabled zero-duration deadline is reported as elapsed here even
223    /// though polling it remains pending. Use this method to determine whether
224    /// there is an active future deadline to wait for.
225    #[inline]
226    pub fn is_elapsed(&self) -> bool {
227        self.hnd.as_ref().is_none_or(TimerHandle::is_elapsed)
228    }
229
230    #[inline]
231    /// Polls until this deadline has elapsed.
232    ///
233    /// A zero-duration deadline remains pending until it is reset.
234    pub fn poll_elapsed(&self, cx: &mut task::Context<'_>) -> Poll<()> {
235        self.hnd
236            .as_ref()
237            .map_or(Poll::Pending, |t| t.poll_elapsed(cx))
238    }
239}
240
241impl Future for Deadline {
242    type Output = ();
243
244    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
245        self.poll_elapsed(cx)
246    }
247}
248
249pin_project_lite::pin_project! {
250    /// Future returned by [`timeout`](timeout).
251    #[must_use = "futures do nothing unless you `.await` or poll them"]
252    #[derive(Debug)]
253    pub struct Timeout<T> {
254        #[pin]
255        value: T,
256        delay: Sleep,
257    }
258}
259
260impl<T> Timeout<T> {
261    pub(crate) fn new_with_delay(value: T, delay: Sleep) -> Timeout<T> {
262        Timeout { value, delay }
263    }
264}
265
266impl<T> Future for Timeout<T>
267where
268    T: Future,
269{
270    type Output = Result<T::Output, ()>;
271
272    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
273        let this = self.project();
274
275        // First, try polling the future
276        if let Poll::Ready(v) = this.value.poll(cx) {
277            return Poll::Ready(Ok(v));
278        }
279
280        // Now check the timer
281        match this.delay.poll_elapsed(cx) {
282            Poll::Ready(()) => Poll::Ready(Err(())),
283            Poll::Pending => Poll::Pending,
284        }
285    }
286}
287
288pin_project_lite::pin_project! {
289    /// Future returned by [`timeout_checked`](timeout_checked).
290    #[must_use = "futures do nothing unless you `.await` or poll them"]
291    pub struct TimeoutChecked<T> {
292        #[pin]
293        state: TimeoutCheckedState<T>,
294    }
295}
296
297pin_project_lite::pin_project! {
298    #[project = TimeoutCheckedStateProject]
299    enum TimeoutCheckedState<T> {
300        Timeout{ #[pin] fut: Timeout<T> },
301        NoTimeout{ #[pin] fut: T },
302    }
303}
304
305impl<T> TimeoutChecked<T> {
306    pub(crate) fn new_with_delay(value: T, delay: Millis) -> TimeoutChecked<T> {
307        if delay.is_zero() {
308            TimeoutChecked {
309                state: TimeoutCheckedState::NoTimeout { fut: value },
310            }
311        } else {
312            TimeoutChecked {
313                state: TimeoutCheckedState::Timeout {
314                    fut: Timeout::new_with_delay(value, sleep(delay)),
315                },
316            }
317        }
318    }
319}
320
321impl<T> Future for TimeoutChecked<T>
322where
323    T: Future,
324{
325    type Output = Result<T::Output, ()>;
326
327    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
328        match self.project().state.as_mut().project() {
329            TimeoutCheckedStateProject::Timeout { fut } => fut.poll(cx),
330            TimeoutCheckedStateProject::NoTimeout { fut } => fut.poll(cx).map(Result::Ok),
331        }
332    }
333}
334
335/// An interval returned by [`interval`].
336///
337/// This type allows you to wait on a sequence of instants with a certain
338/// duration between each instant.
339#[must_use = "futures do nothing unless you `.await` or poll them"]
340#[derive(Debug)]
341pub struct Interval {
342    hnd: TimerHandle,
343    period: u32,
344}
345
346impl Interval {
347    /// Creates an interval with the specified period.
348    ///
349    /// The first tick completes immediately. A zero period is rounded up to
350    /// 1 ms.
351    #[inline]
352    pub fn new(period: Millis) -> Interval {
353        Interval {
354            hnd: TimerHandle::new(0),
355            period: cmp::max(period.0, 1),
356        }
357    }
358
359    #[inline]
360    /// Waits for the next interval tick.
361    pub async fn tick(&self) {
362        poll_fn(|cx| self.poll_tick(cx)).await;
363    }
364
365    #[inline]
366    /// Polls for the next interval tick.
367    pub fn poll_tick(&self, cx: &mut task::Context<'_>) -> Poll<()> {
368        if self.hnd.poll_elapsed(cx).is_ready() {
369            self.hnd.reset(u64::from(self.period));
370            Poll::Ready(())
371        } else {
372            Poll::Pending
373        }
374    }
375}
376
377impl crate::Stream for Interval {
378    type Item = ();
379
380    #[inline]
381    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
382        self.poll_tick(cx).map(|()| Some(()))
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use futures_util::StreamExt;
389    use std::{future::poll_fn, rc::Rc, time};
390
391    use super::*;
392    use crate::future::lazy;
393
394    /// STATEMENT: Cached `now()` is not in the future and stays close to the current instant.
395    ///
396    /// HOW:
397    /// - Sleep, then compare `now()` with `Instant::now`.
398    /// - Verify the cached value is no later and less than 50 milliseconds behind.
399    #[ntex::test]
400    async fn now_is_current() {
401        sleep(Millis(25)).await;
402
403        let now = now();
404        let cur = time::Instant::now();
405        assert!(cur >= now);
406        assert!(cur - now < time::Duration::from_millis(50));
407    }
408
409    /// STATEMENT: Cached `now()` advances across an awaited sleep.
410    ///
411    /// HOW:
412    /// - Read `now()` before and after a 25-millisecond sleep.
413    /// - Verify the second value advances by at least that duration.
414    #[ntex::test]
415    async fn now_updates_after_sleep() {
416        sleep(Millis(50)).await;
417
418        let first_time = now();
419
420        sleep(Millis(25)).await;
421
422        let second_time = now();
423        assert!(second_time - first_time >= time::Duration::from_millis(25));
424    }
425
426    /// STATEMENT: Back-to-back system-time reads share the cached value.
427    ///
428    /// HOW:
429    /// - Let the runtime advance, then compare two consecutive `system_time()` calls.
430    #[ntex::test]
431    async fn system_time_service_time_does_not_immediately_change() {
432        sleep(Seconds(1)).await;
433
434        assert_eq!(system_time(), system_time());
435    }
436
437    /// STATEMENT: Cached system time advances after sleeping beyond its refresh interval.
438    ///
439    /// HOW:
440    /// - Read system time around a 600-millisecond sleep.
441    /// - Compare epoch-relative durations and require at least that much advancement.
442    #[ntex::test]
443    async fn system_time_service_time_updates_after_resolution_interval() {
444        sleep(Millis(100)).await;
445
446        let wait_time = 600;
447
448        let first_time = system_time()
449            .duration_since(time::SystemTime::UNIX_EPOCH)
450            .unwrap();
451
452        sleep(Millis(wait_time)).await;
453
454        let second_time = system_time()
455            .duration_since(time::SystemTime::UNIX_EPOCH)
456            .unwrap();
457
458        assert!(
459            second_time.checked_sub(first_time).unwrap()
460                >= time::Duration::from_millis(u64::from(wait_time))
461        );
462    }
463
464    /// STATEMENT: Zero sleeps round up, while explicitly elapsed timers complete promptly.
465    ///
466    /// HOW:
467    /// - Measure zero, one-millisecond, and reset-to-zero sleeps and check their elapsed state.
468    /// - Await an already-elapsed handle and a manually elapsed shared sleep within short bounds.
469    #[ntex::test]
470    async fn test_sleep_0() {
471        sleep(Seconds(1)).await;
472
473        let first_time = time::Instant::now();
474        sleep(Millis(0)).await;
475        let second_time = time::Instant::now();
476        assert!(second_time - first_time >= time::Duration::from_millis(1));
477
478        let first_time = time::Instant::now();
479        sleep(Millis(1)).await;
480        let second_time = time::Instant::now();
481        assert!(second_time - first_time >= time::Duration::from_millis(1));
482
483        let first_time = time::Instant::now();
484        let fut = sleep(Millis(10000));
485        assert!(!fut.is_elapsed());
486        // a zero delay is rounded up to 1 ms
487        fut.reset(Millis::ZERO);
488        assert!(!fut.is_elapsed());
489        fut.await;
490        let second_time = time::Instant::now();
491        assert!(second_time - first_time >= time::Duration::from_millis(1));
492
493        let fut = sleep(Millis::ZERO);
494        assert!(!fut.is_elapsed());
495        fut.await;
496
497        // `now()` may advance between two calls, measure
498        // with the clock. An elapsed timer completes without waiting for the
499        // timer driver, the bound leaves room for scheduling latency
500        let first_time = time::Instant::now();
501        let fut = Sleep {
502            hnd: TimerHandle::new(0),
503        };
504        assert!(fut.is_elapsed());
505        fut.await;
506        let second_time = time::Instant::now();
507        assert!(second_time - first_time < time::Duration::from_millis(50));
508
509        let first_time = time::Instant::now();
510        let fut = Rc::new(sleep(Millis(10_0000)));
511        let s = fut.clone();
512        ntex::rt::spawn(async move {
513            s.elapse();
514        });
515        poll_fn(|cx| fut.poll_elapsed(cx)).await;
516        assert!(fut.is_elapsed());
517        let second_time = time::Instant::now();
518        assert!(second_time - first_time < time::Duration::from_millis(50));
519    }
520
521    /// STATEMENT: Deadlines can be extended, disabled with zero, and re-enabled by reset.
522    ///
523    /// HOW:
524    /// - Measure initial and extended deadlines and time out a zero deadline.
525    /// - Poll disabled and re-enabled deadlines, then inspect debug output.
526    #[ntex::test]
527    async fn test_deadline() {
528        sleep(Seconds(1)).await;
529
530        let first_time = now();
531        let dl = deadline(Millis(1));
532        dl.await;
533        let second_time = now();
534        assert!(second_time - first_time >= time::Duration::from_millis(1));
535        assert!(timeout(Millis(100), deadline(Millis(0))).await.is_err());
536
537        let mut dl = deadline(Millis(1));
538        dl.reset(Millis::ZERO);
539        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
540
541        let mut dl = deadline(Millis(1));
542        dl.reset(Millis(100));
543        let first_time = now();
544        dl.await;
545        let second_time = now();
546        assert!(second_time - first_time >= time::Duration::from_millis(100));
547
548        let mut dl = deadline(Millis(0));
549        assert!(dl.is_elapsed());
550        dl.reset(Millis(1));
551        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
552
553        assert!(format!("{dl:?}").contains("Deadline"));
554    }
555
556    /// STATEMENT: A zero-period interval waits between ticks instead of staying immediately ready.
557    ///
558    /// HOW:
559    /// - Consume the first tick, verify the next poll is pending, and await another tick.
560    #[ntex::test]
561    async fn test_interval_zero_period() {
562        let int = interval(Millis::ZERO);
563        int.tick().await;
564
565        // a zero period is rounded up to 1 ms instead of ticking on every poll
566        assert!(lazy(|cx| int.poll_tick(cx)).await.is_pending());
567        int.tick().await;
568    }
569
570    /// STATEMENT: Intervals tick immediately once, then wait for their period through both APIs.
571    ///
572    /// HOW:
573    /// - Measure the first and second explicit ticks of a 250-millisecond interval.
574    /// - Receive another tick through the stream API and check the same timing bounds.
575    #[ntex::test]
576    async fn test_interval() {
577        let mut int = interval(Millis(250));
578
579        // the first tick completes immediately
580        let time = time::Instant::now();
581        int.tick().await;
582        assert!(time.elapsed() < time::Duration::from_millis(50));
583
584        let time = time::Instant::now();
585        int.tick().await;
586        let elapsed = time.elapsed();
587        assert!(
588            elapsed > time::Duration::from_millis(200)
589                && elapsed < time::Duration::from_millis(450),
590            "elapsed: {elapsed:?}"
591        );
592
593        let time = time::Instant::now();
594        int.next().await;
595        let elapsed = time.elapsed();
596        assert!(
597            elapsed > time::Duration::from_millis(200)
598                && elapsed < time::Duration::from_millis(450),
599            "elapsed: {elapsed:?}"
600        );
601    }
602
603    /// STATEMENT: A one-second interval keeps subsequent ticks within the expected timing bounds.
604    ///
605    /// HOW:
606    /// - Consume the immediate tick, then measure three ticks between one and 1.3 seconds each.
607    #[ntex::test]
608    async fn test_interval_one_sec() {
609        let int = interval(Millis::ONE_SEC);
610        int.tick().await;
611
612        for _i in 0..3 {
613            let time = time::Instant::now();
614            int.tick().await;
615            let elapsed = time.elapsed();
616            assert!(
617                elapsed > time::Duration::from_secs(1)
618                    && elapsed < time::Duration::from_millis(1300),
619                "elapsed: {elapsed:?}"
620            );
621        }
622    }
623
624    /// STATEMENT: Checked timeouts preserve timely results, expire pending work, and disable at zero.
625    ///
626    /// HOW:
627    /// - Run a shorter sleep under a deadline and a permanently pending future under a short one.
628    /// - Verify a zero timeout allows a delayed future to finish.
629    #[ntex::test]
630    async fn test_timeout_checked() {
631        let result = timeout_checked(Millis(200), sleep(Millis(100))).await;
632        assert!(result.is_ok());
633
634        // a future that never completes, a late timer driver would elapse
635        // a competing sleep in the same pass
636        let result = timeout_checked(Millis(5), std::future::pending::<()>()).await;
637        assert!(result.is_err());
638
639        let result = timeout_checked(Millis(0), sleep(Millis(100))).await;
640        assert!(result.is_ok());
641    }
642
643    /// STATEMENT: Sleep and deadline wait helpers complete except for a disabled zero deadline.
644    ///
645    /// HOW:
646    /// - Await both helpers and check the clock has not moved backward.
647    /// - Wrap a zero deadline's wait in a timeout and verify it never completes first.
648    #[ntex::test]
649    async fn sleep_and_deadline_wait() {
650        let start = now();
651        sleep(Millis(5)).wait().await;
652        deadline(Millis(5)).wait().await;
653        assert!(now() >= start);
654
655        let hnd = Deadline::new(Millis::ZERO);
656        assert!(
657            crate::time::timeout(Millis(20), hnd.wait()).await.is_err(),
658            "zero deadline never completes"
659        );
660    }
661}