Skip to main content

ic_timers/
schedule.rs

1//! Typed decisions for work completed by a timer callback.
2
3use std::time::Duration;
4use thiserror::Error;
5
6/// Scheduling decision returned after one bounded invocation.
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum TimerDirective {
9    /// Do not schedule another invocation.
10    Stop,
11    /// Continue as soon as the runtime can execute another message.
12    ContinueImmediately,
13    /// Retry after the provided delay.
14    RetryAfter(Duration),
15    /// Schedule at an absolute IC timestamp in nanoseconds.
16    ScheduleAt(u64),
17    /// Recur after the provided delay measured from completion.
18    RecurAfter(Duration),
19}
20
21/// Failure to represent a requested deadline.
22#[non_exhaustive]
23#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
24pub enum ScheduleError {
25    /// The duration cannot be represented as nanoseconds in a `u64`.
26    #[error("timer delay exceeds the supported nanosecond range")]
27    DelayOutOfRange,
28    /// Adding the delay to the current time overflows a `u64`.
29    #[error("timer deadline exceeds the supported timestamp range")]
30    DeadlineOverflow,
31}
32
33impl TimerDirective {
34    /// Resolve this directive to its next absolute nanosecond deadline.
35    ///
36    /// `None` means the timer should stop. Scheduling mode and retry semantics
37    /// remain available from the directive itself.
38    pub fn deadline_ns(self, now_ns: u64) -> Result<Option<u64>, ScheduleError> {
39        match self {
40            Self::Stop => Ok(None),
41            Self::ContinueImmediately => Ok(Some(now_ns)),
42            Self::ScheduleAt(deadline_ns) => Ok(Some(deadline_ns)),
43            Self::RetryAfter(delay) | Self::RecurAfter(delay) => {
44                deadline_after(now_ns, delay).map(Some)
45            }
46        }
47    }
48}
49
50fn deadline_after(now_ns: u64, delay: Duration) -> Result<u64, ScheduleError> {
51    let delay_ns = u64::try_from(delay.as_nanos()).map_err(|_| ScheduleError::DelayOutOfRange)?;
52    now_ns
53        .checked_add(delay_ns)
54        .ok_or(ScheduleError::DeadlineOverflow)
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn resolves_all_directive_deadlines() {
63        assert_eq!(TimerDirective::Stop.deadline_ns(10), Ok(None));
64        assert_eq!(
65            TimerDirective::ContinueImmediately.deadline_ns(10),
66            Ok(Some(10))
67        );
68        assert_eq!(TimerDirective::ScheduleAt(50).deadline_ns(10), Ok(Some(50)));
69        assert_eq!(
70            TimerDirective::RetryAfter(Duration::from_nanos(5)).deadline_ns(10),
71            Ok(Some(15))
72        );
73        assert_eq!(
74            TimerDirective::RecurAfter(Duration::from_nanos(7)).deadline_ns(10),
75            Ok(Some(17))
76        );
77    }
78
79    #[test]
80    fn rejects_overflowing_deadline() {
81        assert_eq!(
82            TimerDirective::RetryAfter(Duration::from_nanos(1)).deadline_ns(u64::MAX),
83            Err(ScheduleError::DeadlineOverflow)
84        );
85    }
86}