Skip to main content

ic_timers/registry/
mod.rs

1//! Bounded canonical registry and provider-neutral policy transition engine.
2//!
3//! The registry emits provider-neutral effects. The runtime binds them to
4//! linear provider handles while the registry remains the sole logical state
5//! authority.
6
7use crate::{
8    control::{TimerControl, TimerControlAction, TimerControlError, TimerRegistration},
9    platform::TimerHandle,
10    runtime::TimerContext,
11    schedule::{ScheduleError, TimerCadence, TimerDirective, TimerSchedule},
12    snapshot::{
13        DeclarationLifetime, InactiveReason, OrdinaryRuntimeStateSnapshot, TimerCompletion,
14        TimerCompletionOutcome, TimerControlFailure, TimerDirectiveSnapshot, TimerEpoch,
15        TimerIdentity, TimerObservabilitySnapshot, TimerPolicy, TimerRunResult,
16        TimerRuntimeStateSnapshot, TimerSchedulingMode, TimerSnapshot, WatchdogAttemptSnapshot,
17        WatchdogAttemptStatus, WatchdogDecision, WatchdogRunResult, WatchdogRuntimeStateSnapshot,
18    },
19};
20use std::{cell::RefCell, collections::BTreeMap, future::Future, pin::Pin, rc::Rc};
21use thiserror::Error;
22
23/// Maximum declarations owned by one canonical registry.
24pub const MAX_TIMER_REGISTRATIONS: usize = 64;
25
26/// Opaque logical ownership claim returned by pure registration.
27#[derive(Debug, Eq, PartialEq)]
28pub struct RegistrationClaim {
29    identity: TimerIdentity,
30    claim_generation: u64,
31}
32
33impl RegistrationClaim {
34    pub(crate) const fn identity(&self) -> &TimerIdentity {
35        &self.identity
36    }
37
38    pub(crate) const fn claim_generation(&self) -> u64 {
39        self.claim_generation
40    }
41
42    pub(crate) const fn delegated(identity: TimerIdentity, claim_generation: u64) -> Self {
43        Self {
44            identity,
45            claim_generation,
46        }
47    }
48}
49
50/// Internal callback role carried by a generation-checked dispatch token.
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub enum CallbackRole {
53    OrdinaryWork,
54    WatchdogScheduler,
55    WatchdogWork,
56}
57
58/// Identity and generations an internal callback must present.
59#[derive(Clone, Debug, Eq, PartialEq)]
60pub struct CallbackToken {
61    identity: TimerIdentity,
62    claim_generation: u64,
63    callback_generation: u64,
64    role: CallbackRole,
65}
66
67impl CallbackToken {
68    const fn new(
69        identity: TimerIdentity,
70        claim_generation: u64,
71        callback_generation: u64,
72        role: CallbackRole,
73    ) -> Self {
74        Self {
75            identity,
76            claim_generation,
77            callback_generation,
78            role,
79        }
80    }
81
82    pub(crate) const fn identity(&self) -> &TimerIdentity {
83        &self.identity
84    }
85
86    #[cfg(test)]
87    pub(crate) const fn callback_generation(&self) -> u64 {
88        self.callback_generation
89    }
90
91    pub(crate) const fn claim_generation(&self) -> u64 {
92        self.claim_generation
93    }
94
95    pub(crate) const fn role(&self) -> CallbackRole {
96        self.role
97    }
98}
99
100/// Provider-neutral effect emitted by one pure transition.
101#[derive(Debug, Eq, PartialEq)]
102pub enum RegistryEffect {
103    None,
104    ArmWakeup {
105        token: CallbackToken,
106        deadline_ns: u64,
107        delay_ns: u64,
108        replace: bool,
109    },
110    ClearCallbacks {
111        identity: TimerIdentity,
112        clear_wakeup: bool,
113        clear_work: bool,
114    },
115    DispatchWatchdog {
116        successor: CallbackToken,
117        successor_deadline_ns: u64,
118        successor_delay_ns: u64,
119        work: CallbackToken,
120    },
121}
122
123/// One pure transition and any terminal checked-control failure it produced.
124#[derive(Debug, Eq, PartialEq)]
125pub struct RegistryTransition {
126    effect: RegistryEffect,
127    failure: Option<TimerControlFailure>,
128}
129
130impl RegistryTransition {
131    const fn normal(effect: RegistryEffect) -> Self {
132        Self {
133            effect,
134            failure: None,
135        }
136    }
137
138    const fn terminal(effect: RegistryEffect, failure: TimerControlFailure) -> Self {
139        Self {
140            effect,
141            failure: Some(failure),
142        }
143    }
144
145    pub(crate) const fn effect(&self) -> &RegistryEffect {
146        &self.effect
147    }
148
149    pub(crate) const fn failure(&self) -> Option<TimerControlFailure> {
150        self.failure
151    }
152
153    pub(crate) fn into_effect(self) -> RegistryEffect {
154        self.effect
155    }
156}
157
158/// Whether an internal callback generation won arbitration.
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub enum CallbackAcceptance {
161    Accepted,
162    Stale,
163}
164
165/// Failure to claim one canonical timer identity.
166#[non_exhaustive]
167#[derive(Clone, Debug, Eq, Error, PartialEq)]
168pub enum RegisterError {
169    /// The identity already has one canonical claimant.
170    #[error("timer identity is already registered: {0:?}")]
171    IdentityAlreadyRegistered(TimerIdentity),
172    /// The fixed registry capacity has been reached.
173    #[error("timer registry capacity of {max} declarations has been reached")]
174    CapacityExceeded {
175        /// Fixed registry capacity.
176        max: usize,
177    },
178    /// Logical registration claim generations are exhausted.
179    #[error("timer registration claim generation exhausted")]
180    ClaimGenerationExhausted,
181}
182
183/// Invalid request against a logical registration claim.
184#[non_exhaustive]
185#[derive(Clone, Debug, Eq, Error, PartialEq)]
186pub enum RegistryError {
187    #[error("timer registration is no longer present")]
188    UnknownRegistration,
189    #[error("timer registration claim is stale")]
190    StaleRegistration,
191    #[error("timer operation is not legal for policy {actual}")]
192    WrongPolicy { actual: &'static str },
193    #[error("timer callback token is stale")]
194    StaleCallback,
195    #[error("timer registration has no ordinary callback")]
196    MissingCallback,
197    #[error("timer registration already owns the provider handle for this callback role")]
198    ProviderHandleAlreadyOwned,
199    #[error(transparent)]
200    Schedule(#[from] ScheduleError),
201}
202
203type OrdinaryFuture = Pin<Box<dyn Future<Output = TimerRunResult>>>;
204pub type OrdinaryCallback = Rc<RefCell<Box<dyn FnMut(TimerContext) -> OrdinaryFuture>>>;
205pub type WatchdogCallback = Rc<RefCell<Box<dyn FnMut(TimerContext) -> WatchdogRunResult>>>;
206
207enum EntryCallback {
208    #[cfg(test)]
209    None,
210    Ordinary(OrdinaryCallback),
211    Watchdog(WatchdogCallback),
212}
213
214struct OwnedProviderHandle {
215    callback_generation: u64,
216    role: CallbackRole,
217    handle: TimerHandle,
218}
219
220/// One temporarily detached exact provider capability.
221pub struct ProviderHandle {
222    token: CallbackToken,
223    handle: TimerHandle,
224}
225
226impl ProviderHandle {
227    pub(crate) fn into_parts(self) -> (CallbackToken, TimerHandle) {
228        (self.token, self.handle)
229    }
230}
231
232/// The at-most-two provider capabilities owned by one timer entry.
233#[derive(Default)]
234pub struct ProviderHandles {
235    wakeup: Option<ProviderHandle>,
236    work: Option<ProviderHandle>,
237}
238
239impl ProviderHandles {
240    pub(crate) const fn from_parts(
241        wakeup: Option<ProviderHandle>,
242        work: Option<ProviderHandle>,
243    ) -> Self {
244        Self { wakeup, work }
245    }
246
247    pub(crate) const fn take_wakeup(&mut self) -> Option<ProviderHandle> {
248        self.wakeup.take()
249    }
250
251    pub(crate) const fn take_work(&mut self) -> Option<ProviderHandle> {
252        self.work.take()
253    }
254}
255
256#[derive(Clone, Copy, Debug, Eq, PartialEq)]
257struct PendingSchedule {
258    deadline_ns: u64,
259    requested_delay_ns: Option<u64>,
260    mode: TimerSchedulingMode,
261}
262
263#[derive(Clone, Copy, Debug, Eq, PartialEq)]
264enum OrdinaryPending {
265    Cancel,
266    Reconcile(PendingSchedule),
267    Unregister,
268    Schedule(PendingSchedule),
269}
270
271#[derive(Clone, Copy, Debug, Eq, PartialEq)]
272enum OrdinaryRequest {
273    Ensure,
274    Reconcile,
275}
276
277#[derive(Clone, Copy, Debug, Eq, PartialEq)]
278enum WatchdogPending {
279    Cancel,
280    Ensure,
281    Unregister,
282}
283
284#[derive(Debug)]
285enum EntryControl {
286    Ordinary {
287        control: TimerControl,
288        pending: Option<OrdinaryPending>,
289        inactive_reason: InactiveReason,
290    },
291    Watchdog(WatchdogControl),
292}
293
294#[derive(Clone, Copy, Debug, Eq, PartialEq)]
295enum WatchdogState {
296    Inactive,
297    Scheduled {
298        scheduler_generation: u64,
299        deadline_ns: u64,
300    },
301    AwaitingWork {
302        successor_generation: u64,
303        successor_deadline_ns: u64,
304        attempt_generation: u64,
305        attempt_status: WatchdogAttemptStatus,
306    },
307}
308
309#[derive(Debug)]
310struct WatchdogControl {
311    scheduler_generation: u64,
312    attempt_generation: u64,
313    request_sequence: u64,
314    state: WatchdogState,
315    pending: Option<WatchdogPending>,
316    inactive_reason: InactiveReason,
317}
318
319impl Default for WatchdogControl {
320    fn default() -> Self {
321        Self {
322            scheduler_generation: 0,
323            attempt_generation: 0,
324            request_sequence: 0,
325            state: WatchdogState::Inactive,
326            pending: None,
327            inactive_reason: InactiveReason::NeverScheduled,
328        }
329    }
330}
331
332struct Entry {
333    claim_generation: u64,
334    policy: TimerPolicy,
335    lifetime: DeclarationLifetime,
336    control: EntryControl,
337    scheduling_mode: TimerSchedulingMode,
338    latest_directive: Option<TimerDirectiveSnapshot>,
339    latest_requested_delay_ns: Option<u64>,
340    latest_armed_delay_ns: Option<u64>,
341    confirmed_wakeup_generation: Option<u64>,
342    confirmed_work_generation: Option<u64>,
343    callback: EntryCallback,
344    wakeup: Option<OwnedProviderHandle>,
345    work: Option<OwnedProviderHandle>,
346    observability: TimerObservabilitySnapshot,
347}
348
349impl Entry {
350    fn new(
351        claim_generation: u64,
352        policy: TimerPolicy,
353        lifetime: DeclarationLifetime,
354        epoch: TimerEpoch,
355        callback: EntryCallback,
356    ) -> Self {
357        let (control, scheduling_mode) = match policy {
358            TimerPolicy::Once => (
359                EntryControl::Ordinary {
360                    control: TimerControl::default(),
361                    pending: None,
362                    inactive_reason: InactiveReason::NeverScheduled,
363                },
364                TimerSchedulingMode::Once,
365            ),
366            TimerPolicy::AfterCompletion { .. } => (
367                EntryControl::Ordinary {
368                    control: TimerControl::default(),
369                    pending: None,
370                    inactive_reason: InactiveReason::NeverScheduled,
371                },
372                TimerSchedulingMode::AfterCompletion,
373            ),
374            TimerPolicy::Watchdog { .. } => (
375                EntryControl::Watchdog(WatchdogControl::default()),
376                TimerSchedulingMode::Watchdog,
377            ),
378        };
379
380        Self {
381            claim_generation,
382            policy,
383            lifetime,
384            control,
385            scheduling_mode,
386            latest_directive: None,
387            latest_requested_delay_ns: None,
388            latest_armed_delay_ns: None,
389            confirmed_wakeup_generation: None,
390            confirmed_work_generation: None,
391            callback,
392            wakeup: None,
393            work: None,
394            observability: TimerObservabilitySnapshot::new(epoch),
395        }
396    }
397
398    const fn snapshot(&self, identity: TimerIdentity) -> TimerSnapshot {
399        let state = match &self.control {
400            EntryControl::Ordinary {
401                control,
402                inactive_reason,
403                ..
404            } => match control.registration() {
405                TimerRegistration::Unregistered => TimerRuntimeStateSnapshot::Inactive {
406                    reason: *inactive_reason,
407                },
408                TimerRegistration::Scheduled {
409                    generation,
410                    deadline_ns,
411                } => TimerRuntimeStateSnapshot::Ordinary(OrdinaryRuntimeStateSnapshot::Scheduled {
412                    generation,
413                    deadline_ns,
414                }),
415                TimerRegistration::Running { generation } => {
416                    TimerRuntimeStateSnapshot::Ordinary(OrdinaryRuntimeStateSnapshot::Running {
417                        generation,
418                    })
419                }
420            },
421            EntryControl::Watchdog(control) => match control.state {
422                WatchdogState::Inactive => TimerRuntimeStateSnapshot::Inactive {
423                    reason: control.inactive_reason,
424                },
425                WatchdogState::Scheduled {
426                    scheduler_generation,
427                    deadline_ns,
428                } => TimerRuntimeStateSnapshot::Watchdog(WatchdogRuntimeStateSnapshot::Scheduled {
429                    scheduler_generation,
430                    deadline_ns,
431                }),
432                WatchdogState::AwaitingWork {
433                    successor_generation,
434                    successor_deadline_ns,
435                    attempt_generation,
436                    attempt_status,
437                } => TimerRuntimeStateSnapshot::Watchdog(
438                    WatchdogRuntimeStateSnapshot::AwaitingWork {
439                        successor_generation,
440                        successor_deadline_ns,
441                        attempt: WatchdogAttemptSnapshot::new(attempt_generation, attempt_status),
442                    },
443                ),
444            },
445        };
446
447        TimerSnapshot::new(
448            identity,
449            self.policy,
450            self.lifetime,
451            state,
452            self.scheduling_mode,
453            self.latest_directive,
454            self.latest_requested_delay_ns,
455            self.latest_armed_delay_ns,
456            &self.observability,
457        )
458    }
459}
460
461/// Pure, fixed-capacity canonical registry.
462pub struct TimerRegistry {
463    epoch: TimerEpoch,
464    next_claim_generation: u64,
465    entries: BTreeMap<TimerIdentity, Entry>,
466}
467
468impl TimerRegistry {
469    pub(crate) const fn new(epoch: TimerEpoch) -> Self {
470        Self {
471            epoch,
472            next_claim_generation: 0,
473            entries: BTreeMap::new(),
474        }
475    }
476
477    #[cfg(test)]
478    pub(crate) fn len(&self) -> usize {
479        self.entries.len()
480    }
481
482    #[cfg(test)]
483    pub(crate) fn is_empty(&self) -> bool {
484        self.entries.is_empty()
485    }
486
487    pub(crate) const fn epoch(&self) -> TimerEpoch {
488        self.epoch
489    }
490
491    #[cfg(test)]
492    pub(crate) fn register_once(
493        &mut self,
494        identity: TimerIdentity,
495        lifetime: DeclarationLifetime,
496    ) -> Result<RegistrationClaim, RegisterError> {
497        self.register(identity, TimerPolicy::Once, lifetime, EntryCallback::None)
498    }
499
500    #[cfg(test)]
501    pub(crate) fn register_after_completion(
502        &mut self,
503        identity: TimerIdentity,
504        cadence: TimerCadence,
505        lifetime: DeclarationLifetime,
506    ) -> Result<RegistrationClaim, RegisterError> {
507        self.register(
508            identity,
509            TimerPolicy::AfterCompletion { cadence },
510            lifetime,
511            EntryCallback::None,
512        )
513    }
514
515    #[cfg(test)]
516    pub(crate) fn register_watchdog(
517        &mut self,
518        identity: TimerIdentity,
519        cadence: TimerCadence,
520        lifetime: DeclarationLifetime,
521    ) -> Result<RegistrationClaim, RegisterError> {
522        self.register(
523            identity,
524            TimerPolicy::Watchdog { cadence },
525            lifetime,
526            EntryCallback::None,
527        )
528    }
529
530    pub(crate) fn register_once_with_callback(
531        &mut self,
532        identity: TimerIdentity,
533        lifetime: DeclarationLifetime,
534        callback: OrdinaryCallback,
535    ) -> Result<RegistrationClaim, RegisterError> {
536        self.register(
537            identity,
538            TimerPolicy::Once,
539            lifetime,
540            EntryCallback::Ordinary(callback),
541        )
542    }
543
544    pub(crate) fn register_after_completion_with_callback(
545        &mut self,
546        identity: TimerIdentity,
547        cadence: TimerCadence,
548        lifetime: DeclarationLifetime,
549        callback: OrdinaryCallback,
550    ) -> Result<RegistrationClaim, RegisterError> {
551        self.register(
552            identity,
553            TimerPolicy::AfterCompletion { cadence },
554            lifetime,
555            EntryCallback::Ordinary(callback),
556        )
557    }
558
559    pub(crate) fn register_watchdog_with_callback(
560        &mut self,
561        identity: TimerIdentity,
562        cadence: TimerCadence,
563        lifetime: DeclarationLifetime,
564        callback: WatchdogCallback,
565    ) -> Result<RegistrationClaim, RegisterError> {
566        self.register(
567            identity,
568            TimerPolicy::Watchdog { cadence },
569            lifetime,
570            EntryCallback::Watchdog(callback),
571        )
572    }
573
574    fn register(
575        &mut self,
576        identity: TimerIdentity,
577        policy: TimerPolicy,
578        lifetime: DeclarationLifetime,
579        callback: EntryCallback,
580    ) -> Result<RegistrationClaim, RegisterError> {
581        if self.entries.contains_key(&identity) {
582            return Err(RegisterError::IdentityAlreadyRegistered(identity));
583        }
584        if self.entries.len() == MAX_TIMER_REGISTRATIONS {
585            return Err(RegisterError::CapacityExceeded {
586                max: MAX_TIMER_REGISTRATIONS,
587            });
588        }
589        let claim_generation = self
590            .next_claim_generation
591            .checked_add(1)
592            .ok_or(RegisterError::ClaimGenerationExhausted)?;
593
594        self.next_claim_generation = claim_generation;
595        self.entries.insert(
596            identity.clone(),
597            Entry::new(claim_generation, policy, lifetime, self.epoch, callback),
598        );
599        Ok(RegistrationClaim {
600            identity,
601            claim_generation,
602        })
603    }
604
605    pub(crate) fn ensure_once(
606        &mut self,
607        claim: &RegistrationClaim,
608        now_ns: u64,
609        schedule: TimerSchedule,
610    ) -> Result<RegistryTransition, RegistryError> {
611        let resolved = schedule.resolve(now_ns)?;
612        let mode = match schedule {
613            TimerSchedule::After(_) => TimerSchedulingMode::Once,
614            TimerSchedule::At(_) => TimerSchedulingMode::Deadline,
615        };
616        self.schedule_ordinary(
617            claim,
618            now_ns,
619            PendingSchedule {
620                deadline_ns: resolved.deadline_ns,
621                requested_delay_ns: resolved.requested_delay_ns,
622                mode,
623            },
624            true,
625        )
626    }
627
628    /// Reconcile an ordinary declaration to one exact desired schedule.
629    ///
630    /// Unlike `ensure`, reconciliation may move an existing deadline later.
631    /// `None` cancels live work; declaration lifetime determines whether the
632    /// callback authority remains registered.
633    pub(crate) fn reconcile_ordinary(
634        &mut self,
635        claim: &RegistrationClaim,
636        now_ns: u64,
637        schedule: Option<TimerSchedule>,
638    ) -> Result<RegistryTransition, RegistryError> {
639        self.validate_ordinary_claim(claim)?;
640        let Some(schedule) = schedule else {
641            return self.cancel(claim);
642        };
643        let resolved = schedule.resolve(now_ns)?;
644        let mode = match schedule {
645            TimerSchedule::After(_) => TimerSchedulingMode::Once,
646            TimerSchedule::At(_) => TimerSchedulingMode::Deadline,
647        };
648        self.request_ordinary(
649            claim,
650            now_ns,
651            PendingSchedule {
652                deadline_ns: resolved.deadline_ns,
653                requested_delay_ns: resolved.requested_delay_ns,
654                mode,
655            },
656            false,
657            OrdinaryRequest::Reconcile,
658        )
659    }
660
661    pub(crate) fn validate_ordinary_claim(
662        &self,
663        claim: &RegistrationClaim,
664    ) -> Result<(), RegistryError> {
665        let entry = self.entry(claim)?;
666        if matches!(entry.control, EntryControl::Ordinary { .. }) {
667            Ok(())
668        } else {
669            Err(RegistryError::WrongPolicy {
670                actual: entry.policy.label(),
671            })
672        }
673    }
674
675    pub(crate) fn ensure_recurring(
676        &mut self,
677        claim: &RegistrationClaim,
678        now_ns: u64,
679    ) -> Result<RegistryTransition, RegistryError> {
680        let entry = self.entry(claim)?;
681        let (cadence, mode, ordinary, already_scheduled) = match entry.policy {
682            TimerPolicy::Once => {
683                return Err(RegistryError::WrongPolicy { actual: "once" });
684            }
685            TimerPolicy::AfterCompletion { cadence } => {
686                let already_scheduled = matches!(
687                    entry.control,
688                    EntryControl::Ordinary {
689                        ref control,
690                        ..
691                    } if matches!(
692                        control.registration(),
693                        TimerRegistration::Scheduled { .. }
694                    )
695                );
696                (
697                    cadence,
698                    TimerSchedulingMode::AfterCompletion,
699                    true,
700                    already_scheduled,
701                )
702            }
703            TimerPolicy::Watchdog { cadence } => {
704                (cadence, TimerSchedulingMode::Watchdog, false, false)
705            }
706        };
707        if ordinary {
708            if already_scheduled {
709                let entry = self.entry_mut(claim)?;
710                entry.observability.counters_mut().record_schedule_request();
711                entry.observability.counters_mut().record_coalesced();
712                entry.latest_requested_delay_ns = Some(cadence.as_nanos());
713                return Ok(RegistryTransition::normal(RegistryEffect::None));
714            }
715            let deadline_ns = cadence.deadline_after(now_ns)?;
716            self.schedule_ordinary(
717                claim,
718                now_ns,
719                PendingSchedule {
720                    deadline_ns,
721                    requested_delay_ns: Some(cadence.as_nanos()),
722                    mode,
723                },
724                false,
725            )
726        } else {
727            self.ensure_watchdog(claim, now_ns, cadence)
728        }
729    }
730
731    fn schedule_ordinary(
732        &mut self,
733        claim: &RegistrationClaim,
734        now_ns: u64,
735        requested: PendingSchedule,
736        require_once: bool,
737    ) -> Result<RegistryTransition, RegistryError> {
738        self.request_ordinary(
739            claim,
740            now_ns,
741            requested,
742            require_once,
743            OrdinaryRequest::Ensure,
744        )
745    }
746
747    fn request_ordinary(
748        &mut self,
749        claim: &RegistrationClaim,
750        now_ns: u64,
751        requested: PendingSchedule,
752        require_once: bool,
753        request: OrdinaryRequest,
754    ) -> Result<RegistryTransition, RegistryError> {
755        let entry = self.entry_mut(claim)?;
756        if require_once && !matches!(entry.policy, TimerPolicy::Once) {
757            return Err(RegistryError::WrongPolicy {
758                actual: entry.policy.label(),
759            });
760        }
761        if !matches!(entry.control, EntryControl::Ordinary { .. }) {
762            return Err(RegistryError::WrongPolicy {
763                actual: entry.policy.label(),
764            });
765        }
766
767        entry.observability.counters_mut().record_schedule_request();
768        entry.latest_requested_delay_ns = requested.requested_delay_ns;
769
770        let (action, was_running) = match &mut entry.control {
771            EntryControl::Ordinary { control, .. } => {
772                let was_running =
773                    matches!(control.registration(), TimerRegistration::Running { .. });
774                let action = match request {
775                    OrdinaryRequest::Ensure => control.schedule(requested.deadline_ns),
776                    OrdinaryRequest::Reconcile => control.reconcile(requested.deadline_ns),
777                };
778                (action, was_running)
779            }
780            EntryControl::Watchdog(_) => {
781                return Err(RegistryError::WrongPolicy {
782                    actual: entry.policy.label(),
783                });
784            }
785        };
786
787        let action = match action {
788            Ok(action) => action,
789            Err(error) => {
790                return Ok(terminal_ordinary(entry, claim.identity.clone(), error));
791            }
792        };
793
794        if was_running {
795            let EntryControl::Ordinary { pending, .. } = &mut entry.control else {
796                return Err(RegistryError::WrongPolicy {
797                    actual: entry.policy.label(),
798                });
799            };
800            *pending = Some(select_pending_ordinary(*pending, request, requested));
801        }
802        if matches!(request, OrdinaryRequest::Reconcile) {
803            entry.scheduling_mode = requested.mode;
804        }
805
806        Ok(apply_ordinary_action(
807            entry,
808            claim.identity.clone(),
809            now_ns,
810            action,
811            requested,
812        ))
813    }
814
815    fn ensure_watchdog(
816        &mut self,
817        claim: &RegistrationClaim,
818        now_ns: u64,
819        cadence: TimerCadence,
820    ) -> Result<RegistryTransition, RegistryError> {
821        let entry = self.entry_mut(claim)?;
822        if !matches!(entry.policy, TimerPolicy::Watchdog { .. }) {
823            return Err(RegistryError::WrongPolicy {
824                actual: entry.policy.label(),
825            });
826        }
827        entry.observability.counters_mut().record_schedule_request();
828        entry.latest_requested_delay_ns = Some(cadence.as_nanos());
829
830        let EntryControl::Watchdog(control) = &mut entry.control else {
831            return Err(RegistryError::WrongPolicy {
832                actual: entry.policy.label(),
833            });
834        };
835        match control.state {
836            WatchdogState::Inactive => {
837                let deadline_ns = cadence.deadline_after(now_ns)?;
838                let Some(generation) = control.scheduler_generation.checked_add(1) else {
839                    control.inactive_reason =
840                        InactiveReason::ControlFailure(TimerControlFailure::GenerationExhausted);
841                    return Ok(RegistryTransition::terminal(
842                        RegistryEffect::None,
843                        TimerControlFailure::GenerationExhausted,
844                    ));
845                };
846                control.scheduler_generation = generation;
847                control.state = WatchdogState::Scheduled {
848                    scheduler_generation: generation,
849                    deadline_ns,
850                };
851                control.pending = None;
852                entry.scheduling_mode = TimerSchedulingMode::Watchdog;
853                Ok(RegistryTransition::normal(RegistryEffect::ArmWakeup {
854                    token: token_for(claim, generation, CallbackRole::WatchdogScheduler),
855                    deadline_ns,
856                    delay_ns: deadline_ns.saturating_sub(now_ns),
857                    replace: false,
858                }))
859            }
860            WatchdogState::Scheduled { .. }
861            | WatchdogState::AwaitingWork {
862                attempt_status: WatchdogAttemptStatus::Dispatched,
863                ..
864            } => {
865                entry.observability.counters_mut().record_coalesced();
866                Ok(RegistryTransition::normal(RegistryEffect::None))
867            }
868            WatchdogState::AwaitingWork {
869                attempt_status: WatchdogAttemptStatus::Running,
870                ..
871            } => {
872                let Some(sequence) = control.request_sequence.checked_add(1) else {
873                    control.state = WatchdogState::Inactive;
874                    control.inactive_reason = InactiveReason::ControlFailure(
875                        TimerControlFailure::RequestSequenceExhausted,
876                    );
877                    return Ok(RegistryTransition::terminal(
878                        clear_callbacks(claim.identity.clone(), true, false),
879                        TimerControlFailure::RequestSequenceExhausted,
880                    ));
881                };
882                control.request_sequence = sequence;
883                if !matches!(control.pending, Some(WatchdogPending::Unregister)) {
884                    control.pending = Some(WatchdogPending::Ensure);
885                }
886                entry.observability.counters_mut().record_coalesced();
887                Ok(RegistryTransition::normal(RegistryEffect::None))
888            }
889        }
890    }
891
892    pub(crate) fn cancel(
893        &mut self,
894        claim: &RegistrationClaim,
895    ) -> Result<RegistryTransition, RegistryError> {
896        let identity = claim.identity.clone();
897        let (transition, remove) = {
898            let entry = self.entry_mut(claim)?;
899            match &mut entry.control {
900                EntryControl::Ordinary {
901                    control,
902                    pending,
903                    inactive_reason,
904                } => {
905                    let before = control.registration();
906                    let action = match control.cancel() {
907                        Ok(action) => action,
908                        Err(error) => {
909                            return Ok(terminal_ordinary(entry, identity.clone(), error));
910                        }
911                    };
912                    let mut remove = false;
913                    let transition = match (before, action) {
914                        (TimerRegistration::Unregistered, TimerControlAction::None) => {
915                            RegistryTransition::normal(RegistryEffect::None)
916                        }
917                        (TimerRegistration::Running { .. }, TimerControlAction::None) => {
918                            if !matches!(*pending, Some(OrdinaryPending::Unregister)) {
919                                *pending = Some(OrdinaryPending::Cancel);
920                            }
921                            RegistryTransition::normal(RegistryEffect::None)
922                        }
923                        (_, TimerControlAction::Clear) => {
924                            *inactive_reason = InactiveReason::Cancelled;
925                            entry.observability.counters_mut().record_cancellation();
926                            remove =
927                                matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped);
928                            RegistryTransition::normal(clear_callbacks(
929                                identity.clone(),
930                                true,
931                                false,
932                            ))
933                        }
934                        (TimerRegistration::Scheduled { .. }, TimerControlAction::None)
935                        | (
936                            _,
937                            TimerControlAction::Arm { .. }
938                            | TimerControlAction::Replace { .. }
939                            | TimerControlAction::Disarm { .. },
940                        ) => {
941                            let clear_wakeup = control.terminate();
942                            *pending = None;
943                            *inactive_reason = InactiveReason::ControlFailure(
944                                TimerControlFailure::DirectiveNotAllowed,
945                            );
946                            RegistryTransition::terminal(
947                                clear_callbacks(identity.clone(), clear_wakeup, false),
948                                TimerControlFailure::DirectiveNotAllowed,
949                            )
950                        }
951                    };
952                    (transition, remove)
953                }
954                EntryControl::Watchdog(control) => {
955                    let (transition, changed) = cancel_watchdog(control, &identity);
956                    if changed && transition.failure().is_none() {
957                        entry.observability.counters_mut().record_cancellation();
958                    }
959                    let stopped = changed || transition.failure().is_some();
960                    (
961                        transition,
962                        stopped && matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped),
963                    )
964                }
965            }
966        };
967
968        if remove {
969            self.entries.remove(&identity);
970        }
971        Ok(transition)
972    }
973
974    /// Consume one logical claim and remove its declaration.
975    ///
976    /// Removal requested by running work is finalized by that work's normal
977    /// completion. A trapping work message rolls the request back with the
978    /// rest of its heap mutations.
979    #[allow(clippy::needless_pass_by_value)] // Ownership consumption invalidates the claim.
980    pub(crate) fn unregister(
981        &mut self,
982        claim: RegistrationClaim,
983    ) -> Result<RegistryTransition, RegistryError> {
984        let identity = claim.identity.clone();
985        let running_role = {
986            let entry = self.entry(&claim)?;
987            match &entry.control {
988                EntryControl::Ordinary { control, .. }
989                    if matches!(control.registration(), TimerRegistration::Running { .. }) =>
990                {
991                    Some(CallbackRole::OrdinaryWork)
992                }
993                EntryControl::Watchdog(control)
994                    if matches!(
995                        control.state,
996                        WatchdogState::AwaitingWork {
997                            attempt_status: WatchdogAttemptStatus::Running,
998                            ..
999                        }
1000                    ) =>
1001                {
1002                    Some(CallbackRole::WatchdogWork)
1003                }
1004                EntryControl::Ordinary { .. } | EntryControl::Watchdog(_) => None,
1005            }
1006        };
1007
1008        match running_role {
1009            Some(CallbackRole::OrdinaryWork) => {
1010                let entry = self.entry_mut(&claim)?;
1011                let EntryControl::Ordinary {
1012                    control,
1013                    pending,
1014                    inactive_reason,
1015                } = &mut entry.control
1016                else {
1017                    return Err(RegistryError::WrongPolicy {
1018                        actual: entry.policy.label(),
1019                    });
1020                };
1021                let transition = match control.cancel() {
1022                    Ok(TimerControlAction::None) => {
1023                        *pending = Some(OrdinaryPending::Unregister);
1024                        RegistryTransition::normal(RegistryEffect::None)
1025                    }
1026                    Ok(_) => RegistryTransition::terminal(
1027                        RegistryEffect::None,
1028                        TimerControlFailure::DirectiveNotAllowed,
1029                    ),
1030                    Err(error) => {
1031                        let failure = map_control_failure(error);
1032                        let clear_wakeup = control.terminate();
1033                        *pending = None;
1034                        *inactive_reason = InactiveReason::ControlFailure(failure);
1035                        RegistryTransition::terminal(
1036                            clear_callbacks(identity.clone(), clear_wakeup, false),
1037                            failure,
1038                        )
1039                    }
1040                };
1041                if transition.failure().is_some() {
1042                    self.entries.remove(&identity);
1043                }
1044                Ok(transition)
1045            }
1046            Some(CallbackRole::WatchdogWork) => {
1047                let entry = self.entry_mut(&claim)?;
1048                let EntryControl::Watchdog(control) = &mut entry.control else {
1049                    return Err(RegistryError::WrongPolicy {
1050                        actual: entry.policy.label(),
1051                    });
1052                };
1053                let Some(sequence) = control.request_sequence.checked_add(1) else {
1054                    control.state = WatchdogState::Inactive;
1055                    control.inactive_reason = InactiveReason::ControlFailure(
1056                        TimerControlFailure::RequestSequenceExhausted,
1057                    );
1058                    let transition = RegistryTransition::terminal(
1059                        clear_callbacks(identity.clone(), true, false),
1060                        TimerControlFailure::RequestSequenceExhausted,
1061                    );
1062                    self.entries.remove(&identity);
1063                    return Ok(transition);
1064                };
1065                control.request_sequence = sequence;
1066                control.pending = Some(WatchdogPending::Unregister);
1067                Ok(RegistryTransition::normal(RegistryEffect::None))
1068            }
1069            Some(CallbackRole::WatchdogScheduler) => Err(RegistryError::StaleCallback),
1070            None => {
1071                let transition = self.cancel(&claim)?;
1072                self.entries.remove(&identity);
1073                Ok(transition)
1074            }
1075        }
1076    }
1077
1078    pub(crate) fn begin_ordinary(&mut self, token: &CallbackToken) -> CallbackAcceptance {
1079        let Some(entry) = self.entries.get_mut(token.identity()) else {
1080            return CallbackAcceptance::Stale;
1081        };
1082        if token.role != CallbackRole::OrdinaryWork
1083            || token.claim_generation != entry.claim_generation
1084        {
1085            entry.observability.counters_mut().record_stale_wakeup();
1086            return CallbackAcceptance::Stale;
1087        }
1088        let EntryControl::Ordinary { control, .. } = &mut entry.control else {
1089            entry.observability.counters_mut().record_stale_wakeup();
1090            return CallbackAcceptance::Stale;
1091        };
1092        if control.begin(token.callback_generation) {
1093            entry.observability.counters_mut().record_work_started();
1094            CallbackAcceptance::Accepted
1095        } else {
1096            entry.observability.counters_mut().record_stale_wakeup();
1097            CallbackAcceptance::Stale
1098        }
1099    }
1100
1101    #[allow(clippy::too_many_lines)] // One atomic policy transition; splitting obscures rollback state.
1102    pub(crate) fn complete_ordinary(
1103        &mut self,
1104        token: &CallbackToken,
1105        now_ns: u64,
1106        result: TimerRunResult,
1107    ) -> Result<RegistryTransition, RegistryError> {
1108        let identity = token.identity.clone();
1109        let (transition, remove) = {
1110            let entry = self.entry_by_token_mut(token, CallbackRole::OrdinaryWork)?;
1111            let EntryControl::Ordinary {
1112                control,
1113                pending,
1114                inactive_reason,
1115            } = &mut entry.control
1116            else {
1117                return Err(RegistryError::StaleCallback);
1118            };
1119            if control.registration()
1120                != (TimerRegistration::Running {
1121                    generation: token.callback_generation,
1122                })
1123            {
1124                return Err(RegistryError::StaleCallback);
1125            }
1126
1127            let completion = result.completion();
1128            let pending_command = *pending;
1129            let terminal_pending = matches!(
1130                pending_command,
1131                Some(OrdinaryPending::Cancel | OrdinaryPending::Unregister)
1132            );
1133            if completion.outcome() == TimerCompletionOutcome::InvariantFailure {
1134                let transition =
1135                    invariant_completion(entry, token.callback_generation, completion, now_ns);
1136                let remove = matches!(pending_command, Some(OrdinaryPending::Unregister))
1137                    || matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped);
1138                (transition, remove)
1139            } else if !terminal_pending
1140                && matches!(entry.policy, TimerPolicy::Once)
1141                && matches!(result.directive(), TimerDirective::RecurAfterCompletion)
1142            {
1143                let transition = terminal_completion(
1144                    entry,
1145                    token.callback_generation,
1146                    completion.work_count(),
1147                    now_ns,
1148                    TimerControlFailure::DirectiveNotAllowed,
1149                );
1150                let remove = matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped);
1151                (transition, remove)
1152            } else {
1153                let effective_directive = if terminal_pending {
1154                    TimerDirective::Stop
1155                } else {
1156                    result.directive()
1157                };
1158                let cadence = match entry.policy {
1159                    TimerPolicy::AfterCompletion { cadence } => Some(cadence),
1160                    TimerPolicy::Once => None,
1161                    TimerPolicy::Watchdog { .. } => {
1162                        return Err(RegistryError::StaleCallback);
1163                    }
1164                };
1165                let resolved = match effective_directive.resolve(now_ns, cadence) {
1166                    Ok(value) => value,
1167                    Err(error) => {
1168                        let transition = terminal_completion(
1169                            entry,
1170                            token.callback_generation,
1171                            completion.work_count(),
1172                            now_ns,
1173                            map_schedule_failure(error),
1174                        );
1175                        let remove = matches!(pending_command, Some(OrdinaryPending::Unregister))
1176                            || matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped);
1177                        return Ok(remove_after(
1178                            &mut self.entries,
1179                            &identity,
1180                            transition,
1181                            remove,
1182                        ));
1183                    }
1184                };
1185                let directive_snapshot = TimerDirectiveSnapshot::try_from(effective_directive)
1186                    .map_err(RegistryError::Schedule)?;
1187                let callback_schedule = resolved.deadline_ns.map(|deadline_ns| PendingSchedule {
1188                    deadline_ns,
1189                    requested_delay_ns: resolved.requested_delay_ns,
1190                    mode: directive_snapshot
1191                        .scheduling_mode()
1192                        .unwrap_or(entry.scheduling_mode),
1193                });
1194                let selected_schedule =
1195                    select_completion_schedule(pending_command, callback_schedule);
1196                let action = match control.complete(
1197                    token.callback_generation,
1198                    selected_schedule.map(|value| value.deadline_ns),
1199                    terminal_pending,
1200                ) {
1201                    Ok(action) => action,
1202                    Err(TimerControlError::StaleCompletion) => {
1203                        return Err(RegistryError::StaleCallback);
1204                    }
1205                    Err(error) => {
1206                        let transition = terminal_completion(
1207                            entry,
1208                            token.callback_generation,
1209                            completion.work_count(),
1210                            now_ns,
1211                            map_control_failure(error),
1212                        );
1213                        let remove = matches!(pending_command, Some(OrdinaryPending::Unregister))
1214                            || matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped);
1215                        return Ok(remove_after(
1216                            &mut self.entries,
1217                            &identity,
1218                            transition,
1219                            remove,
1220                        ));
1221                    }
1222                };
1223                *pending = None;
1224                entry.latest_directive = Some(directive_snapshot);
1225                entry.observability.record_completion(completion, now_ns);
1226
1227                let mut remove = false;
1228                let transition = match action {
1229                    TimerControlAction::Arm {
1230                        generation,
1231                        deadline_ns,
1232                    }
1233                    | TimerControlAction::Replace {
1234                        generation,
1235                        deadline_ns,
1236                    } => {
1237                        let selected = selected_schedule.unwrap_or(PendingSchedule {
1238                            deadline_ns,
1239                            requested_delay_ns: None,
1240                            mode: entry.scheduling_mode,
1241                        });
1242                        let replace = matches!(action, TimerControlAction::Replace { .. });
1243                        entry.scheduling_mode = selected.mode;
1244                        entry.latest_requested_delay_ns = selected.requested_delay_ns;
1245                        RegistryTransition::normal(RegistryEffect::ArmWakeup {
1246                            token: CallbackToken::new(
1247                                identity.clone(),
1248                                entry.claim_generation,
1249                                generation,
1250                                CallbackRole::OrdinaryWork,
1251                            ),
1252                            deadline_ns,
1253                            delay_ns: deadline_ns.saturating_sub(now_ns),
1254                            replace,
1255                        })
1256                    }
1257                    TimerControlAction::Disarm { cancelled } => {
1258                        *inactive_reason = if cancelled {
1259                            entry.observability.counters_mut().record_cancellation();
1260                            InactiveReason::Cancelled
1261                        } else {
1262                            InactiveReason::Stopped
1263                        };
1264                        remove = matches!(pending_command, Some(OrdinaryPending::Unregister))
1265                            || matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped);
1266                        RegistryTransition::normal(RegistryEffect::None)
1267                    }
1268                    TimerControlAction::None | TimerControlAction::Clear => {
1269                        let clear_wakeup = control.terminate();
1270                        *inactive_reason = InactiveReason::ControlFailure(
1271                            TimerControlFailure::DirectiveNotAllowed,
1272                        );
1273                        remove = matches!(pending_command, Some(OrdinaryPending::Unregister))
1274                            || matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped);
1275                        RegistryTransition::terminal(
1276                            clear_callbacks(identity.clone(), clear_wakeup, false),
1277                            TimerControlFailure::DirectiveNotAllowed,
1278                        )
1279                    }
1280                };
1281                (transition, remove)
1282            }
1283        };
1284
1285        Ok(remove_after(
1286            &mut self.entries,
1287            &identity,
1288            transition,
1289            remove,
1290        ))
1291    }
1292
1293    #[allow(clippy::too_many_lines)] // The bounded scheduler protocol is audited as one path.
1294    pub(crate) fn begin_watchdog_scheduler(
1295        &mut self,
1296        token: &CallbackToken,
1297        now_ns: u64,
1298    ) -> RegistryTransition {
1299        let Some(entry) = self.entries.get_mut(token.identity()) else {
1300            return RegistryTransition::normal(RegistryEffect::None);
1301        };
1302        if token.role != CallbackRole::WatchdogScheduler
1303            || token.claim_generation != entry.claim_generation
1304        {
1305            entry.observability.counters_mut().record_stale_wakeup();
1306            return RegistryTransition::normal(RegistryEffect::None);
1307        }
1308        let TimerPolicy::Watchdog { cadence } = entry.policy else {
1309            entry.observability.counters_mut().record_stale_wakeup();
1310            return RegistryTransition::normal(RegistryEffect::None);
1311        };
1312        let EntryControl::Watchdog(control) = &mut entry.control else {
1313            entry.observability.counters_mut().record_stale_wakeup();
1314            return RegistryTransition::normal(RegistryEffect::None);
1315        };
1316
1317        let accepted = match control.state {
1318            WatchdogState::Scheduled {
1319                scheduler_generation,
1320                ..
1321            } => scheduler_generation == token.callback_generation,
1322            WatchdogState::AwaitingWork {
1323                successor_generation,
1324                attempt_status: WatchdogAttemptStatus::Dispatched,
1325                ..
1326            } => successor_generation == token.callback_generation,
1327            WatchdogState::Inactive
1328            | WatchdogState::AwaitingWork {
1329                attempt_status: WatchdogAttemptStatus::Running,
1330                ..
1331            } => false,
1332        };
1333        if !accepted {
1334            entry.observability.counters_mut().record_stale_wakeup();
1335            return RegistryTransition::normal(RegistryEffect::None);
1336        }
1337
1338        entry
1339            .observability
1340            .counters_mut()
1341            .record_scheduler_started();
1342        if matches!(control.state, WatchdogState::AwaitingWork { .. }) {
1343            entry.observability.record_unacknowledged(now_ns);
1344        }
1345        let Some(successor_generation) = control.scheduler_generation.checked_add(1) else {
1346            control.state = WatchdogState::Inactive;
1347            control.inactive_reason =
1348                InactiveReason::ControlFailure(TimerControlFailure::GenerationExhausted);
1349            return RegistryTransition::terminal(
1350                clear_callbacks(token.identity.clone(), false, true),
1351                TimerControlFailure::GenerationExhausted,
1352            );
1353        };
1354        let Some(attempt_generation) = control.attempt_generation.checked_add(1) else {
1355            control.state = WatchdogState::Inactive;
1356            control.inactive_reason =
1357                InactiveReason::ControlFailure(TimerControlFailure::GenerationExhausted);
1358            return RegistryTransition::terminal(
1359                clear_callbacks(token.identity.clone(), false, true),
1360                TimerControlFailure::GenerationExhausted,
1361            );
1362        };
1363        let Ok(successor_deadline_ns) = cadence.deadline_after(now_ns) else {
1364            control.state = WatchdogState::Inactive;
1365            control.inactive_reason =
1366                InactiveReason::ControlFailure(TimerControlFailure::DeadlineOverflow);
1367            return RegistryTransition::terminal(
1368                clear_callbacks(token.identity.clone(), false, true),
1369                TimerControlFailure::DeadlineOverflow,
1370            );
1371        };
1372
1373        control.scheduler_generation = successor_generation;
1374        control.attempt_generation = attempt_generation;
1375        control.state = WatchdogState::AwaitingWork {
1376            successor_generation,
1377            successor_deadline_ns,
1378            attempt_generation,
1379            attempt_status: WatchdogAttemptStatus::Dispatched,
1380        };
1381        control.pending = None;
1382        RegistryTransition::normal(RegistryEffect::DispatchWatchdog {
1383            successor: CallbackToken::new(
1384                token.identity.clone(),
1385                entry.claim_generation,
1386                successor_generation,
1387                CallbackRole::WatchdogScheduler,
1388            ),
1389            successor_deadline_ns,
1390            successor_delay_ns: cadence.as_nanos(),
1391            work: CallbackToken::new(
1392                token.identity.clone(),
1393                entry.claim_generation,
1394                attempt_generation,
1395                CallbackRole::WatchdogWork,
1396            ),
1397        })
1398    }
1399
1400    pub(crate) fn begin_watchdog_work(&mut self, token: &CallbackToken) -> CallbackAcceptance {
1401        let Some(entry) = self.entries.get_mut(token.identity()) else {
1402            return CallbackAcceptance::Stale;
1403        };
1404        if token.role != CallbackRole::WatchdogWork
1405            || token.claim_generation != entry.claim_generation
1406        {
1407            entry.observability.counters_mut().record_stale_work();
1408            return CallbackAcceptance::Stale;
1409        }
1410        let EntryControl::Watchdog(control) = &mut entry.control else {
1411            entry.observability.counters_mut().record_stale_work();
1412            return CallbackAcceptance::Stale;
1413        };
1414        match &mut control.state {
1415            WatchdogState::AwaitingWork {
1416                attempt_generation,
1417                attempt_status,
1418                ..
1419            } if *attempt_generation == token.callback_generation
1420                && *attempt_status == WatchdogAttemptStatus::Dispatched =>
1421            {
1422                *attempt_status = WatchdogAttemptStatus::Running;
1423                entry.observability.counters_mut().record_work_started();
1424                CallbackAcceptance::Accepted
1425            }
1426            WatchdogState::Inactive
1427            | WatchdogState::Scheduled { .. }
1428            | WatchdogState::AwaitingWork { .. } => {
1429                entry.observability.counters_mut().record_stale_work();
1430                CallbackAcceptance::Stale
1431            }
1432        }
1433    }
1434
1435    pub(crate) fn complete_watchdog_work(
1436        &mut self,
1437        token: &CallbackToken,
1438        now_ns: u64,
1439        result: WatchdogRunResult,
1440    ) -> Result<RegistryTransition, RegistryError> {
1441        let identity = token.identity.clone();
1442        let (transition, remove) = {
1443            let entry = self.entry_by_token_mut(token, CallbackRole::WatchdogWork)?;
1444            let EntryControl::Watchdog(control) = &mut entry.control else {
1445                return Err(RegistryError::StaleCallback);
1446            };
1447            let (successor_generation, successor_deadline_ns) = match control.state {
1448                WatchdogState::AwaitingWork {
1449                    successor_generation,
1450                    successor_deadline_ns,
1451                    attempt_generation,
1452                    attempt_status: WatchdogAttemptStatus::Running,
1453                } if attempt_generation == token.callback_generation => {
1454                    (successor_generation, successor_deadline_ns)
1455                }
1456                WatchdogState::Inactive
1457                | WatchdogState::Scheduled { .. }
1458                | WatchdogState::AwaitingWork { .. } => {
1459                    return Err(RegistryError::StaleCallback);
1460                }
1461            };
1462
1463            let completion = result.completion();
1464            entry.observability.record_completion(completion, now_ns);
1465            let decision = if completion.outcome() == TimerCompletionOutcome::InvariantFailure {
1466                WatchdogDecision::Stop
1467            } else {
1468                match control.pending {
1469                    Some(WatchdogPending::Cancel | WatchdogPending::Unregister) => {
1470                        WatchdogDecision::Stop
1471                    }
1472                    Some(WatchdogPending::Ensure) => WatchdogDecision::Continue,
1473                    None => result.decision(),
1474                }
1475            };
1476            let cancelled = matches!(control.pending, Some(WatchdogPending::Cancel));
1477            let unregister = matches!(control.pending, Some(WatchdogPending::Unregister));
1478            control.pending = None;
1479
1480            match decision {
1481                WatchdogDecision::Continue => {
1482                    control.state = WatchdogState::Scheduled {
1483                        scheduler_generation: successor_generation,
1484                        deadline_ns: successor_deadline_ns,
1485                    };
1486                    (RegistryTransition::normal(RegistryEffect::None), false)
1487                }
1488                WatchdogDecision::Stop => {
1489                    control.state = WatchdogState::Inactive;
1490                    control.inactive_reason = if cancelled {
1491                        entry.observability.counters_mut().record_cancellation();
1492                        InactiveReason::Cancelled
1493                    } else if completion.outcome() == TimerCompletionOutcome::InvariantFailure {
1494                        InactiveReason::InvariantFailure
1495                    } else {
1496                        InactiveReason::Stopped
1497                    };
1498                    (
1499                        RegistryTransition::normal(clear_callbacks(identity.clone(), true, false)),
1500                        unregister
1501                            || matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped),
1502                    )
1503                }
1504            }
1505        };
1506
1507        Ok(remove_after(
1508            &mut self.entries,
1509            &identity,
1510            transition,
1511            remove,
1512        ))
1513    }
1514
1515    pub(crate) fn snapshot(&self, identity: &TimerIdentity) -> Option<TimerSnapshot> {
1516        self.entries
1517            .get(identity)
1518            .map(|entry| entry.snapshot(identity.clone()))
1519    }
1520
1521    pub(crate) fn snapshots(&self) -> Vec<TimerSnapshot> {
1522        self.entries
1523            .iter()
1524            .map(|(identity, entry)| entry.snapshot(identity.clone()))
1525            .collect()
1526    }
1527
1528    pub(crate) fn consecutive_expected_failures(&self, identity: &TimerIdentity) -> Option<u64> {
1529        self.entries
1530            .get(identity)
1531            .map(|entry| entry.observability.consecutive_expected_failures())
1532    }
1533
1534    pub(crate) fn has_armed_wakeup(
1535        &self,
1536        claim: &RegistrationClaim,
1537    ) -> Result<bool, RegistryError> {
1538        Ok(self.entry(claim)?.wakeup.is_some())
1539    }
1540
1541    pub(crate) fn record_scheduler_instructions(
1542        &mut self,
1543        token: &CallbackToken,
1544        instructions: u64,
1545    ) {
1546        let Some(entry) = self.entries.get_mut(token.identity()) else {
1547            return;
1548        };
1549        if token.claim_generation == entry.claim_generation
1550            && token.role == CallbackRole::WatchdogScheduler
1551            && matches!(entry.control, EntryControl::Watchdog(_))
1552        {
1553            entry
1554                .observability
1555                .record_scheduler_instructions(instructions);
1556        }
1557    }
1558
1559    pub(crate) fn record_work_instructions(&mut self, token: &CallbackToken, instructions: u64) {
1560        let Some(entry) = self.entries.get_mut(token.identity()) else {
1561            return;
1562        };
1563        let role_matches = matches!(
1564            (&entry.control, token.role),
1565            (EntryControl::Ordinary { .. }, CallbackRole::OrdinaryWork)
1566                | (EntryControl::Watchdog(_), CallbackRole::WatchdogWork)
1567        );
1568        if token.claim_generation == entry.claim_generation && role_matches {
1569            entry.observability.record_work_instructions(instructions);
1570        }
1571    }
1572
1573    pub(crate) fn fail_registration(
1574        &mut self,
1575        claim: &RegistrationClaim,
1576        failure: TimerControlFailure,
1577    ) -> Result<ProviderHandles, RegistryError> {
1578        let identity = claim.identity.clone();
1579        let (handles, remove) = {
1580            let entry = self.entry_mut(claim)?;
1581            match &mut entry.control {
1582                EntryControl::Ordinary {
1583                    control,
1584                    pending,
1585                    inactive_reason,
1586                } => {
1587                    control.terminate();
1588                    *pending = None;
1589                    *inactive_reason = InactiveReason::ControlFailure(failure);
1590                }
1591                EntryControl::Watchdog(control) => {
1592                    control.state = WatchdogState::Inactive;
1593                    control.pending = None;
1594                    control.inactive_reason = InactiveReason::ControlFailure(failure);
1595                }
1596            }
1597            let handles =
1598                ProviderHandles {
1599                    wakeup: entry.wakeup.take().map(|owned| {
1600                        detach_provider_handle(&identity, entry.claim_generation, owned)
1601                    }),
1602                    work: entry.work.take().map(|owned| {
1603                        detach_provider_handle(&identity, entry.claim_generation, owned)
1604                    }),
1605                };
1606            (
1607                handles,
1608                matches!(entry.lifetime, DeclarationLifetime::RemoveWhenStopped),
1609            )
1610        };
1611        if remove {
1612            self.entries.remove(&identity);
1613        }
1614        Ok(handles)
1615    }
1616
1617    pub(crate) fn ordinary_callback(
1618        &self,
1619        token: &CallbackToken,
1620    ) -> Result<OrdinaryCallback, RegistryError> {
1621        let entry = self.running_work_entry(token)?;
1622        match &entry.callback {
1623            EntryCallback::Ordinary(callback) => Ok(Rc::clone(callback)),
1624            EntryCallback::Watchdog(_) => Err(RegistryError::MissingCallback),
1625            #[cfg(test)]
1626            EntryCallback::None => Err(RegistryError::MissingCallback),
1627        }
1628    }
1629
1630    pub(crate) fn watchdog_callback(
1631        &self,
1632        token: &CallbackToken,
1633    ) -> Result<WatchdogCallback, RegistryError> {
1634        let entry = self.running_work_entry(token)?;
1635        match &entry.callback {
1636            EntryCallback::Watchdog(callback) => Ok(Rc::clone(callback)),
1637            EntryCallback::Ordinary(_) => Err(RegistryError::MissingCallback),
1638            #[cfg(test)]
1639            EntryCallback::None => Err(RegistryError::MissingCallback),
1640        }
1641    }
1642
1643    pub(crate) fn validate_running_context(
1644        &self,
1645        token: &CallbackToken,
1646    ) -> Result<(), RegistryError> {
1647        self.running_work_entry(token).map(|_| ())
1648    }
1649
1650    pub(crate) fn install_provider_handle(
1651        &mut self,
1652        token: &CallbackToken,
1653        handle: TimerHandle,
1654    ) -> Result<(), (RegistryError, TimerHandle)> {
1655        let entry = match self.entry_by_token_mut(token, token.role) {
1656            Ok(entry) => entry,
1657            Err(error) => return Err((error, handle)),
1658        };
1659        let valid = match (&entry.control, token.role) {
1660            (EntryControl::Ordinary { control, .. }, CallbackRole::OrdinaryWork) => matches!(
1661                control.registration(),
1662                TimerRegistration::Scheduled { generation, .. }
1663                    if generation == token.callback_generation
1664            ),
1665            (EntryControl::Watchdog(control), CallbackRole::WatchdogScheduler) => {
1666                matches!(
1667                    control.state,
1668                    WatchdogState::Scheduled {
1669                        scheduler_generation,
1670                        ..
1671                    } if scheduler_generation == token.callback_generation
1672                ) || matches!(
1673                    control.state,
1674                    WatchdogState::AwaitingWork {
1675                        successor_generation,
1676                        ..
1677                    } if successor_generation == token.callback_generation
1678                )
1679            }
1680            (EntryControl::Watchdog(control), CallbackRole::WatchdogWork) => matches!(
1681                control.state,
1682                WatchdogState::AwaitingWork {
1683                    attempt_generation,
1684                    attempt_status: WatchdogAttemptStatus::Dispatched,
1685                    ..
1686                } if attempt_generation == token.callback_generation
1687            ),
1688            (
1689                EntryControl::Ordinary { .. },
1690                CallbackRole::WatchdogScheduler | CallbackRole::WatchdogWork,
1691            )
1692            | (EntryControl::Watchdog(_), CallbackRole::OrdinaryWork) => false,
1693        };
1694        if !valid {
1695            return Err((RegistryError::StaleCallback, handle));
1696        }
1697        let slot = match token.role {
1698            CallbackRole::OrdinaryWork | CallbackRole::WatchdogScheduler => &mut entry.wakeup,
1699            CallbackRole::WatchdogWork => &mut entry.work,
1700        };
1701        if slot.is_some() {
1702            return Err((RegistryError::ProviderHandleAlreadyOwned, handle));
1703        }
1704        *slot = Some(OwnedProviderHandle {
1705            callback_generation: token.callback_generation,
1706            role: token.role,
1707            handle,
1708        });
1709        Ok(())
1710    }
1711
1712    pub(crate) fn take_wakeup_handle(
1713        &mut self,
1714        identity: &TimerIdentity,
1715    ) -> Option<ProviderHandle> {
1716        let entry = self.entries.get_mut(identity)?;
1717        let owned = entry.wakeup.take()?;
1718        Some(detach_provider_handle(
1719            identity,
1720            entry.claim_generation,
1721            owned,
1722        ))
1723    }
1724
1725    pub(crate) fn take_work_handle(&mut self, identity: &TimerIdentity) -> Option<ProviderHandle> {
1726        let entry = self.entries.get_mut(identity)?;
1727        let owned = entry.work.take()?;
1728        Some(detach_provider_handle(
1729            identity,
1730            entry.claim_generation,
1731            owned,
1732        ))
1733    }
1734
1735    pub(crate) fn take_provider_handles_for_claim(
1736        &mut self,
1737        claim: &RegistrationClaim,
1738    ) -> Result<ProviderHandles, RegistryError> {
1739        let identity = claim.identity.clone();
1740        let entry = self.entry_mut(claim)?;
1741        Ok(ProviderHandles {
1742            wakeup: entry
1743                .wakeup
1744                .take()
1745                .map(|owned| detach_provider_handle(&identity, entry.claim_generation, owned)),
1746            work: entry
1747                .work
1748                .take()
1749                .map(|owned| detach_provider_handle(&identity, entry.claim_generation, owned)),
1750        })
1751    }
1752
1753    pub(crate) fn consume_provider_handle(&mut self, token: &CallbackToken) {
1754        let Some(entry) = self.entries.get_mut(token.identity()) else {
1755            return;
1756        };
1757        let slot = match token.role {
1758            CallbackRole::OrdinaryWork | CallbackRole::WatchdogScheduler => &mut entry.wakeup,
1759            CallbackRole::WatchdogWork => &mut entry.work,
1760        };
1761        let matches_token = slot.as_ref().is_some_and(|owned| {
1762            owned.callback_generation == token.callback_generation && owned.role == token.role
1763        });
1764        if matches_token {
1765            *slot = None;
1766        }
1767    }
1768
1769    /// Confirm that the platform successfully applied one emitted arm effect.
1770    ///
1771    /// The runtime calls this synchronously after binding the returned provider
1772    /// handles. Pure tests use it to distinguish requested effects from actual
1773    /// provider operations.
1774    pub(crate) fn confirm_effect_applied(
1775        &mut self,
1776        effect: &RegistryEffect,
1777    ) -> Result<(), RegistryError> {
1778        match effect {
1779            RegistryEffect::None | RegistryEffect::ClearCallbacks { .. } => Ok(()),
1780            RegistryEffect::ArmWakeup {
1781                token, delay_ns, ..
1782            } => {
1783                let entry = self.entry_by_token_mut(token, token.role)?;
1784                let valid_generation = match (&entry.control, token.role) {
1785                    (EntryControl::Ordinary { control, .. }, CallbackRole::OrdinaryWork) => {
1786                        matches!(
1787                            control.registration(),
1788                            TimerRegistration::Scheduled { generation, .. }
1789                                if generation == token.callback_generation
1790                        )
1791                    }
1792                    (EntryControl::Watchdog(control), CallbackRole::WatchdogScheduler) => matches!(
1793                        control.state,
1794                        WatchdogState::Scheduled {
1795                            scheduler_generation,
1796                            ..
1797                        } if scheduler_generation == token.callback_generation
1798                    ),
1799                    (EntryControl::Ordinary { .. } | EntryControl::Watchdog(_), _) => false,
1800                };
1801                if !valid_generation {
1802                    return Err(RegistryError::StaleCallback);
1803                }
1804                if entry.confirmed_wakeup_generation == Some(token.callback_generation) {
1805                    return Ok(());
1806                }
1807                entry.confirmed_wakeup_generation = Some(token.callback_generation);
1808                entry.latest_armed_delay_ns = Some(*delay_ns);
1809                entry.observability.counters_mut().record_wakeup_armed();
1810                Ok(())
1811            }
1812            RegistryEffect::DispatchWatchdog {
1813                successor,
1814                successor_delay_ns,
1815                work,
1816                ..
1817            } => {
1818                let successor_claim_generation = successor.claim_generation;
1819                let successor_callback_generation = successor.callback_generation;
1820                let successor_role = successor.role;
1821                let entry = self.entry_by_token_mut(successor, successor_role)?;
1822                if successor_role != CallbackRole::WatchdogScheduler
1823                    || work.role != CallbackRole::WatchdogWork
1824                    || work.claim_generation != successor_claim_generation
1825                {
1826                    return Err(RegistryError::StaleCallback);
1827                }
1828                let EntryControl::Watchdog(control) = &entry.control else {
1829                    return Err(RegistryError::StaleCallback);
1830                };
1831                if !matches!(
1832                    control.state,
1833                    WatchdogState::AwaitingWork {
1834                        successor_generation,
1835                        attempt_generation,
1836                        ..
1837                    } if successor_generation == successor_callback_generation
1838                        && attempt_generation == work.callback_generation
1839                ) {
1840                    return Err(RegistryError::StaleCallback);
1841                }
1842                if entry.confirmed_wakeup_generation == Some(successor_callback_generation)
1843                    && entry.confirmed_work_generation == Some(work.callback_generation)
1844                {
1845                    return Ok(());
1846                }
1847                entry.confirmed_wakeup_generation = Some(successor_callback_generation);
1848                entry.confirmed_work_generation = Some(work.callback_generation);
1849                entry.latest_armed_delay_ns = Some(*successor_delay_ns);
1850                entry.observability.counters_mut().record_wakeup_armed();
1851                entry.observability.counters_mut().record_work_dispatched();
1852                Ok(())
1853            }
1854        }
1855    }
1856
1857    fn entry(&self, claim: &RegistrationClaim) -> Result<&Entry, RegistryError> {
1858        let entry = self
1859            .entries
1860            .get(claim.identity())
1861            .ok_or(RegistryError::UnknownRegistration)?;
1862        if entry.claim_generation != claim.claim_generation() {
1863            return Err(RegistryError::StaleRegistration);
1864        }
1865        Ok(entry)
1866    }
1867
1868    fn entry_mut(&mut self, claim: &RegistrationClaim) -> Result<&mut Entry, RegistryError> {
1869        let entry = self
1870            .entries
1871            .get_mut(claim.identity())
1872            .ok_or(RegistryError::UnknownRegistration)?;
1873        if entry.claim_generation != claim.claim_generation() {
1874            return Err(RegistryError::StaleRegistration);
1875        }
1876        Ok(entry)
1877    }
1878
1879    fn entry_by_token_mut(
1880        &mut self,
1881        token: &CallbackToken,
1882        role: CallbackRole,
1883    ) -> Result<&mut Entry, RegistryError> {
1884        let entry = self
1885            .entries
1886            .get_mut(token.identity())
1887            .ok_or(RegistryError::StaleCallback)?;
1888        if entry.claim_generation != token.claim_generation || token.role != role {
1889            return Err(RegistryError::StaleCallback);
1890        }
1891        Ok(entry)
1892    }
1893
1894    fn running_work_entry(&self, token: &CallbackToken) -> Result<&Entry, RegistryError> {
1895        let entry = self
1896            .entries
1897            .get(token.identity())
1898            .ok_or(RegistryError::StaleCallback)?;
1899        if token.claim_generation != entry.claim_generation {
1900            return Err(RegistryError::StaleCallback);
1901        }
1902        let active = match (&entry.control, token.role) {
1903            (EntryControl::Ordinary { control, .. }, CallbackRole::OrdinaryWork) => matches!(
1904                control.registration(),
1905                TimerRegistration::Running { generation }
1906                    if generation == token.callback_generation
1907            ),
1908            (EntryControl::Watchdog(control), CallbackRole::WatchdogWork) => matches!(
1909                control.state,
1910                WatchdogState::AwaitingWork {
1911                    attempt_generation,
1912                    attempt_status: WatchdogAttemptStatus::Running,
1913                    ..
1914                } if attempt_generation == token.callback_generation
1915            ),
1916            (
1917                EntryControl::Ordinary { .. },
1918                CallbackRole::WatchdogScheduler | CallbackRole::WatchdogWork,
1919            )
1920            | (
1921                EntryControl::Watchdog(_),
1922                CallbackRole::OrdinaryWork | CallbackRole::WatchdogScheduler,
1923            ) => false,
1924        };
1925        if active {
1926            Ok(entry)
1927        } else {
1928            Err(RegistryError::StaleCallback)
1929        }
1930    }
1931}
1932
1933fn token_for(
1934    claim: &RegistrationClaim,
1935    callback_generation: u64,
1936    role: CallbackRole,
1937) -> CallbackToken {
1938    CallbackToken::new(
1939        claim.identity.clone(),
1940        claim.claim_generation,
1941        callback_generation,
1942        role,
1943    )
1944}
1945
1946fn detach_provider_handle(
1947    identity: &TimerIdentity,
1948    claim_generation: u64,
1949    owned: OwnedProviderHandle,
1950) -> ProviderHandle {
1951    ProviderHandle {
1952        token: CallbackToken::new(
1953            identity.clone(),
1954            claim_generation,
1955            owned.callback_generation,
1956            owned.role,
1957        ),
1958        handle: owned.handle,
1959    }
1960}
1961
1962const fn clear_callbacks(
1963    identity: TimerIdentity,
1964    clear_wakeup: bool,
1965    clear_work: bool,
1966) -> RegistryEffect {
1967    RegistryEffect::ClearCallbacks {
1968        identity,
1969        clear_wakeup,
1970        clear_work,
1971    }
1972}
1973
1974fn apply_ordinary_action(
1975    entry: &mut Entry,
1976    identity: TimerIdentity,
1977    now_ns: u64,
1978    action: TimerControlAction,
1979    requested: PendingSchedule,
1980) -> RegistryTransition {
1981    match action {
1982        TimerControlAction::Arm {
1983            generation,
1984            deadline_ns,
1985        }
1986        | TimerControlAction::Replace {
1987            generation,
1988            deadline_ns,
1989        } => {
1990            let replace = matches!(action, TimerControlAction::Replace { .. });
1991            entry.scheduling_mode = requested.mode;
1992            RegistryTransition::normal(RegistryEffect::ArmWakeup {
1993                token: CallbackToken::new(
1994                    identity,
1995                    entry.claim_generation,
1996                    generation,
1997                    CallbackRole::OrdinaryWork,
1998                ),
1999                deadline_ns,
2000                delay_ns: deadline_ns.saturating_sub(now_ns),
2001                replace,
2002            })
2003        }
2004        TimerControlAction::None => {
2005            entry.observability.counters_mut().record_coalesced();
2006            RegistryTransition::normal(RegistryEffect::None)
2007        }
2008        TimerControlAction::Clear | TimerControlAction::Disarm { .. } => {
2009            terminal_ordinary(entry, identity, TimerControlError::StaleCompletion)
2010        }
2011    }
2012}
2013
2014fn terminal_ordinary(
2015    entry: &mut Entry,
2016    identity: TimerIdentity,
2017    error: TimerControlError,
2018) -> RegistryTransition {
2019    let failure = map_control_failure(error);
2020    let EntryControl::Ordinary {
2021        control,
2022        pending,
2023        inactive_reason,
2024    } = &mut entry.control
2025    else {
2026        return RegistryTransition::terminal(RegistryEffect::None, failure);
2027    };
2028    let clear_wakeup = control.terminate();
2029    *pending = None;
2030    *inactive_reason = InactiveReason::ControlFailure(failure);
2031    RegistryTransition::terminal(
2032        RegistryEffect::ClearCallbacks {
2033            identity,
2034            clear_wakeup,
2035            clear_work: false,
2036        },
2037        failure,
2038    )
2039}
2040
2041fn terminal_completion(
2042    entry: &mut Entry,
2043    generation: u64,
2044    work_count: u64,
2045    now_ns: u64,
2046    failure: TimerControlFailure,
2047) -> RegistryTransition {
2048    let EntryControl::Ordinary {
2049        control,
2050        pending,
2051        inactive_reason,
2052    } = &mut entry.control
2053    else {
2054        return RegistryTransition::terminal(RegistryEffect::None, failure);
2055    };
2056    if control.registration() == (TimerRegistration::Running { generation }) {
2057        control.terminate();
2058    }
2059    *pending = None;
2060    *inactive_reason = InactiveReason::ControlFailure(failure);
2061    entry.latest_directive = Some(TimerDirectiveSnapshot::Stop);
2062    entry
2063        .observability
2064        .record_completion(TimerCompletion::invariant_failure(work_count), now_ns);
2065    RegistryTransition::terminal(RegistryEffect::None, failure)
2066}
2067
2068fn invariant_completion(
2069    entry: &mut Entry,
2070    generation: u64,
2071    completion: TimerCompletion,
2072    now_ns: u64,
2073) -> RegistryTransition {
2074    let EntryControl::Ordinary {
2075        control,
2076        pending,
2077        inactive_reason,
2078    } = &mut entry.control
2079    else {
2080        return RegistryTransition::normal(RegistryEffect::None);
2081    };
2082    if control.registration() == (TimerRegistration::Running { generation }) {
2083        control.terminate();
2084    }
2085    *pending = None;
2086    *inactive_reason = InactiveReason::InvariantFailure;
2087    entry.latest_directive = Some(TimerDirectiveSnapshot::Stop);
2088    entry.observability.record_completion(completion, now_ns);
2089    RegistryTransition::normal(RegistryEffect::None)
2090}
2091
2092const fn map_control_failure(error: TimerControlError) -> TimerControlFailure {
2093    match error {
2094        TimerControlError::RequestSequenceExhausted => {
2095            TimerControlFailure::RequestSequenceExhausted
2096        }
2097        TimerControlError::GenerationExhausted => TimerControlFailure::GenerationExhausted,
2098        TimerControlError::StaleCompletion => TimerControlFailure::DirectiveNotAllowed,
2099    }
2100}
2101
2102const fn map_schedule_failure(error: ScheduleError) -> TimerControlFailure {
2103    match error {
2104        ScheduleError::DeadlineOverflow => TimerControlFailure::DeadlineOverflow,
2105        ScheduleError::DelayOutOfRange => TimerControlFailure::DelayOutOfRange,
2106        ScheduleError::ZeroCadence | ScheduleError::MissingCadence => {
2107            TimerControlFailure::DirectiveNotAllowed
2108        }
2109    }
2110}
2111
2112const fn select_completion_schedule(
2113    pending: Option<OrdinaryPending>,
2114    callback: Option<PendingSchedule>,
2115) -> Option<PendingSchedule> {
2116    match pending {
2117        Some(OrdinaryPending::Cancel | OrdinaryPending::Unregister) => None,
2118        Some(OrdinaryPending::Reconcile(pending)) => Some(pending),
2119        Some(OrdinaryPending::Schedule(pending)) => match callback {
2120            Some(callback) if callback.deadline_ns < pending.deadline_ns => Some(callback),
2121            Some(_) | None => Some(pending),
2122        },
2123        None => callback,
2124    }
2125}
2126
2127const fn select_pending_ordinary(
2128    current: Option<OrdinaryPending>,
2129    request: OrdinaryRequest,
2130    requested: PendingSchedule,
2131) -> OrdinaryPending {
2132    if matches!(current, Some(OrdinaryPending::Unregister)) {
2133        return OrdinaryPending::Unregister;
2134    }
2135    match request {
2136        OrdinaryRequest::Reconcile => OrdinaryPending::Reconcile(requested),
2137        OrdinaryRequest::Ensure => match current {
2138            Some(OrdinaryPending::Reconcile(current) | OrdinaryPending::Schedule(current))
2139                if current.deadline_ns <= requested.deadline_ns =>
2140            {
2141                OrdinaryPending::Schedule(current)
2142            }
2143            Some(
2144                OrdinaryPending::Cancel
2145                | OrdinaryPending::Reconcile(_)
2146                | OrdinaryPending::Schedule(_),
2147            )
2148            | None => OrdinaryPending::Schedule(requested),
2149            Some(OrdinaryPending::Unregister) => OrdinaryPending::Unregister,
2150        },
2151    }
2152}
2153
2154fn cancel_watchdog(
2155    control: &mut WatchdogControl,
2156    identity: &TimerIdentity,
2157) -> (RegistryTransition, bool) {
2158    match control.state {
2159        WatchdogState::Inactive => (RegistryTransition::normal(RegistryEffect::None), false),
2160        WatchdogState::AwaitingWork {
2161            attempt_status: WatchdogAttemptStatus::Running,
2162            ..
2163        } => {
2164            let Some(sequence) = control.request_sequence.checked_add(1) else {
2165                control.state = WatchdogState::Inactive;
2166                control.inactive_reason =
2167                    InactiveReason::ControlFailure(TimerControlFailure::RequestSequenceExhausted);
2168                return (
2169                    RegistryTransition::terminal(
2170                        clear_callbacks(identity.clone(), true, false),
2171                        TimerControlFailure::RequestSequenceExhausted,
2172                    ),
2173                    true,
2174                );
2175            };
2176            control.request_sequence = sequence;
2177            if !matches!(control.pending, Some(WatchdogPending::Unregister)) {
2178                control.pending = Some(WatchdogPending::Cancel);
2179            }
2180            (RegistryTransition::normal(RegistryEffect::None), false)
2181        }
2182        WatchdogState::Scheduled { .. }
2183        | WatchdogState::AwaitingWork {
2184            attempt_status: WatchdogAttemptStatus::Dispatched,
2185            ..
2186        } => {
2187            let clear_work = matches!(control.state, WatchdogState::AwaitingWork { .. });
2188            let Some(scheduler_generation) = control.scheduler_generation.checked_add(1) else {
2189                control.state = WatchdogState::Inactive;
2190                control.inactive_reason =
2191                    InactiveReason::ControlFailure(TimerControlFailure::GenerationExhausted);
2192                return (
2193                    RegistryTransition::terminal(
2194                        clear_callbacks(identity.clone(), true, clear_work),
2195                        TimerControlFailure::GenerationExhausted,
2196                    ),
2197                    true,
2198                );
2199            };
2200            let Some(attempt_generation) = control.attempt_generation.checked_add(1) else {
2201                control.state = WatchdogState::Inactive;
2202                control.inactive_reason =
2203                    InactiveReason::ControlFailure(TimerControlFailure::GenerationExhausted);
2204                return (
2205                    RegistryTransition::terminal(
2206                        clear_callbacks(identity.clone(), true, clear_work),
2207                        TimerControlFailure::GenerationExhausted,
2208                    ),
2209                    true,
2210                );
2211            };
2212            control.scheduler_generation = scheduler_generation;
2213            control.attempt_generation = attempt_generation;
2214            control.state = WatchdogState::Inactive;
2215            control.pending = None;
2216            control.inactive_reason = InactiveReason::Cancelled;
2217            (
2218                RegistryTransition::normal(clear_callbacks(identity.clone(), true, clear_work)),
2219                true,
2220            )
2221        }
2222    }
2223}
2224
2225fn remove_after(
2226    entries: &mut BTreeMap<TimerIdentity, Entry>,
2227    identity: &TimerIdentity,
2228    transition: RegistryTransition,
2229    remove: bool,
2230) -> RegistryTransition {
2231    if remove {
2232        entries.remove(identity);
2233    }
2234    transition
2235}
2236
2237#[cfg(test)]
2238mod tests;