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