use crate::schedule::{ScheduleError, TimerCadence, TimerDirective, duration_ns};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerPolicy {
Once,
AfterCompletion {
cadence: TimerCadence,
},
Watchdog {
cadence: TimerCadence,
},
}
impl TimerPolicy {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Once => "once",
Self::AfterCompletion { .. } => "after_completion",
Self::Watchdog { .. } => "watchdog",
}
}
#[must_use]
pub const fn cadence(self) -> Option<TimerCadence> {
match self {
Self::Once => None,
Self::AfterCompletion { cadence } | Self::Watchdog { cadence } => Some(cadence),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DeclarationLifetime {
Retained,
RemoveWhenStopped,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerSchedulingMode {
Once,
AfterCompletion,
Deadline,
Retry,
Continuation,
Watchdog,
}
impl TimerSchedulingMode {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Once => "once",
Self::AfterCompletion => "after_completion",
Self::Deadline => "deadline",
Self::Retry => "retry",
Self::Continuation => "continuation",
Self::Watchdog => "watchdog",
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerDirectiveSnapshot {
Stop,
ContinueImmediately,
RetryAfter {
delay_ns: u64,
},
ScheduleAt {
deadline_ns: u64,
},
RecurAfterCompletion,
}
impl TimerDirectiveSnapshot {
pub(crate) const fn scheduling_mode(self) -> Option<TimerSchedulingMode> {
match self {
Self::Stop => None,
Self::ContinueImmediately => Some(TimerSchedulingMode::Continuation),
Self::RetryAfter { .. } => Some(TimerSchedulingMode::Retry),
Self::ScheduleAt { .. } => Some(TimerSchedulingMode::Deadline),
Self::RecurAfterCompletion => Some(TimerSchedulingMode::AfterCompletion),
}
}
}
impl TryFrom<TimerDirective> for TimerDirectiveSnapshot {
type Error = ScheduleError;
fn try_from(value: TimerDirective) -> Result<Self, Self::Error> {
Ok(match value {
TimerDirective::Stop => Self::Stop,
TimerDirective::ContinueImmediately => Self::ContinueImmediately,
TimerDirective::RetryAfter(delay) => Self::RetryAfter {
delay_ns: duration_ns(delay)?,
},
TimerDirective::ScheduleAt(deadline_ns) => Self::ScheduleAt { deadline_ns },
TimerDirective::RecurAfterCompletion => Self::RecurAfterCompletion,
})
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerControlFailure {
GenerationExhausted,
DeadlineOverflow,
DelayOutOfRange,
DirectiveNotAllowed,
ProviderBindingFailed,
}
impl TimerControlFailure {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::GenerationExhausted => "generation_exhausted",
Self::DeadlineOverflow => "deadline_overflow",
Self::DelayOutOfRange => "delay_out_of_range",
Self::DirectiveNotAllowed => "directive_not_allowed",
Self::ProviderBindingFailed => "provider_binding_failed",
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum InactiveReason {
NeverScheduled,
Stopped,
Cancelled,
InvariantFailure,
ControlFailure(TimerControlFailure),
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum OrdinaryRuntimeStateSnapshot {
Scheduled {
generation: u64,
deadline_ns: u64,
},
Running {
generation: u64,
},
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum WatchdogAttemptStatus {
Dispatched,
Running,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WatchdogAttemptSnapshot {
generation: u64,
status: WatchdogAttemptStatus,
}
impl WatchdogAttemptSnapshot {
pub(crate) const fn new(generation: u64, status: WatchdogAttemptStatus) -> Self {
Self { generation, status }
}
#[must_use]
pub const fn generation(self) -> u64 {
self.generation
}
#[must_use]
pub const fn status(self) -> WatchdogAttemptStatus {
self.status
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum WatchdogRuntimeStateSnapshot {
Scheduled {
scheduler_generation: u64,
deadline_ns: u64,
},
AwaitingWork {
successor_generation: u64,
successor_deadline_ns: u64,
attempt: WatchdogAttemptSnapshot,
},
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerRuntimeStateSnapshot {
Inactive {
reason: InactiveReason,
},
Ordinary(OrdinaryRuntimeStateSnapshot),
Watchdog(WatchdogRuntimeStateSnapshot),
}
impl TimerRuntimeStateSnapshot {
pub(crate) const fn next_deadline_ns(self) -> Option<u64> {
match self {
Self::Inactive { .. }
| Self::Ordinary(OrdinaryRuntimeStateSnapshot::Running { .. }) => None,
Self::Ordinary(OrdinaryRuntimeStateSnapshot::Scheduled { deadline_ns, .. })
| Self::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled { deadline_ns, .. }) => {
Some(deadline_ns)
}
Self::Watchdog(WatchdogRuntimeStateSnapshot::AwaitingWork {
successor_deadline_ns,
..
}) => Some(successor_deadline_ns),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerRegistrationStatus {
Unregistered,
Scheduled,
Running,
}
impl TimerRegistrationStatus {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Unregistered => "unregistered",
Self::Scheduled => "scheduled",
Self::Running => "running",
}
}
}
impl From<TimerRuntimeStateSnapshot> for TimerRegistrationStatus {
fn from(value: TimerRuntimeStateSnapshot) -> Self {
match value {
TimerRuntimeStateSnapshot::Inactive { .. } => Self::Unregistered,
TimerRuntimeStateSnapshot::Ordinary(state) => match state {
OrdinaryRuntimeStateSnapshot::Scheduled { .. } => Self::Scheduled,
OrdinaryRuntimeStateSnapshot::Running { .. } => Self::Running,
},
TimerRuntimeStateSnapshot::Watchdog(state) => match state {
WatchdogRuntimeStateSnapshot::Scheduled { .. }
| WatchdogRuntimeStateSnapshot::AwaitingWork {
attempt:
WatchdogAttemptSnapshot {
status: WatchdogAttemptStatus::Dispatched,
..
},
..
} => Self::Scheduled,
WatchdogRuntimeStateSnapshot::AwaitingWork {
attempt:
WatchdogAttemptSnapshot {
status: WatchdogAttemptStatus::Running,
..
},
..
} => Self::Running,
},
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerProcessCondition {
Disabled,
Idle,
Active,
Retrying,
Failed,
}
impl TimerProcessCondition {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Disabled => "disabled",
Self::Idle => "idle",
Self::Active => "active",
Self::Retrying => "retrying",
Self::Failed => "failed",
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerCompletionOutcome {
Success,
NoWork,
RetryableFailure,
InvariantFailure,
}
impl TimerCompletionOutcome {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Success => "success",
Self::NoWork => "no_work",
Self::RetryableFailure => "retryable_failure",
Self::InvariantFailure => "invariant_failure",
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerLastOutcome {
Completed(TimerCompletionOutcome),
Unacknowledged,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimerCompletion {
outcome: TimerCompletionOutcome,
work_count: u64,
}
impl TimerCompletion {
#[must_use]
pub const fn success(work_count: u64) -> Self {
Self {
outcome: TimerCompletionOutcome::Success,
work_count,
}
}
#[must_use]
pub const fn no_work() -> Self {
Self {
outcome: TimerCompletionOutcome::NoWork,
work_count: 0,
}
}
#[must_use]
pub const fn retryable_failure(work_count: u64) -> Self {
Self {
outcome: TimerCompletionOutcome::RetryableFailure,
work_count,
}
}
#[must_use]
pub const fn invariant_failure(work_count: u64) -> Self {
Self {
outcome: TimerCompletionOutcome::InvariantFailure,
work_count,
}
}
#[must_use]
pub const fn outcome(self) -> TimerCompletionOutcome {
self.outcome
}
#[must_use]
pub const fn work_count(self) -> u64 {
self.work_count
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimerRunResult {
completion: TimerCompletion,
directive: TimerDirective,
}
impl TimerRunResult {
#[must_use]
pub const fn new(completion: TimerCompletion, directive: TimerDirective) -> Self {
Self {
directive: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
TimerDirective::Stop
} else {
directive
},
completion,
}
}
#[must_use]
pub const fn completion(self) -> TimerCompletion {
self.completion
}
#[must_use]
pub const fn directive(self) -> TimerDirective {
self.directive
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum WatchdogDecision {
Continue,
ContinueImmediately,
ScheduleAt(u64),
Stop,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WatchdogRunResult {
completion: TimerCompletion,
decision: WatchdogDecision,
}
impl WatchdogRunResult {
#[must_use]
pub const fn new(completion: TimerCompletion, decision: WatchdogDecision) -> Self {
Self {
decision: if matches!(completion.outcome, TimerCompletionOutcome::InvariantFailure) {
WatchdogDecision::Stop
} else {
decision
},
completion,
}
}
#[must_use]
pub const fn completion(self) -> TimerCompletion {
self.completion
}
#[must_use]
pub const fn decision(self) -> WatchdogDecision {
self.decision
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimerOutcomeSnapshot {
last_outcome: Option<TimerLastOutcome>,
last_work_count: Option<u64>,
last_success_at_ns: Option<u64>,
last_failure_at_ns: Option<u64>,
last_unacknowledged_at_ns: Option<u64>,
consecutive_expected_failures: u64,
}
impl TimerOutcomeSnapshot {
pub(crate) const EMPTY: Self = Self {
last_outcome: None,
last_work_count: None,
last_success_at_ns: None,
last_failure_at_ns: None,
last_unacknowledged_at_ns: None,
consecutive_expected_failures: 0,
};
pub(crate) const fn record_completion(
&mut self,
completion: TimerCompletion,
completed_at_ns: u64,
) {
self.last_outcome = Some(TimerLastOutcome::Completed(completion.outcome));
self.last_work_count = Some(completion.work_count);
match completion.outcome {
TimerCompletionOutcome::Success | TimerCompletionOutcome::NoWork => {
self.last_success_at_ns = Some(completed_at_ns);
self.consecutive_expected_failures = 0;
}
TimerCompletionOutcome::RetryableFailure => {
self.last_failure_at_ns = Some(completed_at_ns);
self.consecutive_expected_failures =
self.consecutive_expected_failures.saturating_add(1);
}
TimerCompletionOutcome::InvariantFailure => {
self.last_failure_at_ns = Some(completed_at_ns);
self.consecutive_expected_failures = 0;
}
}
}
pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
self.last_outcome = Some(TimerLastOutcome::Unacknowledged);
self.last_work_count = None;
self.last_unacknowledged_at_ns = Some(observed_at_ns);
}
#[must_use]
pub const fn last_outcome(self) -> Option<TimerLastOutcome> {
self.last_outcome
}
#[must_use]
pub const fn last_work_count(self) -> Option<u64> {
self.last_work_count
}
#[must_use]
pub const fn last_success_at_ns(self) -> Option<u64> {
self.last_success_at_ns
}
#[must_use]
pub const fn last_failure_at_ns(self) -> Option<u64> {
self.last_failure_at_ns
}
#[must_use]
pub const fn last_unacknowledged_at_ns(self) -> Option<u64> {
self.last_unacknowledged_at_ns
}
#[must_use]
pub const fn consecutive_expected_failures(self) -> u64 {
self.consecutive_expected_failures
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TimerEpoch {
canister_version: u64,
started_at_ns: u64,
}
impl TimerEpoch {
pub(crate) const fn new(canister_version: u64, started_at_ns: u64) -> Self {
Self {
canister_version,
started_at_ns,
}
}
#[must_use]
pub const fn canister_version(self) -> u64 {
self.canister_version
}
#[must_use]
pub const fn started_at_ns(self) -> u64 {
self.started_at_ns
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expected_failure_streak_saturates() {
let mut outcomes = TimerOutcomeSnapshot {
consecutive_expected_failures: u64::MAX,
..TimerOutcomeSnapshot::EMPTY
};
outcomes.record_completion(TimerCompletion::retryable_failure(0), 10);
assert_eq!(outcomes.consecutive_expected_failures(), u64::MAX);
}
#[test]
fn invariant_results_are_forced_to_stop() {
let ordinary = TimerRunResult::new(
TimerCompletion::invariant_failure(2),
TimerDirective::ContinueImmediately,
);
assert_eq!(ordinary.directive(), TimerDirective::Stop);
let watchdog = WatchdogRunResult::new(
TimerCompletion::invariant_failure(3),
WatchdogDecision::ContinueImmediately,
);
assert_eq!(watchdog.decision(), WatchdogDecision::Stop);
}
}