Skip to main content

ic_timers/callback/
mod.rs

1//! Policy-specific ordinary callback proposals and private dispatch erasure.
2//!
3//! Public decisions contain only operations valid for their callback policy.
4//! Erasure feeds the existing registry arbitration; it grants no control authority.
5
6use crate::{
7    schedule::OrdinaryDirective,
8    snapshot::{TimerCompletion, TimerCompletionOutcome},
9};
10use std::time::Duration;
11
12/// Scheduling proposal after a normally returned `Once` invocation.
13///
14/// Once permits explicit rescheduling, but has no configured recurrence:
15///
16/// ```compile_fail,E0599
17/// use ic_timers::OnceDecision;
18/// let decision = OnceDecision::RecurAfterCompletion;
19/// ```
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum OnceDecision {
22    /// Stop unless a pending authoritative command selects another schedule.
23    Stop,
24    /// Continue in a later timer message with zero delay.
25    ContinueImmediately,
26    /// Retry after a checked relative delay.
27    RetryAfter(Duration),
28    /// Schedule at an absolute IC timestamp in nanoseconds.
29    ScheduleAt(u64),
30}
31
32/// Scheduling proposal after a normally returned after-completion invocation.
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub enum AfterCompletionDecision {
35    /// Stop unless a pending authoritative command selects another schedule.
36    Stop,
37    /// Continue in a later timer message with zero delay.
38    ContinueImmediately,
39    /// Retry after a checked relative delay.
40    RetryAfter(Duration),
41    /// Schedule at an absolute IC timestamp in nanoseconds.
42    ScheduleAt(u64),
43    /// Recur using the registration's configured cadence after completion.
44    RecurAfterCompletion,
45}
46
47/// A classified `Once` completion with one policy-valid scheduling proposal.
48///
49/// Winning exact reconciliation and terminal pending commands discard the
50/// proposal before validation. An explicit invariant failure always stops.
51/// Other completion classifications preserve the supplied decision: a
52/// retryable failure does not automatically retry, and success does not
53/// automatically continue.
54///
55/// Construction does not validate relative delays. If the proposal survives
56/// command arbitration, the runtime checks duration encoding and deadline
57/// arithmetic. An invalid selected delay stops the declaration with a
58/// [`crate::TimerControlFailure`]; a retained declaration exposes that failure
59/// in its snapshot.
60///
61/// Registration and lifecycle reconstruction use the same typed results:
62///
63/// ```no_run
64/// use ic_timers::{register_once, register_after_completion, reconcile_once,
65///     reconcile_after_completion, OnceDecision, OnceRunResult,
66///     AfterCompletionDecision, AfterCompletionRunResult, TimerIdentity,
67///     TimerCadence, TimerCompletion, TimerReconcileState, DeclarationLifetime, initialize_runtime};
68/// # fn main() -> Result<(), ic_timers::TimerError> {
69/// initialize_runtime()?;
70/// let once_identity = TimerIdentity::try_new("app", "jobs", "once").unwrap();
71/// let recurring_identity = TimerIdentity::try_new("app", "jobs", "recurring").unwrap();
72/// let direct_once_identity = TimerIdentity::try_new("app", "jobs", "direct-once").unwrap();
73/// let direct_recurring_identity = TimerIdentity::try_new("app", "jobs", "direct-recurring").unwrap();
74/// let cadence = TimerCadence::from_nanos(5)?;
75/// let _once = register_once(direct_once_identity, DeclarationLifetime::Retained, |_| async {
76///     OnceRunResult::new(TimerCompletion::no_work(), OnceDecision::Stop)
77/// })?;
78/// let _recurring = register_after_completion(direct_recurring_identity, cadence,
79///     DeclarationLifetime::Retained, |_| async {
80///         AfterCompletionRunResult::new(TimerCompletion::no_work(),
81///             AfterCompletionDecision::RecurAfterCompletion)
82///     })?;
83/// let mut once = None;
84/// reconcile_once(&mut once, &once_identity, None, |_| async {
85///     OnceRunResult::new(TimerCompletion::no_work(), OnceDecision::Stop)
86/// })?;
87/// let mut recurring = None;
88/// reconcile_after_completion(&mut recurring, &recurring_identity, cadence,
89///     TimerReconcileState::Inactive, |_| async {
90///         AfterCompletionRunResult::new(TimerCompletion::no_work(), AfterCompletionDecision::Stop)
91///     })?;
92/// # Ok(())
93/// # }
94/// ```
95///
96/// The result accepts only a [`OnceDecision`]:
97///
98/// ```compile_fail,E0308
99/// use ic_timers::{AfterCompletionDecision, OnceRunResult, TimerCompletion};
100/// let result = OnceRunResult::new(
101///     TimerCompletion::no_work(), AfterCompletionDecision::Stop,
102/// );
103/// ```
104#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub struct OnceRunResult {
106    completion: TimerCompletion,
107    decision: OnceDecision,
108}
109
110impl OnceRunResult {
111    /// Construct a result, forcing invariant failures to stop.
112    #[must_use]
113    pub const fn new(completion: TimerCompletion, decision: OnceDecision) -> Self {
114        Self {
115            decision: if matches!(
116                completion.outcome(),
117                TimerCompletionOutcome::InvariantFailure
118            ) {
119                OnceDecision::Stop
120            } else {
121                decision
122            },
123            completion,
124        }
125    }
126
127    /// Return the completion classification and reported work count.
128    #[must_use]
129    pub const fn completion(self) -> TimerCompletion {
130        self.completion
131    }
132
133    /// Return the post-run scheduling proposal.
134    #[must_use]
135    pub const fn decision(self) -> OnceDecision {
136        self.decision
137    }
138}
139
140/// A classified after-completion result with one policy-valid proposal.
141///
142/// Recurrence uses only the registration's configured cadence. Winning exact
143/// reconciliation and terminal pending commands discard the proposal before
144/// validation. An explicit invariant failure always stops.
145/// Success, no work and retryable failure preserve the supplied decision;
146/// recurrence requires [`AfterCompletionDecision::RecurAfterCompletion`].
147/// A retryable failure with Stop remains Stop.
148///
149/// As with [`OnceRunResult`], construction does not validate relative delays;
150/// the runtime checks a selected proposal after command arbitration. Configured
151/// recurrence also checks the successor deadline against the completion time.
152///
153/// ```compile_fail,E0308
154/// use ic_timers::{AfterCompletionRunResult, OnceDecision, TimerCompletion};
155/// let result = AfterCompletionRunResult::new(
156///     TimerCompletion::no_work(), OnceDecision::Stop,
157/// );
158/// ```
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub struct AfterCompletionRunResult {
161    completion: TimerCompletion,
162    decision: AfterCompletionDecision,
163}
164
165impl AfterCompletionRunResult {
166    /// Construct a result, forcing invariant failures to stop.
167    #[must_use]
168    pub const fn new(completion: TimerCompletion, decision: AfterCompletionDecision) -> Self {
169        Self {
170            decision: if matches!(
171                completion.outcome(),
172                TimerCompletionOutcome::InvariantFailure
173            ) {
174                AfterCompletionDecision::Stop
175            } else {
176                decision
177            },
178            completion,
179        }
180    }
181
182    /// Return the completion classification and reported work count.
183    #[must_use]
184    pub const fn completion(self) -> TimerCompletion {
185        self.completion
186    }
187
188    /// Return the post-run scheduling proposal.
189    #[must_use]
190    pub const fn decision(self) -> AfterCompletionDecision {
191        self.decision
192    }
193}
194
195/// Erased result consumed only by the canonical ordinary completion owner.
196#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197pub struct OrdinaryRunResult {
198    completion: TimerCompletion,
199    directive: OrdinaryDirective,
200}
201
202impl OrdinaryRunResult {
203    pub(super) const fn new(completion: TimerCompletion, directive: OrdinaryDirective) -> Self {
204        Self {
205            directive: if matches!(
206                completion.outcome(),
207                TimerCompletionOutcome::InvariantFailure
208            ) {
209                OrdinaryDirective::Stop
210            } else {
211                directive
212            },
213            completion,
214        }
215    }
216
217    pub(super) const fn completion(self) -> TimerCompletion {
218        self.completion
219    }
220
221    pub(super) const fn directive(self) -> OrdinaryDirective {
222        self.directive
223    }
224}
225
226impl From<OnceDecision> for OrdinaryDirective {
227    fn from(value: OnceDecision) -> Self {
228        match value {
229            OnceDecision::Stop => Self::Stop,
230            OnceDecision::ContinueImmediately => Self::ContinueImmediately,
231            OnceDecision::RetryAfter(delay) => Self::RetryAfter(delay),
232            OnceDecision::ScheduleAt(deadline) => Self::ScheduleAt(deadline),
233        }
234    }
235}
236
237impl From<AfterCompletionDecision> for OrdinaryDirective {
238    fn from(value: AfterCompletionDecision) -> Self {
239        match value {
240            AfterCompletionDecision::Stop => Self::Stop,
241            AfterCompletionDecision::ContinueImmediately => Self::ContinueImmediately,
242            AfterCompletionDecision::RetryAfter(delay) => Self::RetryAfter(delay),
243            AfterCompletionDecision::ScheduleAt(deadline) => Self::ScheduleAt(deadline),
244            AfterCompletionDecision::RecurAfterCompletion => Self::RecurAfterCompletion,
245        }
246    }
247}
248
249impl From<OnceRunResult> for OrdinaryRunResult {
250    fn from(value: OnceRunResult) -> Self {
251        Self {
252            completion: value.completion,
253            directive: value.decision.into(),
254        }
255    }
256}
257
258impl From<AfterCompletionRunResult> for OrdinaryRunResult {
259    fn from(value: AfterCompletionRunResult) -> Self {
260        Self {
261            completion: value.completion,
262            directive: value.decision.into(),
263        }
264    }
265}
266
267#[cfg(test)]
268mod tests;