Skip to main content

ic_timers/snapshot/
mod.rs

1//! Provider-neutral identity and coherent canonical snapshot values.
2//!
3//! Public snapshots are inert observations with private top-level fields. The
4//! registry is their only constructor and mutation authority.
5
6mod identity;
7mod metrics;
8mod model;
9
10/// Inert identity of one registration and its cumulative measurements.
11///
12/// Equality proves a shared counter lifetime only within one canister's
13/// observed runtime history. Reinstalls or restored/forked canister state are
14/// separate histories. The sequence never wraps: exhaustion rejects registration.
15/// Values are not provider handles or mutation capabilities.
16#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
17pub struct TimerRegistrationId {
18    epoch: TimerEpoch,
19    sequence: u64,
20}
21
22impl TimerRegistrationId {
23    pub(crate) const fn new(epoch: TimerEpoch, sequence: u64) -> Self {
24        Self { epoch, sequence }
25    }
26
27    /// Return the runtime reset boundary containing this registration.
28    #[must_use]
29    pub const fn epoch(self) -> TimerEpoch {
30        self.epoch
31    }
32
33    /// Return the nonzero registration sequence within this runtime epoch.
34    #[must_use]
35    pub const fn sequence(self) -> u64 {
36        self.sequence
37    }
38}
39
40pub use identity::{
41    MAX_TIMER_IDENTITY_COMPONENT_BYTES, TimerIdentity, TimerIdentityError, TimerIdentityField,
42};
43pub use metrics::{
44    MeasurementSummary, MemoryPageExtent, MemoryPageSample, MemoryPageSummary, TimerCounters,
45    TimerObservabilitySnapshot, TimerPerformance,
46};
47pub use model::{
48    DeclarationLifetime, InactiveReason, OrdinaryRuntimeStateSnapshot, TimerCompletion,
49    TimerCompletionOutcome, TimerControlFailure, TimerDirectiveSnapshot, TimerEpoch,
50    TimerLastOutcome, TimerOutcomeSnapshot, TimerPolicy, TimerProcessCondition,
51    TimerRegistrationStatus, TimerRunResult, TimerRuntimeStateSnapshot, TimerSchedulingMode,
52    WatchdogAttemptSnapshot, WatchdogAttemptStatus, WatchdogDecision, WatchdogRunResult,
53    WatchdogRuntimeStateSnapshot,
54};
55
56/// Atomic provider-neutral snapshot of one complete canister-local inventory.
57///
58/// The epoch remains observable even when no timer is declared. Timer order is
59/// deterministic by [`TimerIdentity`], every contained timer belongs to the
60/// returned epoch, and the bounded registry is the only constructor.
61#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct TimerInventorySnapshot {
63    epoch: TimerEpoch,
64    timers: Vec<TimerSnapshot>,
65}
66
67impl TimerInventorySnapshot {
68    pub(crate) const fn new(epoch: TimerEpoch, timers: Vec<TimerSnapshot>) -> Self {
69        Self { epoch, timers }
70    }
71
72    /// Return the volatile runtime epoch shared by the complete inventory.
73    #[must_use]
74    pub const fn epoch(&self) -> TimerEpoch {
75        self.epoch
76    }
77
78    /// Return all timer snapshots in deterministic identity order.
79    #[must_use]
80    pub fn timers(&self) -> &[TimerSnapshot] {
81        &self.timers
82    }
83
84    /// Consume the inventory and return its ordered timer snapshots.
85    #[must_use]
86    pub fn into_timers(self) -> Vec<TimerSnapshot> {
87        self.timers
88    }
89
90    /// Return the number of declared logical timers.
91    #[must_use]
92    pub const fn len(&self) -> usize {
93        self.timers.len()
94    }
95
96    /// Return whether the initialized registry has no declarations.
97    #[must_use]
98    pub const fn is_empty(&self) -> bool {
99        self.timers.is_empty()
100    }
101}
102
103/// Canonical provider-neutral operator snapshot for one logical timer.
104#[derive(Clone, Debug, Eq, PartialEq)]
105pub struct TimerSnapshot {
106    identity: TimerIdentity,
107    registration_id: TimerRegistrationId,
108    policy: TimerPolicy,
109    lifetime: DeclarationLifetime,
110    state: TimerRuntimeStateSnapshot,
111    scheduling_mode: TimerSchedulingMode,
112    latest_directive: Option<TimerDirectiveSnapshot>,
113    latest_requested_delay_ns: Option<u64>,
114    latest_armed_delay_ns: Option<u64>,
115    observability: TimerObservabilitySnapshot,
116}
117
118impl TimerSnapshot {
119    #[expect(
120        clippy::too_many_arguments,
121        reason = "Registry-only constructor keeps one coherent boundary."
122    )]
123    pub(crate) const fn new(
124        identity: TimerIdentity,
125        registration_id: TimerRegistrationId,
126        policy: TimerPolicy,
127        lifetime: DeclarationLifetime,
128        state: TimerRuntimeStateSnapshot,
129        scheduling_mode: TimerSchedulingMode,
130        latest_directive: Option<TimerDirectiveSnapshot>,
131        latest_requested_delay_ns: Option<u64>,
132        latest_armed_delay_ns: Option<u64>,
133        observability: &TimerObservabilitySnapshot,
134    ) -> Self {
135        Self {
136            identity,
137            registration_id,
138            policy,
139            lifetime,
140            state,
141            scheduling_mode,
142            latest_directive,
143            latest_requested_delay_ns,
144            latest_armed_delay_ns,
145            observability: *observability,
146        }
147    }
148
149    /// Return the stable structured identity.
150    #[must_use]
151    pub const fn identity(&self) -> &TimerIdentity {
152        &self.identity
153    }
154
155    /// Return the inert identity of this registration's counter lifetime.
156    ///
157    /// Cancellation, rescheduling and completion preserve it. Unregistering
158    /// and registering again changes it, even within the same runtime epoch.
159    /// Compare only snapshots from the same canister. This value grants no
160    /// timer-control authority and cannot be used to reconstruct a registration.
161    #[must_use]
162    pub const fn registration_id(&self) -> TimerRegistrationId {
163        self.registration_id
164    }
165
166    /// Return the configured scheduling policy.
167    #[must_use]
168    pub const fn policy(&self) -> TimerPolicy {
169        self.policy
170    }
171
172    /// Return whether the declaration remains after terminal stop.
173    #[must_use]
174    pub const fn lifetime(&self) -> DeclarationLifetime {
175        self.lifetime
176    }
177
178    /// Return the closed policy-specific runtime state.
179    #[must_use]
180    pub const fn state(&self) -> TimerRuntimeStateSnapshot {
181        self.state
182    }
183
184    /// Return the effective scheduling mode.
185    ///
186    /// A new declaration starts with its configured policy mode. Later
187    /// requests and completed directives update this value, including after
188    /// the declaration becomes inactive.
189    #[must_use]
190    pub const fn scheduling_mode(&self) -> TimerSchedulingMode {
191        self.scheduling_mode
192    }
193
194    /// Return the latest effective ordinary directive at completion.
195    ///
196    /// A winning exact reconciliation projects `ScheduleAt`, even if the
197    /// callback returned a different or invalid proposal. Cancellation and
198    /// explicit invariant failure project `Stop`.
199    #[must_use]
200    pub const fn latest_directive(&self) -> Option<TimerDirectiveSnapshot> {
201        self.latest_directive
202    }
203
204    /// Return the latest requested relative delay.
205    #[must_use]
206    pub const fn latest_requested_delay_ns(&self) -> Option<u64> {
207        self.latest_requested_delay_ns
208    }
209
210    /// Return the latest relative delay whose provider arm committed.
211    #[must_use]
212    pub const fn latest_armed_delay_ns(&self) -> Option<u64> {
213        self.latest_armed_delay_ns
214    }
215
216    /// Return the next authoritative absolute deadline.
217    #[must_use]
218    pub const fn next_deadline_ns(&self) -> Option<u64> {
219        self.state.next_deadline_ns()
220    }
221
222    /// Return a portable registration projection.
223    #[must_use]
224    pub fn registration_status(&self) -> TimerRegistrationStatus {
225        self.state.into()
226    }
227
228    /// Return an operator-facing condition derived from coherent state.
229    #[must_use]
230    pub const fn process_condition(&self) -> TimerProcessCondition {
231        match self.state {
232            TimerRuntimeStateSnapshot::Inactive {
233                reason: InactiveReason::Cancelled,
234            } => TimerProcessCondition::Disabled,
235            TimerRuntimeStateSnapshot::Inactive {
236                reason: InactiveReason::InvariantFailure | InactiveReason::ControlFailure(_),
237            } => TimerProcessCondition::Failed,
238            TimerRuntimeStateSnapshot::Inactive {
239                reason: InactiveReason::Stopped,
240            } if matches!(
241                self.observability.outcomes().last_outcome(),
242                Some(TimerLastOutcome::Completed(
243                    TimerCompletionOutcome::RetryableFailure
244                ))
245            ) =>
246            {
247                TimerProcessCondition::Failed
248            }
249            TimerRuntimeStateSnapshot::Inactive { .. } => TimerProcessCondition::Idle,
250            TimerRuntimeStateSnapshot::Ordinary(_) | TimerRuntimeStateSnapshot::Watchdog(_)
251                if matches!(self.scheduling_mode, TimerSchedulingMode::Retry) =>
252            {
253                TimerProcessCondition::Retrying
254            }
255            TimerRuntimeStateSnapshot::Ordinary(_) | TimerRuntimeStateSnapshot::Watchdog(_) => {
256                TimerProcessCondition::Active
257            }
258        }
259    }
260
261    /// Return the latest authoritative callback generation.
262    ///
263    /// Scheduling changes this value. Use [`Self::registration_id`] to check
264    /// counter continuity across observations.
265    #[must_use]
266    pub const fn generation(&self) -> Option<u64> {
267        match self.state {
268            TimerRuntimeStateSnapshot::Inactive { .. } => None,
269            TimerRuntimeStateSnapshot::Ordinary(
270                OrdinaryRuntimeStateSnapshot::Scheduled { generation, .. }
271                | OrdinaryRuntimeStateSnapshot::Running { generation },
272            ) => Some(generation),
273            TimerRuntimeStateSnapshot::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled {
274                scheduler_generation,
275                ..
276            }) => Some(scheduler_generation),
277            TimerRuntimeStateSnapshot::Watchdog(WatchdogRuntimeStateSnapshot::AwaitingWork {
278                successor_generation,
279                ..
280            }) => Some(successor_generation),
281        }
282    }
283
284    /// Return registration-scoped outcomes, counters, and measurements.
285    #[must_use]
286    pub const fn observability(&self) -> TimerObservabilitySnapshot {
287        self.observability
288    }
289}
290
291#[cfg(test)]
292mod tests;