Skip to main content

ic_timers/
control.rs

1//! Pure callback-generation state for one ordinary timer.
2//!
3//! This module owns no task execution, platform timer handles, persistence, or
4//! time source. The canonical registry owns pending-command arbitration.
5
6use thiserror::Error;
7
8/// Current registration state for one timer identity.
9#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
10pub enum TimerRegistration {
11    /// No callback is scheduled or running.
12    #[default]
13    Unregistered,
14    /// One generation is scheduled for an absolute nanosecond deadline.
15    Scheduled {
16        /// Generation owned by the scheduled callback.
17        generation: u64,
18        /// Absolute IC timestamp in nanoseconds.
19        deadline_ns: u64,
20    },
21    /// One generation currently owns logical execution.
22    Running {
23        /// Generation owned by the running callback.
24        generation: u64,
25    },
26}
27
28/// Provider-neutral action consumed by the canonical registry.
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum TimerControlAction {
31    /// No platform change is required.
32    None,
33    /// Arm a new callback.
34    Arm {
35        /// Generation the callback must present when it begins.
36        generation: u64,
37        /// Absolute IC timestamp in nanoseconds.
38        deadline_ns: u64,
39        /// Whether the arm fills an empty slot or replaces its current handle.
40        kind: WakeupArm,
41    },
42    /// Clear the existing scheduled handle.
43    Clear,
44    /// The completed run leaves no scheduled successor.
45    Disarm {
46        /// Whether an explicit cancellation won over the run's directive.
47        cancelled: bool,
48    },
49}
50
51/// Whether one arm fills an empty wake-up slot or replaces its current handle.
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub enum WakeupArm {
54    Initial,
55    Replacement,
56}
57
58impl WakeupArm {
59    pub(crate) const fn replaces_existing(self) -> bool {
60        matches!(self, Self::Replacement)
61    }
62}
63
64/// Invalid or exhausted timer-control transition.
65#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
66pub enum TimerControlError {
67    /// The callback generation cannot be incremented.
68    #[error("timer generation exhausted")]
69    GenerationExhausted,
70    /// Completion did not present the generation that owns execution.
71    #[error("timer completion does not own the running generation")]
72    StaleCompletion,
73}
74
75/// Pure state machine for one logical timer identity.
76#[derive(Debug, Default)]
77pub struct TimerControl {
78    generation: u64,
79    registration: TimerRegistration,
80}
81
82#[derive(Clone, Copy)]
83enum DeadlineSelection {
84    Earliest,
85    Exact,
86}
87
88impl DeadlineSelection {
89    const fn replaces(self, current_deadline_ns: u64, requested_deadline_ns: u64) -> bool {
90        match self {
91            Self::Earliest => requested_deadline_ns < current_deadline_ns,
92            Self::Exact => requested_deadline_ns != current_deadline_ns,
93        }
94    }
95}
96
97impl TimerControl {
98    /// Return the latest allocated callback generation.
99    #[must_use]
100    #[cfg(test)]
101    pub(crate) const fn generation(&self) -> u64 {
102        self.generation
103    }
104
105    /// Return the current logical registration.
106    #[must_use]
107    pub(crate) const fn registration(&self) -> TimerRegistration {
108        self.registration
109    }
110
111    /// Terminate pure control after a checked terminal failure.
112    ///
113    /// Returns whether a scheduled wake-up must be cleared. A running callback
114    /// has already consumed its provider wake-up.
115    pub(crate) const fn terminate(&mut self) -> bool {
116        let clear_wakeup = matches!(self.registration, TimerRegistration::Scheduled { .. });
117        self.registration = TimerRegistration::Unregistered;
118        clear_wakeup
119    }
120
121    /// Schedule a deadline, retaining an already scheduled earlier deadline.
122    pub(crate) fn schedule(
123        &mut self,
124        deadline_ns: u64,
125    ) -> Result<TimerControlAction, TimerControlError> {
126        self.request_deadline(deadline_ns, DeadlineSelection::Earliest)
127    }
128
129    /// Cancel scheduled state immediately.
130    ///
131    /// Running work returns no direct action so the canonical registry can
132    /// arbitrate its pending command without a second pending-state machine.
133    pub(crate) fn cancel(&mut self) -> Result<TimerControlAction, TimerControlError> {
134        match self.registration {
135            TimerRegistration::Scheduled { .. } => {
136                let generation = self.next_generation()?;
137                self.generation = generation;
138                self.registration = TimerRegistration::Unregistered;
139                Ok(TimerControlAction::Clear)
140            }
141            TimerRegistration::Unregistered | TimerRegistration::Running { .. } => {
142                Ok(TimerControlAction::None)
143            }
144        }
145    }
146
147    /// Reconcile this timer to one authoritative deadline.
148    pub(crate) fn reconcile(
149        &mut self,
150        deadline_ns: u64,
151    ) -> Result<TimerControlAction, TimerControlError> {
152        self.request_deadline(deadline_ns, DeadlineSelection::Exact)
153    }
154
155    fn request_deadline(
156        &mut self,
157        deadline_ns: u64,
158        selection: DeadlineSelection,
159    ) -> Result<TimerControlAction, TimerControlError> {
160        let replace = match self.registration {
161            TimerRegistration::Unregistered => Some(false),
162            TimerRegistration::Scheduled {
163                deadline_ns: current_deadline_ns,
164                ..
165            } if selection.replaces(current_deadline_ns, deadline_ns) => Some(true),
166            TimerRegistration::Scheduled { .. } | TimerRegistration::Running { .. } => None,
167        };
168        let Some(replace) = replace else {
169            return Ok(TimerControlAction::None);
170        };
171
172        let generation = self.next_generation()?;
173        self.generation = generation;
174        self.registration = TimerRegistration::Scheduled {
175            generation,
176            deadline_ns,
177        };
178        Ok(TimerControlAction::Arm {
179            generation,
180            deadline_ns,
181            kind: if replace {
182                WakeupArm::Replacement
183            } else {
184                WakeupArm::Initial
185            },
186        })
187    }
188
189    /// Begin the scheduled generation, rejecting stale callbacks.
190    pub(crate) const fn begin(&mut self, generation: u64) -> bool {
191        match self.registration {
192            TimerRegistration::Scheduled {
193                generation: scheduled_generation,
194                ..
195            } if scheduled_generation == generation => {
196                self.registration = TimerRegistration::Running { generation };
197                true
198            }
199            TimerRegistration::Unregistered
200            | TimerRegistration::Scheduled { .. }
201            | TimerRegistration::Running { .. } => false,
202        }
203    }
204
205    /// Complete the running generation with the registry's already-arbitrated
206    /// successor decision.
207    pub(crate) fn complete(
208        &mut self,
209        generation: u64,
210        next_deadline_ns: Option<u64>,
211        cancelled: bool,
212    ) -> Result<TimerControlAction, TimerControlError> {
213        if self.registration != (TimerRegistration::Running { generation }) {
214            return Err(TimerControlError::StaleCompletion);
215        }
216
217        let next_generation = if next_deadline_ns.is_some() {
218            Some(self.next_generation()?)
219        } else {
220            None
221        };
222
223        if let (Some(deadline_ns), Some(next_generation)) = (next_deadline_ns, next_generation) {
224            self.generation = next_generation;
225            self.registration = TimerRegistration::Scheduled {
226                generation: next_generation,
227                deadline_ns,
228            };
229            Ok(TimerControlAction::Arm {
230                generation: next_generation,
231                deadline_ns,
232                kind: WakeupArm::Initial,
233            })
234        } else {
235            self.registration = TimerRegistration::Unregistered;
236            Ok(TimerControlAction::Disarm { cancelled })
237        }
238    }
239
240    fn next_generation(&self) -> Result<u64, TimerControlError> {
241        self.generation
242            .checked_add(1)
243            .ok_or(TimerControlError::GenerationExhausted)
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    fn arm(control: &mut TimerControl, deadline_ns: u64) -> u64 {
252        let TimerControlAction::Arm { generation, .. } = control
253            .schedule(deadline_ns)
254            .expect("initial schedule should succeed")
255        else {
256            panic!("initial schedule should arm");
257        };
258        generation
259    }
260
261    #[test]
262    fn duplicate_and_later_schedules_keep_one_earliest_handle() {
263        let mut control = TimerControl::default();
264        assert_eq!(arm(&mut control, 100), 1);
265        assert_eq!(control.schedule(100), Ok(TimerControlAction::None));
266        assert_eq!(control.schedule(200), Ok(TimerControlAction::None));
267        assert_eq!(
268            control.registration(),
269            TimerRegistration::Scheduled {
270                generation: 1,
271                deadline_ns: 100
272            }
273        );
274    }
275
276    #[test]
277    fn earlier_schedule_replaces_and_invalidates_old_generation() {
278        let mut control = TimerControl::default();
279        let old_generation = arm(&mut control, 100);
280        assert_eq!(
281            control.schedule(50),
282            Ok(TimerControlAction::Arm {
283                generation: 2,
284                deadline_ns: 50,
285                kind: WakeupArm::Replacement,
286            })
287        );
288        assert!(!control.begin(old_generation));
289        assert!(control.begin(2));
290    }
291
292    #[test]
293    fn authoritative_reconciliation_can_move_scheduled_deadline_later() {
294        let mut control = TimerControl::default();
295        let old_generation = arm(&mut control, 100);
296        assert_eq!(
297            control.reconcile(200),
298            Ok(TimerControlAction::Arm {
299                generation: 2,
300                deadline_ns: 200,
301                kind: WakeupArm::Replacement,
302            })
303        );
304        assert!(!control.begin(old_generation));
305        assert!(control.begin(2));
306    }
307
308    #[test]
309    fn completion_uses_the_registrys_authoritative_deadline() {
310        let mut control = TimerControl::default();
311        let generation = arm(&mut control, 100);
312        assert!(control.begin(generation));
313        assert_eq!(control.reconcile(300), Ok(TimerControlAction::None));
314        assert_eq!(
315            control.complete(generation, Some(300), false),
316            Ok(TimerControlAction::Arm {
317                generation: 2,
318                deadline_ns: 300,
319                kind: WakeupArm::Initial,
320            })
321        );
322    }
323
324    #[test]
325    fn completion_uses_the_registrys_pending_schedule() {
326        let mut control = TimerControl::default();
327        let generation = arm(&mut control, 100);
328        assert!(control.begin(generation));
329        assert_eq!(control.schedule(90), Ok(TimerControlAction::None));
330        assert_eq!(
331            control.complete(generation, Some(90), false),
332            Ok(TimerControlAction::Arm {
333                generation: 2,
334                deadline_ns: 90,
335                kind: WakeupArm::Initial,
336            })
337        );
338    }
339
340    #[test]
341    fn running_callback_can_request_its_own_cancellation() {
342        let mut control = TimerControl::default();
343        let generation = arm(&mut control, 100);
344        assert!(control.begin(generation));
345        assert_eq!(control.cancel(), Ok(TimerControlAction::None));
346        assert_eq!(
347            control.complete(generation, None, true),
348            Ok(TimerControlAction::Disarm { cancelled: true })
349        );
350        assert_eq!(control.registration(), TimerRegistration::Unregistered);
351    }
352
353    #[test]
354    fn completion_arms_the_registrys_selected_earliest_deadline() {
355        let mut control = TimerControl::default();
356        let generation = arm(&mut control, 100);
357        assert!(control.begin(generation));
358        assert_eq!(
359            control.complete(generation, Some(250), false),
360            Ok(TimerControlAction::Arm {
361                generation: 2,
362                deadline_ns: 250,
363                kind: WakeupArm::Initial,
364            })
365        );
366    }
367
368    #[test]
369    fn completion_honors_the_registrys_cancellation() {
370        let mut control = TimerControl::default();
371        let generation = arm(&mut control, 100);
372        assert!(control.begin(generation));
373        assert_eq!(
374            control.complete(generation, None, true),
375            Ok(TimerControlAction::Disarm { cancelled: true })
376        );
377    }
378
379    #[test]
380    fn completion_honors_the_registrys_later_schedule() {
381        let mut control = TimerControl::default();
382        let generation = arm(&mut control, 100);
383        assert!(control.begin(generation));
384        assert_eq!(
385            control.complete(generation, Some(90), false),
386            Ok(TimerControlAction::Arm {
387                generation: 2,
388                deadline_ns: 90,
389                kind: WakeupArm::Initial,
390            })
391        );
392    }
393
394    #[test]
395    fn scheduled_cancel_invalidates_consumed_generation() {
396        let mut control = TimerControl::default();
397        let generation = arm(&mut control, 100);
398        assert_eq!(control.cancel(), Ok(TimerControlAction::Clear));
399        assert!(!control.begin(generation));
400        assert_eq!(control.generation(), 2);
401        assert_eq!(control.registration(), TimerRegistration::Unregistered);
402    }
403
404    #[test]
405    fn stale_completion_cannot_change_current_registration() {
406        let mut control = TimerControl::default();
407        let generation = arm(&mut control, 100);
408        assert!(control.begin(generation));
409        assert_eq!(
410            control.complete(generation + 1, None, false),
411            Err(TimerControlError::StaleCompletion)
412        );
413        assert_eq!(
414            control.registration(),
415            TimerRegistration::Running { generation }
416        );
417    }
418
419    #[test]
420    fn exhausted_generation_fails_without_replacing_current_handle() {
421        let mut control = TimerControl {
422            generation: u64::MAX,
423            registration: TimerRegistration::Scheduled {
424                generation: u64::MAX,
425                deadline_ns: 100,
426            },
427        };
428
429        assert_eq!(
430            control.schedule(50),
431            Err(TimerControlError::GenerationExhausted)
432        );
433        assert_eq!(
434            control.registration(),
435            TimerRegistration::Scheduled {
436                generation: u64::MAX,
437                deadline_ns: 100
438            }
439        );
440    }
441}