1use crate::snapshot::{TimerControlFailure, TimerDirectiveSnapshot, 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 OrdinaryDirective {
72 Stop,
74 ContinueImmediately,
76 RetryAfter(Duration),
78 ScheduleAt(u64),
80 RecurAfterCompletion,
82}
83
84impl OrdinaryDirective {
85 pub(super) fn resolve(
86 self,
87 now_ns: u64,
88 cadence: Option<TimerCadence>,
89 ) -> Result<(Option<ResolvedSchedule>, TimerDirectiveSnapshot), TimerControlFailure> {
90 let snapshot =
91 TimerDirectiveSnapshot::try_from(self).map_err(ScheduleError::control_failure)?;
92 let schedule = match snapshot {
93 TimerDirectiveSnapshot::Stop => None,
94 TimerDirectiveSnapshot::ContinueImmediately => Some(ResolvedSchedule {
95 deadline_ns: now_ns,
96 requested_delay_ns: Some(0),
97 mode: TimerSchedulingMode::Continuation,
98 }),
99 TimerDirectiveSnapshot::RetryAfter { delay_ns } => Some(ResolvedSchedule {
100 deadline_ns: checked_deadline_after(now_ns, delay_ns)
101 .map_err(ScheduleError::control_failure)?,
102 requested_delay_ns: Some(delay_ns),
103 mode: TimerSchedulingMode::Retry,
104 }),
105 TimerDirectiveSnapshot::ScheduleAt { deadline_ns } => Some(ResolvedSchedule {
106 deadline_ns,
107 requested_delay_ns: None,
108 mode: TimerSchedulingMode::Deadline,
109 }),
110 TimerDirectiveSnapshot::RecurAfterCompletion => {
111 let cadence = cadence.ok_or(TimerControlFailure::DirectiveNotAllowed)?;
112 Some(ResolvedSchedule {
113 deadline_ns: cadence
114 .deadline_after(now_ns)
115 .map_err(ScheduleError::control_failure)?,
116 requested_delay_ns: Some(cadence.as_nanos()),
117 mode: TimerSchedulingMode::AfterCompletion,
118 })
119 }
120 };
121 Ok((schedule, snapshot))
122 }
123}
124
125#[non_exhaustive]
127#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
128pub enum ScheduleError {
129 #[error("timer cadence must be greater than zero")]
131 ZeroCadence,
132 #[error("timer delay exceeds the supported nanosecond range")]
134 DelayOutOfRange,
135 #[error("timer deadline exceeds the supported timestamp range")]
137 DeadlineOverflow,
138}
139
140impl ScheduleError {
141 const fn control_failure(self) -> TimerControlFailure {
144 match self {
145 Self::ZeroCadence => TimerControlFailure::DirectiveNotAllowed,
146 Self::DelayOutOfRange => TimerControlFailure::DelayOutOfRange,
147 Self::DeadlineOverflow => TimerControlFailure::DeadlineOverflow,
148 }
149 }
150}
151
152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
153pub struct ResolvedSchedule {
154 pub(crate) deadline_ns: u64,
155 pub(crate) requested_delay_ns: Option<u64>,
156 pub(crate) mode: TimerSchedulingMode,
157}
158
159pub const fn checked_deadline_after(now_ns: u64, delay_ns: u64) -> Result<u64, ScheduleError> {
160 match now_ns.checked_add(delay_ns) {
161 Some(deadline_ns) => Ok(deadline_ns),
162 None => Err(ScheduleError::DeadlineOverflow),
163 }
164}
165
166pub fn duration_ns(duration: Duration) -> Result<u64, ScheduleError> {
167 u64::try_from(duration.as_nanos()).map_err(|_| ScheduleError::DelayOutOfRange)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn cadence_is_positive_bounded_and_checked() {
176 assert_eq!(
177 TimerCadence::new(Duration::ZERO),
178 Err(ScheduleError::ZeroCadence)
179 );
180 assert_eq!(
181 TimerCadence::new(Duration::from_secs(u64::MAX)),
182 Err(ScheduleError::DelayOutOfRange)
183 );
184
185 let cadence =
186 TimerCadence::new(Duration::from_nanos(5)).expect("positive cadence should be valid");
187 assert_eq!(cadence.as_nanos(), 5);
188 assert_eq!(cadence.deadline_after(10), Ok(15));
189 assert_eq!(
190 cadence.deadline_after(u64::MAX),
191 Err(ScheduleError::DeadlineOverflow)
192 );
193 }
194
195 #[test]
196 fn schedules_and_directives_resolve_without_truncation() {
197 assert_eq!(
198 TimerSchedule::After(Duration::from_nanos(5)).resolve(10),
199 Ok(ResolvedSchedule {
200 deadline_ns: 15,
201 requested_delay_ns: Some(5),
202 mode: TimerSchedulingMode::Once,
203 })
204 );
205 assert_eq!(
206 TimerSchedule::At(7).resolve(10),
207 Ok(ResolvedSchedule {
208 deadline_ns: 7,
209 requested_delay_ns: None,
210 mode: TimerSchedulingMode::Deadline,
211 })
212 );
213 assert_eq!(
214 OrdinaryDirective::Stop.resolve(10, None),
215 Ok((None, TimerDirectiveSnapshot::Stop))
216 );
217 assert_eq!(
218 OrdinaryDirective::ContinueImmediately.resolve(10, None),
219 Ok((
220 Some(ResolvedSchedule {
221 deadline_ns: 10,
222 requested_delay_ns: Some(0),
223 mode: TimerSchedulingMode::Continuation,
224 }),
225 TimerDirectiveSnapshot::ContinueImmediately
226 ))
227 );
228 for (now_ns, delay_ns, deadline_ns) in [
229 (10, 5, 15),
230 (u64::MAX, 0, u64::MAX),
231 (0, u64::MAX, u64::MAX),
232 ] {
233 assert_eq!(
234 OrdinaryDirective::RetryAfter(Duration::from_nanos(delay_ns)).resolve(now_ns, None),
235 Ok((
236 Some(ResolvedSchedule {
237 deadline_ns,
238 requested_delay_ns: Some(delay_ns),
239 mode: TimerSchedulingMode::Retry,
240 }),
241 TimerDirectiveSnapshot::RetryAfter { delay_ns }
242 ))
243 );
244 }
245 assert_eq!(
246 OrdinaryDirective::ScheduleAt(7).resolve(10, None),
247 Ok((
248 Some(ResolvedSchedule {
249 deadline_ns: 7,
250 requested_delay_ns: None,
251 mode: TimerSchedulingMode::Deadline,
252 }),
253 TimerDirectiveSnapshot::ScheduleAt { deadline_ns: 7 }
254 ))
255 );
256
257 let cadence = TimerCadence::from_nanos(9).expect("fixture cadence should be valid");
258 assert_eq!(
259 OrdinaryDirective::RecurAfterCompletion.resolve(10, Some(cadence)),
260 Ok((
261 Some(ResolvedSchedule {
262 deadline_ns: 19,
263 requested_delay_ns: Some(9),
264 mode: TimerSchedulingMode::AfterCompletion,
265 }),
266 TimerDirectiveSnapshot::RecurAfterCompletion
267 ))
268 );
269 assert_eq!(
270 OrdinaryDirective::RecurAfterCompletion.resolve(10, None),
271 Err(TimerControlFailure::DirectiveNotAllowed)
272 );
273 assert_eq!(
274 OrdinaryDirective::RetryAfter(Duration::from_nanos(1)).resolve(u64::MAX, None),
275 Err(TimerControlFailure::DeadlineOverflow)
276 );
277 assert_eq!(
278 OrdinaryDirective::RetryAfter(Duration::MAX).resolve(0, None),
279 Err(TimerControlFailure::DelayOutOfRange)
280 );
281 assert_eq!(
282 OrdinaryDirective::RecurAfterCompletion.resolve(u64::MAX, Some(cadence)),
283 Err(TimerControlFailure::DeadlineOverflow)
284 );
285 }
286}