1use crate::{ScheduleError, TimerCadence, TimerDirective};
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)]
64pub enum TimerSchedulingMode {
65 Once,
67 AfterCompletion,
69 Deadline,
71 Retry,
73 Continuation,
75 Watchdog,
77}
78
79impl TimerSchedulingMode {
80 #[must_use]
82 pub const fn label(self) -> &'static str {
83 match self {
84 Self::Once => "once",
85 Self::AfterCompletion => "after_completion",
86 Self::Deadline => "deadline",
87 Self::Retry => "retry",
88 Self::Continuation => "continuation",
89 Self::Watchdog => "watchdog",
90 }
91 }
92}
93
94#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
96pub enum TimerDirectiveSnapshot {
97 Stop,
99 ContinueImmediately,
101 RetryAfter {
103 delay_ns: u64,
105 },
106 ScheduleAt {
108 deadline_ns: u64,
110 },
111 RecurAfterCompletion,
113}
114
115impl TimerDirectiveSnapshot {
116 #[must_use]
118 pub const fn scheduling_mode(self) -> Option<TimerSchedulingMode> {
119 match self {
120 Self::Stop => None,
121 Self::ContinueImmediately => Some(TimerSchedulingMode::Continuation),
122 Self::RetryAfter { .. } => Some(TimerSchedulingMode::Retry),
123 Self::ScheduleAt { .. } => Some(TimerSchedulingMode::Deadline),
124 Self::RecurAfterCompletion => Some(TimerSchedulingMode::AfterCompletion),
125 }
126 }
127}
128
129impl TryFrom<TimerDirective> for TimerDirectiveSnapshot {
130 type Error = ScheduleError;
131
132 fn try_from(value: TimerDirective) -> Result<Self, Self::Error> {
133 Ok(match value {
134 TimerDirective::Stop => Self::Stop,
135 TimerDirective::ContinueImmediately => Self::ContinueImmediately,
136 TimerDirective::RetryAfter(delay) => Self::RetryAfter {
137 delay_ns: duration_ns(delay)?,
138 },
139 TimerDirective::ScheduleAt(deadline_ns) => Self::ScheduleAt { deadline_ns },
140 TimerDirective::RecurAfterCompletion => Self::RecurAfterCompletion,
141 })
142 }
143}
144
145impl From<TimerDirectiveSnapshot> for TimerDirective {
146 fn from(value: TimerDirectiveSnapshot) -> Self {
147 match value {
148 TimerDirectiveSnapshot::Stop => Self::Stop,
149 TimerDirectiveSnapshot::ContinueImmediately => Self::ContinueImmediately,
150 TimerDirectiveSnapshot::RetryAfter { delay_ns } => {
151 Self::RetryAfter(Duration::from_nanos(delay_ns))
152 }
153 TimerDirectiveSnapshot::ScheduleAt { deadline_ns } => Self::ScheduleAt(deadline_ns),
154 TimerDirectiveSnapshot::RecurAfterCompletion => Self::RecurAfterCompletion,
155 }
156 }
157}
158
159fn duration_ns(duration: Duration) -> Result<u64, ScheduleError> {
160 u64::try_from(duration.as_nanos()).map_err(|_| ScheduleError::DelayOutOfRange)
161}
162
163#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
165pub enum TimerControlFailure {
166 GenerationExhausted,
168 RequestSequenceExhausted,
170 DeadlineOverflow,
172 DelayOutOfRange,
174 DirectiveNotAllowed,
176 ProviderBindingFailed,
178}
179
180impl TimerControlFailure {
181 #[must_use]
183 pub const fn label(self) -> &'static str {
184 match self {
185 Self::GenerationExhausted => "generation_exhausted",
186 Self::RequestSequenceExhausted => "request_sequence_exhausted",
187 Self::DeadlineOverflow => "deadline_overflow",
188 Self::DelayOutOfRange => "delay_out_of_range",
189 Self::DirectiveNotAllowed => "directive_not_allowed",
190 Self::ProviderBindingFailed => "provider_binding_failed",
191 }
192 }
193}
194
195#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
197pub enum InactiveReason {
198 NeverScheduled,
200 Stopped,
202 Cancelled,
204 InvariantFailure,
206 ControlFailure(TimerControlFailure),
208}
209
210#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
212pub enum OrdinaryRuntimeStateSnapshot {
213 Scheduled {
215 generation: u64,
217 deadline_ns: u64,
219 },
220 Running {
222 generation: u64,
224 },
225}
226
227#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
229pub enum WatchdogAttemptStatus {
230 Dispatched,
232 Running,
234}
235
236#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
238pub struct WatchdogAttemptSnapshot {
239 generation: u64,
240 status: WatchdogAttemptStatus,
241}
242
243impl WatchdogAttemptSnapshot {
244 pub(crate) const fn new(generation: u64, status: WatchdogAttemptStatus) -> Self {
245 Self { generation, status }
246 }
247
248 #[must_use]
250 pub const fn generation(self) -> u64 {
251 self.generation
252 }
253
254 #[must_use]
256 pub const fn status(self) -> WatchdogAttemptStatus {
257 self.status
258 }
259}
260
261#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
263pub enum WatchdogRuntimeStateSnapshot {
264 Scheduled {
266 scheduler_generation: u64,
268 deadline_ns: u64,
270 },
271 AwaitingWork {
273 successor_generation: u64,
275 successor_deadline_ns: u64,
277 attempt: WatchdogAttemptSnapshot,
279 },
280}
281
282#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
284pub enum TimerRuntimeStateSnapshot {
285 Inactive {
287 reason: InactiveReason,
289 },
290 Ordinary(OrdinaryRuntimeStateSnapshot),
292 Watchdog(WatchdogRuntimeStateSnapshot),
294}
295
296impl TimerRuntimeStateSnapshot {
297 #[must_use]
299 pub const fn next_deadline_ns(self) -> Option<u64> {
300 match self {
301 Self::Inactive { .. }
302 | Self::Ordinary(OrdinaryRuntimeStateSnapshot::Running { .. }) => None,
303 Self::Ordinary(OrdinaryRuntimeStateSnapshot::Scheduled { deadline_ns, .. })
304 | Self::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled { deadline_ns, .. }) => {
305 Some(deadline_ns)
306 }
307 Self::Watchdog(WatchdogRuntimeStateSnapshot::AwaitingWork {
308 successor_deadline_ns,
309 ..
310 }) => Some(successor_deadline_ns),
311 }
312 }
313}
314
315#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
317pub enum TimerRegistrationStatus {
318 Unregistered,
320 Scheduled,
322 Running,
324}
325
326impl TimerRegistrationStatus {
327 #[must_use]
329 pub const fn label(self) -> &'static str {
330 match self {
331 Self::Unregistered => "unregistered",
332 Self::Scheduled => "scheduled",
333 Self::Running => "running",
334 }
335 }
336}
337
338impl From<TimerRuntimeStateSnapshot> for TimerRegistrationStatus {
339 fn from(value: TimerRuntimeStateSnapshot) -> Self {
340 match value {
341 TimerRuntimeStateSnapshot::Inactive { .. } => Self::Unregistered,
342 TimerRuntimeStateSnapshot::Ordinary(state) => match state {
343 OrdinaryRuntimeStateSnapshot::Scheduled { .. } => Self::Scheduled,
344 OrdinaryRuntimeStateSnapshot::Running { .. } => Self::Running,
345 },
346 TimerRuntimeStateSnapshot::Watchdog(state) => match state {
347 WatchdogRuntimeStateSnapshot::Scheduled { .. }
348 | WatchdogRuntimeStateSnapshot::AwaitingWork {
349 attempt:
350 WatchdogAttemptSnapshot {
351 status: WatchdogAttemptStatus::Dispatched,
352 ..
353 },
354 ..
355 } => Self::Scheduled,
356 WatchdogRuntimeStateSnapshot::AwaitingWork {
357 attempt:
358 WatchdogAttemptSnapshot {
359 status: WatchdogAttemptStatus::Running,
360 ..
361 },
362 ..
363 } => Self::Running,
364 },
365 }
366 }
367}
368
369#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
371pub enum TimerProcessCondition {
372 Disabled,
374 Idle,
376 Active,
378 Retrying,
380 Failed,
382}
383
384impl TimerProcessCondition {
385 #[must_use]
387 pub const fn label(self) -> &'static str {
388 match self {
389 Self::Disabled => "disabled",
390 Self::Idle => "idle",
391 Self::Active => "active",
392 Self::Retrying => "retrying",
393 Self::Failed => "failed",
394 }
395 }
396}
397
398#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
400pub enum TimerCompletionOutcome {
401 Success,
403 NoWork,
405 RetryableFailure,
407 InvariantFailure,
409}
410
411impl TimerCompletionOutcome {
412 #[must_use]
414 pub const fn label(self) -> &'static str {
415 match self {
416 Self::Success => "success",
417 Self::NoWork => "no_work",
418 Self::RetryableFailure => "retryable_failure",
419 Self::InvariantFailure => "invariant_failure",
420 }
421 }
422}
423
424#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
426pub enum TimerLastOutcome {
427 Completed(TimerCompletionOutcome),
429 Unacknowledged,
431}
432
433#[derive(Clone, Copy, Debug, Eq, PartialEq)]
435pub struct TimerCompletion {
436 outcome: TimerCompletionOutcome,
437 work_count: u64,
438}
439
440impl TimerCompletion {
441 #[must_use]
443 pub const fn success(work_count: u64) -> Self {
444 Self {
445 outcome: TimerCompletionOutcome::Success,
446 work_count,
447 }
448 }
449
450 #[must_use]
452 pub const fn no_work() -> Self {
453 Self {
454 outcome: TimerCompletionOutcome::NoWork,
455 work_count: 0,
456 }
457 }
458
459 #[must_use]
461 pub const fn retryable_failure(work_count: u64) -> Self {
462 Self {
463 outcome: TimerCompletionOutcome::RetryableFailure,
464 work_count,
465 }
466 }
467
468 #[must_use]
470 pub const fn invariant_failure(work_count: u64) -> Self {
471 Self {
472 outcome: TimerCompletionOutcome::InvariantFailure,
473 work_count,
474 }
475 }
476
477 #[must_use]
479 pub const fn outcome(self) -> TimerCompletionOutcome {
480 self.outcome
481 }
482
483 #[must_use]
485 pub const fn work_count(self) -> u64 {
486 self.work_count
487 }
488}
489
490#[derive(Clone, Copy, Debug, Eq, PartialEq)]
492pub struct TimerRunResult {
493 completion: TimerCompletion,
494 directive: TimerDirective,
495}
496
497impl TimerRunResult {
498 #[must_use]
500 pub const fn new(completion: TimerCompletion, directive: TimerDirective) -> Self {
501 Self {
502 directive: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
503 TimerDirective::Stop
504 } else {
505 directive
506 },
507 completion,
508 }
509 }
510
511 #[must_use]
513 pub const fn completion(self) -> TimerCompletion {
514 self.completion
515 }
516
517 #[must_use]
519 pub const fn directive(self) -> TimerDirective {
520 self.directive
521 }
522}
523
524#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
526pub enum WatchdogDecision {
527 Continue,
529 Stop,
531}
532
533#[derive(Clone, Copy, Debug, Eq, PartialEq)]
535pub struct WatchdogRunResult {
536 completion: TimerCompletion,
537 decision: WatchdogDecision,
538}
539
540impl WatchdogRunResult {
541 #[must_use]
543 pub const fn new(completion: TimerCompletion, decision: WatchdogDecision) -> Self {
544 Self {
545 decision: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
546 WatchdogDecision::Stop
547 } else {
548 decision
549 },
550 completion,
551 }
552 }
553
554 #[must_use]
556 pub const fn completion(self) -> TimerCompletion {
557 self.completion
558 }
559
560 #[must_use]
562 pub const fn decision(self) -> WatchdogDecision {
563 self.decision
564 }
565}
566
567#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
569pub struct TimerOutcomeSnapshot {
570 last_outcome: Option<TimerLastOutcome>,
571 last_work_count: Option<u64>,
572 last_success_at_ns: Option<u64>,
573 last_failure_at_ns: Option<u64>,
574 last_unacknowledged_at_ns: Option<u64>,
575 consecutive_expected_failures: u64,
576}
577
578impl TimerOutcomeSnapshot {
579 pub(crate) const fn new() -> Self {
580 Self {
581 last_outcome: None,
582 last_work_count: None,
583 last_success_at_ns: None,
584 last_failure_at_ns: None,
585 last_unacknowledged_at_ns: None,
586 consecutive_expected_failures: 0,
587 }
588 }
589
590 pub(crate) const fn record_completion(
591 &mut self,
592 completion: TimerCompletion,
593 completed_at_ns: u64,
594 ) {
595 self.last_outcome = Some(TimerLastOutcome::Completed(completion.outcome));
596 self.last_work_count = Some(completion.work_count);
597 match completion.outcome {
598 TimerCompletionOutcome::Success | TimerCompletionOutcome::NoWork => {
599 self.last_success_at_ns = Some(completed_at_ns);
600 self.consecutive_expected_failures = 0;
601 }
602 TimerCompletionOutcome::RetryableFailure => {
603 self.last_failure_at_ns = Some(completed_at_ns);
604 self.consecutive_expected_failures =
605 self.consecutive_expected_failures.saturating_add(1);
606 }
607 TimerCompletionOutcome::InvariantFailure => {
608 self.last_failure_at_ns = Some(completed_at_ns);
609 self.consecutive_expected_failures = 0;
610 }
611 }
612 }
613
614 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
615 self.last_outcome = Some(TimerLastOutcome::Unacknowledged);
616 self.last_work_count = None;
617 self.last_unacknowledged_at_ns = Some(observed_at_ns);
618 }
619
620 #[must_use]
622 pub const fn last_outcome(self) -> Option<TimerLastOutcome> {
623 self.last_outcome
624 }
625
626 #[must_use]
628 pub const fn last_work_count(self) -> Option<u64> {
629 self.last_work_count
630 }
631
632 #[must_use]
634 pub const fn last_success_at_ns(self) -> Option<u64> {
635 self.last_success_at_ns
636 }
637
638 #[must_use]
640 pub const fn last_failure_at_ns(self) -> Option<u64> {
641 self.last_failure_at_ns
642 }
643
644 #[must_use]
646 pub const fn last_unacknowledged_at_ns(self) -> Option<u64> {
647 self.last_unacknowledged_at_ns
648 }
649
650 #[must_use]
652 pub const fn consecutive_expected_failures(self) -> u64 {
653 self.consecutive_expected_failures
654 }
655}
656
657#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
659pub struct TimerEpoch {
660 canister_version: u64,
661 started_at_ns: u64,
662}
663
664impl TimerEpoch {
665 pub(crate) const fn new(canister_version: u64, started_at_ns: u64) -> Self {
666 Self {
667 canister_version,
668 started_at_ns,
669 }
670 }
671
672 #[must_use]
674 pub const fn canister_version(self) -> u64 {
675 self.canister_version
676 }
677
678 #[must_use]
680 pub const fn started_at_ns(self) -> u64 {
681 self.started_at_ns
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688
689 #[test]
690 fn expected_failure_streak_saturates() {
691 let mut outcomes = TimerOutcomeSnapshot {
692 consecutive_expected_failures: u64::MAX,
693 ..TimerOutcomeSnapshot::default()
694 };
695
696 outcomes.record_completion(TimerCompletion::retryable_failure(0), 10);
697
698 assert_eq!(outcomes.consecutive_expected_failures(), u64::MAX);
699 }
700
701 #[test]
702 fn invariant_results_are_forced_to_stop() {
703 let ordinary = TimerRunResult::new(
704 TimerCompletion::invariant_failure(2),
705 TimerDirective::ContinueImmediately,
706 );
707 assert_eq!(ordinary.directive(), TimerDirective::Stop);
708
709 let watchdog = WatchdogRunResult::new(
710 TimerCompletion::invariant_failure(3),
711 WatchdogDecision::Continue,
712 );
713 assert_eq!(watchdog.decision(), WatchdogDecision::Stop);
714 }
715}