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    #[allow(clippy::too_many_arguments)] // Registry-only constructor keeps one coherent boundary.
120    pub(crate) const fn new(
121        identity: TimerIdentity,
122        registration_id: TimerRegistrationId,
123        policy: TimerPolicy,
124        lifetime: DeclarationLifetime,
125        state: TimerRuntimeStateSnapshot,
126        scheduling_mode: TimerSchedulingMode,
127        latest_directive: Option<TimerDirectiveSnapshot>,
128        latest_requested_delay_ns: Option<u64>,
129        latest_armed_delay_ns: Option<u64>,
130        observability: &TimerObservabilitySnapshot,
131    ) -> Self {
132        Self {
133            identity,
134            registration_id,
135            policy,
136            lifetime,
137            state,
138            scheduling_mode,
139            latest_directive,
140            latest_requested_delay_ns,
141            latest_armed_delay_ns,
142            observability: *observability,
143        }
144    }
145
146    /// Return the stable structured identity.
147    #[must_use]
148    pub const fn identity(&self) -> &TimerIdentity {
149        &self.identity
150    }
151
152    /// Return the inert identity of this registration's counter lifetime.
153    ///
154    /// Cancellation, rescheduling and completion preserve it. Unregistering
155    /// and registering again changes it, even within the same runtime epoch.
156    /// Compare only snapshots from the same canister. This value grants no
157    /// timer-control authority and cannot be used to reconstruct a registration.
158    #[must_use]
159    pub const fn registration_id(&self) -> TimerRegistrationId {
160        self.registration_id
161    }
162
163    /// Return the configured scheduling policy.
164    #[must_use]
165    pub const fn policy(&self) -> TimerPolicy {
166        self.policy
167    }
168
169    /// Return whether the declaration remains after terminal stop.
170    #[must_use]
171    pub const fn lifetime(&self) -> DeclarationLifetime {
172        self.lifetime
173    }
174
175    /// Return the closed policy-specific runtime state.
176    #[must_use]
177    pub const fn state(&self) -> TimerRuntimeStateSnapshot {
178        self.state
179    }
180
181    /// Return the effective scheduling mode.
182    ///
183    /// A new declaration starts with its configured policy mode. Later
184    /// requests and completed directives update this value, including after
185    /// the declaration becomes inactive.
186    #[must_use]
187    pub const fn scheduling_mode(&self) -> TimerSchedulingMode {
188        self.scheduling_mode
189    }
190
191    /// Return the latest completed ordinary directive.
192    #[must_use]
193    pub const fn latest_directive(&self) -> Option<TimerDirectiveSnapshot> {
194        self.latest_directive
195    }
196
197    /// Return the latest requested relative delay.
198    #[must_use]
199    pub const fn latest_requested_delay_ns(&self) -> Option<u64> {
200        self.latest_requested_delay_ns
201    }
202
203    /// Return the latest relative delay whose provider arm committed.
204    #[must_use]
205    pub const fn latest_armed_delay_ns(&self) -> Option<u64> {
206        self.latest_armed_delay_ns
207    }
208
209    /// Return the next authoritative absolute deadline.
210    #[must_use]
211    pub const fn next_deadline_ns(&self) -> Option<u64> {
212        self.state.next_deadline_ns()
213    }
214
215    /// Return a portable registration projection.
216    #[must_use]
217    pub fn registration_status(&self) -> TimerRegistrationStatus {
218        self.state.into()
219    }
220
221    /// Return an operator-facing condition derived from coherent state.
222    #[must_use]
223    pub const fn process_condition(&self) -> TimerProcessCondition {
224        match self.state {
225            TimerRuntimeStateSnapshot::Inactive {
226                reason: InactiveReason::Cancelled,
227            } => TimerProcessCondition::Disabled,
228            TimerRuntimeStateSnapshot::Inactive {
229                reason: InactiveReason::InvariantFailure | InactiveReason::ControlFailure(_),
230            } => TimerProcessCondition::Failed,
231            TimerRuntimeStateSnapshot::Inactive {
232                reason: InactiveReason::Stopped,
233            } if matches!(
234                self.observability.outcomes().last_outcome(),
235                Some(TimerLastOutcome::Completed(
236                    TimerCompletionOutcome::RetryableFailure
237                ))
238            ) =>
239            {
240                TimerProcessCondition::Failed
241            }
242            TimerRuntimeStateSnapshot::Inactive { .. } => TimerProcessCondition::Idle,
243            TimerRuntimeStateSnapshot::Ordinary(_) | TimerRuntimeStateSnapshot::Watchdog(_)
244                if matches!(self.scheduling_mode, TimerSchedulingMode::Retry) =>
245            {
246                TimerProcessCondition::Retrying
247            }
248            TimerRuntimeStateSnapshot::Ordinary(_) | TimerRuntimeStateSnapshot::Watchdog(_) => {
249                TimerProcessCondition::Active
250            }
251        }
252    }
253
254    /// Return the latest authoritative callback generation.
255    ///
256    /// Scheduling changes this value. Use [`Self::registration_id`] to check
257    /// counter continuity across observations.
258    #[must_use]
259    pub const fn generation(&self) -> Option<u64> {
260        match self.state {
261            TimerRuntimeStateSnapshot::Inactive { .. } => None,
262            TimerRuntimeStateSnapshot::Ordinary(
263                OrdinaryRuntimeStateSnapshot::Scheduled { generation, .. }
264                | OrdinaryRuntimeStateSnapshot::Running { generation },
265            ) => Some(generation),
266            TimerRuntimeStateSnapshot::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled {
267                scheduler_generation,
268                ..
269            }) => Some(scheduler_generation),
270            TimerRuntimeStateSnapshot::Watchdog(WatchdogRuntimeStateSnapshot::AwaitingWork {
271                successor_generation,
272                ..
273            }) => Some(successor_generation),
274        }
275    }
276
277    /// Return registration-scoped outcomes, counters, and measurements.
278    #[must_use]
279    pub const fn observability(&self) -> TimerObservabilitySnapshot {
280        self.observability
281    }
282}
283
284#[cfg(test)]
285mod tests;