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
144    // ── Additional sleep tests ─────────────────────────────────────────────
145
146    #[test]
147    fn sleep_past_deadline_returns_immediately() {
148        block_on_with_spawn(async {
149            let deadline = Instant::now() - Duration::from_millis(100);
150            let before = Instant::now();
151            sleep_until(deadline).await;
152            assert!(before.elapsed() < Duration::from_millis(50));
153        });
154    }
155
156    #[test]
157    fn sleep_1ms_completes() {
158        block_on_with_spawn(async {
159            sleep(Duration::from_millis(1)).await;
160        });
161    }
162
163    #[test]
164    fn sleep_concurrent_multiple() {
165        use crate::executor::spawn;
166        block_on_with_spawn(async {
167            let before = Instant::now();
168            let h1 = spawn(async { sleep(Duration::from_millis(50)).await });
169            let h2 = spawn(async { sleep(Duration::from_millis(50)).await });
170            h1.await.unwrap();
171            h2.await.unwrap();
172            // Both sleep concurrently, total time ≈50ms not ≈100ms
173            assert!(before.elapsed() < Duration::from_millis(500));
174        });
175    }
176
177    #[test]
178    fn sleep_until_future_instant() {
179        block_on_with_spawn(async {
180            let deadline = Instant::now() + Duration::from_millis(20);
181            sleep_until(deadline).await;
182            assert!(Instant::now() >= deadline);
183        });
184    }
185
186    #[test]
187    fn sleep_drop_does_not_leak_timer() {
188        // Create many sleeps and drop them; timer wheel must not accumulate stale entries.
189        // If timers were leaked, the executor would never exit (this test completing proves drops worked).
190        block_on_with_spawn(async {
191            for _ in 0..50 {
192                let s = sleep(Duration::from_secs(10));
193                drop(s);
194            }
195        });
196    }
197
198    #[test]
199    fn sleep_10ms_completes() {
200        block_on_with_spawn(async {
201            let before = Instant::now();
202            sleep(Duration::from_millis(10)).await;
203            assert!(before.elapsed() >= Duration::from_millis(5));
204        });
205    }
206
207    #[test]
208    fn sleep_two_sequential_sleeps() {
209        block_on_with_spawn(async {
210            sleep(Duration::from_millis(5)).await;
211            sleep(Duration::from_millis(5)).await;
212            // Simply verifies no hang and no panic.
213        });
214    }
215
216    #[test]
217    fn sleep_until_already_past_instant() {
218        block_on_with_spawn(async {
219            // Instant in the past
220            let past = Instant::now() - Duration::from_secs(1);
221            let before = Instant::now();
222            sleep_until(past).await;
223            // Must complete nearly immediately
224            assert!(before.elapsed() < Duration::from_millis(100));
225        });
226    }
227
228    #[test]
229    fn sleep_duration_zero_uses_zero_const() {
230        block_on_with_spawn(async {
231            // Duration::ZERO is a valid sleep
232            let before = Instant::now();
233            sleep(Duration::ZERO).await;
234            assert!(before.elapsed() < Duration::from_millis(100));
235        });
236    }
237
238    #[test]
239    fn sleep_3_sequential_1ms_each() {
240        block_on_with_spawn(async {
241            for _ in 0..3 {
242                sleep(Duration::from_millis(1)).await;
243            }
244        });
245    }
246
247    #[test]
248    fn sleep_until_now_returns_immediately() {
249        block_on_with_spawn(async {
250            let now = Instant::now();
251            let before = Instant::now();
252            sleep_until(now).await;
253            assert!(before.elapsed() < Duration::from_millis(100));
254        });
255    }
256
257    #[test]
258    fn sleep_multiple_concurrent_different_durations() {
259        use crate::executor::spawn;
260        block_on_with_spawn(async {
261            let before = Instant::now();
262            let h1 = spawn(async { sleep(Duration::from_millis(10)).await });
263            let h2 = spawn(async { sleep(Duration::from_millis(20)).await });
264            let h3 = spawn(async { sleep(Duration::from_millis(30)).await });
265            h1.await.unwrap();
266            h2.await.unwrap();
267            h3.await.unwrap();
268            // Total should be ~30ms if concurrent, not 60ms sequential
269            assert!(before.elapsed() < Duration::from_millis(500));
270        });
271    }
272}