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)]
90pub enum TimerDirectiveSnapshot {
91 Stop,
93 ContinueImmediately,
95 RetryAfter {
97 delay_ns: u64,
99 },
100 ScheduleAt {
102 deadline_ns: u64,
104 },
105 RecurAfterCompletion,
107}
108
109impl TryFrom<TimerDirective> for TimerDirectiveSnapshot {
110 type Error = ScheduleError;
111
112 fn try_from(value: TimerDirective) -> Result<Self, Self::Error> {
113 Ok(match value {
114 TimerDirective::Stop => Self::Stop,
115 TimerDirective::ContinueImmediately => Self::ContinueImmediately,
116 TimerDirective::RetryAfter(delay) => Self::RetryAfter {
117 delay_ns: duration_ns(delay)?,
118 },
119 TimerDirective::ScheduleAt(deadline_ns) => Self::ScheduleAt { deadline_ns },
120 TimerDirective::RecurAfterCompletion => Self::RecurAfterCompletion,
121 })
122 }
123}
124
125#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
127pub enum TimerControlFailure {
128 GenerationExhausted,
130 DeadlineOverflow,
132 DelayOutOfRange,
134 DirectiveNotAllowed,
136 ProviderBindingFailed,
138}
139
140impl TimerControlFailure {
141 #[must_use]
143 pub const fn label(self) -> &'static str {
144 match self {
145 Self::GenerationExhausted => "generation_exhausted",
146 Self::DeadlineOverflow => "deadline_overflow",
147 Self::DelayOutOfRange => "delay_out_of_range",
148 Self::DirectiveNotAllowed => "directive_not_allowed",
149 Self::ProviderBindingFailed => "provider_binding_failed",
150 }
151 }
152}
153
154#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
156pub enum InactiveReason {
157 NeverScheduled,
159 Stopped,
161 Cancelled,
163 InvariantFailure,
165 ControlFailure(TimerControlFailure),
167}
168
169#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
171pub enum OrdinaryRuntimeStateSnapshot {
172 Scheduled {
174 generation: u64,
176 deadline_ns: u64,
178 },
179 Running {
181 generation: u64,
183 },
184}
185
186#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
188pub enum WatchdogAttemptStatus {
189 Dispatched,
191 Running,
193}
194
195#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
197pub struct WatchdogAttemptSnapshot {
198 generation: u64,
199 status: WatchdogAttemptStatus,
200}
201
202impl WatchdogAttemptSnapshot {
203 pub(crate) const fn new(generation: u64, status: WatchdogAttemptStatus) -> Self {
204 Self { generation, status }
205 }
206
207 #[must_use]
209 pub const fn generation(self) -> u64 {
210 self.generation
211 }
212
213 #[must_use]
215 pub const fn status(self) -> WatchdogAttemptStatus {
216 self.status
217 }
218}
219
220#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
222pub enum WatchdogRuntimeStateSnapshot {
223 Scheduled {
225 scheduler_generation: u64,
227 deadline_ns: u64,
229 },
230 AwaitingWork {
232 successor_generation: u64,
234 successor_deadline_ns: u64,
236 attempt: WatchdogAttemptSnapshot,
238 },
239}
240
241#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
243pub enum TimerRuntimeStateSnapshot {
244 Inactive {
246 reason: InactiveReason,
248 },
249 Ordinary(OrdinaryRuntimeStateSnapshot),
251 Watchdog(WatchdogRuntimeStateSnapshot),
253}
254
255impl TimerRuntimeStateSnapshot {
256 pub(crate) const fn next_deadline_ns(self) -> Option<u64> {
257 match self {
258 Self::Inactive { .. }
259 | Self::Ordinary(OrdinaryRuntimeStateSnapshot::Running { .. }) => None,
260 Self::Ordinary(OrdinaryRuntimeStateSnapshot::Scheduled { deadline_ns, .. })
261 | Self::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled { deadline_ns, .. }) => {
262 Some(deadline_ns)
263 }
264 Self::Watchdog(WatchdogRuntimeStateSnapshot::AwaitingWork {
265 successor_deadline_ns,
266 ..
267 }) => Some(successor_deadline_ns),
268 }
269 }
270}
271
272#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
277pub enum TimerRegistrationStatus {
278 Unregistered,
280 Scheduled,
282 Running,
284}
285
286impl TimerRegistrationStatus {
287 #[must_use]
289 pub const fn label(self) -> &'static str {
290 match self {
291 Self::Unregistered => "unregistered",
292 Self::Scheduled => "scheduled",
293 Self::Running => "running",
294 }
295 }
296}
297
298impl From<TimerRuntimeStateSnapshot> for TimerRegistrationStatus {
299 fn from(value: TimerRuntimeStateSnapshot) -> Self {
300 match value {
301 TimerRuntimeStateSnapshot::Inactive { .. } => Self::Unregistered,
302 TimerRuntimeStateSnapshot::Ordinary(state) => match state {
303 OrdinaryRuntimeStateSnapshot::Scheduled { .. } => Self::Scheduled,
304 OrdinaryRuntimeStateSnapshot::Running { .. } => Self::Running,
305 },
306 TimerRuntimeStateSnapshot::Watchdog(state) => match state {
307 WatchdogRuntimeStateSnapshot::Scheduled { .. }
308 | WatchdogRuntimeStateSnapshot::AwaitingWork {
309 attempt:
310 WatchdogAttemptSnapshot {
311 status: WatchdogAttemptStatus::Dispatched,
312 ..
313 },
314 ..
315 } => Self::Scheduled,
316 WatchdogRuntimeStateSnapshot::AwaitingWork {
317 attempt:
318 WatchdogAttemptSnapshot {
319 status: WatchdogAttemptStatus::Running,
320 ..
321 },
322 ..
323 } => Self::Running,
324 },
325 }
326 }
327}
328
329#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
331pub enum TimerProcessCondition {
332 Disabled,
334 Idle,
336 Active,
338 Retrying,
340 Failed,
342}
343
344impl TimerProcessCondition {
345 #[must_use]
347 pub const fn label(self) -> &'static str {
348 match self {
349 Self::Disabled => "disabled",
350 Self::Idle => "idle",
351 Self::Active => "active",
352 Self::Retrying => "retrying",
353 Self::Failed => "failed",
354 }
355 }
356}
357
358#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
360pub enum TimerCompletionOutcome {
361 Success,
363 NoWork,
365 RetryableFailure,
367 InvariantFailure,
369}
370
371impl TimerCompletionOutcome {
372 #[must_use]
374 pub const fn label(self) -> &'static str {
375 match self {
376 Self::Success => "success",
377 Self::NoWork => "no_work",
378 Self::RetryableFailure => "retryable_failure",
379 Self::InvariantFailure => "invariant_failure",
380 }
381 }
382}
383
384#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
386pub enum TimerLastOutcome {
387 Completed(TimerCompletionOutcome),
389 Unacknowledged,
391}
392
393#[derive(Clone, Copy, Debug, Eq, PartialEq)]
395pub struct TimerCompletion {
396 outcome: TimerCompletionOutcome,
397 work_count: u64,
398}
399
400impl TimerCompletion {
401 #[must_use]
403 pub const fn success(work_count: u64) -> Self {
404 Self {
405 outcome: TimerCompletionOutcome::Success,
406 work_count,
407 }
408 }
409
410 #[must_use]
412 pub const fn no_work() -> Self {
413 Self {
414 outcome: TimerCompletionOutcome::NoWork,
415 work_count: 0,
416 }
417 }
418
419 #[must_use]
421 pub const fn retryable_failure(work_count: u64) -> Self {
422 Self {
423 outcome: TimerCompletionOutcome::RetryableFailure,
424 work_count,
425 }
426 }
427
428 #[must_use]
430 pub const fn invariant_failure(work_count: u64) -> Self {
431 Self {
432 outcome: TimerCompletionOutcome::InvariantFailure,
433 work_count,
434 }
435 }
436
437 #[must_use]
439 pub const fn outcome(self) -> TimerCompletionOutcome {
440 self.outcome
441 }
442
443 #[must_use]
445 pub const fn work_count(self) -> u64 {
446 self.work_count
447 }
448}
449
450#[derive(Clone, Copy, Debug, Eq, PartialEq)]
456pub struct TimerRunResult {
457 completion: TimerCompletion,
458 directive: TimerDirective,
459}
460
461impl TimerRunResult {
462 #[must_use]
464 pub const fn new(completion: TimerCompletion, directive: TimerDirective) -> Self {
465 Self {
466 directive: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
467 TimerDirective::Stop
468 } else {
469 directive
470 },
471 completion,
472 }
473 }
474
475 #[must_use]
477 pub const fn completion(self) -> TimerCompletion {
478 self.completion
479 }
480
481 #[must_use]
483 pub const fn directive(self) -> TimerDirective {
484 self.directive
485 }
486}
487
488#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
490pub enum WatchdogDecision {
491 Continue,
493 ContinueImmediately,
498 ScheduleAt(u64),
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::ContinueImmediately,
684 );
685 assert_eq!(watchdog.decision(), WatchdogDecision::Stop);
686 }
687}