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