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 RequestSequenceExhausted,
143 DeadlineOverflow,
145 DelayOutOfRange,
147 DirectiveNotAllowed,
149 ProviderBindingFailed,
151}
152
153impl TimerControlFailure {
154 #[must_use]
156 pub const fn label(self) -> &'static str {
157 match self {
158 Self::GenerationExhausted => "generation_exhausted",
159 Self::RequestSequenceExhausted => "request_sequence_exhausted",
160 Self::DeadlineOverflow => "deadline_overflow",
161 Self::DelayOutOfRange => "delay_out_of_range",
162 Self::DirectiveNotAllowed => "directive_not_allowed",
163 Self::ProviderBindingFailed => "provider_binding_failed",
164 }
165 }
166}
167
168#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
170pub enum InactiveReason {
171 NeverScheduled,
173 Stopped,
175 Cancelled,
177 InvariantFailure,
179 ControlFailure(TimerControlFailure),
181}
182
183#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
185pub enum OrdinaryRuntimeStateSnapshot {
186 Scheduled {
188 generation: u64,
190 deadline_ns: u64,
192 },
193 Running {
195 generation: u64,
197 },
198}
199
200#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
202pub enum WatchdogAttemptStatus {
203 Dispatched,
205 Running,
207}
208
209#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
211pub struct WatchdogAttemptSnapshot {
212 generation: u64,
213 status: WatchdogAttemptStatus,
214}
215
216impl WatchdogAttemptSnapshot {
217 pub(crate) const fn new(generation: u64, status: WatchdogAttemptStatus) -> Self {
218 Self { generation, status }
219 }
220
221 #[must_use]
223 pub const fn generation(self) -> u64 {
224 self.generation
225 }
226
227 #[must_use]
229 pub const fn status(self) -> WatchdogAttemptStatus {
230 self.status
231 }
232}
233
234#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
236pub enum WatchdogRuntimeStateSnapshot {
237 Scheduled {
239 scheduler_generation: u64,
241 deadline_ns: u64,
243 },
244 AwaitingWork {
246 successor_generation: u64,
248 successor_deadline_ns: u64,
250 attempt: WatchdogAttemptSnapshot,
252 },
253}
254
255#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
257pub enum TimerRuntimeStateSnapshot {
258 Inactive {
260 reason: InactiveReason,
262 },
263 Ordinary(OrdinaryRuntimeStateSnapshot),
265 Watchdog(WatchdogRuntimeStateSnapshot),
267}
268
269impl TimerRuntimeStateSnapshot {
270 pub(crate) const fn next_deadline_ns(self) -> Option<u64> {
271 match self {
272 Self::Inactive { .. }
273 | Self::Ordinary(OrdinaryRuntimeStateSnapshot::Running { .. }) => None,
274 Self::Ordinary(OrdinaryRuntimeStateSnapshot::Scheduled { deadline_ns, .. })
275 | Self::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled { deadline_ns, .. }) => {
276 Some(deadline_ns)
277 }
278 Self::Watchdog(WatchdogRuntimeStateSnapshot::AwaitingWork {
279 successor_deadline_ns,
280 ..
281 }) => Some(successor_deadline_ns),
282 }
283 }
284}
285
286#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
291pub enum TimerRegistrationStatus {
292 Unregistered,
294 Scheduled,
296 Running,
298}
299
300impl TimerRegistrationStatus {
301 #[must_use]
303 pub const fn label(self) -> &'static str {
304 match self {
305 Self::Unregistered => "unregistered",
306 Self::Scheduled => "scheduled",
307 Self::Running => "running",
308 }
309 }
310}
311
312impl From<TimerRuntimeStateSnapshot> for TimerRegistrationStatus {
313 fn from(value: TimerRuntimeStateSnapshot) -> Self {
314 match value {
315 TimerRuntimeStateSnapshot::Inactive { .. } => Self::Unregistered,
316 TimerRuntimeStateSnapshot::Ordinary(state) => match state {
317 OrdinaryRuntimeStateSnapshot::Scheduled { .. } => Self::Scheduled,
318 OrdinaryRuntimeStateSnapshot::Running { .. } => Self::Running,
319 },
320 TimerRuntimeStateSnapshot::Watchdog(state) => match state {
321 WatchdogRuntimeStateSnapshot::Scheduled { .. }
322 | WatchdogRuntimeStateSnapshot::AwaitingWork {
323 attempt:
324 WatchdogAttemptSnapshot {
325 status: WatchdogAttemptStatus::Dispatched,
326 ..
327 },
328 ..
329 } => Self::Scheduled,
330 WatchdogRuntimeStateSnapshot::AwaitingWork {
331 attempt:
332 WatchdogAttemptSnapshot {
333 status: WatchdogAttemptStatus::Running,
334 ..
335 },
336 ..
337 } => Self::Running,
338 },
339 }
340 }
341}
342
343#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
345pub enum TimerProcessCondition {
346 Disabled,
348 Idle,
350 Active,
352 Retrying,
354 Failed,
356}
357
358impl TimerProcessCondition {
359 #[must_use]
361 pub const fn label(self) -> &'static str {
362 match self {
363 Self::Disabled => "disabled",
364 Self::Idle => "idle",
365 Self::Active => "active",
366 Self::Retrying => "retrying",
367 Self::Failed => "failed",
368 }
369 }
370}
371
372#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
374pub enum TimerCompletionOutcome {
375 Success,
377 NoWork,
379 RetryableFailure,
381 InvariantFailure,
383}
384
385impl TimerCompletionOutcome {
386 #[must_use]
388 pub const fn label(self) -> &'static str {
389 match self {
390 Self::Success => "success",
391 Self::NoWork => "no_work",
392 Self::RetryableFailure => "retryable_failure",
393 Self::InvariantFailure => "invariant_failure",
394 }
395 }
396}
397
398#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
400pub enum TimerLastOutcome {
401 Completed(TimerCompletionOutcome),
403 Unacknowledged,
405}
406
407#[derive(Clone, Copy, Debug, Eq, PartialEq)]
409pub struct TimerCompletion {
410 outcome: TimerCompletionOutcome,
411 work_count: u64,
412}
413
414impl TimerCompletion {
415 #[must_use]
417 pub const fn success(work_count: u64) -> Self {
418 Self {
419 outcome: TimerCompletionOutcome::Success,
420 work_count,
421 }
422 }
423
424 #[must_use]
426 pub const fn no_work() -> Self {
427 Self {
428 outcome: TimerCompletionOutcome::NoWork,
429 work_count: 0,
430 }
431 }
432
433 #[must_use]
435 pub const fn retryable_failure(work_count: u64) -> Self {
436 Self {
437 outcome: TimerCompletionOutcome::RetryableFailure,
438 work_count,
439 }
440 }
441
442 #[must_use]
444 pub const fn invariant_failure(work_count: u64) -> Self {
445 Self {
446 outcome: TimerCompletionOutcome::InvariantFailure,
447 work_count,
448 }
449 }
450
451 #[must_use]
453 pub const fn outcome(self) -> TimerCompletionOutcome {
454 self.outcome
455 }
456
457 #[must_use]
459 pub const fn work_count(self) -> u64 {
460 self.work_count
461 }
462}
463
464#[derive(Clone, Copy, Debug, Eq, PartialEq)]
469pub struct TimerRunResult {
470 completion: TimerCompletion,
471 directive: TimerDirective,
472}
473
474impl TimerRunResult {
475 #[must_use]
477 pub const fn new(completion: TimerCompletion, directive: TimerDirective) -> Self {
478 Self {
479 directive: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
480 TimerDirective::Stop
481 } else {
482 directive
483 },
484 completion,
485 }
486 }
487
488 #[must_use]
490 pub const fn completion(self) -> TimerCompletion {
491 self.completion
492 }
493
494 #[must_use]
496 pub const fn directive(self) -> TimerDirective {
497 self.directive
498 }
499}
500
501#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
503pub enum WatchdogDecision {
504 Continue,
506 Stop,
508}
509
510#[derive(Clone, Copy, Debug, Eq, PartialEq)]
512pub struct WatchdogRunResult {
513 completion: TimerCompletion,
514 decision: WatchdogDecision,
515}
516
517impl WatchdogRunResult {
518 #[must_use]
520 pub const fn new(completion: TimerCompletion, decision: WatchdogDecision) -> Self {
521 Self {
522 decision: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
523 WatchdogDecision::Stop
524 } else {
525 decision
526 },
527 completion,
528 }
529 }
530
531 #[must_use]
533 pub const fn completion(self) -> TimerCompletion {
534 self.completion
535 }
536
537 #[must_use]
539 pub const fn decision(self) -> WatchdogDecision {
540 self.decision
541 }
542}
543
544#[derive(Clone, Copy, Debug, Eq, PartialEq)]
546pub struct TimerOutcomeSnapshot {
547 last_outcome: Option<TimerLastOutcome>,
548 last_work_count: Option<u64>,
549 last_success_at_ns: Option<u64>,
550 last_failure_at_ns: Option<u64>,
551 last_unacknowledged_at_ns: Option<u64>,
552 consecutive_expected_failures: u64,
553}
554
555impl TimerOutcomeSnapshot {
556 pub(crate) const EMPTY: Self = Self {
557 last_outcome: None,
558 last_work_count: None,
559 last_success_at_ns: None,
560 last_failure_at_ns: None,
561 last_unacknowledged_at_ns: None,
562 consecutive_expected_failures: 0,
563 };
564
565 pub(crate) const fn record_completion(
566 &mut self,
567 completion: TimerCompletion,
568 completed_at_ns: u64,
569 ) {
570 self.last_outcome = Some(TimerLastOutcome::Completed(completion.outcome));
571 self.last_work_count = Some(completion.work_count);
572 match completion.outcome {
573 TimerCompletionOutcome::Success | TimerCompletionOutcome::NoWork => {
574 self.last_success_at_ns = Some(completed_at_ns);
575 self.consecutive_expected_failures = 0;
576 }
577 TimerCompletionOutcome::RetryableFailure => {
578 self.last_failure_at_ns = Some(completed_at_ns);
579 self.consecutive_expected_failures =
580 self.consecutive_expected_failures.saturating_add(1);
581 }
582 TimerCompletionOutcome::InvariantFailure => {
583 self.last_failure_at_ns = Some(completed_at_ns);
584 self.consecutive_expected_failures = 0;
585 }
586 }
587 }
588
589 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
590 self.last_outcome = Some(TimerLastOutcome::Unacknowledged);
591 self.last_work_count = None;
592 self.last_unacknowledged_at_ns = Some(observed_at_ns);
593 }
594
595 #[must_use]
597 pub const fn last_outcome(self) -> Option<TimerLastOutcome> {
598 self.last_outcome
599 }
600
601 #[must_use]
603 pub const fn last_work_count(self) -> Option<u64> {
604 self.last_work_count
605 }
606
607 #[must_use]
609 pub const fn last_success_at_ns(self) -> Option<u64> {
610 self.last_success_at_ns
611 }
612
613 #[must_use]
615 pub const fn last_failure_at_ns(self) -> Option<u64> {
616 self.last_failure_at_ns
617 }
618
619 #[must_use]
621 pub const fn last_unacknowledged_at_ns(self) -> Option<u64> {
622 self.last_unacknowledged_at_ns
623 }
624
625 #[must_use]
627 pub const fn consecutive_expected_failures(self) -> u64 {
628 self.consecutive_expected_failures
629 }
630}
631
632#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
634pub struct TimerEpoch {
635 canister_version: u64,
636 started_at_ns: u64,
637}
638
639impl TimerEpoch {
640 pub(crate) const fn new(canister_version: u64, started_at_ns: u64) -> Self {
641 Self {
642 canister_version,
643 started_at_ns,
644 }
645 }
646
647 #[must_use]
649 pub const fn canister_version(self) -> u64 {
650 self.canister_version
651 }
652
653 #[must_use]
655 pub const fn started_at_ns(self) -> u64 {
656 self.started_at_ns
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn expected_failure_streak_saturates() {
666 let mut outcomes = TimerOutcomeSnapshot {
667 consecutive_expected_failures: u64::MAX,
668 ..TimerOutcomeSnapshot::EMPTY
669 };
670
671 outcomes.record_completion(TimerCompletion::retryable_failure(0), 10);
672
673 assert_eq!(outcomes.consecutive_expected_failures(), u64::MAX);
674 }
675
676 #[test]
677 fn invariant_results_are_forced_to_stop() {
678 let ordinary = TimerRunResult::new(
679 TimerCompletion::invariant_failure(2),
680 TimerDirective::ContinueImmediately,
681 );
682 assert_eq!(ordinary.directive(), TimerDirective::Stop);
683
684 let watchdog = WatchdogRunResult::new(
685 TimerCompletion::invariant_failure(3),
686 WatchdogDecision::Continue,
687 );
688 assert_eq!(watchdog.decision(), WatchdogDecision::Stop);
689 }
690}