1use crate::{
4 platform::{self, TimerHandle},
5 registry::{
6 CallbackAcceptance, CallbackRole, CallbackToken, OrdinaryCallback, ProviderHandle,
7 ProviderHandles, RegisterError, RegistrationClaim, RegistryEffect, RegistryError,
8 RegistryTransition, TimerRegistry, WatchdogCallback,
9 },
10 schedule::{ScheduleError, TimerCadence, TimerDirective, TimerSchedule},
11 snapshot::{
12 DeclarationLifetime, TimerCompletion, TimerControlFailure, TimerEpoch, TimerIdentity,
13 TimerPolicy, TimerRunResult, TimerSnapshot, WatchdogRunResult,
14 },
15};
16use std::{cell::RefCell, future::Future, rc::Rc, time::Duration};
17use thiserror::Error;
18
19thread_local! {
20 static RUNTIME: RefCell<Option<TimerRegistry>> = const { RefCell::new(None) };
21}
22
23#[non_exhaustive]
25#[derive(Debug, Error)]
26pub enum TimerError {
27 #[error("timer runtime is not initialized")]
29 NotInitialized,
30 #[error("timer runtime is already borrowed")]
32 RuntimeBusy,
33 #[error(transparent)]
35 Register(#[from] RegisterError),
36 #[error(transparent)]
38 Schedule(#[from] ScheduleError),
39 #[error("timer registration is no longer authoritative")]
41 RegistrationExpired,
42 #[error("timer operation does not match its registered policy")]
44 WrongPolicy,
45 #[error("timer control failed: {0:?}")]
47 ControlFailure(TimerControlFailure),
48 #[error("timer runtime ownership invariant failed")]
50 OwnershipInvariant,
51 #[error("timer lifecycle reconciliation conflicts with the canonical declaration")]
53 ReconciliationConflict,
54}
55
56impl From<RegistryError> for TimerError {
57 fn from(value: RegistryError) -> Self {
58 match value {
59 RegistryError::UnknownRegistration
60 | RegistryError::StaleRegistration
61 | RegistryError::StaleCallback => Self::RegistrationExpired,
62 RegistryError::WrongPolicy { .. } => Self::WrongPolicy,
63 RegistryError::Schedule(error) => Self::Schedule(error),
64 RegistryError::MissingCallback | RegistryError::ProviderHandleAlreadyOwned => {
65 Self::OwnershipInvariant
66 }
67 }
68 }
69}
70
71pub fn initialize_runtime() -> Result<TimerEpoch, TimerError> {
76 let epoch = TimerEpoch::new(platform::canister_version(), platform::time_ns());
77 RUNTIME.with(|runtime| {
78 let mut runtime = runtime
79 .try_borrow_mut()
80 .map_err(|_| TimerError::RuntimeBusy)?;
81 if let Some(registry) = runtime.as_ref() {
82 return Ok(registry.epoch());
83 }
84 *runtime = Some(TimerRegistry::new(epoch));
85 Ok(epoch)
86 })
87}
88
89pub struct TimerContext {
95 token: CallbackToken,
96}
97
98impl TimerContext {
99 const fn new(token: CallbackToken) -> Self {
100 Self { token }
101 }
102
103 fn claim(&self) -> RegistrationClaim {
104 RegistrationClaim::from_callback(&self.token)
105 }
106
107 #[must_use]
109 pub const fn identity(&self) -> &TimerIdentity {
110 self.token.identity()
111 }
112
113 pub fn ensure_once(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
118 ensure_once_claim(&self.claim(), Some(&self.token), schedule)
119 }
120
121 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
129 reconcile_ordinary_claim(&self.claim(), Some(&self.token), schedule)
130 }
131
132 pub fn ensure_recurring(&self) -> Result<(), TimerError> {
136 ensure_recurring_claim(&self.claim(), Some(&self.token))
137 }
138
139 pub fn cancel(&self) -> Result<(), TimerError> {
146 cancel_claim(&self.claim(), Some(&self.token))
147 }
148}
149
150#[must_use = "retain the registration claim so the timer remains controllable"]
152pub struct OnceRegistration {
153 claim: RegistrationClaim,
154}
155
156impl OnceRegistration {
157 #[must_use]
159 pub const fn identity(&self) -> &TimerIdentity {
160 self.claim.identity()
161 }
162
163 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
169 has_armed_wakeup_claim(&self.claim)
170 }
171
172 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
174 ensure_once_claim(&self.claim, None, schedule)
175 }
176
177 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
184 reconcile_ordinary_claim(&self.claim, None, schedule)
185 }
186
187 pub fn cancel(&self) -> Result<(), TimerError> {
193 cancel_claim(&self.claim, None)
194 }
195
196 pub fn unregister(self) -> Result<(), TimerError> {
201 unregister_claim(&self.claim)
202 }
203}
204
205#[must_use = "retain the registration claim so the timer remains controllable"]
208pub struct AfterCompletionRegistration {
209 claim: RegistrationClaim,
210}
211
212#[must_use = "retain the registration claim so the timer remains controllable"]
214pub struct WatchdogRegistration {
215 claim: RegistrationClaim,
216}
217
218trait RegistrationClaimOwner {
219 fn registration_claim(&self) -> &RegistrationClaim;
220}
221
222impl RegistrationClaimOwner for OnceRegistration {
223 fn registration_claim(&self) -> &RegistrationClaim {
224 &self.claim
225 }
226}
227
228impl RegistrationClaimOwner for AfterCompletionRegistration {
229 fn registration_claim(&self) -> &RegistrationClaim {
230 &self.claim
231 }
232}
233
234impl RegistrationClaimOwner for WatchdogRegistration {
235 fn registration_claim(&self) -> &RegistrationClaim {
236 &self.claim
237 }
238}
239
240#[derive(Clone, Copy, Debug, Eq, PartialEq)]
242pub enum TimerReconcileState {
243 Inactive,
245 Scheduled,
247}
248
249impl WatchdogRegistration {
250 #[must_use]
252 pub const fn identity(&self) -> &TimerIdentity {
253 self.claim.identity()
254 }
255
256 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
263 has_armed_wakeup_claim(&self.claim)
264 }
265
266 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
268 ensure_recurring_claim(&self.claim, None)
269 }
270
271 pub fn cancel(&self) -> Result<(), TimerError> {
278 cancel_claim(&self.claim, None)
279 }
280
281 pub fn unregister(self) -> Result<(), TimerError> {
286 unregister_claim(&self.claim)
287 }
288}
289
290impl AfterCompletionRegistration {
291 #[must_use]
293 pub const fn identity(&self) -> &TimerIdentity {
294 self.claim.identity()
295 }
296
297 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
303 has_armed_wakeup_claim(&self.claim)
304 }
305
306 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
309 ensure_recurring_claim(&self.claim, None)
310 }
311
312 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
317 reconcile_ordinary_claim(&self.claim, None, schedule)
318 }
319
320 pub fn cancel(&self) -> Result<(), TimerError> {
326 cancel_claim(&self.claim, None)
327 }
328
329 pub fn unregister(self) -> Result<(), TimerError> {
334 unregister_claim(&self.claim)
335 }
336}
337
338pub fn register_once<F, Fut>(
340 identity: TimerIdentity,
341 lifetime: DeclarationLifetime,
342 callback: F,
343) -> Result<OnceRegistration, TimerError>
344where
345 F: FnMut(TimerContext) -> Fut + 'static,
346 Fut: Future<Output = TimerRunResult> + 'static,
347{
348 let callback = erase_ordinary_callback(callback);
349 let claim = with_registry_mut(|registry| {
350 registry
351 .register_once_with_callback(identity, lifetime, callback)
352 .map_err(TimerError::from)
353 })?;
354 Ok(OnceRegistration { claim })
355}
356
357pub fn register_after_completion<F, Fut>(
359 identity: TimerIdentity,
360 cadence: TimerCadence,
361 lifetime: DeclarationLifetime,
362 callback: F,
363) -> Result<AfterCompletionRegistration, TimerError>
364where
365 F: FnMut(TimerContext) -> Fut + 'static,
366 Fut: Future<Output = TimerRunResult> + 'static,
367{
368 let callback = erase_ordinary_callback(callback);
369 let claim = with_registry_mut(|registry| {
370 registry
371 .register_after_completion_with_callback(identity, cadence, lifetime, callback)
372 .map_err(TimerError::from)
373 })?;
374 Ok(AfterCompletionRegistration { claim })
375}
376
377pub fn register_watchdog<F>(
383 identity: TimerIdentity,
384 cadence: TimerCadence,
385 lifetime: DeclarationLifetime,
386 callback: F,
387) -> Result<WatchdogRegistration, TimerError>
388where
389 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
390{
391 let callback: WatchdogCallback = Rc::new(RefCell::new(Box::new(callback)));
392 let claim = with_registry_mut(|registry| {
393 registry
394 .register_watchdog_with_callback(identity, cadence, lifetime, callback)
395 .map_err(TimerError::from)
396 })?;
397 Ok(WatchdogRegistration { claim })
398}
399
400pub fn reconcile_once<F, Fut>(
408 registration: &mut Option<OnceRegistration>,
409 identity: &TimerIdentity,
410 desired: Option<TimerSchedule>,
411 callback: F,
412) -> Result<(), TimerError>
413where
414 F: FnMut(TimerContext) -> Fut + 'static,
415 Fut: Future<Output = TimerRunResult> + 'static,
416{
417 let registration = reconcile_registration(registration, identity, TimerPolicy::Once, || {
418 register_once(identity.clone(), DeclarationLifetime::Retained, callback)
419 })?;
420 registration.reconcile_schedule(desired)
421}
422
423pub fn reconcile_after_completion<F, Fut>(
432 registration: &mut Option<AfterCompletionRegistration>,
433 identity: &TimerIdentity,
434 cadence: TimerCadence,
435 desired: TimerReconcileState,
436 callback: F,
437) -> Result<(), TimerError>
438where
439 F: FnMut(TimerContext) -> Fut + 'static,
440 Fut: Future<Output = TimerRunResult> + 'static,
441{
442 let registration = reconcile_registration(
443 registration,
444 identity,
445 TimerPolicy::AfterCompletion { cadence },
446 || {
447 register_after_completion(
448 identity.clone(),
449 cadence,
450 DeclarationLifetime::Retained,
451 callback,
452 )
453 },
454 )?;
455 match desired {
456 TimerReconcileState::Inactive => registration.cancel(),
457 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
458 }
459}
460
461pub fn reconcile_watchdog<F>(
468 registration: &mut Option<WatchdogRegistration>,
469 identity: &TimerIdentity,
470 cadence: TimerCadence,
471 desired: TimerReconcileState,
472 callback: F,
473) -> Result<(), TimerError>
474where
475 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
476{
477 let registration = reconcile_registration(
478 registration,
479 identity,
480 TimerPolicy::Watchdog { cadence },
481 || {
482 register_watchdog(
483 identity.clone(),
484 cadence,
485 DeclarationLifetime::Retained,
486 callback,
487 )
488 },
489 )?;
490 match desired {
491 TimerReconcileState::Inactive => registration.cancel(),
492 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
493 }
494}
495
496fn reconcile_registration<'a, Registration>(
497 registration: &'a mut Option<Registration>,
498 identity: &TimerIdentity,
499 policy: TimerPolicy,
500 register: impl FnOnce() -> Result<Registration, TimerError>,
501) -> Result<&'a Registration, TimerError>
502where
503 Registration: RegistrationClaimOwner,
504{
505 if registration.is_none() {
506 *registration = Some(register()?);
507 }
508 let registration = registration
509 .as_ref()
510 .ok_or(TimerError::ReconciliationConflict)?;
511 verify_declaration(registration.registration_claim(), identity, policy)?;
512 Ok(registration)
513}
514
515fn verify_declaration(
516 claim: &RegistrationClaim,
517 identity: &TimerIdentity,
518 policy: TimerPolicy,
519) -> Result<(), TimerError> {
520 if claim.identity() != identity {
521 return Err(TimerError::ReconciliationConflict);
522 }
523 with_registry(|registry| {
524 registry
525 .declaration_matches(claim, policy, DeclarationLifetime::Retained)
526 .map_err(TimerError::from)?
527 .then_some(())
528 .ok_or(TimerError::ReconciliationConflict)
529 })
530}
531
532pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
534 with_registry(|registry| Ok(registry.snapshot(identity)))
535}
536
537pub fn timer_snapshots() -> Result<Vec<TimerSnapshot>, TimerError> {
539 with_registry(|registry| Ok(registry.snapshots()))
540}
541
542pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
544 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
545}
546
547fn has_armed_wakeup_claim(claim: &RegistrationClaim) -> Result<bool, TimerError> {
548 with_registry(|registry| registry.has_armed_wakeup(claim).map_err(TimerError::from))
549}
550
551fn erase_ordinary_callback<F, Fut>(mut callback: F) -> OrdinaryCallback
552where
553 F: FnMut(TimerContext) -> Fut + 'static,
554 Fut: Future<Output = TimerRunResult> + 'static,
555{
556 Rc::new(RefCell::new(Box::new(move |context| {
557 Box::pin(callback(context))
558 })))
559}
560
561fn apply_claim_transition(
562 claim: &RegistrationClaim,
563 context: Option<&CallbackToken>,
564 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
565) -> Result<(), TimerError> {
566 let transition = with_registry_mut(|registry| {
567 validate_context(registry, context)?;
568 operation(registry).map_err(TimerError::from)
569 })?;
570 finish_claim_transition(claim, transition, ProviderHandles::default())
571}
572
573fn ensure_once_claim(
574 claim: &RegistrationClaim,
575 context: Option<&CallbackToken>,
576 schedule: TimerSchedule,
577) -> Result<(), TimerError> {
578 apply_claim_transition(claim, context, |registry| {
579 registry.ensure_once(claim, platform::time_ns(), schedule)
580 })
581}
582
583fn reconcile_ordinary_claim(
584 claim: &RegistrationClaim,
585 context: Option<&CallbackToken>,
586 schedule: Option<TimerSchedule>,
587) -> Result<(), TimerError> {
588 if schedule.is_none() {
589 let (handles, transition) = with_registry_mut(|registry| {
590 validate_context(registry, context)?;
591 registry
592 .validate_ordinary_claim(claim)
593 .map_err(TimerError::from)?;
594 let handles = registry
595 .take_provider_handles_for_claim(claim)
596 .map_err(TimerError::from)?;
597 let transition = registry
598 .reconcile_ordinary(claim, platform::time_ns(), None)
599 .map_err(TimerError::from);
600 Ok((handles, transition))
601 })?;
602 return finish_detached_claim_transition(claim, handles, transition);
603 }
604 apply_claim_transition(claim, context, |registry| {
605 registry.reconcile_ordinary(claim, platform::time_ns(), schedule)
606 })
607}
608
609fn ensure_recurring_claim(
610 claim: &RegistrationClaim,
611 context: Option<&CallbackToken>,
612) -> Result<(), TimerError> {
613 apply_claim_transition(claim, context, |registry| {
614 registry.ensure_recurring(claim, platform::time_ns())
615 })
616}
617
618fn cancel_claim(
619 claim: &RegistrationClaim,
620 context: Option<&CallbackToken>,
621) -> Result<(), TimerError> {
622 apply_detached_claim_transition(claim, context, |registry| registry.cancel(claim))
623}
624
625fn validate_context(
626 registry: &TimerRegistry,
627 context: Option<&CallbackToken>,
628) -> Result<(), TimerError> {
629 context.map_or(Ok(()), |token| {
630 registry
631 .validate_running_context(token)
632 .map_err(TimerError::from)
633 })
634}
635
636fn unregister_claim(claim: &RegistrationClaim) -> Result<(), TimerError> {
637 apply_detached_claim_transition(claim, None, |registry| registry.unregister(claim))
638}
639
640fn apply_detached_claim_transition(
641 claim: &RegistrationClaim,
642 context: Option<&CallbackToken>,
643 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
644) -> Result<(), TimerError> {
645 let (handles, transition) = with_registry_mut(|registry| {
646 validate_context(registry, context)?;
647 let handles = registry
648 .take_provider_handles_for_claim(claim)
649 .map_err(TimerError::from)?;
650 let transition = operation(registry).map_err(TimerError::from);
651 Ok((handles, transition))
652 })?;
653 finish_detached_claim_transition(claim, handles, transition)
654}
655
656fn finish_detached_claim_transition(
657 claim: &RegistrationClaim,
658 handles: ProviderHandles,
659 transition: Result<RegistryTransition, TimerError>,
660) -> Result<(), TimerError> {
661 match transition {
662 Ok(transition) => finish_claim_transition(claim, transition, handles),
663 Err(error) => match restore_provider_handles(handles) {
664 Ok(()) => Err(error),
665 Err(restoration_error) => retire_failed_claim(claim, restoration_error),
666 },
667 }
668}
669
670fn finish_claim_transition(
671 claim: &RegistrationClaim,
672 transition: RegistryTransition,
673 handles: ProviderHandles,
674) -> Result<(), TimerError> {
675 match finish_transition(transition, handles) {
676 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
677 Err(error) => retire_failed_claim(claim, error),
678 }
679}
680
681fn retire_failed_claim(claim: &RegistrationClaim, error: TimerError) -> Result<(), TimerError> {
682 match fail_claim_provider_binding(claim) {
683 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
684 Err(cleanup_error) => Err(cleanup_error),
685 }
686}
687
688fn finish_transition(
689 transition: RegistryTransition,
690 handles: ProviderHandles,
691) -> Result<(), TimerError> {
692 let failure = transition.failure();
693 let effect = transition.into_effect();
694 apply_effect(&effect, handles)?;
695 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
696}
697
698fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
699 if !effect.has_valid_shape() {
700 clear_provider_handles(handles);
701 return Err(TimerError::OwnershipInvariant);
702 }
703 match effect {
704 RegistryEffect::None => restore_provider_handles(handles),
705 RegistryEffect::ArmWakeup { token, arm, .. } => {
706 if arm.replaces_existing() {
707 let replaced = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
708 registry.take_wakeup_handle(token.identity())
709 })?;
710 if let Some(replaced) = replaced {
711 clear_provider_handle(replaced);
712 }
713 }
714 restore_provider_handles(handles)?;
715 arm_wakeup(effect)
716 }
717 RegistryEffect::ClearCallbacks {
718 identity,
719 handles: selected,
720 } => {
721 if selected.includes_wakeup() {
722 let wakeup = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
723 registry.take_wakeup_handle(identity)
724 })?;
725 if let Some(wakeup) = wakeup {
726 clear_provider_handle(wakeup);
727 }
728 }
729 if selected.includes_work() {
730 let work = take_detached_or_owned_handle(handles.take_work(), |registry| {
731 registry.take_work_handle(identity)
732 })?;
733 if let Some(work) = work {
734 clear_provider_handle(work);
735 }
736 }
737 restore_provider_handles(handles)
738 }
739 RegistryEffect::DispatchWatchdog { successor, .. } => {
740 if let Some(wakeup) = handles.take_wakeup() {
741 clear_provider_handle(wakeup);
742 }
743 let replaced_work = take_detached_or_owned_handle(handles.take_work(), |registry| {
744 registry.take_work_handle(successor.identity())
745 })?;
746 if let Some(replaced_work) = replaced_work {
747 clear_provider_handle(replaced_work);
748 }
749 dispatch_watchdog_effect(effect)
750 }
751 }
752}
753
754fn take_detached_or_owned_handle(
755 detached: Option<ProviderHandle>,
756 take_owned: impl FnOnce(&mut TimerRegistry) -> Option<ProviderHandle>,
757) -> Result<Option<ProviderHandle>, TimerError> {
758 detached.map_or_else(
759 || with_registry_mut(|registry| Ok(take_owned(registry))),
760 |handle| Ok(Some(handle)),
761 )
762}
763
764fn arm_wakeup(effect: &RegistryEffect) -> Result<(), TimerError> {
765 let RegistryEffect::ArmWakeup {
766 token, delay_ns, ..
767 } = effect
768 else {
769 return Err(TimerError::OwnershipInvariant);
770 };
771 let task_token = token.clone();
772 let handle = platform::set_timer(Duration::from_nanos(*delay_ns), async move {
773 dispatch_wakeup(task_token).await;
774 });
775 if let Err((error, handle)) = install_provider_handle(token, handle) {
776 platform::clear_timer(handle);
777 return Err(error);
778 }
779 if let Err(error) = confirm_effect(effect) {
780 let handle = with_registry_mut(|registry| {
781 registry
782 .take_wakeup_handle(token.identity())
783 .ok_or(TimerError::OwnershipInvariant)
784 })?;
785 clear_provider_handle(handle);
786 return Err(error);
787 }
788 Ok(())
789}
790
791fn dispatch_watchdog_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
792 let RegistryEffect::DispatchWatchdog {
793 successor,
794 successor_delay_ns,
795 work,
796 ..
797 } = effect
798 else {
799 return Err(TimerError::OwnershipInvariant);
800 };
801 let successor_token = successor.clone();
802 let successor_handle =
803 platform::set_timer(Duration::from_nanos(*successor_delay_ns), async move {
804 dispatch_watchdog_scheduler(&successor_token);
805 });
806 if let Err((error, handle)) = install_provider_handle(successor, successor_handle) {
807 platform::clear_timer(handle);
808 return Err(error);
809 }
810
811 let work_token = work.clone();
812 let work_handle = platform::set_timer(Duration::ZERO, async move {
813 dispatch_watchdog_work(&work_token);
814 });
815 if let Err((error, handle)) = install_provider_handle(work, work_handle) {
816 platform::clear_timer(handle);
817 clear_entry_provider_handles(successor.identity())?;
818 return Err(error);
819 }
820 if let Err(error) = confirm_effect(effect) {
821 clear_entry_provider_handles(successor.identity())?;
822 return Err(error);
823 }
824 Ok(())
825}
826
827fn install_provider_handle(
828 token: &CallbackToken,
829 handle: TimerHandle,
830) -> Result<(), (TimerError, TimerHandle)> {
831 #[cfg(test)]
832 if take_provider_install_fault() {
833 return Err((TimerError::OwnershipInvariant, handle));
834 }
835 RUNTIME.with(|runtime| {
836 let Ok(mut runtime) = runtime.try_borrow_mut() else {
837 return Err((TimerError::RuntimeBusy, handle));
838 };
839 let Some(registry) = runtime.as_mut() else {
840 return Err((TimerError::NotInitialized, handle));
841 };
842 match registry.install_provider_handle(token, handle) {
843 Ok(()) => Ok(()),
844 Err((error, handle)) => Err((TimerError::from(error), handle)),
845 }
846 })
847}
848
849fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
850 #[cfg(test)]
851 if take_provider_confirmation_fault() {
852 return Err(TimerError::OwnershipInvariant);
853 }
854 with_registry_mut(|registry| {
855 registry
856 .confirm_effect_applied(effect)
857 .map_err(TimerError::from)
858 })
859}
860
861fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
862 let wakeup_failure = handles
865 .take_wakeup()
866 .and_then(|handle| restore_provider_handle(handle).err());
867 let work_failure = handles
868 .take_work()
869 .and_then(|handle| restore_provider_handle(handle).err());
870 wakeup_failure.or(work_failure).map_or(Ok(()), Err)
871}
872
873fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
874 let (token, handle) = handle.into_parts();
875 match install_provider_handle(&token, handle) {
876 Ok(()) => Ok(()),
877 Err((error, handle)) => {
878 platform::clear_timer(handle);
879 Err(error)
880 }
881 }
882}
883
884fn clear_provider_handle(handle: ProviderHandle) {
885 let (_, handle) = handle.into_parts();
886 platform::clear_timer(handle);
887}
888
889fn clear_provider_handles(mut handles: ProviderHandles) {
890 if let Some(wakeup) = handles.take_wakeup() {
891 clear_provider_handle(wakeup);
892 }
893 if let Some(work) = handles.take_work() {
894 clear_provider_handle(work);
895 }
896}
897
898fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
899 let handles = with_registry_mut(|registry| {
900 Ok(ProviderHandles::from_parts(
901 registry.take_wakeup_handle(identity),
902 registry.take_work_handle(identity),
903 ))
904 })?;
905 clear_provider_handles(handles);
906 Ok(())
907}
908
909#[allow(clippy::future_not_send)] async fn dispatch_wakeup(token: CallbackToken) {
911 match token.role() {
912 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
913 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
914 CallbackRole::WatchdogWork => {}
915 }
916}
917
918#[allow(clippy::future_not_send)] async fn dispatch_ordinary(token: CallbackToken) {
920 let instructions_before = platform::instruction_counter();
921 let accepted = with_registry_mut(|registry| {
922 registry.consume_provider_handle(&token);
923 Ok(registry.begin_ordinary(&token))
924 });
925 match accepted {
926 Ok(CallbackAcceptance::Accepted) => {}
927 Ok(CallbackAcceptance::Stale) => return,
928 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
929 }
930
931 let callback = match with_registry(|registry| {
932 registry.ordinary_callback(&token).map_err(TimerError::from)
933 }) {
934 Ok(callback) => callback,
935 Err(TimerError::OwnershipInvariant) => {
936 fail_ordinary_dispatch(&token);
937 return;
938 }
939 Err(error) => trap_callback_failure("ordinary callback lookup", &error),
940 };
941 let context = TimerContext::new(token.clone());
942 let future = {
943 let Ok(mut callback) = callback.try_borrow_mut() else {
944 fail_ordinary_dispatch(&token);
945 return;
946 };
947 callback(context)
948 };
949 let result = future.await;
950 let transition = with_registry_mut(|registry| {
951 registry
952 .complete_ordinary(&token, platform::time_ns(), result)
953 .map_err(TimerError::from)
954 });
955 let transition = transition
956 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
957 finish_callback_transition(&token, transition, ProviderHandles::default());
958 record_work_instructions(&token, instructions_before);
959}
960
961fn fail_ordinary_dispatch(token: &CallbackToken) {
962 let transition = with_registry_mut(|registry| {
963 registry
964 .complete_ordinary(
965 token,
966 platform::time_ns(),
967 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
968 )
969 .map_err(TimerError::from)
970 });
971 let transition = transition.unwrap_or_else(|error| {
972 trap_callback_failure("ordinary invariant-failure completion", &error)
973 });
974 finish_callback_transition(token, transition, ProviderHandles::default());
975}
976
977fn dispatch_watchdog_scheduler(token: &CallbackToken) {
978 let instructions_before = platform::instruction_counter();
979 let transition = with_registry_mut(|registry| {
980 registry.consume_provider_handle(token);
981 Ok(registry.begin_watchdog_scheduler(token, platform::time_ns()))
982 });
983 let transition = transition
984 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
985 let accepted = !matches!(transition.effect(), RegistryEffect::None);
986 finish_callback_transition(token, transition, ProviderHandles::default());
987 if accepted {
988 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
989 with_registry_mut(|registry| {
990 registry.record_scheduler_instructions(token, instructions);
991 Ok(())
992 })
993 .unwrap_or_else(|error| {
994 trap_callback_failure("watchdog scheduler instruction accounting", &error)
995 });
996 }
997}
998
999fn dispatch_watchdog_work(token: &CallbackToken) {
1000 let instructions_before = platform::instruction_counter();
1001 let accepted = with_registry_mut(|registry| {
1002 registry.consume_provider_handle(token);
1003 Ok(registry.begin_watchdog_work(token))
1004 });
1005 match accepted {
1006 Ok(CallbackAcceptance::Accepted) => {}
1007 Ok(CallbackAcceptance::Stale) => return,
1008 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
1009 }
1010
1011 let callback =
1012 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
1013 {
1014 Ok(callback) => callback,
1015 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
1016 };
1017 let context = TimerContext::new(token.clone());
1018 let result = {
1019 let Ok(mut callback) = callback.try_borrow_mut() else {
1020 trap_callback_failure(
1021 "watchdog callback ownership",
1022 &TimerError::OwnershipInvariant,
1023 );
1024 };
1025 callback(context)
1026 };
1027 finish_watchdog_dispatch(token, result);
1028 record_work_instructions(token, instructions_before);
1029}
1030
1031fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
1032 let claim = RegistrationClaim::from_callback(token);
1033 let completed = with_registry_mut(|registry| {
1038 let handles = registry
1039 .take_provider_handles_for_claim(&claim)
1040 .map_err(TimerError::from)?;
1041 #[cfg(test)]
1042 {
1043 if take_watchdog_completion_fault() {
1044 return Err(TimerError::OwnershipInvariant);
1045 }
1046 }
1047 let transition = registry
1048 .complete_watchdog_work(token, platform::time_ns(), result)
1049 .map_err(TimerError::from)?;
1050 Ok((transition, handles))
1051 });
1052 let (transition, handles) =
1053 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
1054 finish_callback_transition(token, transition, handles);
1055}
1056
1057fn finish_callback_transition(
1058 token: &CallbackToken,
1059 transition: RegistryTransition,
1060 handles: ProviderHandles,
1061) {
1062 match finish_transition(transition, handles) {
1063 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
1064 Err(
1065 error @ (TimerError::NotInitialized
1066 | TimerError::RuntimeBusy
1067 | TimerError::Register(_)
1068 | TimerError::Schedule(_)
1069 | TimerError::RegistrationExpired
1070 | TimerError::WrongPolicy
1071 | TimerError::OwnershipInvariant
1072 | TimerError::ReconciliationConflict),
1073 ) => {
1074 if token.role() == CallbackRole::WatchdogWork {
1075 trap_callback_failure("watchdog provider-handle completion", &error);
1076 }
1077 fail_provider_binding(token).unwrap_or_else(|binding_error| {
1078 trap_callback_failure("provider-binding failure cleanup", &binding_error)
1079 });
1080 }
1081 }
1082}
1083
1084fn fail_provider_binding(token: &CallbackToken) -> Result<(), TimerError> {
1085 let claim = RegistrationClaim::from_callback(token);
1086 fail_claim_provider_binding(&claim)
1087}
1088
1089fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
1090 let failed = with_registry_mut(|registry| {
1091 registry
1092 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
1093 .map_err(TimerError::from)
1094 });
1095 clear_provider_handles(failed?);
1096 Ok(())
1097}
1098
1099fn record_work_instructions(token: &CallbackToken, instructions_before: u64) {
1100 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
1101 with_registry_mut(|registry| {
1102 registry.record_work_instructions(token, instructions);
1103 Ok(())
1104 })
1105 .unwrap_or_else(|error| trap_callback_failure("work instruction accounting", &error));
1106}
1107
1108fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
1109 platform::trap(&format!("ic-timers {context} failed: {error}"))
1110}
1111
1112fn with_registry<T>(
1113 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
1114) -> Result<T, TimerError> {
1115 RUNTIME.with(|runtime| {
1116 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1117 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1118 operation(registry)
1119 })
1120}
1121
1122fn with_registry_mut<T>(
1123 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1124) -> Result<T, TimerError> {
1125 RUNTIME.with(|runtime| {
1126 let mut runtime = runtime
1127 .try_borrow_mut()
1128 .map_err(|_| TimerError::RuntimeBusy)?;
1129 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1130 operation(registry)
1131 })
1132}
1133
1134#[cfg(test)]
1135fn reset_for_test(now_ns: u64, canister_version: u64) {
1136 platform::reset(now_ns, canister_version);
1137 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1138 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(None));
1139 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(false));
1140 RUNTIME.with(|runtime| {
1141 *runtime.borrow_mut() = None;
1142 });
1143}
1144
1145#[cfg(test)]
1146thread_local! {
1147 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1148 static PROVIDER_INSTALL_FAULT_AFTER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1149 static PROVIDER_CONFIRMATION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1150}
1151
1152#[cfg(test)]
1153fn inject_watchdog_completion_fault() {
1154 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1155}
1156
1157#[cfg(test)]
1158fn take_watchdog_completion_fault() -> bool {
1159 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1160}
1161
1162#[cfg(test)]
1163fn inject_provider_install_fault() {
1164 inject_provider_install_fault_after(0);
1165}
1166
1167#[cfg(test)]
1168fn inject_provider_install_fault_after(successful_installs: u64) {
1169 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(Some(successful_installs)));
1170}
1171
1172#[cfg(test)]
1173fn take_provider_install_fault() -> bool {
1174 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| match fault.get() {
1175 Some(0) => {
1176 fault.set(None);
1177 true
1178 }
1179 Some(remaining) => {
1180 fault.set(Some(remaining - 1));
1181 false
1182 }
1183 None => false,
1184 })
1185}
1186
1187#[cfg(test)]
1188fn inject_provider_confirmation_fault() {
1189 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(true));
1190}
1191
1192#[cfg(test)]
1193fn take_provider_confirmation_fault() -> bool {
1194 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.replace(false))
1195}
1196
1197#[cfg(test)]
1198mod tests;