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, MemoryPageExtent, TimerCompletion, TimerControlFailure, TimerEpoch,
13 TimerIdentity, TimerInventorySnapshot, TimerPolicy, TimerRunResult, TimerSnapshot,
14 WatchdogRunResult,
15 },
16};
17use std::{cell::RefCell, future::Future, rc::Rc, time::Duration};
18use thiserror::Error;
19
20thread_local! {
21 static RUNTIME: RefCell<Option<TimerRegistry>> = const { RefCell::new(None) };
22}
23
24#[non_exhaustive]
26#[derive(Debug, Error)]
27pub enum TimerError {
28 #[error("timer runtime is not initialized")]
30 NotInitialized,
31 #[error("timer runtime is already borrowed")]
33 RuntimeBusy,
34 #[error(transparent)]
36 Register(#[from] RegisterError),
37 #[error(transparent)]
39 Schedule(#[from] ScheduleError),
40 #[error("timer registration is no longer authoritative")]
42 RegistrationExpired,
43 #[error("timer control failed: {0:?}")]
45 ControlFailure(TimerControlFailure),
46 #[error("timer runtime ownership invariant failed")]
48 OwnershipInvariant,
49 #[error("timer lifecycle reconciliation conflicts with the canonical declaration")]
51 ReconciliationConflict,
52}
53
54impl From<RegistryError> for TimerError {
55 fn from(value: RegistryError) -> Self {
56 match value {
57 RegistryError::UnknownRegistration
58 | RegistryError::StaleRegistration
59 | RegistryError::StaleCallback => Self::RegistrationExpired,
60 RegistryError::PolicyMismatch { .. } | RegistryError::ProviderHandleAlreadyOwned => {
61 Self::OwnershipInvariant
62 }
63 RegistryError::Schedule(error) => Self::Schedule(error),
64 }
65 }
66}
67
68pub fn initialize_runtime() -> Result<TimerEpoch, TimerError> {
73 RUNTIME.with(|runtime| {
74 let mut runtime = runtime
75 .try_borrow_mut()
76 .map_err(|_| TimerError::RuntimeBusy)?;
77 let registry = runtime.get_or_insert_with(|| {
78 TimerRegistry::new(TimerEpoch::new(
79 platform::canister_version(),
80 platform::time_ns(),
81 ))
82 });
83 Ok(registry.epoch())
84 })
85}
86
87struct CallbackContext {
88 token: CallbackToken,
89}
90
91impl CallbackContext {
92 const fn new(token: CallbackToken) -> Self {
93 Self { token }
94 }
95
96 const fn identity(&self) -> &TimerIdentity {
97 self.token.identity()
98 }
99
100 fn cancel(&self) -> Result<(), TimerError> {
101 cancel_claim(self.token.claim(), Some(&self.token))
102 }
103
104 fn schedule_once(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
105 ensure_once_claim(self.token.claim(), Some(&self.token), schedule)
106 }
107
108 fn schedule_recurring(&self) -> Result<(), TimerError> {
109 ensure_recurring_claim(self.token.claim(), Some(&self.token))
110 }
111
112 fn schedule_watchdog_immediately(&self) -> Result<(), TimerError> {
113 ensure_watchdog_immediately_claim(self.token.claim(), Some(&self.token))
114 }
115
116 fn reconcile_watchdog(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
117 reconcile_watchdog_claim(self.token.claim(), Some(&self.token), schedule)
118 }
119
120 fn reconcile_ordinary(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
121 reconcile_ordinary_claim(self.token.claim(), Some(&self.token), schedule)
122 }
123}
124
125pub struct OnceContext {
131 inner: CallbackContext,
132}
133
134impl OnceContext {
135 const fn new(token: CallbackToken) -> Self {
136 Self {
137 inner: CallbackContext::new(token),
138 }
139 }
140
141 #[must_use]
143 pub const fn identity(&self) -> &TimerIdentity {
144 self.inner.identity()
145 }
146
147 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
152 self.inner.schedule_once(schedule)
153 }
154
155 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
165 self.inner.reconcile_ordinary(schedule)
166 }
167
168 pub fn cancel(&self) -> Result<(), TimerError> {
175 self.inner.cancel()
176 }
177}
178
179pub struct AfterCompletionContext {
185 inner: CallbackContext,
186}
187
188impl AfterCompletionContext {
189 const fn new(token: CallbackToken) -> Self {
190 Self {
191 inner: CallbackContext::new(token),
192 }
193 }
194
195 #[must_use]
197 pub const fn identity(&self) -> &TimerIdentity {
198 self.inner.identity()
199 }
200
201 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
203 self.inner.schedule_recurring()
204 }
205
206 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
217 self.inner.reconcile_ordinary(schedule)
218 }
219
220 pub fn cancel(&self) -> Result<(), TimerError> {
227 self.inner.cancel()
228 }
229}
230
231pub struct WatchdogContext {
236 inner: CallbackContext,
237}
238
239impl WatchdogContext {
240 const fn new(token: CallbackToken) -> Self {
241 Self {
242 inner: CallbackContext::new(token),
243 }
244 }
245
246 #[must_use]
248 pub const fn identity(&self) -> &TimerIdentity {
249 self.inner.identity()
250 }
251
252 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
258 self.inner.schedule_recurring()
259 }
260
261 pub fn ensure_scheduled_immediately(&self) -> Result<(), TimerError> {
268 self.inner.schedule_watchdog_immediately()
269 }
270
271 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
277 self.inner.reconcile_watchdog(schedule)
278 }
279
280 pub fn cancel(&self) -> Result<(), TimerError> {
287 self.inner.cancel()
288 }
289}
290
291#[must_use = "retain the registration claim so the timer remains controllable"]
293pub struct OnceRegistration {
294 claim: RegistrationClaim,
295}
296
297impl OnceRegistration {
298 #[must_use]
300 pub const fn identity(&self) -> &TimerIdentity {
301 self.claim.identity()
302 }
303
304 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
310 has_armed_wakeup_claim(&self.claim)
311 }
312
313 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
315 ensure_once_claim(&self.claim, None, schedule)
316 }
317
318 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
328 reconcile_ordinary_claim(&self.claim, None, schedule)
329 }
330
331 pub fn cancel(&self) -> Result<(), TimerError> {
337 cancel_claim(&self.claim, None)
338 }
339
340 pub fn unregister(self) -> Result<(), TimerError> {
345 unregister_claim(&self.claim)
346 }
347}
348
349#[must_use = "retain the registration claim so the timer remains controllable"]
352pub struct AfterCompletionRegistration {
353 claim: RegistrationClaim,
354}
355
356#[must_use = "retain the registration claim so the timer remains controllable"]
358pub struct WatchdogRegistration {
359 claim: RegistrationClaim,
360}
361
362trait RegistrationClaimOwner {
363 fn registration_claim(&self) -> &RegistrationClaim;
364}
365
366impl RegistrationClaimOwner for OnceRegistration {
367 fn registration_claim(&self) -> &RegistrationClaim {
368 &self.claim
369 }
370}
371
372impl RegistrationClaimOwner for AfterCompletionRegistration {
373 fn registration_claim(&self) -> &RegistrationClaim {
374 &self.claim
375 }
376}
377
378impl RegistrationClaimOwner for WatchdogRegistration {
379 fn registration_claim(&self) -> &RegistrationClaim {
380 &self.claim
381 }
382}
383
384#[derive(Clone, Copy, Debug, Eq, PartialEq)]
386pub enum TimerReconcileState {
387 Inactive,
389 Scheduled,
391}
392
393#[derive(Clone, Copy, Debug, Eq, PartialEq)]
395pub enum WatchdogReconcileState {
396 Inactive,
398 Scheduled,
400 ScheduledImmediately,
405 ScheduledAt(u64),
407}
408
409impl WatchdogRegistration {
410 #[must_use]
412 pub const fn identity(&self) -> &TimerIdentity {
413 self.claim.identity()
414 }
415
416 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
423 has_armed_wakeup_claim(&self.claim)
424 }
425
426 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
430 ensure_recurring_claim(&self.claim, None)
431 }
432
433 pub fn ensure_scheduled_immediately(&self) -> Result<(), TimerError> {
441 ensure_watchdog_immediately_claim(&self.claim, None)
442 }
443
444 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
452 reconcile_watchdog_claim(&self.claim, None, schedule)
453 }
454
455 pub fn cancel(&self) -> Result<(), TimerError> {
462 cancel_claim(&self.claim, None)
463 }
464
465 pub fn unregister(self) -> Result<(), TimerError> {
470 unregister_claim(&self.claim)
471 }
472}
473
474impl AfterCompletionRegistration {
475 #[must_use]
477 pub const fn identity(&self) -> &TimerIdentity {
478 self.claim.identity()
479 }
480
481 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
487 has_armed_wakeup_claim(&self.claim)
488 }
489
490 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
493 ensure_recurring_claim(&self.claim, None)
494 }
495
496 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
504 reconcile_ordinary_claim(&self.claim, None, schedule)
505 }
506
507 pub fn cancel(&self) -> Result<(), TimerError> {
513 cancel_claim(&self.claim, None)
514 }
515
516 pub fn unregister(self) -> Result<(), TimerError> {
521 unregister_claim(&self.claim)
522 }
523}
524
525pub fn register_once<F, Fut>(
527 identity: TimerIdentity,
528 lifetime: DeclarationLifetime,
529 callback: F,
530) -> Result<OnceRegistration, TimerError>
531where
532 F: FnMut(OnceContext) -> Fut + 'static,
533 Fut: Future<Output = TimerRunResult> + 'static,
534{
535 let callback = erase_ordinary_callback(callback, OnceContext::new);
536 let claim = with_registry_mut(|registry| {
537 registry
538 .register_once_with_callback(identity, lifetime, callback)
539 .map_err(TimerError::from)
540 })?;
541 Ok(OnceRegistration { claim })
542}
543
544pub fn register_after_completion<F, Fut>(
546 identity: TimerIdentity,
547 cadence: TimerCadence,
548 lifetime: DeclarationLifetime,
549 callback: F,
550) -> Result<AfterCompletionRegistration, TimerError>
551where
552 F: FnMut(AfterCompletionContext) -> Fut + 'static,
553 Fut: Future<Output = TimerRunResult> + 'static,
554{
555 let callback = erase_ordinary_callback(callback, AfterCompletionContext::new);
556 let claim = with_registry_mut(|registry| {
557 registry
558 .register_after_completion_with_callback(identity, cadence, lifetime, callback)
559 .map_err(TimerError::from)
560 })?;
561 Ok(AfterCompletionRegistration { claim })
562}
563
564pub fn register_watchdog<F>(
571 identity: TimerIdentity,
572 cadence: TimerCadence,
573 lifetime: DeclarationLifetime,
574 callback: F,
575) -> Result<WatchdogRegistration, TimerError>
576where
577 F: FnMut(WatchdogContext) -> WatchdogRunResult + 'static,
578{
579 let mut callback = callback;
580 let callback: WatchdogCallback = Rc::new(RefCell::new(Box::new(move |token| {
581 callback(WatchdogContext::new(token))
582 })));
583 let claim = with_registry_mut(|registry| {
584 registry
585 .register_watchdog_with_callback(identity, cadence, lifetime, callback)
586 .map_err(TimerError::from)
587 })?;
588 Ok(WatchdogRegistration { claim })
589}
590
591pub fn reconcile_once<F, Fut>(
599 registration: &mut Option<OnceRegistration>,
600 identity: &TimerIdentity,
601 desired: Option<TimerSchedule>,
602 callback: F,
603) -> Result<(), TimerError>
604where
605 F: FnMut(OnceContext) -> Fut + 'static,
606 Fut: Future<Output = TimerRunResult> + 'static,
607{
608 let registration = reconcile_registration(registration, identity, TimerPolicy::Once, || {
609 register_once(identity.clone(), DeclarationLifetime::Retained, callback)
610 })?;
611 registration.reconcile_schedule(desired)
612}
613
614pub fn reconcile_after_completion<F, Fut>(
623 registration: &mut Option<AfterCompletionRegistration>,
624 identity: &TimerIdentity,
625 cadence: TimerCadence,
626 desired: TimerReconcileState,
627 callback: F,
628) -> Result<(), TimerError>
629where
630 F: FnMut(AfterCompletionContext) -> Fut + 'static,
631 Fut: Future<Output = TimerRunResult> + 'static,
632{
633 let registration = reconcile_registration(
634 registration,
635 identity,
636 TimerPolicy::AfterCompletion { cadence },
637 || {
638 register_after_completion(
639 identity.clone(),
640 cadence,
641 DeclarationLifetime::Retained,
642 callback,
643 )
644 },
645 )?;
646 match desired {
647 TimerReconcileState::Inactive => registration.cancel(),
648 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
649 }
650}
651
652pub fn reconcile_watchdog<F>(
662 registration: &mut Option<WatchdogRegistration>,
663 identity: &TimerIdentity,
664 cadence: TimerCadence,
665 desired: WatchdogReconcileState,
666 callback: F,
667) -> Result<(), TimerError>
668where
669 F: FnMut(WatchdogContext) -> WatchdogRunResult + 'static,
670{
671 let registration = reconcile_registration(
672 registration,
673 identity,
674 TimerPolicy::Watchdog { cadence },
675 || {
676 register_watchdog(
677 identity.clone(),
678 cadence,
679 DeclarationLifetime::Retained,
680 callback,
681 )
682 },
683 )?;
684 match desired {
685 WatchdogReconcileState::Inactive => registration.cancel(),
686 WatchdogReconcileState::Scheduled => registration.ensure_scheduled(),
687 WatchdogReconcileState::ScheduledImmediately => registration.ensure_scheduled_immediately(),
688 WatchdogReconcileState::ScheduledAt(deadline_ns) => {
689 registration.reconcile_schedule(Some(TimerSchedule::At(deadline_ns)))
690 }
691 }
692}
693
694fn reconcile_registration<'a, Registration>(
695 registration: &'a mut Option<Registration>,
696 identity: &TimerIdentity,
697 policy: TimerPolicy,
698 register: impl FnOnce() -> Result<Registration, TimerError>,
699) -> Result<&'a Registration, TimerError>
700where
701 Registration: RegistrationClaimOwner,
702{
703 if registration.is_none() {
704 *registration = Some(register()?);
705 }
706 let registration = registration
707 .as_ref()
708 .ok_or(TimerError::ReconciliationConflict)?;
709 let claim = registration.registration_claim();
710 if claim.identity() != identity {
711 return Err(TimerError::ReconciliationConflict);
712 }
713 with_registry(|registry| {
714 registry
715 .declaration_matches(claim, policy, DeclarationLifetime::Retained)
716 .map_err(TimerError::from)?
717 .then_some(())
718 .ok_or(TimerError::ReconciliationConflict)
719 })?;
720 Ok(registration)
721}
722
723pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
725 with_registry(|registry| Ok(registry.snapshot(identity)))
726}
727
728pub fn timer_inventory() -> Result<TimerInventorySnapshot, TimerError> {
733 with_registry(|registry| Ok(registry.inventory()))
734}
735
736pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
738 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
739}
740
741fn has_armed_wakeup_claim(claim: &RegistrationClaim) -> Result<bool, TimerError> {
742 with_registry(|registry| registry.has_armed_wakeup(claim).map_err(TimerError::from))
743}
744
745fn erase_ordinary_callback<Context: 'static, F, Fut>(
746 mut callback: F,
747 context: fn(CallbackToken) -> Context,
748) -> OrdinaryCallback
749where
750 F: FnMut(Context) -> Fut + 'static,
751 Fut: Future<Output = TimerRunResult> + 'static,
752{
753 Rc::new(RefCell::new(Box::new(move |token| {
754 Box::pin(callback(context(token)))
755 })))
756}
757
758fn apply_claim_transition(
759 claim: &RegistrationClaim,
760 context: Option<&CallbackToken>,
761 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
762) -> Result<(), TimerError> {
763 let (handles, transition) = with_registry_mut(|registry| {
764 if let Some(token) = context {
765 registry
766 .validate_running_context(token)
767 .map_err(TimerError::from)?;
768 }
769 let handles = registry
772 .take_provider_handles_for_claim(claim)
773 .map_err(TimerError::from)?;
774 let transition = operation(registry).map_err(TimerError::from);
775 Ok((handles, transition))
776 })?;
777 finish_detached_claim_transition(claim, handles, transition)
778}
779
780fn ensure_once_claim(
781 claim: &RegistrationClaim,
782 context: Option<&CallbackToken>,
783 schedule: TimerSchedule,
784) -> Result<(), TimerError> {
785 apply_claim_transition(claim, context, |registry| {
786 registry.ensure_once(claim, platform::time_ns(), schedule)
787 })
788}
789
790fn reconcile_ordinary_claim(
791 claim: &RegistrationClaim,
792 context: Option<&CallbackToken>,
793 schedule: Option<TimerSchedule>,
794) -> Result<(), TimerError> {
795 apply_claim_transition(claim, context, |registry| {
796 registry.reconcile_ordinary(claim, platform::time_ns(), schedule)
797 })
798}
799
800fn ensure_recurring_claim(
801 claim: &RegistrationClaim,
802 context: Option<&CallbackToken>,
803) -> Result<(), TimerError> {
804 apply_claim_transition(claim, context, |registry| {
805 registry.ensure_recurring(claim, platform::time_ns())
806 })
807}
808
809fn ensure_watchdog_immediately_claim(
810 claim: &RegistrationClaim,
811 context: Option<&CallbackToken>,
812) -> Result<(), TimerError> {
813 apply_claim_transition(claim, context, |registry| {
814 registry.ensure_watchdog_immediately(claim, platform::time_ns())
815 })
816}
817
818fn reconcile_watchdog_claim(
819 claim: &RegistrationClaim,
820 context: Option<&CallbackToken>,
821 schedule: Option<TimerSchedule>,
822) -> Result<(), TimerError> {
823 apply_claim_transition(claim, context, |registry| {
824 registry.reconcile_watchdog_schedule(claim, platform::time_ns(), schedule)
825 })
826}
827
828fn cancel_claim(
829 claim: &RegistrationClaim,
830 context: Option<&CallbackToken>,
831) -> Result<(), TimerError> {
832 apply_claim_transition(claim, context, |registry| registry.cancel(claim))
833}
834
835fn unregister_claim(claim: &RegistrationClaim) -> Result<(), TimerError> {
836 apply_claim_transition(claim, None, |registry| registry.unregister(claim))
837}
838
839fn finish_detached_claim_transition(
840 claim: &RegistrationClaim,
841 handles: ProviderHandles,
842 transition: Result<RegistryTransition, TimerError>,
843) -> Result<(), TimerError> {
844 match transition {
845 Ok(transition) => match finish_transition(transition, handles) {
846 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
847 Err(error) => retire_failed_claim(claim, error),
848 },
849 Err(error) => match restore_provider_handles(handles) {
850 Ok(()) => Err(error),
851 Err(restoration_error) => retire_failed_claim(claim, restoration_error),
852 },
853 }
854}
855
856fn retire_failed_claim(claim: &RegistrationClaim, error: TimerError) -> Result<(), TimerError> {
857 match fail_claim_provider_binding(claim) {
858 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
859 Err(cleanup_error) => Err(cleanup_error),
860 }
861}
862
863fn finish_transition(
864 transition: RegistryTransition,
865 handles: ProviderHandles,
866) -> Result<(), TimerError> {
867 let failure = transition.failure();
868 let effect = transition.into_effect();
869 apply_effect(&effect, handles)?;
870 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
871}
872
873fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
874 if !effect.has_valid_shape() {
875 clear_provider_handles(handles);
876 return Err(TimerError::OwnershipInvariant);
877 }
878 match effect {
879 RegistryEffect::None => restore_provider_handles(handles),
880 RegistryEffect::ArmWakeup {
881 token,
882 delay_ns,
883 arm,
884 } => {
885 if arm.replaces_existing() {
886 let replaced = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
887 registry.take_wakeup_handle(token.identity())
888 })?;
889 if let Some(replaced) = replaced {
890 clear_provider_handle(replaced);
891 }
892 }
893 restore_provider_handles(handles)?;
894 let task_token = token.clone();
895 let handle = platform::set_timer(Duration::from_nanos(*delay_ns), async move {
896 dispatch_wakeup(task_token).await;
897 });
898 bind_provider_handle(token, handle)?;
899 if let Err(error) = confirm_effect(effect) {
900 let handle = with_registry_mut(|registry| {
901 registry
902 .take_wakeup_handle(token.identity())
903 .ok_or(TimerError::OwnershipInvariant)
904 })?;
905 clear_provider_handle(handle);
906 return Err(error);
907 }
908 Ok(())
909 }
910 RegistryEffect::ClearCallbacks {
911 identity,
912 handles: selected,
913 } => {
914 if selected.includes_wakeup() {
915 let wakeup = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
916 registry.take_wakeup_handle(identity)
917 })?;
918 if let Some(wakeup) = wakeup {
919 clear_provider_handle(wakeup);
920 }
921 }
922 if selected.includes_work() {
923 let work = take_detached_or_owned_handle(handles.take_work(), |registry| {
924 registry.take_work_handle(identity)
925 })?;
926 if let Some(work) = work {
927 clear_provider_handle(work);
928 }
929 }
930 restore_provider_handles(handles)
931 }
932 RegistryEffect::DispatchWatchdog {
933 successor,
934 successor_delay_ns,
935 work,
936 } => {
937 if let Some(wakeup) = handles.take_wakeup() {
938 clear_provider_handle(wakeup);
939 }
940 let replaced_work = take_detached_or_owned_handle(handles.take_work(), |registry| {
941 registry.take_work_handle(successor.identity())
942 })?;
943 if let Some(replaced_work) = replaced_work {
944 clear_provider_handle(replaced_work);
945 }
946 let successor_token = successor.clone();
947 let successor_handle =
948 platform::set_timer(Duration::from_nanos(*successor_delay_ns), async move {
949 dispatch_watchdog_scheduler(&successor_token);
950 });
951 bind_provider_handle(successor, successor_handle)?;
952
953 let work_token = work.clone();
954 let work_handle = platform::set_timer(Duration::ZERO, async move {
955 dispatch_watchdog_work(&work_token);
956 });
957 if let Err(error) =
958 bind_provider_handle(work, work_handle).and_then(|()| confirm_effect(effect))
959 {
960 clear_entry_provider_handles(successor.identity())?;
961 return Err(error);
962 }
963 Ok(())
964 }
965 }
966}
967
968fn take_detached_or_owned_handle(
969 detached: Option<ProviderHandle>,
970 take_owned: impl FnOnce(&mut TimerRegistry) -> Option<ProviderHandle>,
971) -> Result<Option<ProviderHandle>, TimerError> {
972 detached.map_or_else(
973 || with_registry_mut(|registry| Ok(take_owned(registry))),
974 |handle| Ok(Some(handle)),
975 )
976}
977
978fn bind_provider_handle(token: &CallbackToken, handle: TimerHandle) -> Result<(), TimerError> {
980 RUNTIME
981 .with(|runtime| {
982 #[cfg(test)]
983 if take_provider_install_fault() {
984 return Err((TimerError::OwnershipInvariant, handle));
985 }
986 let Ok(mut runtime) = runtime.try_borrow_mut() else {
987 return Err((TimerError::RuntimeBusy, handle));
988 };
989 let Some(registry) = runtime.as_mut() else {
990 return Err((TimerError::NotInitialized, handle));
991 };
992 match registry.install_provider_handle(token, handle) {
993 Ok(()) => Ok(()),
994 Err((error, handle)) => Err((TimerError::from(error), handle)),
995 }
996 })
997 .map_err(|(error, handle)| {
998 platform::clear_timer(handle);
1000 error
1001 })
1002}
1003
1004fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
1005 #[cfg(test)]
1006 if take_provider_confirmation_fault() {
1007 return Err(TimerError::OwnershipInvariant);
1008 }
1009 with_registry_mut(|registry| {
1010 registry
1011 .confirm_effect_applied(effect)
1012 .map_err(TimerError::from)
1013 })
1014}
1015
1016fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
1017 let wakeup_failure = handles
1020 .take_wakeup()
1021 .and_then(|handle| restore_provider_handle(handle).err());
1022 let work_failure = handles
1023 .take_work()
1024 .and_then(|handle| restore_provider_handle(handle).err());
1025 wakeup_failure.or(work_failure).map_or(Ok(()), Err)
1026}
1027
1028fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
1029 let (token, handle) = handle.into_parts();
1030 bind_provider_handle(&token, handle)
1031}
1032
1033fn clear_provider_handle(handle: ProviderHandle) {
1034 let (_, handle) = handle.into_parts();
1035 platform::clear_timer(handle);
1036}
1037
1038fn clear_provider_handles(mut handles: ProviderHandles) {
1039 if let Some(wakeup) = handles.take_wakeup() {
1040 clear_provider_handle(wakeup);
1041 }
1042 if let Some(work) = handles.take_work() {
1043 clear_provider_handle(work);
1044 }
1045}
1046
1047fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
1048 let handles = with_registry_mut(|registry| {
1049 Ok(ProviderHandles::from_parts(
1050 registry.take_wakeup_handle(identity),
1051 registry.take_work_handle(identity),
1052 ))
1053 })?;
1054 clear_provider_handles(handles);
1055 Ok(())
1056}
1057
1058#[expect(
1059 clippy::future_not_send,
1060 reason = "IC callbacks and canister-local state are single-threaded."
1061)]
1062async fn dispatch_wakeup(token: CallbackToken) {
1063 match token.role() {
1064 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
1065 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
1066 CallbackRole::WatchdogWork => {}
1067 }
1068}
1069
1070#[expect(
1071 clippy::future_not_send,
1072 reason = "IC callbacks and canister-local state are single-threaded."
1073)]
1074async fn dispatch_ordinary(token: CallbackToken) {
1075 let measurement = CallbackMeasurementStart::capture();
1076 let accepted = with_registry_mut(|registry| {
1077 registry.consume_provider_handle(&token);
1078 Ok(registry.begin_ordinary(&token))
1079 });
1080 match accepted {
1081 Ok(CallbackAcceptance::Accepted) => {}
1082 Ok(CallbackAcceptance::Stale) => return,
1083 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
1084 }
1085
1086 let callback =
1089 with_registry(|registry| registry.ordinary_callback(&token).map_err(TimerError::from))
1090 .unwrap_or_else(|error| trap_callback_failure("ordinary callback lookup", &error));
1091 let future = {
1092 let Ok(mut callback) = callback.try_borrow_mut() else {
1093 finish_ordinary_callback(
1094 &token,
1095 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
1096 );
1097 return;
1098 };
1099 callback(token.clone())
1100 };
1101 let result = future.await;
1102 finish_ordinary_callback(&token, result);
1103 record_callback_measurements(&token, measurement.finish());
1104}
1105
1106fn finish_ordinary_callback(token: &CallbackToken, result: TimerRunResult) {
1107 let transition = with_registry_mut(|registry| {
1108 registry
1109 .complete_ordinary(token, platform::time_ns(), result)
1110 .map_err(TimerError::from)
1111 });
1112 let transition = transition
1113 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
1114 finish_callback_transition(token, transition, ProviderHandles::default());
1115}
1116
1117fn dispatch_watchdog_scheduler(token: &CallbackToken) {
1118 let measurement = CallbackMeasurementStart::capture();
1119 let dispatched = with_registry_mut(|registry| {
1120 registry.consume_provider_handle(token);
1121 let handles = match registry.take_provider_handles_for_claim(token.claim()) {
1122 Ok(handles) => handles,
1123 Err(RegistryError::UnknownRegistration | RegistryError::StaleRegistration) => {
1125 ProviderHandles::default()
1126 }
1127 Err(error) => return Err(TimerError::from(error)),
1128 };
1129 Ok((
1130 registry.begin_watchdog_scheduler(token, platform::time_ns()),
1131 handles,
1132 ))
1133 });
1134 let (transition, handles) = dispatched
1135 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
1136 let accepted = !matches!(transition.effect(), RegistryEffect::None);
1137 finish_callback_transition(token, transition, handles);
1138 if accepted {
1139 record_callback_measurements(token, measurement.finish());
1140 }
1141}
1142
1143fn dispatch_watchdog_work(token: &CallbackToken) {
1144 let measurement = CallbackMeasurementStart::capture();
1145 let accepted = with_registry_mut(|registry| {
1146 registry.consume_provider_handle(token);
1147 Ok(registry.begin_watchdog_work(token))
1148 });
1149 match accepted {
1150 Ok(CallbackAcceptance::Accepted) => {}
1151 Ok(CallbackAcceptance::Stale) => return,
1152 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
1153 }
1154
1155 let callback =
1156 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
1157 {
1158 Ok(callback) => callback,
1159 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
1160 };
1161 let result = {
1162 let Ok(mut callback) = callback.try_borrow_mut() else {
1163 trap_callback_failure(
1164 "watchdog callback ownership",
1165 &TimerError::OwnershipInvariant,
1166 );
1167 };
1168 callback(token.clone())
1169 };
1170 finish_watchdog_dispatch(token, result);
1171 record_callback_measurements(token, measurement.finish());
1172}
1173
1174fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
1175 let completed = with_registry_mut(|registry| {
1180 let handles = registry
1181 .take_provider_handles_for_claim(token.claim())
1182 .map_err(TimerError::from)?;
1183 #[cfg(test)]
1184 {
1185 if take_watchdog_completion_fault() {
1186 return Err(TimerError::OwnershipInvariant);
1187 }
1188 }
1189 let transition = registry
1190 .complete_watchdog_work(token, platform::time_ns(), result)
1191 .map_err(TimerError::from)?;
1192 Ok((transition, handles))
1193 });
1194 let (transition, handles) =
1195 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
1196 finish_callback_transition(token, transition, handles);
1197}
1198
1199fn finish_callback_transition(
1200 token: &CallbackToken,
1201 transition: RegistryTransition,
1202 handles: ProviderHandles,
1203) {
1204 match finish_transition(transition, handles) {
1207 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
1208 Err(error) => {
1209 if token.role() == CallbackRole::WatchdogWork {
1210 trap_callback_failure("watchdog provider-handle completion", &error);
1211 }
1212 fail_claim_provider_binding(token.claim()).unwrap_or_else(|binding_error| {
1213 trap_callback_failure("provider-binding failure cleanup", &binding_error)
1214 });
1215 }
1216 }
1217}
1218
1219fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
1220 let failed = with_registry_mut(|registry| {
1221 registry
1222 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
1223 .map_err(TimerError::from)
1224 });
1225 clear_provider_handles(failed?);
1226 Ok(())
1227}
1228
1229#[derive(Clone, Copy)]
1230struct CallbackMeasurementStart {
1231 instructions_before: u64,
1232 memory_start: MemoryPageExtent,
1233}
1234
1235impl CallbackMeasurementStart {
1236 fn capture() -> Self {
1237 let memory_start = platform::memory_pages();
1239 let instructions_before = platform::instruction_counter();
1240 Self {
1241 instructions_before,
1242 memory_start,
1243 }
1244 }
1245
1246 fn finish(self) -> CallbackMeasurement {
1247 let instructions = platform::instruction_counter().saturating_sub(self.instructions_before);
1249 let memory_end = platform::memory_pages();
1250 CallbackMeasurement {
1251 instructions,
1252 memory_start: self.memory_start,
1253 memory_end,
1254 }
1255 }
1256}
1257
1258#[derive(Clone, Copy)]
1259struct CallbackMeasurement {
1260 instructions: u64,
1261 memory_start: MemoryPageExtent,
1262 memory_end: MemoryPageExtent,
1263}
1264
1265fn record_callback_measurements(token: &CallbackToken, measurement: CallbackMeasurement) {
1266 with_registry_mut(|registry| {
1267 registry
1268 .record_callback_measurements(
1269 token,
1270 measurement.instructions,
1271 measurement.memory_start,
1272 measurement.memory_end,
1273 )
1274 .map_err(TimerError::from)
1275 })
1276 .unwrap_or_else(|error| trap_callback_failure("callback measurement accounting", &error));
1277}
1278
1279fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
1280 platform::trap(&format!("ic-timers {context} failed: {error}"))
1281}
1282
1283fn with_registry<T>(
1284 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
1285) -> Result<T, TimerError> {
1286 RUNTIME.with(|runtime| {
1287 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1288 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1289 operation(registry)
1290 })
1291}
1292
1293fn with_registry_mut<T>(
1294 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1295) -> Result<T, TimerError> {
1296 RUNTIME.with(|runtime| {
1297 let mut runtime = runtime
1298 .try_borrow_mut()
1299 .map_err(|_| TimerError::RuntimeBusy)?;
1300 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1301 operation(registry)
1302 })
1303}
1304
1305#[cfg(test)]
1306fn reset_for_test(now_ns: u64, canister_version: u64) {
1307 platform::reset(now_ns, canister_version);
1308 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1309 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(None));
1310 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(false));
1311 RUNTIME.with(|runtime| {
1312 *runtime.borrow_mut() = None;
1313 });
1314}
1315
1316#[cfg(test)]
1317thread_local! {
1318 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1319 static PROVIDER_INSTALL_FAULT_AFTER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1320 static PROVIDER_CONFIRMATION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1321}
1322
1323#[cfg(test)]
1324fn inject_watchdog_completion_fault() {
1325 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1326}
1327
1328#[cfg(test)]
1329fn take_watchdog_completion_fault() -> bool {
1330 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1331}
1332
1333#[cfg(test)]
1334fn inject_provider_install_fault() {
1335 inject_provider_install_fault_after(0);
1336}
1337
1338#[cfg(test)]
1339fn inject_provider_install_fault_after(successful_installs: u64) {
1340 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(Some(successful_installs)));
1341}
1342
1343#[cfg(test)]
1344fn take_provider_install_fault() -> bool {
1345 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| match fault.get() {
1346 Some(0) => {
1347 fault.set(None);
1348 true
1349 }
1350 Some(remaining) => {
1351 fault.set(Some(remaining - 1));
1352 false
1353 }
1354 None => false,
1355 })
1356}
1357
1358#[cfg(test)]
1359fn inject_provider_confirmation_fault() {
1360 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(true));
1361}
1362
1363#[cfg(test)]
1364fn take_provider_confirmation_fault() -> bool {
1365 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.replace(false))
1366}
1367
1368#[cfg(test)]
1369mod tests;