1use crate::schedule::{ScheduleError, TimerCadence, TimerDirective, duration_ns};
4use std::time::Duration;
5
6#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub enum TimerPolicy {
9 Once,
11 AfterCompletion {
13 cadence: TimerCadence,
15 },
16 Watchdog {
18 cadence: TimerCadence,
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(self) -> Option<TimerCadence> {
37 match self {
38 Self::Once => None,
39 Self::AfterCompletion { cadence } | Self::Watchdog { cadence } => Some(cadence),
40 }
41 }
42
43 #[must_use]
45 pub const fn cadence_ns(self) -> Option<u64> {
46 match self.cadence() {
47 Some(cadence) => Some(cadence.as_nanos()),
48 None => None,
49 }
50 }
51}
52
53#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
55pub enum DeclarationLifetime {
56 Retained,
58 RemoveWhenStopped,
60}
61
62#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
67pub enum TimerSchedulingMode {
68 Once,
70 AfterCompletion,
72 Deadline,
74 Retry,
76 Continuation,
78 Watchdog,
80}
81
82impl TimerSchedulingMode {
83 #[must_use]
85 pub const fn label(self) -> &'static str {
86 match self {
87 Self::Once => "once",
88 Self::AfterCompletion => "after_completion",
89 Self::Deadline => "deadline",
90 Self::Retry => "retry",
91 Self::Continuation => "continuation",
92 Self::Watchdog => "watchdog",
93 }
94 }
95}
96
97#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
99pub enum TimerDirectiveSnapshot {
100 Stop,
102 ContinueImmediately,
104 RetryAfter {
106 delay_ns: u64,
108 },
109 ScheduleAt {
111 deadline_ns: u64,
113 },
114 RecurAfterCompletion,
116}
117
118impl TimerDirectiveSnapshot {
119 #[must_use]
121 pub const fn scheduling_mode(self) -> Option<TimerSchedulingMode> {
122 match self {
123 Self::Stop => None,
124 Self::ContinueImmediately => Some(TimerSchedulingMode::Continuation),
125 Self::RetryAfter { .. } => Some(TimerSchedulingMode::Retry),
126 Self::ScheduleAt { .. } => Some(TimerSchedulingMode::Deadline),
127 Self::RecurAfterCompletion => Some(TimerSchedulingMode::AfterCompletion),
128 }
129 }
130}
131
132impl TryFrom<TimerDirective> for TimerDirectiveSnapshot {
133 type Error = ScheduleError;
134
135 fn try_from(value: TimerDirective) -> Result<Self, Self::Error> {
136 Ok(match value {
137 TimerDirective::Stop => Self::Stop,
138 TimerDirective::ContinueImmediately => Self::ContinueImmediately,
139 TimerDirective::RetryAfter(delay) => Self::RetryAfter {
140 delay_ns: duration_ns(delay)?,
141 },
142 TimerDirective::ScheduleAt(deadline_ns) => Self::ScheduleAt { deadline_ns },
143 TimerDirective::RecurAfterCompletion => Self::RecurAfterCompletion,
144 })
145 }
146}
147
148impl From<TimerDirectiveSnapshot> for TimerDirective {
149 fn from(value: TimerDirectiveSnapshot) -> Self {
150 match value {
151 TimerDirectiveSnapshot::Stop => Self::Stop,
152 TimerDirectiveSnapshot::ContinueImmediately => Self::ContinueImmediately,
153 TimerDirectiveSnapshot::RetryAfter { delay_ns } => {
154 Self::RetryAfter(Duration::from_nanos(delay_ns))
155 }
156 TimerDirectiveSnapshot::ScheduleAt { deadline_ns } => Self::ScheduleAt(deadline_ns),
157 TimerDirectiveSnapshot::RecurAfterCompletion => Self::RecurAfterCompletion,
158 }
159 }
160}
161
162#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
164pub enum TimerControlFailure {
165 GenerationExhausted,
167 RequestSequenceExhausted,
169 DeadlineOverflow,
171 DelayOutOfRange,
173 DirectiveNotAllowed,
175 ProviderBindingFailed,
177}
178
179impl TimerControlFailure {
180 #[must_use]
182 pub const fn label(self) -> &'static str {
183 match self {
184 Self::GenerationExhausted => "generation_exhausted",
185 Self::RequestSequenceExhausted => "request_sequence_exhausted",
186 Self::DeadlineOverflow => "deadline_overflow",
187 Self::DelayOutOfRange => "delay_out_of_range",
188 Self::DirectiveNotAllowed => "directive_not_allowed",
189 Self::ProviderBindingFailed => "provider_binding_failed",
190 }
191 }
192}
193
194#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
196pub enum InactiveReason {
197 NeverScheduled,
199 Stopped,
201 Cancelled,
203 InvariantFailure,
205 ControlFailure(TimerControlFailure),
207}
208
209#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
211pub enum OrdinaryRuntimeStateSnapshot {
212 Scheduled {
214 generation: u64,
216 deadline_ns: u64,
218 },
219 Running {
221 generation: u64,
223 },
224}
225
226#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
228pub enum WatchdogAttemptStatus {
229 Dispatched,
231 Running,
233}
234
235#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
237pub struct WatchdogAttemptSnapshot {
238 generation: u64,
239 status: WatchdogAttemptStatus,
240}
241
242impl WatchdogAttemptSnapshot {
243 pub(crate) const fn new(generation: u64, status: WatchdogAttemptStatus) -> Self {
244 Self { generation, status }
245 }
246
247 #[must_use]
249 pub const fn generation(self) -> u64 {
250 self.generation
251 }
252
253 #[must_use]
255 pub const fn status(self) -> WatchdogAttemptStatus {
256 self.status
257 }
258}
259
260#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
262pub enum WatchdogRuntimeStateSnapshot {
263 Scheduled {
265 scheduler_generation: u64,
267 deadline_ns: u64,
269 },
270 AwaitingWork {
272 successor_generation: u64,
274 successor_deadline_ns: u64,
276 attempt: WatchdogAttemptSnapshot,
278 },
279}
280
281#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
283pub enum TimerRuntimeStateSnapshot {
284 Inactive {
286 reason: InactiveReason,
288 },
289 Ordinary(OrdinaryRuntimeStateSnapshot),
291 Watchdog(WatchdogRuntimeStateSnapshot),
293}
294
295impl TimerRuntimeStateSnapshot {
296 #[must_use]
298 pub const fn next_deadline_ns(self) -> Option<u64> {
299 match self {
300 Self::Inactive { .. }
301 | Self::Ordinary(OrdinaryRuntimeStateSnapshot::Running { .. }) => None,
302 Self::Ordinary(OrdinaryRuntimeStateSnapshot::Scheduled { deadline_ns, .. })
303 | Self::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled { deadline_ns, .. }) => {
304 Some(deadline_ns)
305 }
306 Self::Watchdog(WatchdogRuntimeStateSnapshot::AwaitingWork {
307 successor_deadline_ns,
308 ..
309 }) => Some(successor_deadline_ns),
310 }
311 }
312}
313
314#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
316pub enum TimerRegistrationStatus {
317 Unregistered,
319 Scheduled,
321 Running,
323}
324
325impl TimerRegistrationStatus {
326 #[must_use]
328 pub const fn label(self) -> &'static str {
329 match self {
330 Self::Unregistered => "unregistered",
331 Self::Scheduled => "scheduled",
332 Self::Running => "running",
333 }
334 }
335}
336
337impl From<TimerRuntimeStateSnapshot> for TimerRegistrationStatus {
338 fn from(value: TimerRuntimeStateSnapshot) -> Self {
339 match value {
340 TimerRuntimeStateSnapshot::Inactive { .. } => Self::Unregistered,
341 TimerRuntimeStateSnapshot::Ordinary(state) => match state {
342 OrdinaryRuntimeStateSnapshot::Scheduled { .. } => Self::Scheduled,
343 OrdinaryRuntimeStateSnapshot::Running { .. } => Self::Running,
344 },
345 TimerRuntimeStateSnapshot::Watchdog(state) => match state {
346 WatchdogRuntimeStateSnapshot::Scheduled { .. }
347 | WatchdogRuntimeStateSnapshot::AwaitingWork {
348 attempt:
349 WatchdogAttemptSnapshot {
350 status: WatchdogAttemptStatus::Dispatched,
351 ..
352 },
353 ..
354 } => Self::Scheduled,
355 WatchdogRuntimeStateSnapshot::AwaitingWork {
356 attempt:
357 WatchdogAttemptSnapshot {
358 status: WatchdogAttemptStatus::Running,
359 ..
360 },
361 ..
362 } => Self::Running,
363 },
364 }
365 }
366}
367
368#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
370pub enum TimerProcessCondition {
371 Disabled,
373 Idle,
375 Active,
377 Retrying,
379 Failed,
381}
382
383impl TimerProcessCondition {
384 #[must_use]
386 pub const fn label(self) -> &'static str {
387 match self {
388 Self::Disabled => "disabled",
389 Self::Idle => "idle",
390 Self::Active => "active",
391 Self::Retrying => "retrying",
392 Self::Failed => "failed",
393 }
394 }
395}
396
397#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
399pub enum TimerCompletionOutcome {
400 Success,
402 NoWork,
404 RetryableFailure,
406 InvariantFailure,
408}
409
410impl TimerCompletionOutcome {
411 #[must_use]
413 pub const fn label(self) -> &'static str {
414 match self {
415 Self::Success => "success",
416 Self::NoWork => "no_work",
417 Self::RetryableFailure => "retryable_failure",
418 Self::InvariantFailure => "invariant_failure",
419 }
420 }
421}
422
423#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
425pub enum TimerLastOutcome {
426 Completed(TimerCompletionOutcome),
428 Unacknowledged,
430}
431
432#[derive(Clone, Copy, Debug, Eq, PartialEq)]
434pub struct TimerCompletion {
435 outcome: TimerCompletionOutcome,
436 work_count: u64,
437}
438
439impl TimerCompletion {
440 #[must_use]
442 pub const fn success(work_count: u64) -> Self {
443 Self {
444 outcome: TimerCompletionOutcome::Success,
445 work_count,
446 }
447 }
448
449 #[must_use]
451 pub const fn no_work() -> Self {
452 Self {
453 outcome: TimerCompletionOutcome::NoWork,
454 work_count: 0,
455 }
456 }
457
458 #[must_use]
460 pub const fn retryable_failure(work_count: u64) -> Self {
461 Self {
462 outcome: TimerCompletionOutcome::RetryableFailure,
463 work_count,
464 }
465 }
466
467 #[must_use]
469 pub const fn invariant_failure(work_count: u64) -> Self {
470 Self {
471 outcome: TimerCompletionOutcome::InvariantFailure,
472 work_count,
473 }
474 }
475
476 #[must_use]
478 pub const fn outcome(self) -> TimerCompletionOutcome {
479 self.outcome
480 }
481
482 #[must_use]
484 pub const fn work_count(self) -> u64 {
485 self.work_count
486 }
487}
488
489#[derive(Clone, Copy, Debug, Eq, PartialEq)]
494pub struct TimerRunResult {
495 completion: TimerCompletion,
496 directive: TimerDirective,
497}
498
499impl TimerRunResult {
500 #[must_use]
502 pub const fn new(completion: TimerCompletion, directive: TimerDirective) -> Self {
503 Self {
504 directive: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
505 TimerDirective::Stop
506 } else {
507 directive
508 },
509 completion,
510 }
511 }
512
513 #[must_use]
515 pub const fn completion(self) -> TimerCompletion {
516 self.completion
517 }
518
519 #[must_use]
521 pub const fn directive(self) -> TimerDirective {
522 self.directive
523 }
524}
525
526#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
528pub enum WatchdogDecision {
529 Continue,
531 Stop,
533}
534
535#[derive(Clone, Copy, Debug, Eq, PartialEq)]
537pub struct WatchdogRunResult {
538 completion: TimerCompletion,
539 decision: WatchdogDecision,
540}
541
542impl WatchdogRunResult {
543 #[must_use]
545 pub const fn new(completion: TimerCompletion, decision: WatchdogDecision) -> Self {
546 Self {
547 decision: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
548 WatchdogDecision::Stop
549 } else {
550 decision
551 },
552 completion,
553 }
554 }
555
556 #[must_use]
558 pub const fn completion(self) -> TimerCompletion {
559 self.completion
560 }
561
562 #[must_use]
564 pub const fn decision(self) -> WatchdogDecision {
565 self.decision
566 }
567}
568
569#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
571pub struct TimerOutcomeSnapshot {
572 last_outcome: Option<TimerLastOutcome>,
573 last_work_count: Option<u64>,
574 last_success_at_ns: Option<u64>,
575 last_failure_at_ns: Option<u64>,
576 last_unacknowledged_at_ns: Option<u64>,
577 consecutive_expected_failures: u64,
578}
579
580impl TimerOutcomeSnapshot {
581 pub(crate) const fn new() -> Self {
582 Self {
583 last_outcome: None,
584 last_work_count: None,
585 last_success_at_ns: None,
586 last_failure_at_ns: None,
587 last_unacknowledged_at_ns: None,
588 consecutive_expected_failures: 0,
589 }
590 }
591
592 pub(crate) const fn record_completion(
593 &mut self,
594 completion: TimerCompletion,
595 completed_at_ns: u64,
596 ) {
597 self.last_outcome = Some(TimerLastOutcome::Completed(completion.outcome));
598 self.last_work_count = Some(completion.work_count);
599 match completion.outcome {
600 TimerCompletionOutcome::Success | TimerCompletionOutcome::NoWork => {
601 self.last_success_at_ns = Some(completed_at_ns);
602 self.consecutive_expected_failures = 0;
603 }
604 TimerCompletionOutcome::RetryableFailure => {
605 self.last_failure_at_ns = Some(completed_at_ns);
606 self.consecutive_expected_failures =
607 self.consecutive_expected_failures.saturating_add(1);
608 }
609 TimerCompletionOutcome::InvariantFailure => {
610 self.last_failure_at_ns = Some(completed_at_ns);
611 self.consecutive_expected_failures = 0;
612 }
613 }
614 }
615
616 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
617 self.last_outcome = Some(TimerLastOutcome::Unacknowledged);
618 self.last_work_count = None;
619 self.last_unacknowledged_at_ns = Some(observed_at_ns);
620 }
621
622 #[must_use]
624 pub const fn last_outcome(self) -> Option<TimerLastOutcome> {
625 self.last_outcome
626 }
627
628 #[must_use]
630 pub const fn last_work_count(self) -> Option<u64> {
631 self.last_work_count
632 }
633
634 #[must_use]
636 pub const fn last_success_at_ns(self) -> Option<u64> {
637 self.last_success_at_ns
638 }
639
640 #[must_use]
642 pub const fn last_failure_at_ns(self) -> Option<u64> {
643 self.last_failure_at_ns
644 }
645
646 #[must_use]
648 pub const fn last_unacknowledged_at_ns(self) -> Option<u64> {
649 self.last_unacknowledged_at_ns
650 }
651
652 #[must_use]
654 pub const fn consecutive_expected_failures(self) -> u64 {
655 self.consecutive_expected_failures
656 }
657}
658
659#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
661pub struct TimerEpoch {
662 canister_version: u64,
663 started_at_ns: u64,
664}
665
666impl TimerEpoch {
667 pub(crate) const fn new(canister_version: u64, started_at_ns: u64) -> Self {
668 Self {
669 canister_version,
670 started_at_ns,
671 }
672 }
673
674 #[must_use]
676 pub const fn canister_version(self) -> u64 {
677 self.canister_version
678 }
679
680 #[must_use]
682 pub const fn started_at_ns(self) -> u64 {
683 self.started_at_ns
684 }
685}
686
687#[cfg(test)]
688mod tests {
689 use super::*;
690
691 #[test]
692 fn expected_failure_streak_saturates() {
693 let mut outcomes = TimerOutcomeSnapshot {
694 consecutive_expected_failures: u64::MAX,
695 ..TimerOutcomeSnapshot::default()
696 };
697
698 outcomes.record_completion(TimerCompletion::retryable_failure(0), 10);
699
700 assert_eq!(outcomes.consecutive_expected_failures(), u64::MAX);
701 }
702
703 #[test]
704 fn invariant_results_are_forced_to_stop() {
705 let ordinary = TimerRunResult::new(
706 TimerCompletion::invariant_failure(2),
707 TimerDirective::ContinueImmediately,
708 );
709 assert_eq!(ordinary.directive(), TimerDirective::Stop);
710
711 let watchdog = WatchdogRunResult::new(
712 TimerCompletion::invariant_failure(3),
713 WatchdogDecision::Continue,
714 );
715 assert_eq!(watchdog.decision(), WatchdogDecision::Stop);
716 }
717}