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