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