1use crate::schedule::{ScheduleError, TimerCadence, TimerDirective, duration_ns};
4
5#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub enum TimerPolicy {
8 Once,
10 AfterCompletion {
12 cadence: TimerCadence,
14 },
15 Watchdog {
17 cadence: TimerCadence,
19 },
20}
21
22impl TimerPolicy {
23 #[must_use]
25 pub const fn label(self) -> &'static str {
26 match self {
27 Self::Once => "once",
28 Self::AfterCompletion { .. } => "after_completion",
29 Self::Watchdog { .. } => "watchdog",
30 }
31 }
32
33 #[must_use]
35 pub const fn cadence(self) -> Option<TimerCadence> {
36 match self {
37 Self::Once => None,
38 Self::AfterCompletion { cadence } | Self::Watchdog { cadence } => Some(cadence),
39 }
40 }
41}
42
43#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
45pub enum DeclarationLifetime {
46 Retained,
48 RemoveWhenStopped,
50}
51
52#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
57pub enum TimerSchedulingMode {
58 Once,
60 AfterCompletion,
62 Deadline,
64 Retry,
66 Continuation,
68 Watchdog,
70}
71
72impl TimerSchedulingMode {
73 #[must_use]
75 pub const fn label(self) -> &'static str {
76 match self {
77 Self::Once => "once",
78 Self::AfterCompletion => "after_completion",
79 Self::Deadline => "deadline",
80 Self::Retry => "retry",
81 Self::Continuation => "continuation",
82 Self::Watchdog => "watchdog",
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
89pub enum TimerDirectiveSnapshot {
90 Stop,
92 ContinueImmediately,
94 RetryAfter {
96 delay_ns: u64,
98 },
99 ScheduleAt {
101 deadline_ns: u64,
103 },
104 RecurAfterCompletion,
106}
107
108impl TimerDirectiveSnapshot {
109 pub(crate) const fn scheduling_mode(self) -> Option<TimerSchedulingMode> {
110 match self {
111 Self::Stop => None,
112 Self::ContinueImmediately => Some(TimerSchedulingMode::Continuation),
113 Self::RetryAfter { .. } => Some(TimerSchedulingMode::Retry),
114 Self::ScheduleAt { .. } => Some(TimerSchedulingMode::Deadline),
115 Self::RecurAfterCompletion => Some(TimerSchedulingMode::AfterCompletion),
116 }
117 }
118}
119
120impl TryFrom<TimerDirective> for TimerDirectiveSnapshot {
121 type Error = ScheduleError;
122
123 fn try_from(value: TimerDirective) -> Result<Self, Self::Error> {
124 Ok(match value {
125 TimerDirective::Stop => Self::Stop,
126 TimerDirective::ContinueImmediately => Self::ContinueImmediately,
127 TimerDirective::RetryAfter(delay) => Self::RetryAfter {
128 delay_ns: duration_ns(delay)?,
129 },
130 TimerDirective::ScheduleAt(deadline_ns) => Self::ScheduleAt { deadline_ns },
131 TimerDirective::RecurAfterCompletion => Self::RecurAfterCompletion,
132 })
133 }
134}
135
136#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
138pub enum TimerControlFailure {
139 GenerationExhausted,
141 DeadlineOverflow,
143 DelayOutOfRange,
145 DirectiveNotAllowed,
147 ProviderBindingFailed,
149}
150
151impl TimerControlFailure {
152 #[must_use]
154 pub const fn label(self) -> &'static str {
155 match self {
156 Self::GenerationExhausted => "generation_exhausted",
157 Self::DeadlineOverflow => "deadline_overflow",
158 Self::DelayOutOfRange => "delay_out_of_range",
159 Self::DirectiveNotAllowed => "directive_not_allowed",
160 Self::ProviderBindingFailed => "provider_binding_failed",
161 }
162 }
163}
164
165#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
167pub enum InactiveReason {
168 NeverScheduled,
170 Stopped,
172 Cancelled,
174 InvariantFailure,
176 ControlFailure(TimerControlFailure),
178}
179
180#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
182pub enum OrdinaryRuntimeStateSnapshot {
183 Scheduled {
185 generation: u64,
187 deadline_ns: u64,
189 },
190 Running {
192 generation: u64,
194 },
195}
196
197#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
199pub enum WatchdogAttemptStatus {
200 Dispatched,
202 Running,
204}
205
206#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
208pub struct WatchdogAttemptSnapshot {
209 generation: u64,
210 status: WatchdogAttemptStatus,
211}
212
213impl WatchdogAttemptSnapshot {
214 pub(crate) const fn new(generation: u64, status: WatchdogAttemptStatus) -> Self {
215 Self { generation, status }
216 }
217
218 #[must_use]
220 pub const fn generation(self) -> u64 {
221 self.generation
222 }
223
224 #[must_use]
226 pub const fn status(self) -> WatchdogAttemptStatus {
227 self.status
228 }
229}
230
231#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
233pub enum WatchdogRuntimeStateSnapshot {
234 Scheduled {
236 scheduler_generation: u64,
238 deadline_ns: u64,
240 },
241 AwaitingWork {
243 successor_generation: u64,
245 successor_deadline_ns: u64,
247 attempt: WatchdogAttemptSnapshot,
249 },
250}
251
252#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
254pub enum TimerRuntimeStateSnapshot {
255 Inactive {
257 reason: InactiveReason,
259 },
260 Ordinary(OrdinaryRuntimeStateSnapshot),
262 Watchdog(WatchdogRuntimeStateSnapshot),
264}
265
266impl TimerRuntimeStateSnapshot {
267 pub(crate) const fn next_deadline_ns(self) -> Option<u64> {
268 match self {
269 Self::Inactive { .. }
270 | Self::Ordinary(OrdinaryRuntimeStateSnapshot::Running { .. }) => None,
271 Self::Ordinary(OrdinaryRuntimeStateSnapshot::Scheduled { deadline_ns, .. })
272 | Self::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled { deadline_ns, .. }) => {
273 Some(deadline_ns)
274 }
275 Self::Watchdog(WatchdogRuntimeStateSnapshot::AwaitingWork {
276 successor_deadline_ns,
277 ..
278 }) => Some(successor_deadline_ns),
279 }
280 }
281}
282
283#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
288pub enum TimerRegistrationStatus {
289 Unregistered,
291 Scheduled,
293 Running,
295}
296
297impl TimerRegistrationStatus {
298 #[must_use]
300 pub const fn label(self) -> &'static str {
301 match self {
302 Self::Unregistered => "unregistered",
303 Self::Scheduled => "scheduled",
304 Self::Running => "running",
305 }
306 }
307}
308
309impl From<TimerRuntimeStateSnapshot> for TimerRegistrationStatus {
310 fn from(value: TimerRuntimeStateSnapshot) -> Self {
311 match value {
312 TimerRuntimeStateSnapshot::Inactive { .. } => Self::Unregistered,
313 TimerRuntimeStateSnapshot::Ordinary(state) => match state {
314 OrdinaryRuntimeStateSnapshot::Scheduled { .. } => Self::Scheduled,
315 OrdinaryRuntimeStateSnapshot::Running { .. } => Self::Running,
316 },
317 TimerRuntimeStateSnapshot::Watchdog(state) => match state {
318 WatchdogRuntimeStateSnapshot::Scheduled { .. }
319 | WatchdogRuntimeStateSnapshot::AwaitingWork {
320 attempt:
321 WatchdogAttemptSnapshot {
322 status: WatchdogAttemptStatus::Dispatched,
323 ..
324 },
325 ..
326 } => Self::Scheduled,
327 WatchdogRuntimeStateSnapshot::AwaitingWork {
328 attempt:
329 WatchdogAttemptSnapshot {
330 status: WatchdogAttemptStatus::Running,
331 ..
332 },
333 ..
334 } => Self::Running,
335 },
336 }
337 }
338}
339
340#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
342pub enum TimerProcessCondition {
343 Disabled,
345 Idle,
347 Active,
349 Retrying,
351 Failed,
353}
354
355impl TimerProcessCondition {
356 #[must_use]
358 pub const fn label(self) -> &'static str {
359 match self {
360 Self::Disabled => "disabled",
361 Self::Idle => "idle",
362 Self::Active => "active",
363 Self::Retrying => "retrying",
364 Self::Failed => "failed",
365 }
366 }
367}
368
369#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
371pub enum TimerCompletionOutcome {
372 Success,
374 NoWork,
376 RetryableFailure,
378 InvariantFailure,
380}
381
382impl TimerCompletionOutcome {
383 #[must_use]
385 pub const fn label(self) -> &'static str {
386 match self {
387 Self::Success => "success",
388 Self::NoWork => "no_work",
389 Self::RetryableFailure => "retryable_failure",
390 Self::InvariantFailure => "invariant_failure",
391 }
392 }
393}
394
395#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
397pub enum TimerLastOutcome {
398 Completed(TimerCompletionOutcome),
400 Unacknowledged,
402}
403
404#[derive(Clone, Copy, Debug, Eq, PartialEq)]
406pub struct TimerCompletion {
407 outcome: TimerCompletionOutcome,
408 work_count: u64,
409}
410
411impl TimerCompletion {
412 #[must_use]
414 pub const fn success(work_count: u64) -> Self {
415 Self {
416 outcome: TimerCompletionOutcome::Success,
417 work_count,
418 }
419 }
420
421 #[must_use]
423 pub const fn no_work() -> Self {
424 Self {
425 outcome: TimerCompletionOutcome::NoWork,
426 work_count: 0,
427 }
428 }
429
430 #[must_use]
432 pub const fn retryable_failure(work_count: u64) -> Self {
433 Self {
434 outcome: TimerCompletionOutcome::RetryableFailure,
435 work_count,
436 }
437 }
438
439 #[must_use]
441 pub const fn invariant_failure(work_count: u64) -> Self {
442 Self {
443 outcome: TimerCompletionOutcome::InvariantFailure,
444 work_count,
445 }
446 }
447
448 #[must_use]
450 pub const fn outcome(self) -> TimerCompletionOutcome {
451 self.outcome
452 }
453
454 #[must_use]
456 pub const fn work_count(self) -> u64 {
457 self.work_count
458 }
459}
460
461#[derive(Clone, Copy, Debug, Eq, PartialEq)]
466pub struct TimerRunResult {
467 completion: TimerCompletion,
468 directive: TimerDirective,
469}
470
471impl TimerRunResult {
472 #[must_use]
474 pub const fn new(completion: TimerCompletion, directive: TimerDirective) -> Self {
475 Self {
476 directive: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
477 TimerDirective::Stop
478 } else {
479 directive
480 },
481 completion,
482 }
483 }
484
485 #[must_use]
487 pub const fn completion(self) -> TimerCompletion {
488 self.completion
489 }
490
491 #[must_use]
493 pub const fn directive(self) -> TimerDirective {
494 self.directive
495 }
496}
497
498#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
500pub enum WatchdogDecision {
501 Continue,
503 Stop,
505}
506
507#[derive(Clone, Copy, Debug, Eq, PartialEq)]
509pub struct WatchdogRunResult {
510 completion: TimerCompletion,
511 decision: WatchdogDecision,
512}
513
514impl WatchdogRunResult {
515 #[must_use]
517 pub const fn new(completion: TimerCompletion, decision: WatchdogDecision) -> Self {
518 Self {
519 decision: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
520 WatchdogDecision::Stop
521 } else {
522 decision
523 },
524 completion,
525 }
526 }
527
528 #[must_use]
530 pub const fn completion(self) -> TimerCompletion {
531 self.completion
532 }
533
534 #[must_use]
536 pub const fn decision(self) -> WatchdogDecision {
537 self.decision
538 }
539}
540
541#[derive(Clone, Copy, Debug, Eq, PartialEq)]
543pub struct TimerOutcomeSnapshot {
544 last_outcome: Option<TimerLastOutcome>,
545 last_work_count: Option<u64>,
546 last_success_at_ns: Option<u64>,
547 last_failure_at_ns: Option<u64>,
548 last_unacknowledged_at_ns: Option<u64>,
549 consecutive_expected_failures: u64,
550}
551
552impl TimerOutcomeSnapshot {
553 pub(crate) const EMPTY: Self = Self {
554 last_outcome: None,
555 last_work_count: None,
556 last_success_at_ns: None,
557 last_failure_at_ns: None,
558 last_unacknowledged_at_ns: None,
559 consecutive_expected_failures: 0,
560 };
561
562 pub(crate) const fn record_completion(
563 &mut self,
564 completion: TimerCompletion,
565 completed_at_ns: u64,
566 ) {
567 self.last_outcome = Some(TimerLastOutcome::Completed(completion.outcome));
568 self.last_work_count = Some(completion.work_count);
569 match completion.outcome {
570 TimerCompletionOutcome::Success | TimerCompletionOutcome::NoWork => {
571 self.last_success_at_ns = Some(completed_at_ns);
572 self.consecutive_expected_failures = 0;
573 }
574 TimerCompletionOutcome::RetryableFailure => {
575 self.last_failure_at_ns = Some(completed_at_ns);
576 self.consecutive_expected_failures =
577 self.consecutive_expected_failures.saturating_add(1);
578 }
579 TimerCompletionOutcome::InvariantFailure => {
580 self.last_failure_at_ns = Some(completed_at_ns);
581 self.consecutive_expected_failures = 0;
582 }
583 }
584 }
585
586 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
587 self.last_outcome = Some(TimerLastOutcome::Unacknowledged);
588 self.last_work_count = None;
589 self.last_unacknowledged_at_ns = Some(observed_at_ns);
590 }
591
592 #[must_use]
594 pub const fn last_outcome(self) -> Option<TimerLastOutcome> {
595 self.last_outcome
596 }
597
598 #[must_use]
600 pub const fn last_work_count(self) -> Option<u64> {
601 self.last_work_count
602 }
603
604 #[must_use]
606 pub const fn last_success_at_ns(self) -> Option<u64> {
607 self.last_success_at_ns
608 }
609
610 #[must_use]
612 pub const fn last_failure_at_ns(self) -> Option<u64> {
613 self.last_failure_at_ns
614 }
615
616 #[must_use]
618 pub const fn last_unacknowledged_at_ns(self) -> Option<u64> {
619 self.last_unacknowledged_at_ns
620 }
621
622 #[must_use]
624 pub const fn consecutive_expected_failures(self) -> u64 {
625 self.consecutive_expected_failures
626 }
627}
628
629#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
631pub struct TimerEpoch {
632 canister_version: u64,
633 started_at_ns: u64,
634}
635
636impl TimerEpoch {
637 pub(crate) const fn new(canister_version: u64, started_at_ns: u64) -> Self {
638 Self {
639 canister_version,
640 started_at_ns,
641 }
642 }
643
644 #[must_use]
646 pub const fn canister_version(self) -> u64 {
647 self.canister_version
648 }
649
650 #[must_use]
652 pub const fn started_at_ns(self) -> u64 {
653 self.started_at_ns
654 }
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660
661 #[test]
662 fn expected_failure_streak_saturates() {
663 let mut outcomes = TimerOutcomeSnapshot {
664 consecutive_expected_failures: u64::MAX,
665 ..TimerOutcomeSnapshot::EMPTY
666 };
667
668 outcomes.record_completion(TimerCompletion::retryable_failure(0), 10);
669
670 assert_eq!(outcomes.consecutive_expected_failures(), u64::MAX);
671 }
672
673 #[test]
674 fn invariant_results_are_forced_to_stop() {
675 let ordinary = TimerRunResult::new(
676 TimerCompletion::invariant_failure(2),
677 TimerDirective::ContinueImmediately,
678 );
679 assert_eq!(ordinary.directive(), TimerDirective::Stop);
680
681 let watchdog = WatchdogRunResult::new(
682 TimerCompletion::invariant_failure(3),
683 WatchdogDecision::Continue,
684 );
685 assert_eq!(watchdog.decision(), WatchdogDecision::Stop);
686 }
687}