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///
98/// # Single waiter
99///
100/// A timer stores one waker. [`wait`](Self::wait) and
101/// [`poll_elapsed`](Self::poll_elapsed) take `&self`, but only the task that
102/// polled most recently is woken when the timer elapses. Wait from one task at
103/// a time; an earlier concurrent waiter may stay pending after the deadline.
104/// The same applies to [`Deadline`] and [`TimerHandle`].
105#[derive(Debug)]
106#[must_use = "futures do nothing unless you `.await` or poll them"]
107pub struct Sleep {
108    // The link between the `Sleep` instance and the timer that drives it.
109    hnd: TimerHandle,
110}
111
112impl Sleep {
113    /// Creates a new sleep future.
114    ///
115    /// A zero duration is rounded up to 1 ms.
116    #[inline]
117    pub fn new(duration: Millis) -> Sleep {
118        Sleep {
119            hnd: TimerHandle::new(u64::from(cmp::max(duration.0, 1))),
120        }
121    }
122
123    /// Returns `true` if `Sleep` has elapsed.
124    #[inline]
125    pub fn is_elapsed(&self) -> bool {
126        self.hnd.is_elapsed()
127    }
128
129    /// Completes the timer immediately.
130    #[inline]
131    pub fn elapse(&self) {
132        self.hnd.elapse();
133    }
134
135    /// Resets the `Sleep` instance to a new deadline.
136    ///
137    /// Calling this function allows changing the instant at which the `Sleep`
138    /// future completes without having to create new associated state.
139    ///
140    /// This function can be called both before and after the future has
141    /// completed. A zero duration is rounded up to 1 ms.
142    pub fn reset<T: Into<Millis>>(&self, millis: T) {
143        self.hnd.reset(u64::from(cmp::max(millis.into().0, 1)));
144    }
145
146    #[inline]
147    /// Waits until this timer has elapsed.
148    ///
149    /// Only one task may wait at a time; see [single waiter](Self#single-waiter).
150    pub async fn wait(&self) {
151        poll_fn(|cx| self.hnd.poll_elapsed(cx)).await;
152    }
153
154    #[inline]
155    /// Polls until this timer has elapsed.
156    ///
157    /// A pending poll replaces the previously registered waker; see
158    /// [single waiter](Self#single-waiter).
159    pub fn poll_elapsed(&self, cx: &mut task::Context<'_>) -> Poll<()> {
160        self.hnd.poll_elapsed(cx)
161    }
162}
163
164impl Future for Sleep {
165    type Output = ();
166
167    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
168        self.hnd.poll_elapsed(cx)
169    }
170}
171
172/// Future returned by [`deadline`].
173///
174/// # Examples
175///
176/// Wait 100ms and print "100 ms have elapsed".
177///
178/// ```
179/// use ntex::time::{deadline, Millis};
180///
181/// #[ntex::main]
182/// async fn main() {
183///     deadline(Millis(100)).await;
184///     println!("100 ms have elapsed");
185/// }
186/// ```
187#[derive(Debug)]
188#[must_use = "futures do nothing unless you `.await` or poll them"]
189pub struct Deadline {
190    hnd: Option<TimerHandle>,
191}
192
193impl Deadline {
194    /// Creates a new deadline future.
195    ///
196    /// A zero duration creates a deadline that never completes.
197    #[inline]
198    pub fn new(duration: Millis) -> Deadline {
199        if duration.0 != 0 {
200            Deadline {
201                hnd: Some(TimerHandle::new(u64::from(duration.0))),
202            }
203        } else {
204            Deadline { hnd: None }
205        }
206    }
207
208    #[inline]
209    /// Waits until this deadline has elapsed.
210    ///
211    /// Only one task may wait at a time; see
212    /// [single waiter](Sleep#single-waiter).
213    pub async fn wait(&self) {
214        poll_fn(|cx| self.poll_elapsed(cx)).await;
215    }
216
217    /// Resets the `Deadline` instance to a new deadline.
218    ///
219    /// Calling this function allows changing the instant at which the `Deadline`
220    /// future completes without having to create new associated state.
221    ///
222    /// This function can be called both before and after the future has
223    /// completed.
224    pub fn reset<T: Into<Millis>>(&mut self, millis: T) {
225        let millis = millis.into();
226        if millis.0 != 0 {
227            if let Some(ref mut hnd) = self.hnd {
228                hnd.reset(u64::from(millis.0));
229            } else {
230                self.hnd = Some(TimerHandle::new(u64::from(millis.0)));
231            }
232        } else {
233            let _ = self.hnd.take();
234        }
235    }
236
237    /// Returns `true` if `Deadline` has elapsed.
238    ///
239    /// This agrees with polling: it returns `true` exactly when polling the
240    /// deadline completes. A disabled zero-duration deadline never elapses,
241    /// so it returns `false`.
242    #[inline]
243    pub fn is_elapsed(&self) -> bool {
244        self.hnd.as_ref().is_some_and(TimerHandle::is_elapsed)
245    }
246
247    #[inline]
248    /// Polls until this deadline has elapsed.
249    ///
250    /// A zero-duration deadline remains pending until it is reset. A pending
251    /// poll replaces the previously registered waker; see
252    /// [single waiter](Sleep#single-waiter).
253    pub fn poll_elapsed(&self, cx: &mut task::Context<'_>) -> Poll<()> {
254        self.hnd
255            .as_ref()
256            .map_or(Poll::Pending, |t| t.poll_elapsed(cx))
257    }
258}
259
260impl Future for Deadline {
261    type Output = ();
262
263    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
264        self.poll_elapsed(cx)
265    }
266}
267
268pin_project_lite::pin_project! {
269    /// Future returned by [`timeout`](timeout).
270    #[must_use = "futures do nothing unless you `.await` or poll them"]
271    #[derive(Debug)]
272    pub struct Timeout<T> {
273        #[pin]
274        value: T,
275        delay: Sleep,
276    }
277}
278
279impl<T> Timeout<T> {
280    pub(crate) fn new_with_delay(value: T, delay: Sleep) -> Timeout<T> {
281        Timeout { value, delay }
282    }
283}
284
285impl<T> Future for Timeout<T>
286where
287    T: Future,
288{
289    type Output = Result<T::Output, ()>;
290
291    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
292        let this = self.project();
293
294        // First, try polling the future
295        if let Poll::Ready(v) = this.value.poll(cx) {
296            return Poll::Ready(Ok(v));
297        }
298
299        // Now check the timer
300        match this.delay.poll_elapsed(cx) {
301            Poll::Ready(()) => Poll::Ready(Err(())),
302            Poll::Pending => Poll::Pending,
303        }
304    }
305}
306
307pin_project_lite::pin_project! {
308    /// Future returned by [`timeout_checked`](timeout_checked).
309    #[must_use = "futures do nothing unless you `.await` or poll them"]
310    pub struct TimeoutChecked<T> {
311        #[pin]
312        state: TimeoutCheckedState<T>,
313    }
314}
315
316pin_project_lite::pin_project! {
317    #[project = TimeoutCheckedStateProject]
318    enum TimeoutCheckedState<T> {
319        Timeout{ #[pin] fut: Timeout<T> },
320        NoTimeout{ #[pin] fut: T },
321    }
322}
323
324impl<T> TimeoutChecked<T> {
325    pub(crate) fn new_with_delay(value: T, delay: Millis) -> TimeoutChecked<T> {
326        if delay.is_zero() {
327            TimeoutChecked {
328                state: TimeoutCheckedState::NoTimeout { fut: value },
329            }
330        } else {
331            TimeoutChecked {
332                state: TimeoutCheckedState::Timeout {
333                    fut: Timeout::new_with_delay(value, sleep(delay)),
334                },
335            }
336        }
337    }
338}
339
340impl<T> Future for TimeoutChecked<T>
341where
342    T: Future,
343{
344    type Output = Result<T::Output, ()>;
345
346    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
347        match self.project().state.as_mut().project() {
348            TimeoutCheckedStateProject::Timeout { fut } => fut.poll(cx),
349            TimeoutCheckedStateProject::NoTimeout { fut } => fut.poll(cx).map(Result::Ok),
350        }
351    }
352}
353
354/// An interval returned by [`interval`].
355///
356/// This type allows you to wait on a sequence of instants with a certain
357/// duration between each instant.
358#[must_use = "futures do nothing unless you `.await` or poll them"]
359#[derive(Debug)]
360pub struct Interval {
361    hnd: TimerHandle,
362    period: u32,
363}
364
365impl Interval {
366    /// Creates an interval with the specified period.
367    ///
368    /// The first tick completes immediately. A zero period is rounded up to
369    /// 1 ms.
370    #[inline]
371    pub fn new(period: Millis) -> Interval {
372        Interval {
373            hnd: TimerHandle::new(0),
374            period: cmp::max(period.0, 1),
375        }
376    }
377
378    #[inline]
379    /// Waits for the next interval tick.
380    pub async fn tick(&self) {
381        poll_fn(|cx| self.poll_tick(cx)).await;
382    }
383
384    #[inline]
385    /// Polls for the next interval tick.
386    pub fn poll_tick(&self, cx: &mut task::Context<'_>) -> Poll<()> {
387        if self.hnd.poll_elapsed(cx).is_ready() {
388            self.hnd.reset(u64::from(self.period));
389            Poll::Ready(())
390        } else {
391            Poll::Pending
392        }
393    }
394}
395
396impl crate::Stream for Interval {
397    type Item = ();
398
399    #[inline]
400    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
401        self.poll_tick(cx).map(|()| Some(()))
402    }
403}
404
405#[cfg(test)]
406mod tests {
407    use futures_util::StreamExt;
408    use std::{future::poll_fn, rc::Rc, time};
409
410    use super::*;
411    use crate::future::lazy;
412
413    /// STATEMENT: Cached `now()` is not in the future and stays close to the current instant.
414    ///
415    /// HOW:
416    /// - Sleep, then compare `now()` with `Instant::now`.
417    /// - Verify the cached value is no later and less than 50 milliseconds behind.
418    #[ntex::test]
419    async fn now_is_current() {
420        sleep(Millis(25)).await;
421
422        let now = now();
423        let cur = time::Instant::now();
424        assert!(cur >= now);
425        assert!(cur - now < time::Duration::from_millis(50));
426    }
427
428    /// STATEMENT: Cached `now()` advances across an awaited sleep.
429    ///
430    /// HOW:
431    /// - Read `now()` before and after a 25-millisecond sleep.
432    /// - Verify the second value advances by at least that duration.
433    #[ntex::test]
434    async fn now_updates_after_sleep() {
435        sleep(Millis(50)).await;
436
437        let first_time = now();
438
439        sleep(Millis(25)).await;
440
441        let second_time = now();
442        assert!(second_time - first_time >= time::Duration::from_millis(25));
443    }
444
445    /// STATEMENT: Back-to-back system-time reads share the cached value.
446    ///
447    /// HOW:
448    /// - Let the runtime advance, then compare two consecutive `system_time()` calls.
449    #[ntex::test]
450    async fn system_time_service_time_does_not_immediately_change() {
451        sleep(Seconds(1)).await;
452
453        assert_eq!(system_time(), system_time());
454    }
455
456    /// STATEMENT: Cached system time advances after sleeping beyond its refresh interval.
457    ///
458    /// HOW:
459    /// - Read system time around a 600-millisecond sleep.
460    /// - Compare epoch-relative durations and require at least that much advancement.
461    #[ntex::test]
462    async fn system_time_service_time_updates_after_resolution_interval() {
463        sleep(Millis(100)).await;
464
465        let wait_time = 600;
466
467        let first_time = system_time()
468            .duration_since(time::SystemTime::UNIX_EPOCH)
469            .unwrap();
470
471        sleep(Millis(wait_time)).await;
472
473        let second_time = system_time()
474            .duration_since(time::SystemTime::UNIX_EPOCH)
475            .unwrap();
476
477        assert!(
478            second_time.checked_sub(first_time).unwrap()
479                >= time::Duration::from_millis(u64::from(wait_time))
480        );
481    }
482
483    /// STATEMENT: Zero sleeps round up, while explicitly elapsed timers complete promptly.
484    ///
485    /// HOW:
486    /// - Measure zero, one-millisecond, and reset-to-zero sleeps and check their elapsed state.
487    /// - Await an already-elapsed handle and a manually elapsed shared sleep within short bounds.
488    #[ntex::test]
489    async fn test_sleep_0() {
490        sleep(Seconds(1)).await;
491
492        let first_time = time::Instant::now();
493        sleep(Millis(0)).await;
494        let second_time = time::Instant::now();
495        assert!(second_time - first_time >= time::Duration::from_millis(1));
496
497        let first_time = time::Instant::now();
498        sleep(Millis(1)).await;
499        let second_time = time::Instant::now();
500        assert!(second_time - first_time >= time::Duration::from_millis(1));
501
502        let first_time = time::Instant::now();
503        let fut = sleep(Millis(10000));
504        assert!(!fut.is_elapsed());
505        // a zero delay is rounded up to 1 ms
506        fut.reset(Millis::ZERO);
507        assert!(!fut.is_elapsed());
508        fut.await;
509        let second_time = time::Instant::now();
510        assert!(second_time - first_time >= time::Duration::from_millis(1));
511
512        let fut = sleep(Millis::ZERO);
513        assert!(!fut.is_elapsed());
514        fut.await;
515
516        // `now()` may advance between two calls, measure
517        // with the clock. An elapsed timer completes without waiting for the
518        // timer driver, the bound leaves room for scheduling latency
519        let first_time = time::Instant::now();
520        let fut = Sleep {
521            hnd: TimerHandle::new(0),
522        };
523        assert!(fut.is_elapsed());
524        fut.await;
525        let second_time = time::Instant::now();
526        assert!(second_time - first_time < time::Duration::from_millis(50));
527
528        let first_time = time::Instant::now();
529        let fut = Rc::new(sleep(Millis(10_0000)));
530        let s = fut.clone();
531        ntex::rt::spawn(async move {
532            s.elapse();
533        });
534        poll_fn(|cx| fut.poll_elapsed(cx)).await;
535        assert!(fut.is_elapsed());
536        let second_time = time::Instant::now();
537        assert!(second_time - first_time < time::Duration::from_millis(50));
538    }
539
540    /// STATEMENT: `Deadline::is_elapsed()` agrees with polling for disabled, active, reset and
541    /// expired deadlines.
542    ///
543    /// HOW:
544    /// - Create a zero deadline and assert `is_elapsed()` is `false`, polling stays pending, and
545    ///   `timeout()` around it times out.
546    /// - Create a 50 ms deadline and assert neither the predicate nor polling reports elapsed.
547    /// - Await a 1 ms deadline and assert the predicate is `true` and polling is ready.
548    /// - Reset the expired deadline to 50 ms and assert both report not elapsed again, then reset
549    ///   it to zero and assert both still report not elapsed after 20 ms.
550    #[ntex::test]
551    async fn test_deadline_is_elapsed_matches_polling() {
552        let dl = deadline(Millis::ZERO);
553        assert!(!dl.is_elapsed());
554        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
555        assert!(timeout(Millis(20), dl.wait()).await.is_err());
556        assert!(!dl.is_elapsed());
557
558        let dl = deadline(Millis(50));
559        assert!(!dl.is_elapsed());
560        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
561
562        let mut dl = deadline(Millis(1));
563        dl.wait().await;
564        assert!(dl.is_elapsed());
565        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_ready());
566
567        dl.reset(Millis(50));
568        assert!(!dl.is_elapsed());
569        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
570
571        dl.reset(Millis::ZERO);
572        sleep(Millis(20)).await;
573        assert!(!dl.is_elapsed());
574        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
575    }
576
577    /// STATEMENT: Deadlines can be extended, disabled with zero, and re-enabled by reset.
578    ///
579    /// HOW:
580    /// - Measure initial and extended deadlines and time out a zero deadline.
581    /// - Poll disabled and re-enabled deadlines, then inspect debug output.
582    #[ntex::test]
583    async fn test_deadline() {
584        sleep(Seconds(1)).await;
585
586        let first_time = now();
587        let dl = deadline(Millis(1));
588        dl.await;
589        let second_time = now();
590        assert!(second_time - first_time >= time::Duration::from_millis(1));
591        assert!(timeout(Millis(100), deadline(Millis(0))).await.is_err());
592
593        let mut dl = deadline(Millis(1));
594        dl.reset(Millis::ZERO);
595        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
596
597        let mut dl = deadline(Millis(1));
598        dl.reset(Millis(100));
599        let first_time = now();
600        dl.await;
601        let second_time = now();
602        assert!(second_time - first_time >= time::Duration::from_millis(100));
603
604        let mut dl = deadline(Millis(0));
605        dl.reset(Millis(1));
606        assert!(lazy(|cx| dl.poll_elapsed(cx)).await.is_pending());
607
608        assert!(format!("{dl:?}").contains("Deadline"));
609    }
610
611    /// STATEMENT: A zero-period interval waits between ticks instead of staying immediately ready.
612    ///
613    /// HOW:
614    /// - Consume the first tick, verify the next poll is pending, and await another tick.
615    #[ntex::test]
616    async fn test_interval_zero_period() {
617        let int = interval(Millis::ZERO);
618        int.tick().await;
619
620        // a zero period is rounded up to 1 ms instead of ticking on every poll
621        assert!(lazy(|cx| int.poll_tick(cx)).await.is_pending());
622        int.tick().await;
623    }
624
625    /// STATEMENT: Intervals tick immediately once, then wait for their period through both APIs.
626    ///
627    /// HOW:
628    /// - Measure the first and second explicit ticks of a 250-millisecond interval.
629    /// - Receive another tick through the stream API and check the same timing bounds.
630    #[ntex::test]
631    async fn test_interval() {
632        let mut int = interval(Millis(250));
633
634        // the first tick completes immediately
635        let time = time::Instant::now();
636        int.tick().await;
637        assert!(time.elapsed() < time::Duration::from_millis(50));
638
639        let time = time::Instant::now();
640        int.tick().await;
641        let elapsed = time.elapsed();
642        assert!(
643            elapsed > time::Duration::from_millis(200)
644                && elapsed < time::Duration::from_millis(450),
645            "elapsed: {elapsed:?}"
646        );
647
648        let time = time::Instant::now();
649        int.next().await;
650        let elapsed = time.elapsed();
651        assert!(
652            elapsed > time::Duration::from_millis(200)
653                && elapsed < time::Duration::from_millis(450),
654            "elapsed: {elapsed:?}"
655        );
656    }
657
658    /// STATEMENT: A one-second interval keeps subsequent ticks within the expected timing bounds.
659    ///
660    /// HOW:
661    /// - Consume the immediate tick, then measure three ticks between one and 1.5 seconds each.
662    #[ntex::test]
663    async fn test_interval_one_sec() {
664        let int = interval(Millis::ONE_SEC);
665        int.tick().await;
666
667        for _i in 0..3 {
668            let time = time::Instant::now();
669            int.tick().await;
670            let elapsed = time.elapsed();
671            assert!(
672                elapsed > time::Duration::from_secs(1)
673                    && elapsed < time::Duration::from_millis(1500),
674                "elapsed: {elapsed:?}"
675            );
676        }
677    }
678
679    /// STATEMENT: Checked timeouts preserve timely results, expire pending work, and disable at zero.
680    ///
681    /// HOW:
682    /// - Run a shorter sleep under a deadline and a permanently pending future under a short one.
683    /// - Verify a zero timeout allows a delayed future to finish.
684    #[ntex::test]
685    async fn test_timeout_checked() {
686        let result = timeout_checked(Millis(200), sleep(Millis(100))).await;
687        assert!(result.is_ok());
688
689        // a future that never completes, a late timer driver would elapse
690        // a competing sleep in the same pass
691        let result = timeout_checked(Millis(5), std::future::pending::<()>()).await;
692        assert!(result.is_err());
693
694        let result = timeout_checked(Millis(0), sleep(Millis(100))).await;
695        assert!(result.is_ok());
696    }
697
698    /// STATEMENT: Sleep and deadline wait helpers complete except for a disabled zero deadline.
699    ///
700    /// HOW:
701    /// - Await both helpers and check the clock has not moved backward.
702    /// - Wrap a zero deadline's wait in a timeout and verify it never completes first.
703    #[ntex::test]
704    async fn sleep_and_deadline_wait() {
705        let start = now();
706        sleep(Millis(5)).wait().await;
707        deadline(Millis(5)).wait().await;
708        assert!(now() >= start);
709
710        let hnd = Deadline::new(Millis::ZERO);
711        assert!(
712            crate::time::timeout(Millis(20), hnd.wait()).await.is_err(),
713            "zero deadline never completes"
714        );
715    }
716
717    /// STATEMENT: `Sleep` and `Deadline` wake only the most recently registered task
718    /// when they elapse, and a superseded waiter still observes completion.
719    ///
720    /// HOW:
721    /// - Poll each long timer with two distinct counted wakers, then `elapse` it.
722    /// - Check only the second waker was woken once and re-polling with the first
723    ///   is ready.
724    #[ntex::test]
725    async fn single_waiter_wakes_latest_task_only() {
726        use crate::task::hooks::counted_waker;
727
728        let poll_pair = |poll: &dyn Fn(&mut task::Context<'_>) -> Poll<()>, elapse: &dyn Fn()| {
729            let (first, first_waker) = counted_waker();
730            let (second, second_waker) = counted_waker();
731            let mut first_cx = task::Context::from_waker(&first_waker);
732            let mut second_cx = task::Context::from_waker(&second_waker);
733
734            assert_eq!(poll(&mut first_cx), Poll::Pending);
735            assert_eq!(poll(&mut second_cx), Poll::Pending);
736            elapse();
737            assert_eq!((first.get(), second.get()), (0, 1));
738            assert_eq!(poll(&mut first_cx), Poll::Ready(()));
739        };
740
741        let slp = Sleep::new(Millis(10_000));
742        poll_pair(&|cx| slp.poll_elapsed(cx), &|| slp.elapse());
743
744        let dl = Deadline::new(Millis(10_000));
745        let hnd = dl.hnd.as_ref().unwrap();
746        poll_pair(&|cx| dl.poll_elapsed(cx), &|| hnd.elapse());
747    }
748}