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