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    /// State Under Test: `now()` returns the start of the current runtime turn.
395    ///
396    /// Expected Behavior: `now()` is not later than the current instant and is close to it.
397    #[ntex::test]
398    async fn now_is_current() {
399        sleep(Millis(25)).await;
400
401        let now = now();
402        let cur = time::Instant::now();
403        assert!(cur >= now);
404        assert!(cur - now < time::Duration::from_millis(50));
405    }
406
407    /// State Under Test: `now()` advances between runtime turns.
408    ///
409    /// Expected Behavior: Two calls of `now()` made in subsequent resolution interval return different values
410    /// and second value is greater than the first one at least by a 1ms interval.
411    #[ntex::test]
412    async fn now_updates_after_sleep() {
413        sleep(Millis(50)).await;
414
415        let first_time = now();
416
417        sleep(Millis(25)).await;
418
419        let second_time = now();
420        assert!(second_time - first_time >= time::Duration::from_millis(25));
421    }
422
423    /// State Under Test: Two calls of `system_time()` return the same value if they are done within the
424    /// resolution interval.
425    ///
426    /// Expected Behavior: Two back-to-back calls of `now()` return the same value.
427    #[ntex::test]
428    async fn system_time_service_time_does_not_immediately_change() {
429        sleep(Seconds(1)).await;
430
431        assert_eq!(system_time(), system_time());
432    }
433
434    /// State Under Test: `system_time()` updates returned value every resolution interval (150ms).
435    ///
436    /// Expected Behavior: Two calls of `system_time()` made in subsequent resolution interval return different values
437    /// and second value is greater than the first one at least by a resolution interval.
438    #[ntex::test]
439    async fn system_time_service_time_updates_after_resolution_interval() {
440        sleep(Millis(100)).await;
441
442        let wait_time = 600;
443
444        let first_time = system_time()
445            .duration_since(time::SystemTime::UNIX_EPOCH)
446            .unwrap();
447
448        sleep(Millis(wait_time)).await;
449
450        let second_time = system_time()
451            .duration_since(time::SystemTime::UNIX_EPOCH)
452            .unwrap();
453
454        assert!(
455            second_time.checked_sub(first_time).unwrap()
456                >= time::Duration::from_millis(u64::from(wait_time))
457        );
458    }
459
460    #[ntex::test]
461    async fn test_sleep_0() {
462        sleep(Seconds(1)).await;
463
464        let first_time = time::Instant::now();
465        sleep(Millis(0)).await;
466        let second_time = time::Instant::now();
467        assert!(second_time - first_time >= time::Duration::from_millis(1));
468
469        let first_time = time::Instant::now();
470        sleep(Millis(1)).await;
471        let second_time = time::Instant::now();
472        assert!(second_time - first_time >= time::Duration::from_millis(1));
473
474        let first_time = time::Instant::now();
475        let fut = sleep(Millis(10000));
476        assert!(!fut.is_elapsed());
477        // a zero delay is rounded up to 1 ms
478        fut.reset(Millis::ZERO);
479        assert!(!fut.is_elapsed());
480        fut.await;
481        let second_time = time::Instant::now();
482        assert!(second_time - first_time >= time::Duration::from_millis(1));
483
484        let fut = sleep(Millis::ZERO);
485        assert!(!fut.is_elapsed());
486        fut.await;
487
488        // `now()` may advance between two calls, measure
489        // with the clock. An elapsed timer completes without waiting for the
490        // timer driver, the bound leaves room for scheduling latency
491        let first_time = time::Instant::now();
492        let fut = Sleep {
493            hnd: TimerHandle::new(0),
494        };
495        assert!(fut.is_elapsed());
496        fut.await;
497        let second_time = time::Instant::now();
498        assert!(second_time - first_time < time::Duration::from_millis(50));
499
500        let first_time = time::Instant::now();
501        let fut = Rc::new(sleep(Millis(10_0000)));
502        let s = fut.clone();
503        ntex::rt::spawn(async move {
504            s.elapse();
505        });
506        poll_fn(|cx| fut.poll_elapsed(cx)).await;
507        assert!(fut.is_elapsed());
508        let second_time = time::Instant::now();
509        assert!(second_time - first_time < time::Duration::from_millis(50));
510    }
511
512    #[ntex::test]
513    async fn test_deadline() {
514        sleep(Seconds(1)).await;
515
516        let first_time = now();
517        let dl = deadline(Millis(1));
518        dl.await;
519        let second_time = now();
520        assert!(second_time - first_time >= time::Duration::from_millis(1));
521        assert!(timeout(Millis(100), deadline(Millis(0))).await.is_err());
522
523        let mut dl = deadline(Millis(1));
524        dl.reset(Millis::ZERO);
525        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
526
527        let mut dl = deadline(Millis(1));
528        dl.reset(Millis(100));
529        let first_time = now();
530        dl.await;
531        let second_time = now();
532        assert!(second_time - first_time >= time::Duration::from_millis(100));
533
534        let mut dl = deadline(Millis(0));
535        assert!(dl.is_elapsed());
536        dl.reset(Millis(1));
537        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
538
539        assert!(format!("{dl:?}").contains("Deadline"));
540    }
541
542    #[ntex::test]
543    async fn test_interval_zero_period() {
544        let int = interval(Millis::ZERO);
545        int.tick().await;
546
547        // a zero period is rounded up to 1 ms instead of ticking on every poll
548        assert!(lazy(|cx| int.poll_tick(cx)).await.is_pending());
549        int.tick().await;
550    }
551
552    #[ntex::test]
553    async fn test_interval() {
554        let mut int = interval(Millis(250));
555
556        // the first tick completes immediately
557        let time = time::Instant::now();
558        int.tick().await;
559        assert!(time.elapsed() < time::Duration::from_millis(50));
560
561        let time = time::Instant::now();
562        int.tick().await;
563        let elapsed = time.elapsed();
564        assert!(
565            elapsed > time::Duration::from_millis(200)
566                && elapsed < time::Duration::from_millis(450),
567            "elapsed: {elapsed:?}"
568        );
569
570        let time = time::Instant::now();
571        int.next().await;
572        let elapsed = time.elapsed();
573        assert!(
574            elapsed > time::Duration::from_millis(200)
575                && elapsed < time::Duration::from_millis(450),
576            "elapsed: {elapsed:?}"
577        );
578    }
579
580    #[ntex::test]
581    async fn test_interval_one_sec() {
582        let int = interval(Millis::ONE_SEC);
583        int.tick().await;
584
585        for _i in 0..3 {
586            let time = time::Instant::now();
587            int.tick().await;
588            let elapsed = time.elapsed();
589            assert!(
590                elapsed > time::Duration::from_secs(1)
591                    && elapsed < time::Duration::from_millis(1300),
592                "elapsed: {elapsed:?}"
593            );
594        }
595    }
596
597    #[ntex::test]
598    async fn test_timeout_checked() {
599        let result = timeout_checked(Millis(200), sleep(Millis(100))).await;
600        assert!(result.is_ok());
601
602        // a future that never completes, a late timer driver would elapse
603        // a competing sleep in the same pass
604        let result = timeout_checked(Millis(5), std::future::pending::<()>()).await;
605        assert!(result.is_err());
606
607        let result = timeout_checked(Millis(0), sleep(Millis(100))).await;
608        assert!(result.is_ok());
609    }
610
611    #[ntex::test]
612    async fn sleep_and_deadline_wait() {
613        let start = now();
614        sleep(Millis(5)).wait().await;
615        deadline(Millis(5)).wait().await;
616        assert!(now() >= start);
617
618        let hnd = Deadline::new(Millis::ZERO);
619        assert!(
620            crate::time::timeout(Millis(20), hnd.wait()).await.is_err(),
621            "zero deadline never completes"
622        );
623    }
624}