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 ContinueImmediately,
508 Stop,
510}
511
512#[derive(Clone, Copy, Debug, Eq, PartialEq)]
514pub struct WatchdogRunResult {
515 completion: TimerCompletion,
516 decision: WatchdogDecision,
517}
518
519impl WatchdogRunResult {
520 #[must_use]
522 pub const fn new(completion: TimerCompletion, decision: WatchdogDecision) -> Self {
523 Self {
524 decision: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
525 WatchdogDecision::Stop
526 } else {
527 decision
528 },
529 completion,
530 }
531 }
532
533 #[must_use]
535 pub const fn completion(self) -> TimerCompletion {
536 self.completion
537 }
538
539 #[must_use]
541 pub const fn decision(self) -> WatchdogDecision {
542 self.decision
543 }
544}
545
546#[derive(Clone, Copy, Debug, Eq, PartialEq)]
548pub struct TimerOutcomeSnapshot {
549 last_outcome: Option<TimerLastOutcome>,
550 last_work_count: Option<u64>,
551 last_success_at_ns: Option<u64>,
552 last_failure_at_ns: Option<u64>,
553 last_unacknowledged_at_ns: Option<u64>,
554 consecutive_expected_failures: u64,
555}
556
557impl TimerOutcomeSnapshot {
558 pub(crate) const EMPTY: Self = Self {
559 last_outcome: None,
560 last_work_count: None,
561 last_success_at_ns: None,
562 last_failure_at_ns: None,
563 last_unacknowledged_at_ns: None,
564 consecutive_expected_failures: 0,
565 };
566
567 pub(crate) const fn record_completion(
568 &mut self,
569 completion: TimerCompletion,
570 completed_at_ns: u64,
571 ) {
572 self.last_outcome = Some(TimerLastOutcome::Completed(completion.outcome));
573 self.last_work_count = Some(completion.work_count);
574 match completion.outcome {
575 TimerCompletionOutcome::Success | TimerCompletionOutcome::NoWork => {
576 self.last_success_at_ns = Some(completed_at_ns);
577 self.consecutive_expected_failures = 0;
578 }
579 TimerCompletionOutcome::RetryableFailure => {
580 self.last_failure_at_ns = Some(completed_at_ns);
581 self.consecutive_expected_failures =
582 self.consecutive_expected_failures.saturating_add(1);
583 }
584 TimerCompletionOutcome::InvariantFailure => {
585 self.last_failure_at_ns = Some(completed_at_ns);
586 self.consecutive_expected_failures = 0;
587 }
588 }
589 }
590
591 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
592 self.last_outcome = Some(TimerLastOutcome::Unacknowledged);
593 self.last_work_count = None;
594 self.last_unacknowledged_at_ns = Some(observed_at_ns);
595 }
596
597 #[must_use]
599 pub const fn last_outcome(self) -> Option<TimerLastOutcome> {
600 self.last_outcome
601 }
602
603 #[must_use]
605 pub const fn last_work_count(self) -> Option<u64> {
606 self.last_work_count
607 }
608
609 #[must_use]
611 pub const fn last_success_at_ns(self) -> Option<u64> {
612 self.last_success_at_ns
613 }
614
615 #[must_use]
617 pub const fn last_failure_at_ns(self) -> Option<u64> {
618 self.last_failure_at_ns
619 }
620
621 #[must_use]
623 pub const fn last_unacknowledged_at_ns(self) -> Option<u64> {
624 self.last_unacknowledged_at_ns
625 }
626
627 #[must_use]
629 pub const fn consecutive_expected_failures(self) -> u64 {
630 self.consecutive_expected_failures
631 }
632}
633
634#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
636pub struct TimerEpoch {
637 canister_version: u64,
638 started_at_ns: u64,
639}
640
641impl TimerEpoch {
642 pub(crate) const fn new(canister_version: u64, started_at_ns: u64) -> Self {
643 Self {
644 canister_version,
645 started_at_ns,
646 }
647 }
648
649 #[must_use]
651 pub const fn canister_version(self) -> u64 {
652 self.canister_version
653 }
654
655 #[must_use]
657 pub const fn started_at_ns(self) -> u64 {
658 self.started_at_ns
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665
666 #[test]
667 fn expected_failure_streak_saturates() {
668 let mut outcomes = TimerOutcomeSnapshot {
669 consecutive_expected_failures: u64::MAX,
670 ..TimerOutcomeSnapshot::EMPTY
671 };
672
673 outcomes.record_completion(TimerCompletion::retryable_failure(0), 10);
674
675 assert_eq!(outcomes.consecutive_expected_failures(), u64::MAX);
676 }
677
678 #[test]
679 fn invariant_results_are_forced_to_stop() {
680 let ordinary = TimerRunResult::new(
681 TimerCompletion::invariant_failure(2),
682 TimerDirective::ContinueImmediately,
683 );
684 assert_eq!(ordinary.directive(), TimerDirective::Stop);
685
686 let watchdog = WatchdogRunResult::new(
687 TimerCompletion::invariant_failure(3),
688 WatchdogDecision::ContinueImmediately,
689 );
690 assert_eq!(watchdog.decision(), WatchdogDecision::Stop);
691 }
692}