1use crate::{ScheduleError, TimerDirective, TimerRegistration};
4use std::time::Duration;
5
6#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub enum TimerPolicy {
9 Once,
11 AfterCompletion {
13 cadence_ns: u64,
15 },
16 Watchdog {
18 cadence_ns: u64,
20 },
21}
22
23impl TimerPolicy {
24 #[must_use]
26 pub const fn label(self) -> &'static str {
27 match self {
28 Self::Once => "once",
29 Self::AfterCompletion { .. } => "after_completion",
30 Self::Watchdog { .. } => "watchdog",
31 }
32 }
33
34 #[must_use]
36 pub const fn cadence_ns(self) -> Option<u64> {
37 match self {
38 Self::Once => None,
39 Self::AfterCompletion { cadence_ns } | Self::Watchdog { cadence_ns } => {
40 Some(cadence_ns)
41 }
42 }
43 }
44
45 #[must_use]
47 pub const fn initial_mode(self) -> TimerSchedulingMode {
48 match self {
49 Self::Once => TimerSchedulingMode::Once,
50 Self::AfterCompletion { .. } => TimerSchedulingMode::AfterCompletion,
51 Self::Watchdog { .. } => TimerSchedulingMode::Watchdog,
52 }
53 }
54}
55
56#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub enum TimerSchedulingMode {
59 Once,
61 AfterCompletion,
63 Deadline,
65 Retry,
67 Continuation,
69 Watchdog,
71}
72
73impl TimerSchedulingMode {
74 #[must_use]
76 pub const fn label(self) -> &'static str {
77 match self {
78 Self::Once => "once",
79 Self::AfterCompletion => "after_completion",
80 Self::Deadline => "deadline",
81 Self::Retry => "retry",
82 Self::Continuation => "continuation",
83 Self::Watchdog => "watchdog",
84 }
85 }
86}
87
88#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
90pub enum TimerDirectiveSnapshot {
91 Stop,
93 ContinueImmediately,
95 RetryAfter {
97 delay_ns: u64,
99 },
100 ScheduleAt {
102 deadline_ns: u64,
104 },
105 RecurAfter {
107 delay_ns: u64,
109 },
110}
111
112impl TimerDirectiveSnapshot {
113 #[must_use]
115 pub const fn scheduling_mode(self) -> Option<TimerSchedulingMode> {
116 match self {
117 Self::Stop => None,
118 Self::ContinueImmediately => Some(TimerSchedulingMode::Continuation),
119 Self::RetryAfter { .. } => Some(TimerSchedulingMode::Retry),
120 Self::ScheduleAt { .. } => Some(TimerSchedulingMode::Deadline),
121 Self::RecurAfter { .. } => Some(TimerSchedulingMode::AfterCompletion),
122 }
123 }
124}
125
126impl TryFrom<TimerDirective> for TimerDirectiveSnapshot {
127 type Error = ScheduleError;
128
129 fn try_from(value: TimerDirective) -> Result<Self, Self::Error> {
130 Ok(match value {
131 TimerDirective::Stop => Self::Stop,
132 TimerDirective::ContinueImmediately => Self::ContinueImmediately,
133 TimerDirective::RetryAfter(delay) => Self::RetryAfter {
134 delay_ns: duration_ns(delay)?,
135 },
136 TimerDirective::ScheduleAt(deadline_ns) => Self::ScheduleAt { deadline_ns },
137 TimerDirective::RecurAfter(delay) => Self::RecurAfter {
138 delay_ns: duration_ns(delay)?,
139 },
140 })
141 }
142}
143
144impl From<TimerDirectiveSnapshot> for TimerDirective {
145 fn from(value: TimerDirectiveSnapshot) -> Self {
146 match value {
147 TimerDirectiveSnapshot::Stop => Self::Stop,
148 TimerDirectiveSnapshot::ContinueImmediately => Self::ContinueImmediately,
149 TimerDirectiveSnapshot::RetryAfter { delay_ns } => {
150 Self::RetryAfter(Duration::from_nanos(delay_ns))
151 }
152 TimerDirectiveSnapshot::ScheduleAt { deadline_ns } => Self::ScheduleAt(deadline_ns),
153 TimerDirectiveSnapshot::RecurAfter { delay_ns } => {
154 Self::RecurAfter(Duration::from_nanos(delay_ns))
155 }
156 }
157 }
158}
159
160fn duration_ns(duration: Duration) -> Result<u64, ScheduleError> {
161 u64::try_from(duration.as_nanos()).map_err(|_| ScheduleError::DelayOutOfRange)
162}
163
164#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
166pub struct PreArmedSuccessor {
167 pub generation: u64,
169 pub deadline_ns: u64,
171}
172
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
175pub struct TimerSchedulingSnapshot {
176 pub configured_policy: TimerPolicy,
178 pub current_mode: TimerSchedulingMode,
180 pub latest_directive: Option<TimerDirectiveSnapshot>,
182 pub latest_requested_delay_ns: Option<u64>,
184 pub latest_armed_delay_ns: Option<u64>,
186 pub next_deadline_ns: Option<u64>,
188 pub pre_armed_successor: Option<PreArmedSuccessor>,
190}
191
192impl TimerSchedulingSnapshot {
193 #[must_use]
195 pub const fn new(configured_policy: TimerPolicy) -> Self {
196 Self {
197 configured_policy,
198 current_mode: configured_policy.initial_mode(),
199 latest_directive: None,
200 latest_requested_delay_ns: None,
201 latest_armed_delay_ns: None,
202 next_deadline_ns: None,
203 pre_armed_successor: None,
204 }
205 }
206}
207
208#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
210pub enum TimerRegistrationStatus {
211 Unregistered,
213 Scheduled,
215 Running,
217}
218
219impl TimerRegistrationStatus {
220 #[must_use]
222 pub const fn label(self) -> &'static str {
223 match self {
224 Self::Unregistered => "unregistered",
225 Self::Scheduled => "scheduled",
226 Self::Running => "running",
227 }
228 }
229}
230
231impl From<TimerRegistration> for TimerRegistrationStatus {
232 fn from(value: TimerRegistration) -> Self {
233 match value {
234 TimerRegistration::Unregistered => Self::Unregistered,
235 TimerRegistration::Scheduled { .. } => Self::Scheduled,
236 TimerRegistration::Running { .. } => Self::Running,
237 }
238 }
239}
240
241#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
243pub enum TimerProcessCondition {
244 Disabled,
246 Idle,
248 Active,
250 Retrying,
252 Failed,
254 MissingRegistration,
256}
257
258impl TimerProcessCondition {
259 #[must_use]
261 pub const fn label(self) -> &'static str {
262 match self {
263 Self::Disabled => "disabled",
264 Self::Idle => "idle",
265 Self::Active => "active",
266 Self::Retrying => "retrying",
267 Self::Failed => "failed",
268 Self::MissingRegistration => "missing_registration",
269 }
270 }
271}
272
273#[derive(Clone, Copy, Debug, Eq, PartialEq)]
275pub struct TimerStateSnapshot {
276 pub enabled: bool,
278 pub registration: TimerRegistrationStatus,
280 pub condition: TimerProcessCondition,
282 pub generation: u64,
284 pub in_flight: bool,
286}
287
288impl Default for TimerStateSnapshot {
289 fn default() -> Self {
290 Self {
291 enabled: true,
292 registration: TimerRegistrationStatus::Unregistered,
293 condition: TimerProcessCondition::Idle,
294 generation: 0,
295 in_flight: false,
296 }
297 }
298}
299
300#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
302pub enum TimerCompletionOutcome {
303 Success,
305 NoWork,
307 RetryableFailure,
309 InvariantFailure,
311}
312
313impl TimerCompletionOutcome {
314 #[must_use]
316 pub const fn label(self) -> &'static str {
317 match self {
318 Self::Success => "success",
319 Self::NoWork => "no_work",
320 Self::RetryableFailure => "retryable_failure",
321 Self::InvariantFailure => "invariant_failure",
322 }
323 }
324}
325
326#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
328pub enum TimerLastOutcome {
329 Completed(TimerCompletionOutcome),
331 Interrupted,
333}
334
335#[derive(Clone, Copy, Debug, Eq, PartialEq)]
337pub struct TimerCompletion {
338 pub outcome: TimerCompletionOutcome,
340 pub work_count: u64,
342}
343
344impl TimerCompletion {
345 #[must_use]
347 pub const fn success(work_count: u64) -> Self {
348 Self {
349 outcome: TimerCompletionOutcome::Success,
350 work_count,
351 }
352 }
353
354 #[must_use]
356 pub const fn no_work() -> Self {
357 Self {
358 outcome: TimerCompletionOutcome::NoWork,
359 work_count: 0,
360 }
361 }
362
363 #[must_use]
365 pub const fn retryable_failure(work_count: u64) -> Self {
366 Self {
367 outcome: TimerCompletionOutcome::RetryableFailure,
368 work_count,
369 }
370 }
371
372 #[must_use]
374 pub const fn invariant_failure(work_count: u64) -> Self {
375 Self {
376 outcome: TimerCompletionOutcome::InvariantFailure,
377 work_count,
378 }
379 }
380}
381
382#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
384pub struct TimerOutcomeSnapshot {
385 last_outcome: Option<TimerLastOutcome>,
386 last_work_count: Option<u64>,
387 last_success_at_ns: Option<u64>,
388 last_failure_at_ns: Option<u64>,
389 last_interrupted_at_ns: Option<u64>,
390 consecutive_expected_failures: u64,
391}
392
393impl TimerOutcomeSnapshot {
394 pub const fn record_completion(&mut self, completion: TimerCompletion, completed_at_ns: u64) {
396 self.last_outcome = Some(TimerLastOutcome::Completed(completion.outcome));
397 self.last_work_count = Some(completion.work_count);
398 match completion.outcome {
399 TimerCompletionOutcome::Success | TimerCompletionOutcome::NoWork => {
400 self.last_success_at_ns = Some(completed_at_ns);
401 self.consecutive_expected_failures = 0;
402 }
403 TimerCompletionOutcome::RetryableFailure => {
404 self.last_failure_at_ns = Some(completed_at_ns);
405 self.consecutive_expected_failures =
406 self.consecutive_expected_failures.saturating_add(1);
407 }
408 TimerCompletionOutcome::InvariantFailure => {
409 self.last_failure_at_ns = Some(completed_at_ns);
410 self.consecutive_expected_failures = 0;
411 }
412 }
413 }
414
415 pub const fn record_interruption(&mut self, observed_at_ns: u64) {
420 self.last_outcome = Some(TimerLastOutcome::Interrupted);
421 self.last_work_count = None;
422 self.last_interrupted_at_ns = Some(observed_at_ns);
423 }
424
425 #[must_use]
427 pub const fn last_outcome(self) -> Option<TimerLastOutcome> {
428 self.last_outcome
429 }
430
431 #[must_use]
433 pub const fn last_work_count(self) -> Option<u64> {
434 self.last_work_count
435 }
436
437 #[must_use]
439 pub const fn last_success_at_ns(self) -> Option<u64> {
440 self.last_success_at_ns
441 }
442
443 #[must_use]
445 pub const fn last_failure_at_ns(self) -> Option<u64> {
446 self.last_failure_at_ns
447 }
448
449 #[must_use]
451 pub const fn last_interrupted_at_ns(self) -> Option<u64> {
452 self.last_interrupted_at_ns
453 }
454
455 #[must_use]
457 pub const fn consecutive_expected_failures(self) -> u64 {
458 self.consecutive_expected_failures
459 }
460}
461
462#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
464pub struct TimerEpoch {
465 pub id: u64,
467 pub started_at_ns: u64,
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[test]
476 fn expected_failure_streak_saturates() {
477 let mut outcomes = TimerOutcomeSnapshot {
478 consecutive_expected_failures: u64::MAX,
479 ..TimerOutcomeSnapshot::default()
480 };
481
482 outcomes.record_completion(TimerCompletion::retryable_failure(0), 10);
483
484 assert_eq!(outcomes.consecutive_expected_failures(), u64::MAX);
485 }
486}