Skip to main content

moirai_async/timer/
mod.rs

1//! Async timer primitives for Moirai concurrency library.
2//!
3//! Following SLAP principle with focused responsibility on time-based async operations.
4
5pub mod delay;
6pub(super) mod driver;
7pub mod interval;
8pub mod limiter;
9pub(super) mod registration;
10pub mod timeout;
11pub mod wheel;
12
13pub use delay::Delay;
14pub use interval::Interval;
15pub use limiter::{RateLimiter, RatePermit};
16pub use timeout::{Timeout, TimeoutError};
17pub use wheel::{TimerCommand, TimerWheel};
18
19use std::future::Future;
20use std::time::{Duration, Instant};
21
22/// Compute `base + duration` without panicking on absurd durations.
23///
24/// `Instant + Duration` panics on overflow, so a near-`Duration::MAX` input
25/// (e.g. a caller using `Duration::MAX` as "never") would abort. Clamp the
26/// duration to ~100 years — effectively "never" — which `checked_add` then
27/// resolves without overflowing `Instant`. Mirrors the round-16 hardening of
28/// `moirai_pal::timer::Timer::new`. `unwrap_or(base)` is a safe
29/// (non-panicking) degenerate fallback; it is unreachable on any real
30/// platform, where `Instant` has decades of headroom.
31pub(crate) fn clamped_deadline(base: Instant, duration: Duration) -> Instant {
32    const MAX_TIMER: Duration = Duration::from_secs(100 * 365 * 24 * 60 * 60);
33    base.checked_add(duration.min(MAX_TIMER)).unwrap_or(base)
34}
35
36/// Create a delay future that completes after the specified duration
37pub fn sleep(duration: Duration) -> Delay {
38    Delay::new(duration)
39}
40
41/// Timeout wrapper for futures with comprehensive cancellation
42pub fn timeout<F>(duration: Duration, future: F) -> Timeout<F>
43where
44    F: Future,
45{
46    Timeout::new(future, duration)
47}
48
49/// Create a new interval timer
50pub fn interval(period: Duration) -> Interval {
51    Interval::new(period)
52}
53
54/// Create an interval timer that starts at a specific time
55pub fn interval_at(start: Instant, period: Duration) -> Interval {
56    Interval::new_at(start, period)
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use std::time::Instant;
63
64    #[test]
65    fn test_delay_basic() {
66        let delay = Delay::new(Duration::from_millis(10));
67        assert!(delay.deadline() > Instant::now());
68    }
69
70    #[test]
71    fn test_sleep_function() {
72        let timer = sleep(Duration::from_millis(10));
73        assert!(timer.deadline() > Instant::now());
74    }
75
76    /// A deadline one year out — far below the ~100-year clamp, so a clamped
77    /// extreme duration must land beyond it.
78    fn one_year_from_now() -> Instant {
79        Instant::now() + Duration::from_secs(365 * 24 * 60 * 60)
80    }
81
82    #[test]
83    fn delay_extreme_duration_does_not_panic() {
84        // Regression: `Instant::now() + Duration::MAX` panics on overflow. The
85        // deadline computation must clamp/`checked_add` instead, yielding a
86        // far-future deadline rather than aborting.
87        let delay = Delay::new(Duration::MAX);
88        assert!(delay.deadline() > one_year_from_now());
89    }
90
91    #[test]
92    fn delay_reset_extreme_duration_does_not_panic() {
93        let mut delay = Delay::new(Duration::from_millis(1));
94        delay.reset(Duration::MAX);
95        assert!(delay.deadline() > one_year_from_now());
96    }
97
98    #[test]
99    fn interval_extreme_period_does_not_panic() {
100        let timer = interval(Duration::MAX);
101        assert!(timer.next_tick() > one_year_from_now());
102
103        let mut timer = interval(Duration::from_millis(1));
104        timer.set_period(Duration::MAX);
105        assert!(timer.next_tick() > one_year_from_now());
106    }
107}