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}