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;