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