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 TimerInventorySnapshot, 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 control failed: {0:?}")]
44 ControlFailure(TimerControlFailure),
45 #[error("timer runtime ownership invariant failed")]
47 OwnershipInvariant,
48 #[error("timer lifecycle reconciliation conflicts with the canonical declaration")]
50 ReconciliationConflict,
51}
52
53impl From<RegistryError> for TimerError {
54 fn from(value: RegistryError) -> Self {
55 match value {
56 RegistryError::UnknownRegistration
57 | RegistryError::StaleRegistration
58 | RegistryError::StaleCallback => Self::RegistrationExpired,
59 RegistryError::PolicyMismatch { .. } => Self::OwnershipInvariant,
60 RegistryError::Schedule(error) => Self::Schedule(error),
61 RegistryError::MissingCallback | RegistryError::ProviderHandleAlreadyOwned => {
62 Self::OwnershipInvariant
63 }
64 }
65 }
66}
67
68pub fn initialize_runtime() -> Result<TimerEpoch, TimerError> {
73 let epoch = TimerEpoch::new(platform::canister_version(), platform::time_ns());
74 RUNTIME.with(|runtime| {
75 let mut runtime = runtime
76 .try_borrow_mut()
77 .map_err(|_| TimerError::RuntimeBusy)?;
78 if let Some(registry) = runtime.as_ref() {
79 return Ok(registry.epoch());
80 }
81 *runtime = Some(TimerRegistry::new(epoch));
82 Ok(epoch)
83 })
84}
85
86struct CallbackContext {
87 token: CallbackToken,
88}
89
90impl CallbackContext {
91 const fn new(token: CallbackToken) -> Self {
92 Self { token }
93 }
94
95 fn claim(&self) -> RegistrationClaim {
96 RegistrationClaim::from_callback(&self.token)
97 }
98
99 const fn identity(&self) -> &TimerIdentity {
100 self.token.identity()
101 }
102
103 fn cancel(&self) -> Result<(), TimerError> {
104 cancel_claim(&self.claim(), Some(&self.token))
105 }
106
107 fn schedule_once(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
108 ensure_once_claim(&self.claim(), Some(&self.token), schedule)
109 }
110
111 fn schedule_recurring(&self) -> Result<(), TimerError> {
112 ensure_recurring_claim(&self.claim(), Some(&self.token))
113 }
114
115 fn schedule_watchdog_immediately(&self) -> Result<(), TimerError> {
116 ensure_watchdog_immediately_claim(&self.claim(), Some(&self.token))
117 }
118
119 fn reconcile_ordinary(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
120 reconcile_ordinary_claim(&self.claim(), Some(&self.token), schedule)
121 }
122}
123
124pub struct OnceContext {
130 inner: CallbackContext,
131}
132
133impl OnceContext {
134 const fn new(token: CallbackToken) -> Self {
135 Self {
136 inner: CallbackContext::new(token),
137 }
138 }
139
140 #[must_use]
142 pub const fn identity(&self) -> &TimerIdentity {
143 self.inner.identity()
144 }
145
146 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
151 self.inner.schedule_once(schedule)
152 }
153
154 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
161 self.inner.reconcile_ordinary(schedule)
162 }
163
164 pub fn cancel(&self) -> Result<(), TimerError> {
171 self.inner.cancel()
172 }
173}
174
175pub struct AfterCompletionContext {
181 inner: CallbackContext,
182}
183
184impl AfterCompletionContext {
185 const fn new(token: CallbackToken) -> Self {
186 Self {
187 inner: CallbackContext::new(token),
188 }
189 }
190
191 #[must_use]
193 pub const fn identity(&self) -> &TimerIdentity {
194 self.inner.identity()
195 }
196
197 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
199 self.inner.schedule_recurring()
200 }
201
202 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
210 self.inner.reconcile_ordinary(schedule)
211 }
212
213 pub fn cancel(&self) -> Result<(), TimerError> {
220 self.inner.cancel()
221 }
222}
223
224pub struct WatchdogContext {
229 inner: CallbackContext,
230}
231
232impl WatchdogContext {
233 const fn new(token: CallbackToken) -> Self {
234 Self {
235 inner: CallbackContext::new(token),
236 }
237 }
238
239 #[must_use]
241 pub const fn identity(&self) -> &TimerIdentity {
242 self.inner.identity()
243 }
244
245 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
251 self.inner.schedule_recurring()
252 }
253
254 pub fn ensure_scheduled_immediately(&self) -> Result<(), TimerError> {
261 self.inner.schedule_watchdog_immediately()
262 }
263
264 pub fn cancel(&self) -> Result<(), TimerError> {
271 self.inner.cancel()
272 }
273}
274
275#[must_use = "retain the registration claim so the timer remains controllable"]
277pub struct OnceRegistration {
278 claim: RegistrationClaim,
279}
280
281impl OnceRegistration {
282 #[must_use]
284 pub const fn identity(&self) -> &TimerIdentity {
285 self.claim.identity()
286 }
287
288 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
294 has_armed_wakeup_claim(&self.claim)
295 }
296
297 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
299 ensure_once_claim(&self.claim, None, schedule)
300 }
301
302 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
309 reconcile_ordinary_claim(&self.claim, None, schedule)
310 }
311
312 pub fn cancel(&self) -> Result<(), TimerError> {
318 cancel_claim(&self.claim, None)
319 }
320
321 pub fn unregister(self) -> Result<(), TimerError> {
326 unregister_claim(&self.claim)
327 }
328}
329
330#[must_use = "retain the registration claim so the timer remains controllable"]
333pub struct AfterCompletionRegistration {
334 claim: RegistrationClaim,
335}
336
337#[must_use = "retain the registration claim so the timer remains controllable"]
339pub struct WatchdogRegistration {
340 claim: RegistrationClaim,
341}
342
343trait RegistrationClaimOwner {
344 fn registration_claim(&self) -> &RegistrationClaim;
345}
346
347impl RegistrationClaimOwner for OnceRegistration {
348 fn registration_claim(&self) -> &RegistrationClaim {
349 &self.claim
350 }
351}
352
353impl RegistrationClaimOwner for AfterCompletionRegistration {
354 fn registration_claim(&self) -> &RegistrationClaim {
355 &self.claim
356 }
357}
358
359impl RegistrationClaimOwner for WatchdogRegistration {
360 fn registration_claim(&self) -> &RegistrationClaim {
361 &self.claim
362 }
363}
364
365#[derive(Clone, Copy, Debug, Eq, PartialEq)]
367pub enum TimerReconcileState {
368 Inactive,
370 Scheduled,
372}
373
374#[derive(Clone, Copy, Debug, Eq, PartialEq)]
376pub enum WatchdogReconcileState {
377 Inactive,
379 Scheduled,
381 ScheduledImmediately,
386}
387
388impl WatchdogRegistration {
389 #[must_use]
391 pub const fn identity(&self) -> &TimerIdentity {
392 self.claim.identity()
393 }
394
395 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
402 has_armed_wakeup_claim(&self.claim)
403 }
404
405 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
409 ensure_recurring_claim(&self.claim, None)
410 }
411
412 pub fn ensure_scheduled_immediately(&self) -> Result<(), TimerError> {
420 ensure_watchdog_immediately_claim(&self.claim, None)
421 }
422
423 pub fn cancel(&self) -> Result<(), TimerError> {
430 cancel_claim(&self.claim, None)
431 }
432
433 pub fn unregister(self) -> Result<(), TimerError> {
438 unregister_claim(&self.claim)
439 }
440}
441
442impl AfterCompletionRegistration {
443 #[must_use]
445 pub const fn identity(&self) -> &TimerIdentity {
446 self.claim.identity()
447 }
448
449 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
455 has_armed_wakeup_claim(&self.claim)
456 }
457
458 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
461 ensure_recurring_claim(&self.claim, None)
462 }
463
464 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
469 reconcile_ordinary_claim(&self.claim, None, schedule)
470 }
471
472 pub fn cancel(&self) -> Result<(), TimerError> {
478 cancel_claim(&self.claim, None)
479 }
480
481 pub fn unregister(self) -> Result<(), TimerError> {
486 unregister_claim(&self.claim)
487 }
488}
489
490pub fn register_once<F, Fut>(
492 identity: TimerIdentity,
493 lifetime: DeclarationLifetime,
494 callback: F,
495) -> Result<OnceRegistration, TimerError>
496where
497 F: FnMut(OnceContext) -> Fut + 'static,
498 Fut: Future<Output = TimerRunResult> + 'static,
499{
500 let callback = erase_ordinary_callback(callback, OnceContext::new);
501 let claim = with_registry_mut(|registry| {
502 registry
503 .register_once_with_callback(identity, lifetime, callback)
504 .map_err(TimerError::from)
505 })?;
506 Ok(OnceRegistration { claim })
507}
508
509pub fn register_after_completion<F, Fut>(
511 identity: TimerIdentity,
512 cadence: TimerCadence,
513 lifetime: DeclarationLifetime,
514 callback: F,
515) -> Result<AfterCompletionRegistration, TimerError>
516where
517 F: FnMut(AfterCompletionContext) -> Fut + 'static,
518 Fut: Future<Output = TimerRunResult> + 'static,
519{
520 let callback = erase_ordinary_callback(callback, AfterCompletionContext::new);
521 let claim = with_registry_mut(|registry| {
522 registry
523 .register_after_completion_with_callback(identity, cadence, lifetime, callback)
524 .map_err(TimerError::from)
525 })?;
526 Ok(AfterCompletionRegistration { claim })
527}
528
529pub fn register_watchdog<F>(
536 identity: TimerIdentity,
537 cadence: TimerCadence,
538 lifetime: DeclarationLifetime,
539 callback: F,
540) -> Result<WatchdogRegistration, TimerError>
541where
542 F: FnMut(WatchdogContext) -> WatchdogRunResult + 'static,
543{
544 let mut callback = callback;
545 let callback: WatchdogCallback = Rc::new(RefCell::new(Box::new(move |token| {
546 callback(WatchdogContext::new(token))
547 })));
548 let claim = with_registry_mut(|registry| {
549 registry
550 .register_watchdog_with_callback(identity, cadence, lifetime, callback)
551 .map_err(TimerError::from)
552 })?;
553 Ok(WatchdogRegistration { claim })
554}
555
556pub fn reconcile_once<F, Fut>(
564 registration: &mut Option<OnceRegistration>,
565 identity: &TimerIdentity,
566 desired: Option<TimerSchedule>,
567 callback: F,
568) -> Result<(), TimerError>
569where
570 F: FnMut(OnceContext) -> Fut + 'static,
571 Fut: Future<Output = TimerRunResult> + 'static,
572{
573 let registration = reconcile_registration(registration, identity, TimerPolicy::Once, || {
574 register_once(identity.clone(), DeclarationLifetime::Retained, callback)
575 })?;
576 registration.reconcile_schedule(desired)
577}
578
579pub fn reconcile_after_completion<F, Fut>(
588 registration: &mut Option<AfterCompletionRegistration>,
589 identity: &TimerIdentity,
590 cadence: TimerCadence,
591 desired: TimerReconcileState,
592 callback: F,
593) -> Result<(), TimerError>
594where
595 F: FnMut(AfterCompletionContext) -> Fut + 'static,
596 Fut: Future<Output = TimerRunResult> + 'static,
597{
598 let registration = reconcile_registration(
599 registration,
600 identity,
601 TimerPolicy::AfterCompletion { cadence },
602 || {
603 register_after_completion(
604 identity.clone(),
605 cadence,
606 DeclarationLifetime::Retained,
607 callback,
608 )
609 },
610 )?;
611 match desired {
612 TimerReconcileState::Inactive => registration.cancel(),
613 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
614 }
615}
616
617pub fn reconcile_watchdog<F>(
626 registration: &mut Option<WatchdogRegistration>,
627 identity: &TimerIdentity,
628 cadence: TimerCadence,
629 desired: WatchdogReconcileState,
630 callback: F,
631) -> Result<(), TimerError>
632where
633 F: FnMut(WatchdogContext) -> WatchdogRunResult + 'static,
634{
635 let registration = reconcile_registration(
636 registration,
637 identity,
638 TimerPolicy::Watchdog { cadence },
639 || {
640 register_watchdog(
641 identity.clone(),
642 cadence,
643 DeclarationLifetime::Retained,
644 callback,
645 )
646 },
647 )?;
648 match desired {
649 WatchdogReconcileState::Inactive => registration.cancel(),
650 WatchdogReconcileState::Scheduled => registration.ensure_scheduled(),
651 WatchdogReconcileState::ScheduledImmediately => registration.ensure_scheduled_immediately(),
652 }
653}
654
655fn reconcile_registration<'a, Registration>(
656 registration: &'a mut Option<Registration>,
657 identity: &TimerIdentity,
658 policy: TimerPolicy,
659 register: impl FnOnce() -> Result<Registration, TimerError>,
660) -> Result<&'a Registration, TimerError>
661where
662 Registration: RegistrationClaimOwner,
663{
664 if registration.is_none() {
665 *registration = Some(register()?);
666 }
667 let registration = registration
668 .as_ref()
669 .ok_or(TimerError::ReconciliationConflict)?;
670 verify_declaration(registration.registration_claim(), identity, policy)?;
671 Ok(registration)
672}
673
674fn verify_declaration(
675 claim: &RegistrationClaim,
676 identity: &TimerIdentity,
677 policy: TimerPolicy,
678) -> Result<(), TimerError> {
679 if claim.identity() != identity {
680 return Err(TimerError::ReconciliationConflict);
681 }
682 with_registry(|registry| {
683 registry
684 .declaration_matches(claim, policy, DeclarationLifetime::Retained)
685 .map_err(TimerError::from)?
686 .then_some(())
687 .ok_or(TimerError::ReconciliationConflict)
688 })
689}
690
691pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
693 with_registry(|registry| Ok(registry.snapshot(identity)))
694}
695
696pub fn timer_inventory() -> Result<TimerInventorySnapshot, TimerError> {
701 with_registry(|registry| Ok(registry.inventory()))
702}
703
704pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
706 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
707}
708
709fn has_armed_wakeup_claim(claim: &RegistrationClaim) -> Result<bool, TimerError> {
710 with_registry(|registry| registry.has_armed_wakeup(claim).map_err(TimerError::from))
711}
712
713fn erase_ordinary_callback<Context: 'static, F, Fut>(
714 mut callback: F,
715 context: fn(CallbackToken) -> Context,
716) -> OrdinaryCallback
717where
718 F: FnMut(Context) -> Fut + 'static,
719 Fut: Future<Output = TimerRunResult> + 'static,
720{
721 Rc::new(RefCell::new(Box::new(move |token| {
722 Box::pin(callback(context(token)))
723 })))
724}
725
726fn apply_claim_transition(
727 claim: &RegistrationClaim,
728 context: Option<&CallbackToken>,
729 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
730) -> Result<(), TimerError> {
731 let transition = with_registry_mut(|registry| {
732 validate_context(registry, context)?;
733 operation(registry).map_err(TimerError::from)
734 })?;
735 finish_claim_transition(claim, transition, ProviderHandles::default())
736}
737
738fn ensure_once_claim(
739 claim: &RegistrationClaim,
740 context: Option<&CallbackToken>,
741 schedule: TimerSchedule,
742) -> Result<(), TimerError> {
743 apply_claim_transition(claim, context, |registry| {
744 registry.ensure_once(claim, platform::time_ns(), schedule)
745 })
746}
747
748fn reconcile_ordinary_claim(
749 claim: &RegistrationClaim,
750 context: Option<&CallbackToken>,
751 schedule: Option<TimerSchedule>,
752) -> Result<(), TimerError> {
753 if schedule.is_none() {
754 let (handles, transition) = with_registry_mut(|registry| {
755 validate_context(registry, context)?;
756 registry
757 .validate_ordinary_claim(claim)
758 .map_err(TimerError::from)?;
759 let handles = registry
760 .take_provider_handles_for_claim(claim)
761 .map_err(TimerError::from)?;
762 let transition = registry
763 .reconcile_ordinary(claim, platform::time_ns(), None)
764 .map_err(TimerError::from);
765 Ok((handles, transition))
766 })?;
767 return finish_detached_claim_transition(claim, handles, transition);
768 }
769 apply_claim_transition(claim, context, |registry| {
770 registry.reconcile_ordinary(claim, platform::time_ns(), schedule)
771 })
772}
773
774fn ensure_recurring_claim(
775 claim: &RegistrationClaim,
776 context: Option<&CallbackToken>,
777) -> Result<(), TimerError> {
778 apply_claim_transition(claim, context, |registry| {
779 registry.ensure_recurring(claim, platform::time_ns())
780 })
781}
782
783fn ensure_watchdog_immediately_claim(
784 claim: &RegistrationClaim,
785 context: Option<&CallbackToken>,
786) -> Result<(), TimerError> {
787 apply_claim_transition(claim, context, |registry| {
788 registry.ensure_watchdog_immediately(claim, platform::time_ns())
789 })
790}
791
792fn cancel_claim(
793 claim: &RegistrationClaim,
794 context: Option<&CallbackToken>,
795) -> Result<(), TimerError> {
796 apply_detached_claim_transition(claim, context, |registry| registry.cancel(claim))
797}
798
799fn validate_context(
800 registry: &TimerRegistry,
801 context: Option<&CallbackToken>,
802) -> Result<(), TimerError> {
803 context.map_or(Ok(()), |token| {
804 registry
805 .validate_running_context(token)
806 .map_err(TimerError::from)
807 })
808}
809
810fn unregister_claim(claim: &RegistrationClaim) -> Result<(), TimerError> {
811 apply_detached_claim_transition(claim, None, |registry| registry.unregister(claim))
812}
813
814fn apply_detached_claim_transition(
815 claim: &RegistrationClaim,
816 context: Option<&CallbackToken>,
817 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
818) -> Result<(), TimerError> {
819 let (handles, transition) = with_registry_mut(|registry| {
820 validate_context(registry, context)?;
821 let handles = registry
822 .take_provider_handles_for_claim(claim)
823 .map_err(TimerError::from)?;
824 let transition = operation(registry).map_err(TimerError::from);
825 Ok((handles, transition))
826 })?;
827 finish_detached_claim_transition(claim, handles, transition)
828}
829
830fn finish_detached_claim_transition(
831 claim: &RegistrationClaim,
832 handles: ProviderHandles,
833 transition: Result<RegistryTransition, TimerError>,
834) -> Result<(), TimerError> {
835 match transition {
836 Ok(transition) => finish_claim_transition(claim, transition, handles),
837 Err(error) => match restore_provider_handles(handles) {
838 Ok(()) => Err(error),
839 Err(restoration_error) => retire_failed_claim(claim, restoration_error),
840 },
841 }
842}
843
844fn finish_claim_transition(
845 claim: &RegistrationClaim,
846 transition: RegistryTransition,
847 handles: ProviderHandles,
848) -> Result<(), TimerError> {
849 match finish_transition(transition, handles) {
850 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
851 Err(error) => retire_failed_claim(claim, error),
852 }
853}
854
855fn retire_failed_claim(claim: &RegistrationClaim, error: TimerError) -> Result<(), TimerError> {
856 match fail_claim_provider_binding(claim) {
857 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
858 Err(cleanup_error) => Err(cleanup_error),
859 }
860}
861
862fn finish_transition(
863 transition: RegistryTransition,
864 handles: ProviderHandles,
865) -> Result<(), TimerError> {
866 let failure = transition.failure();
867 let effect = transition.into_effect();
868 apply_effect(&effect, handles)?;
869 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
870}
871
872fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
873 if !effect.has_valid_shape() {
874 clear_provider_handles(handles);
875 return Err(TimerError::OwnershipInvariant);
876 }
877 match effect {
878 RegistryEffect::None => restore_provider_handles(handles),
879 RegistryEffect::ArmWakeup { token, arm, .. } => {
880 if arm.replaces_existing() {
881 let replaced = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
882 registry.take_wakeup_handle(token.identity())
883 })?;
884 if let Some(replaced) = replaced {
885 clear_provider_handle(replaced);
886 }
887 }
888 restore_provider_handles(handles)?;
889 arm_wakeup(effect)
890 }
891 RegistryEffect::ClearCallbacks {
892 identity,
893 handles: selected,
894 } => {
895 if selected.includes_wakeup() {
896 let wakeup = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
897 registry.take_wakeup_handle(identity)
898 })?;
899 if let Some(wakeup) = wakeup {
900 clear_provider_handle(wakeup);
901 }
902 }
903 if selected.includes_work() {
904 let work = take_detached_or_owned_handle(handles.take_work(), |registry| {
905 registry.take_work_handle(identity)
906 })?;
907 if let Some(work) = work {
908 clear_provider_handle(work);
909 }
910 }
911 restore_provider_handles(handles)
912 }
913 RegistryEffect::DispatchWatchdog { successor, .. } => {
914 if let Some(wakeup) = handles.take_wakeup() {
915 clear_provider_handle(wakeup);
916 }
917 let replaced_work = take_detached_or_owned_handle(handles.take_work(), |registry| {
918 registry.take_work_handle(successor.identity())
919 })?;
920 if let Some(replaced_work) = replaced_work {
921 clear_provider_handle(replaced_work);
922 }
923 dispatch_watchdog_effect(effect)
924 }
925 }
926}
927
928fn take_detached_or_owned_handle(
929 detached: Option<ProviderHandle>,
930 take_owned: impl FnOnce(&mut TimerRegistry) -> Option<ProviderHandle>,
931) -> Result<Option<ProviderHandle>, TimerError> {
932 detached.map_or_else(
933 || with_registry_mut(|registry| Ok(take_owned(registry))),
934 |handle| Ok(Some(handle)),
935 )
936}
937
938fn arm_wakeup(effect: &RegistryEffect) -> Result<(), TimerError> {
939 let RegistryEffect::ArmWakeup {
940 token, delay_ns, ..
941 } = effect
942 else {
943 return Err(TimerError::OwnershipInvariant);
944 };
945 let task_token = token.clone();
946 let handle = platform::set_timer(Duration::from_nanos(*delay_ns), async move {
947 dispatch_wakeup(task_token).await;
948 });
949 if let Err((error, handle)) = install_provider_handle(token, handle) {
950 platform::clear_timer(handle);
951 return Err(error);
952 }
953 if let Err(error) = confirm_effect(effect) {
954 let handle = with_registry_mut(|registry| {
955 registry
956 .take_wakeup_handle(token.identity())
957 .ok_or(TimerError::OwnershipInvariant)
958 })?;
959 clear_provider_handle(handle);
960 return Err(error);
961 }
962 Ok(())
963}
964
965fn dispatch_watchdog_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
966 let RegistryEffect::DispatchWatchdog {
967 successor,
968 successor_delay_ns,
969 work,
970 ..
971 } = effect
972 else {
973 return Err(TimerError::OwnershipInvariant);
974 };
975 let successor_token = successor.clone();
976 let successor_handle =
977 platform::set_timer(Duration::from_nanos(*successor_delay_ns), async move {
978 dispatch_watchdog_scheduler(&successor_token);
979 });
980 if let Err((error, handle)) = install_provider_handle(successor, successor_handle) {
981 platform::clear_timer(handle);
982 return Err(error);
983 }
984
985 let work_token = work.clone();
986 let work_handle = platform::set_timer(Duration::ZERO, async move {
987 dispatch_watchdog_work(&work_token);
988 });
989 if let Err((error, handle)) = install_provider_handle(work, work_handle) {
990 platform::clear_timer(handle);
991 clear_entry_provider_handles(successor.identity())?;
992 return Err(error);
993 }
994 if let Err(error) = confirm_effect(effect) {
995 clear_entry_provider_handles(successor.identity())?;
996 return Err(error);
997 }
998 Ok(())
999}
1000
1001fn install_provider_handle(
1002 token: &CallbackToken,
1003 handle: TimerHandle,
1004) -> Result<(), (TimerError, TimerHandle)> {
1005 #[cfg(test)]
1006 if take_provider_install_fault() {
1007 return Err((TimerError::OwnershipInvariant, handle));
1008 }
1009 RUNTIME.with(|runtime| {
1010 let Ok(mut runtime) = runtime.try_borrow_mut() else {
1011 return Err((TimerError::RuntimeBusy, handle));
1012 };
1013 let Some(registry) = runtime.as_mut() else {
1014 return Err((TimerError::NotInitialized, handle));
1015 };
1016 match registry.install_provider_handle(token, handle) {
1017 Ok(()) => Ok(()),
1018 Err((error, handle)) => Err((TimerError::from(error), handle)),
1019 }
1020 })
1021}
1022
1023fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
1024 #[cfg(test)]
1025 if take_provider_confirmation_fault() {
1026 return Err(TimerError::OwnershipInvariant);
1027 }
1028 with_registry_mut(|registry| {
1029 registry
1030 .confirm_effect_applied(effect)
1031 .map_err(TimerError::from)
1032 })
1033}
1034
1035fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
1036 let wakeup_failure = handles
1039 .take_wakeup()
1040 .and_then(|handle| restore_provider_handle(handle).err());
1041 let work_failure = handles
1042 .take_work()
1043 .and_then(|handle| restore_provider_handle(handle).err());
1044 wakeup_failure.or(work_failure).map_or(Ok(()), Err)
1045}
1046
1047fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
1048 let (token, handle) = handle.into_parts();
1049 match install_provider_handle(&token, handle) {
1050 Ok(()) => Ok(()),
1051 Err((error, handle)) => {
1052 platform::clear_timer(handle);
1053 Err(error)
1054 }
1055 }
1056}
1057
1058fn clear_provider_handle(handle: ProviderHandle) {
1059 let (_, handle) = handle.into_parts();
1060 platform::clear_timer(handle);
1061}
1062
1063fn clear_provider_handles(mut handles: ProviderHandles) {
1064 if let Some(wakeup) = handles.take_wakeup() {
1065 clear_provider_handle(wakeup);
1066 }
1067 if let Some(work) = handles.take_work() {
1068 clear_provider_handle(work);
1069 }
1070}
1071
1072fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
1073 let handles = with_registry_mut(|registry| {
1074 Ok(ProviderHandles::from_parts(
1075 registry.take_wakeup_handle(identity),
1076 registry.take_work_handle(identity),
1077 ))
1078 })?;
1079 clear_provider_handles(handles);
1080 Ok(())
1081}
1082
1083#[allow(clippy::future_not_send)] async fn dispatch_wakeup(token: CallbackToken) {
1085 match token.role() {
1086 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
1087 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
1088 CallbackRole::WatchdogWork => {}
1089 }
1090}
1091
1092#[allow(clippy::future_not_send)] async fn dispatch_ordinary(token: CallbackToken) {
1094 let measurement = CallbackMeasurementStart::capture();
1095 let accepted = with_registry_mut(|registry| {
1096 registry.consume_provider_handle(&token);
1097 Ok(registry.begin_ordinary(&token))
1098 });
1099 match accepted {
1100 Ok(CallbackAcceptance::Accepted) => {}
1101 Ok(CallbackAcceptance::Stale) => return,
1102 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
1103 }
1104
1105 let callback = match with_registry(|registry| {
1106 registry.ordinary_callback(&token).map_err(TimerError::from)
1107 }) {
1108 Ok(callback) => callback,
1109 Err(TimerError::OwnershipInvariant) => {
1110 fail_ordinary_dispatch(&token);
1111 return;
1112 }
1113 Err(error) => trap_callback_failure("ordinary callback lookup", &error),
1114 };
1115 let future = {
1116 let Ok(mut callback) = callback.try_borrow_mut() else {
1117 fail_ordinary_dispatch(&token);
1118 return;
1119 };
1120 callback(token.clone())
1121 };
1122 let result = future.await;
1123 let transition = with_registry_mut(|registry| {
1124 registry
1125 .complete_ordinary(&token, platform::time_ns(), result)
1126 .map_err(TimerError::from)
1127 });
1128 let transition = transition
1129 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
1130 finish_callback_transition(&token, transition, ProviderHandles::default());
1131 record_callback_measurements(&token, measurement.finish());
1132}
1133
1134fn fail_ordinary_dispatch(token: &CallbackToken) {
1135 let transition = with_registry_mut(|registry| {
1136 registry
1137 .complete_ordinary(
1138 token,
1139 platform::time_ns(),
1140 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
1141 )
1142 .map_err(TimerError::from)
1143 });
1144 let transition = transition.unwrap_or_else(|error| {
1145 trap_callback_failure("ordinary invariant-failure completion", &error)
1146 });
1147 finish_callback_transition(token, transition, ProviderHandles::default());
1148}
1149
1150fn dispatch_watchdog_scheduler(token: &CallbackToken) {
1151 let measurement = CallbackMeasurementStart::capture();
1152 let transition = with_registry_mut(|registry| {
1153 registry.consume_provider_handle(token);
1154 Ok(registry.begin_watchdog_scheduler(token, platform::time_ns()))
1155 });
1156 let transition = transition
1157 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
1158 let accepted = !matches!(transition.effect(), RegistryEffect::None);
1159 finish_callback_transition(token, transition, ProviderHandles::default());
1160 if accepted {
1161 record_callback_measurements(token, measurement.finish());
1162 }
1163}
1164
1165fn dispatch_watchdog_work(token: &CallbackToken) {
1166 let measurement = CallbackMeasurementStart::capture();
1167 let accepted = with_registry_mut(|registry| {
1168 registry.consume_provider_handle(token);
1169 Ok(registry.begin_watchdog_work(token))
1170 });
1171 match accepted {
1172 Ok(CallbackAcceptance::Accepted) => {}
1173 Ok(CallbackAcceptance::Stale) => return,
1174 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
1175 }
1176
1177 let callback =
1178 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
1179 {
1180 Ok(callback) => callback,
1181 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
1182 };
1183 let result = {
1184 let Ok(mut callback) = callback.try_borrow_mut() else {
1185 trap_callback_failure(
1186 "watchdog callback ownership",
1187 &TimerError::OwnershipInvariant,
1188 );
1189 };
1190 callback(token.clone())
1191 };
1192 finish_watchdog_dispatch(token, result);
1193 record_callback_measurements(token, measurement.finish());
1194}
1195
1196fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
1197 let claim = RegistrationClaim::from_callback(token);
1198 let completed = with_registry_mut(|registry| {
1203 let handles = registry
1204 .take_provider_handles_for_claim(&claim)
1205 .map_err(TimerError::from)?;
1206 #[cfg(test)]
1207 {
1208 if take_watchdog_completion_fault() {
1209 return Err(TimerError::OwnershipInvariant);
1210 }
1211 }
1212 let transition = registry
1213 .complete_watchdog_work(token, platform::time_ns(), result)
1214 .map_err(TimerError::from)?;
1215 Ok((transition, handles))
1216 });
1217 let (transition, handles) =
1218 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
1219 finish_callback_transition(token, transition, handles);
1220}
1221
1222fn finish_callback_transition(
1223 token: &CallbackToken,
1224 transition: RegistryTransition,
1225 handles: ProviderHandles,
1226) {
1227 match finish_transition(transition, handles) {
1228 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
1229 Err(
1230 error @ (TimerError::NotInitialized
1231 | TimerError::RuntimeBusy
1232 | TimerError::Register(_)
1233 | TimerError::Schedule(_)
1234 | TimerError::RegistrationExpired
1235 | TimerError::OwnershipInvariant
1236 | TimerError::ReconciliationConflict),
1237 ) => {
1238 if token.role() == CallbackRole::WatchdogWork {
1239 trap_callback_failure("watchdog provider-handle completion", &error);
1240 }
1241 fail_provider_binding(token).unwrap_or_else(|binding_error| {
1242 trap_callback_failure("provider-binding failure cleanup", &binding_error)
1243 });
1244 }
1245 }
1246}
1247
1248fn fail_provider_binding(token: &CallbackToken) -> Result<(), TimerError> {
1249 let claim = RegistrationClaim::from_callback(token);
1250 fail_claim_provider_binding(&claim)
1251}
1252
1253fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
1254 let failed = with_registry_mut(|registry| {
1255 registry
1256 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
1257 .map_err(TimerError::from)
1258 });
1259 clear_provider_handles(failed?);
1260 Ok(())
1261}
1262
1263#[derive(Clone, Copy)]
1264struct CallbackMeasurementStart {
1265 instructions_before: u64,
1266 memory_start: platform::MemoryPages,
1267}
1268
1269impl CallbackMeasurementStart {
1270 fn capture() -> Self {
1271 let memory_start = platform::memory_pages();
1273 let instructions_before = platform::instruction_counter();
1274 Self {
1275 instructions_before,
1276 memory_start,
1277 }
1278 }
1279
1280 fn finish(self) -> CallbackMeasurement {
1281 let instructions = platform::instruction_counter().saturating_sub(self.instructions_before);
1283 let memory_end = platform::memory_pages();
1284 CallbackMeasurement {
1285 instructions,
1286 memory_start: self.memory_start,
1287 memory_end,
1288 }
1289 }
1290}
1291
1292#[derive(Clone, Copy)]
1293struct CallbackMeasurement {
1294 instructions: u64,
1295 memory_start: platform::MemoryPages,
1296 memory_end: platform::MemoryPages,
1297}
1298
1299fn record_callback_measurements(token: &CallbackToken, measurement: CallbackMeasurement) {
1300 with_registry_mut(|registry| {
1301 registry
1302 .record_callback_measurements(
1303 token,
1304 measurement.instructions,
1305 measurement.memory_start,
1306 measurement.memory_end,
1307 )
1308 .map_err(TimerError::from)
1309 })
1310 .unwrap_or_else(|error| trap_callback_failure("callback measurement accounting", &error));
1311}
1312
1313fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
1314 platform::trap(&format!("ic-timers {context} failed: {error}"))
1315}
1316
1317fn with_registry<T>(
1318 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
1319) -> Result<T, TimerError> {
1320 RUNTIME.with(|runtime| {
1321 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1322 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1323 operation(registry)
1324 })
1325}
1326
1327fn with_registry_mut<T>(
1328 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1329) -> Result<T, TimerError> {
1330 RUNTIME.with(|runtime| {
1331 let mut runtime = runtime
1332 .try_borrow_mut()
1333 .map_err(|_| TimerError::RuntimeBusy)?;
1334 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1335 operation(registry)
1336 })
1337}
1338
1339#[cfg(test)]
1340fn reset_for_test(now_ns: u64, canister_version: u64) {
1341 platform::reset(now_ns, canister_version);
1342 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1343 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(None));
1344 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(false));
1345 RUNTIME.with(|runtime| {
1346 *runtime.borrow_mut() = None;
1347 });
1348}
1349
1350#[cfg(test)]
1351thread_local! {
1352 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1353 static PROVIDER_INSTALL_FAULT_AFTER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1354 static PROVIDER_CONFIRMATION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1355}
1356
1357#[cfg(test)]
1358fn inject_watchdog_completion_fault() {
1359 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1360}
1361
1362#[cfg(test)]
1363fn take_watchdog_completion_fault() -> bool {
1364 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1365}
1366
1367#[cfg(test)]
1368fn inject_provider_install_fault() {
1369 inject_provider_install_fault_after(0);
1370}
1371
1372#[cfg(test)]
1373fn inject_provider_install_fault_after(successful_installs: u64) {
1374 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(Some(successful_installs)));
1375}
1376
1377#[cfg(test)]
1378fn take_provider_install_fault() -> bool {
1379 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| match fault.get() {
1380 Some(0) => {
1381 fault.set(None);
1382 true
1383 }
1384 Some(remaining) => {
1385 fault.set(Some(remaining - 1));
1386 false
1387 }
1388 None => false,
1389 })
1390}
1391
1392#[cfg(test)]
1393fn inject_provider_confirmation_fault() {
1394 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(true));
1395}
1396
1397#[cfg(test)]
1398fn take_provider_confirmation_fault() -> bool {
1399 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.replace(false))
1400}
1401
1402#[cfg(test)]
1403mod tests;