Skip to main content

moduvex_runtime/time/
sleep.rs

1//! `Sleep` future — resolves after a given `Duration`.
2//!
3//! On first poll the deadline is registered with the thread-local timer wheel.
4//! The wheel fires the stored waker when the deadline passes, causing the
5//! executor to re-poll this future, which then returns `Ready(())`.
6
7use std::future::Future;
8use std::pin::Pin;
9use std::task::{Context, Poll};
10use std::time::{Duration, Instant};
11
12use super::{with_timer_wheel, TimerId};
13
14/// Future that completes after `duration` has elapsed.
15///
16/// Created by [`sleep`]. Implements `Future<Output = ()>`.
17pub struct Sleep {
18    /// Absolute deadline computed from the creation time.
19    deadline: Instant,
20    /// Timer wheel entry, set on first poll and cleared on completion.
21    timer_id: Option<TimerId>,
22}
23
24impl Sleep {
25    /// Create a `Sleep` that resolves after `duration`.
26    pub(crate) fn new(deadline: Instant) -> Self {
27        Self {
28            deadline,
29            timer_id: None,
30        }
31    }
32}
33
34impl Future for Sleep {
35    type Output = ();
36
37    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
38        let now = Instant::now();
39
40        // Already past deadline — return immediately.
41        if now >= self.deadline {
42            // Cancel any stale registration (shouldn't normally exist here).
43            if let Some(id) = self.timer_id.take() {
44                with_timer_wheel(|w| {
45                    w.cancel(id);
46                });
47            }
48            return Poll::Ready(());
49        }
50
51        // Register (or re-register) with the timer wheel.
52        // We always re-register on each poll to keep the waker fresh (the
53        // executor may have cloned a new waker since the last poll).
54        if let Some(old_id) = self.timer_id.take() {
55            with_timer_wheel(|w| {
56                w.cancel(old_id);
57            });
58        }
59        let id = with_timer_wheel(|w| w.insert(self.deadline, cx.waker().clone()));
60        self.timer_id = Some(id);
61
62        Poll::Pending
63    }
64}
65
66impl Drop for Sleep {
67    fn drop(&mut self) {
68        // Cancel the timer if the future is dropped before completing.
69        if let Some(id) = self.timer_id.take() {
70            with_timer_wheel(|w| {
71                w.cancel(id);
72            });
73        }
74    }
75}
76
77/// Returns a future that resolves after `duration` has elapsed.
78///
79/// # Example
80/// ```no_run
81/// use moduvex_runtime::time::sleep;
82/// use std::time::Duration;
83///
84/// moduvex_runtime::block_on(async {
85///     sleep(Duration::from_millis(100)).await;
86///     println!("100 ms elapsed");
87/// });
88/// ```
89pub fn sleep(duration: Duration) -> Sleep {
90    Sleep::new(Instant::now() + duration)
91}
92
93/// Returns a future that resolves at the given absolute `deadline`.
94pub fn sleep_until(deadline: Instant) -> Sleep {
95    Sleep::new(deadline)
96}
97
98// ── Tests ─────────────────────────────────────────────────────────────────────
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::executor::block_on_with_spawn;
104    use std::time::Duration;
105
106    #[test]
107    fn sleep_zero_completes_immediately() {
108        block_on_with_spawn(async {
109            let before = Instant::now();
110            sleep(Duration::ZERO).await;
111            // Should complete nearly instantly.
112            assert!(before.elapsed() < Duration::from_millis(50));
113        });
114    }
115
116    #[test]
117    fn sleep_100ms_completes_within_bounds() {
118        block_on_with_spawn(async {
119            let before = Instant::now();
120            sleep(Duration::from_millis(100)).await;
121            let elapsed = before.elapsed();
122            assert!(
123                elapsed >= Duration::from_millis(95),
124                "sleep resolved too early: {:?}",
125                elapsed
126            );
127            assert!(
128                elapsed < Duration::from_millis(500),
129                "sleep took too long: {:?}",
130                elapsed
131            );
132        });
133    }
134
135    #[test]
136    fn sleep_drop_before_completion_does_not_panic() {
137        block_on_with_spawn(async {
138            // Create but immediately drop the sleep future.
139            let s = sleep(Duration::from_millis(1000));
140            drop(s); // Must not panic or leak.
141        });
142    }
143}