1use crate::snapshot::TimerSchedulingMode;
4use std::time::Duration;
5use thiserror::Error;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct TimerCadence(u64);
10
11impl TimerCadence {
12 pub fn new(cadence: Duration) -> Result<Self, ScheduleError> {
14 let cadence_ns = duration_ns(cadence)?;
15 Self::from_nanos(cadence_ns)
16 }
17
18 pub const fn from_nanos(cadence_ns: u64) -> Result<Self, ScheduleError> {
20 if cadence_ns == 0 {
21 Err(ScheduleError::ZeroCadence)
22 } else {
23 Ok(Self(cadence_ns))
24 }
25 }
26
27 #[must_use]
29 pub const fn as_nanos(self) -> u64 {
30 self.0
31 }
32
33 pub(super) const fn deadline_after(self, now_ns: u64) -> Result<u64, ScheduleError> {
35 checked_deadline_after(now_ns, self.0)
36 }
37}
38
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub enum TimerSchedule {
42 After(Duration),
44 At(u64),
46}
47
48impl TimerSchedule {
49 pub(super) fn resolve(self, now_ns: u64) -> Result<ResolvedSchedule, ScheduleError> {
51 match self {
52 Self::After(delay) => {
53 let delay_ns = duration_ns(delay)?;
54 Ok(ResolvedSchedule {
55 deadline_ns: checked_deadline_after(now_ns, delay_ns)?,
56 requested_delay_ns: Some(delay_ns),
57 mode: TimerSchedulingMode::Once,
58 })
59 }
60 Self::At(deadline_ns) => Ok(ResolvedSchedule {
61 deadline_ns,
62 requested_delay_ns: None,
63 mode: TimerSchedulingMode::Deadline,
64 }),
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub enum TimerDirective {
72 Stop,
74 ContinueImmediately,
76 RetryAfter(Duration),
78 ScheduleAt(u64),
80 RecurAfterCompletion,
82}
83
84impl TimerDirective {
85 pub(super) fn resolve(
86 self,
87 now_ns: u64,
88 cadence: Option<TimerCadence>,
89 ) -> Result<Option<ResolvedSchedule>, DirectiveError> {
90 match self {
91 Self::Stop => Ok(None),
92 Self::ContinueImmediately => Ok(Some(ResolvedSchedule {
93 deadline_ns: now_ns,
94 requested_delay_ns: Some(0),
95 mode: TimerSchedulingMode::Continuation,
96 })),
97 Self::RetryAfter(delay) => {
98 let delay_ns = duration_ns(delay)?;
99 Ok(Some(ResolvedSchedule {
100 deadline_ns: checked_deadline_after(now_ns, delay_ns)?,
101 requested_delay_ns: Some(delay_ns),
102 mode: TimerSchedulingMode::Retry,
103 }))
104 }
105 Self::ScheduleAt(deadline_ns) => Ok(Some(ResolvedSchedule {
106 deadline_ns,
107 requested_delay_ns: None,
108 mode: TimerSchedulingMode::Deadline,
109 })),
110 Self::RecurAfterCompletion => {
111 let cadence = cadence.ok_or(DirectiveError::MissingCadence)?;
112 Ok(Some(ResolvedSchedule {
113 deadline_ns: cadence.deadline_after(now_ns)?,
114 requested_delay_ns: Some(cadence.as_nanos()),
115 mode: TimerSchedulingMode::AfterCompletion,
116 }))
117 }
118 }
119 }
120}
121
122#[non_exhaustive]
124#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
125pub enum ScheduleError {
126 #[error("timer cadence must be greater than zero")]
128 ZeroCadence,
129 #[error("timer delay exceeds the supported nanosecond range")]
131 DelayOutOfRange,
132 #[error("timer deadline exceeds the supported timestamp range")]
134 DeadlineOverflow,
135}
136
137#[derive(Clone, Copy, Debug, Eq, PartialEq)]
138pub enum DirectiveError {
139 Schedule(ScheduleError),
140 MissingCadence,
141}
142
143impl From<ScheduleError> for DirectiveError {
144 fn from(error: ScheduleError) -> Self {
145 Self::Schedule(error)
146 }
147}
148
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
150pub struct ResolvedSchedule {
151 pub(crate) deadline_ns: u64,
152 pub(crate) requested_delay_ns: Option<u64>,
153 pub(crate) mode: TimerSchedulingMode,
154}
155
156const fn checked_deadline_after(now_ns: u64, delay_ns: u64) -> Result<u64, ScheduleError> {
157 match now_ns.checked_add(delay_ns) {
158 Some(deadline_ns) => Ok(deadline_ns),
159 None => Err(ScheduleError::DeadlineOverflow),
160 }
161}
162
163pub fn duration_ns(duration: Duration) -> Result<u64, ScheduleError> {
164 u64::try_from(duration.as_nanos()).map_err(|_| ScheduleError::DelayOutOfRange)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn cadence_is_positive_bounded_and_checked() {
173 assert_eq!(
174 TimerCadence::new(Duration::ZERO),
175 Err(ScheduleError::ZeroCadence)
176 );
177 assert_eq!(
178 TimerCadence::new(Duration::from_secs(u64::MAX)),
179 Err(ScheduleError::DelayOutOfRange)
180 );
181
182 let cadence =
183 TimerCadence::new(Duration::from_nanos(5)).expect("positive cadence should be valid");
184 assert_eq!(cadence.as_nanos(), 5);
185 assert_eq!(cadence.deadline_after(10), Ok(15));
186 assert_eq!(
187 cadence.deadline_after(u64::MAX),
188 Err(ScheduleError::DeadlineOverflow)
189 );
190 }
191
192 #[test]
193 fn schedules_and_directives_resolve_without_truncation() {
194 assert_eq!(
195 TimerSchedule::After(Duration::from_nanos(5)).resolve(10),
196 Ok(ResolvedSchedule {
197 deadline_ns: 15,
198 requested_delay_ns: Some(5),
199 mode: TimerSchedulingMode::Once,
200 })
201 );
202 assert_eq!(
203 TimerSchedule::At(7).resolve(10),
204 Ok(ResolvedSchedule {
205 deadline_ns: 7,
206 requested_delay_ns: None,
207 mode: TimerSchedulingMode::Deadline,
208 })
209 );
210 assert_eq!(TimerDirective::Stop.resolve(10, None), Ok(None));
211 assert_eq!(
212 TimerDirective::ContinueImmediately.resolve(10, None),
213 Ok(Some(ResolvedSchedule {
214 deadline_ns: 10,
215 requested_delay_ns: Some(0),
216 mode: TimerSchedulingMode::Continuation,
217 }))
218 );
219 assert_eq!(
220 TimerDirective::RetryAfter(Duration::from_nanos(5)).resolve(10, None),
221 Ok(Some(ResolvedSchedule {
222 deadline_ns: 15,
223 requested_delay_ns: Some(5),
224 mode: TimerSchedulingMode::Retry,
225 }))
226 );
227 assert_eq!(
228 TimerDirective::ScheduleAt(7).resolve(10, None),
229 Ok(Some(ResolvedSchedule {
230 deadline_ns: 7,
231 requested_delay_ns: None,
232 mode: TimerSchedulingMode::Deadline,
233 }))
234 );
235
236 let cadence = TimerCadence::from_nanos(9).expect("fixture cadence should be valid");
237 assert_eq!(
238 TimerDirective::RecurAfterCompletion.resolve(10, Some(cadence)),
239 Ok(Some(ResolvedSchedule {
240 deadline_ns: 19,
241 requested_delay_ns: Some(9),
242 mode: TimerSchedulingMode::AfterCompletion,
243 }))
244 );
245 assert_eq!(
246 TimerDirective::RecurAfterCompletion.resolve(10, None),
247 Err(DirectiveError::MissingCadence)
248 );
249 assert_eq!(
250 TimerDirective::RetryAfter(Duration::from_nanos(1)).resolve(u64::MAX, None),
251 Err(DirectiveError::Schedule(ScheduleError::DeadlineOverflow))
252 );
253 }
254}