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 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 reconcile_ordinary(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
116 reconcile_ordinary_claim(&self.claim(), Some(&self.token), schedule)
117 }
118}
119
120pub struct OnceContext {
126 inner: CallbackContext,
127}
128
129impl OnceContext {
130 const fn new(token: CallbackToken) -> Self {
131 Self {
132 inner: CallbackContext::new(token),
133 }
134 }
135
136 #[must_use]
138 pub const fn identity(&self) -> &TimerIdentity {
139 self.inner.identity()
140 }
141
142 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
147 self.inner.schedule_once(schedule)
148 }
149
150 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
157 self.inner.reconcile_ordinary(schedule)
158 }
159
160 pub fn cancel(&self) -> Result<(), TimerError> {
167 self.inner.cancel()
168 }
169}
170
171pub struct AfterCompletionContext {
177 inner: CallbackContext,
178}
179
180impl AfterCompletionContext {
181 const fn new(token: CallbackToken) -> Self {
182 Self {
183 inner: CallbackContext::new(token),
184 }
185 }
186
187 #[must_use]
189 pub const fn identity(&self) -> &TimerIdentity {
190 self.inner.identity()
191 }
192
193 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
195 self.inner.schedule_recurring()
196 }
197
198 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
206 self.inner.reconcile_ordinary(schedule)
207 }
208
209 pub fn cancel(&self) -> Result<(), TimerError> {
216 self.inner.cancel()
217 }
218}
219
220pub struct WatchdogContext {
225 inner: CallbackContext,
226}
227
228impl WatchdogContext {
229 const fn new(token: CallbackToken) -> Self {
230 Self {
231 inner: CallbackContext::new(token),
232 }
233 }
234
235 #[must_use]
237 pub const fn identity(&self) -> &TimerIdentity {
238 self.inner.identity()
239 }
240
241 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
246 self.inner.schedule_recurring()
247 }
248
249 pub fn cancel(&self) -> Result<(), TimerError> {
256 self.inner.cancel()
257 }
258}
259
260#[must_use = "retain the registration claim so the timer remains controllable"]
262pub struct OnceRegistration {
263 claim: RegistrationClaim,
264}
265
266impl OnceRegistration {
267 #[must_use]
269 pub const fn identity(&self) -> &TimerIdentity {
270 self.claim.identity()
271 }
272
273 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
279 has_armed_wakeup_claim(&self.claim)
280 }
281
282 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
284 ensure_once_claim(&self.claim, None, schedule)
285 }
286
287 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
294 reconcile_ordinary_claim(&self.claim, None, schedule)
295 }
296
297 pub fn cancel(&self) -> Result<(), TimerError> {
303 cancel_claim(&self.claim, None)
304 }
305
306 pub fn unregister(self) -> Result<(), TimerError> {
311 unregister_claim(&self.claim)
312 }
313}
314
315#[must_use = "retain the registration claim so the timer remains controllable"]
318pub struct AfterCompletionRegistration {
319 claim: RegistrationClaim,
320}
321
322#[must_use = "retain the registration claim so the timer remains controllable"]
324pub struct WatchdogRegistration {
325 claim: RegistrationClaim,
326}
327
328trait RegistrationClaimOwner {
329 fn registration_claim(&self) -> &RegistrationClaim;
330}
331
332impl RegistrationClaimOwner for OnceRegistration {
333 fn registration_claim(&self) -> &RegistrationClaim {
334 &self.claim
335 }
336}
337
338impl RegistrationClaimOwner for AfterCompletionRegistration {
339 fn registration_claim(&self) -> &RegistrationClaim {
340 &self.claim
341 }
342}
343
344impl RegistrationClaimOwner for WatchdogRegistration {
345 fn registration_claim(&self) -> &RegistrationClaim {
346 &self.claim
347 }
348}
349
350#[derive(Clone, Copy, Debug, Eq, PartialEq)]
352pub enum TimerReconcileState {
353 Inactive,
355 Scheduled,
357}
358
359impl WatchdogRegistration {
360 #[must_use]
362 pub const fn identity(&self) -> &TimerIdentity {
363 self.claim.identity()
364 }
365
366 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
373 has_armed_wakeup_claim(&self.claim)
374 }
375
376 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
378 ensure_recurring_claim(&self.claim, None)
379 }
380
381 pub fn cancel(&self) -> Result<(), TimerError> {
388 cancel_claim(&self.claim, None)
389 }
390
391 pub fn unregister(self) -> Result<(), TimerError> {
396 unregister_claim(&self.claim)
397 }
398}
399
400impl AfterCompletionRegistration {
401 #[must_use]
403 pub const fn identity(&self) -> &TimerIdentity {
404 self.claim.identity()
405 }
406
407 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
413 has_armed_wakeup_claim(&self.claim)
414 }
415
416 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
419 ensure_recurring_claim(&self.claim, None)
420 }
421
422 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
427 reconcile_ordinary_claim(&self.claim, None, schedule)
428 }
429
430 pub fn cancel(&self) -> Result<(), TimerError> {
436 cancel_claim(&self.claim, None)
437 }
438
439 pub fn unregister(self) -> Result<(), TimerError> {
444 unregister_claim(&self.claim)
445 }
446}
447
448pub fn register_once<F, Fut>(
450 identity: TimerIdentity,
451 lifetime: DeclarationLifetime,
452 callback: F,
453) -> Result<OnceRegistration, TimerError>
454where
455 F: FnMut(OnceContext) -> Fut + 'static,
456 Fut: Future<Output = TimerRunResult> + 'static,
457{
458 let callback = erase_ordinary_callback(callback, OnceContext::new);
459 let claim = with_registry_mut(|registry| {
460 registry
461 .register_once_with_callback(identity, lifetime, callback)
462 .map_err(TimerError::from)
463 })?;
464 Ok(OnceRegistration { claim })
465}
466
467pub fn register_after_completion<F, Fut>(
469 identity: TimerIdentity,
470 cadence: TimerCadence,
471 lifetime: DeclarationLifetime,
472 callback: F,
473) -> Result<AfterCompletionRegistration, TimerError>
474where
475 F: FnMut(AfterCompletionContext) -> Fut + 'static,
476 Fut: Future<Output = TimerRunResult> + 'static,
477{
478 let callback = erase_ordinary_callback(callback, AfterCompletionContext::new);
479 let claim = with_registry_mut(|registry| {
480 registry
481 .register_after_completion_with_callback(identity, cadence, lifetime, callback)
482 .map_err(TimerError::from)
483 })?;
484 Ok(AfterCompletionRegistration { claim })
485}
486
487pub fn register_watchdog<F>(
493 identity: TimerIdentity,
494 cadence: TimerCadence,
495 lifetime: DeclarationLifetime,
496 callback: F,
497) -> Result<WatchdogRegistration, TimerError>
498where
499 F: FnMut(WatchdogContext) -> WatchdogRunResult + 'static,
500{
501 let mut callback = callback;
502 let callback: WatchdogCallback = Rc::new(RefCell::new(Box::new(move |token| {
503 callback(WatchdogContext::new(token))
504 })));
505 let claim = with_registry_mut(|registry| {
506 registry
507 .register_watchdog_with_callback(identity, cadence, lifetime, callback)
508 .map_err(TimerError::from)
509 })?;
510 Ok(WatchdogRegistration { claim })
511}
512
513pub fn reconcile_once<F, Fut>(
521 registration: &mut Option<OnceRegistration>,
522 identity: &TimerIdentity,
523 desired: Option<TimerSchedule>,
524 callback: F,
525) -> Result<(), TimerError>
526where
527 F: FnMut(OnceContext) -> Fut + 'static,
528 Fut: Future<Output = TimerRunResult> + 'static,
529{
530 let registration = reconcile_registration(registration, identity, TimerPolicy::Once, || {
531 register_once(identity.clone(), DeclarationLifetime::Retained, callback)
532 })?;
533 registration.reconcile_schedule(desired)
534}
535
536pub fn reconcile_after_completion<F, Fut>(
545 registration: &mut Option<AfterCompletionRegistration>,
546 identity: &TimerIdentity,
547 cadence: TimerCadence,
548 desired: TimerReconcileState,
549 callback: F,
550) -> Result<(), TimerError>
551where
552 F: FnMut(AfterCompletionContext) -> Fut + 'static,
553 Fut: Future<Output = TimerRunResult> + 'static,
554{
555 let registration = reconcile_registration(
556 registration,
557 identity,
558 TimerPolicy::AfterCompletion { cadence },
559 || {
560 register_after_completion(
561 identity.clone(),
562 cadence,
563 DeclarationLifetime::Retained,
564 callback,
565 )
566 },
567 )?;
568 match desired {
569 TimerReconcileState::Inactive => registration.cancel(),
570 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
571 }
572}
573
574pub fn reconcile_watchdog<F>(
581 registration: &mut Option<WatchdogRegistration>,
582 identity: &TimerIdentity,
583 cadence: TimerCadence,
584 desired: TimerReconcileState,
585 callback: F,
586) -> Result<(), TimerError>
587where
588 F: FnMut(WatchdogContext) -> WatchdogRunResult + 'static,
589{
590 let registration = reconcile_registration(
591 registration,
592 identity,
593 TimerPolicy::Watchdog { cadence },
594 || {
595 register_watchdog(
596 identity.clone(),
597 cadence,
598 DeclarationLifetime::Retained,
599 callback,
600 )
601 },
602 )?;
603 match desired {
604 TimerReconcileState::Inactive => registration.cancel(),
605 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
606 }
607}
608
609fn reconcile_registration<'a, Registration>(
610 registration: &'a mut Option<Registration>,
611 identity: &TimerIdentity,
612 policy: TimerPolicy,
613 register: impl FnOnce() -> Result<Registration, TimerError>,
614) -> Result<&'a Registration, TimerError>
615where
616 Registration: RegistrationClaimOwner,
617{
618 if registration.is_none() {
619 *registration = Some(register()?);
620 }
621 let registration = registration
622 .as_ref()
623 .ok_or(TimerError::ReconciliationConflict)?;
624 verify_declaration(registration.registration_claim(), identity, policy)?;
625 Ok(registration)
626}
627
628fn verify_declaration(
629 claim: &RegistrationClaim,
630 identity: &TimerIdentity,
631 policy: TimerPolicy,
632) -> Result<(), TimerError> {
633 if claim.identity() != identity {
634 return Err(TimerError::ReconciliationConflict);
635 }
636 with_registry(|registry| {
637 registry
638 .declaration_matches(claim, policy, DeclarationLifetime::Retained)
639 .map_err(TimerError::from)?
640 .then_some(())
641 .ok_or(TimerError::ReconciliationConflict)
642 })
643}
644
645pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
647 with_registry(|registry| Ok(registry.snapshot(identity)))
648}
649
650pub fn timer_snapshots() -> Result<Vec<TimerSnapshot>, TimerError> {
652 with_registry(|registry| Ok(registry.snapshots()))
653}
654
655pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
657 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
658}
659
660fn has_armed_wakeup_claim(claim: &RegistrationClaim) -> Result<bool, TimerError> {
661 with_registry(|registry| registry.has_armed_wakeup(claim).map_err(TimerError::from))
662}
663
664fn erase_ordinary_callback<Context: 'static, F, Fut>(
665 mut callback: F,
666 context: fn(CallbackToken) -> Context,
667) -> OrdinaryCallback
668where
669 F: FnMut(Context) -> Fut + 'static,
670 Fut: Future<Output = TimerRunResult> + 'static,
671{
672 Rc::new(RefCell::new(Box::new(move |token| {
673 Box::pin(callback(context(token)))
674 })))
675}
676
677fn apply_claim_transition(
678 claim: &RegistrationClaim,
679 context: Option<&CallbackToken>,
680 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
681) -> Result<(), TimerError> {
682 let transition = with_registry_mut(|registry| {
683 validate_context(registry, context)?;
684 operation(registry).map_err(TimerError::from)
685 })?;
686 finish_claim_transition(claim, transition, ProviderHandles::default())
687}
688
689fn ensure_once_claim(
690 claim: &RegistrationClaim,
691 context: Option<&CallbackToken>,
692 schedule: TimerSchedule,
693) -> Result<(), TimerError> {
694 apply_claim_transition(claim, context, |registry| {
695 registry.ensure_once(claim, platform::time_ns(), schedule)
696 })
697}
698
699fn reconcile_ordinary_claim(
700 claim: &RegistrationClaim,
701 context: Option<&CallbackToken>,
702 schedule: Option<TimerSchedule>,
703) -> Result<(), TimerError> {
704 if schedule.is_none() {
705 let (handles, transition) = with_registry_mut(|registry| {
706 validate_context(registry, context)?;
707 registry
708 .validate_ordinary_claim(claim)
709 .map_err(TimerError::from)?;
710 let handles = registry
711 .take_provider_handles_for_claim(claim)
712 .map_err(TimerError::from)?;
713 let transition = registry
714 .reconcile_ordinary(claim, platform::time_ns(), None)
715 .map_err(TimerError::from);
716 Ok((handles, transition))
717 })?;
718 return finish_detached_claim_transition(claim, handles, transition);
719 }
720 apply_claim_transition(claim, context, |registry| {
721 registry.reconcile_ordinary(claim, platform::time_ns(), schedule)
722 })
723}
724
725fn ensure_recurring_claim(
726 claim: &RegistrationClaim,
727 context: Option<&CallbackToken>,
728) -> Result<(), TimerError> {
729 apply_claim_transition(claim, context, |registry| {
730 registry.ensure_recurring(claim, platform::time_ns())
731 })
732}
733
734fn cancel_claim(
735 claim: &RegistrationClaim,
736 context: Option<&CallbackToken>,
737) -> Result<(), TimerError> {
738 apply_detached_claim_transition(claim, context, |registry| registry.cancel(claim))
739}
740
741fn validate_context(
742 registry: &TimerRegistry,
743 context: Option<&CallbackToken>,
744) -> Result<(), TimerError> {
745 context.map_or(Ok(()), |token| {
746 registry
747 .validate_running_context(token)
748 .map_err(TimerError::from)
749 })
750}
751
752fn unregister_claim(claim: &RegistrationClaim) -> Result<(), TimerError> {
753 apply_detached_claim_transition(claim, None, |registry| registry.unregister(claim))
754}
755
756fn apply_detached_claim_transition(
757 claim: &RegistrationClaim,
758 context: Option<&CallbackToken>,
759 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
760) -> Result<(), TimerError> {
761 let (handles, transition) = with_registry_mut(|registry| {
762 validate_context(registry, context)?;
763 let handles = registry
764 .take_provider_handles_for_claim(claim)
765 .map_err(TimerError::from)?;
766 let transition = operation(registry).map_err(TimerError::from);
767 Ok((handles, transition))
768 })?;
769 finish_detached_claim_transition(claim, handles, transition)
770}
771
772fn finish_detached_claim_transition(
773 claim: &RegistrationClaim,
774 handles: ProviderHandles,
775 transition: Result<RegistryTransition, TimerError>,
776) -> Result<(), TimerError> {
777 match transition {
778 Ok(transition) => finish_claim_transition(claim, transition, handles),
779 Err(error) => match restore_provider_handles(handles) {
780 Ok(()) => Err(error),
781 Err(restoration_error) => retire_failed_claim(claim, restoration_error),
782 },
783 }
784}
785
786fn finish_claim_transition(
787 claim: &RegistrationClaim,
788 transition: RegistryTransition,
789 handles: ProviderHandles,
790) -> Result<(), TimerError> {
791 match finish_transition(transition, handles) {
792 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
793 Err(error) => retire_failed_claim(claim, error),
794 }
795}
796
797fn retire_failed_claim(claim: &RegistrationClaim, error: TimerError) -> Result<(), TimerError> {
798 match fail_claim_provider_binding(claim) {
799 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
800 Err(cleanup_error) => Err(cleanup_error),
801 }
802}
803
804fn finish_transition(
805 transition: RegistryTransition,
806 handles: ProviderHandles,
807) -> Result<(), TimerError> {
808 let failure = transition.failure();
809 let effect = transition.into_effect();
810 apply_effect(&effect, handles)?;
811 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
812}
813
814fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
815 if !effect.has_valid_shape() {
816 clear_provider_handles(handles);
817 return Err(TimerError::OwnershipInvariant);
818 }
819 match effect {
820 RegistryEffect::None => restore_provider_handles(handles),
821 RegistryEffect::ArmWakeup { token, arm, .. } => {
822 if arm.replaces_existing() {
823 let replaced = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
824 registry.take_wakeup_handle(token.identity())
825 })?;
826 if let Some(replaced) = replaced {
827 clear_provider_handle(replaced);
828 }
829 }
830 restore_provider_handles(handles)?;
831 arm_wakeup(effect)
832 }
833 RegistryEffect::ClearCallbacks {
834 identity,
835 handles: selected,
836 } => {
837 if selected.includes_wakeup() {
838 let wakeup = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
839 registry.take_wakeup_handle(identity)
840 })?;
841 if let Some(wakeup) = wakeup {
842 clear_provider_handle(wakeup);
843 }
844 }
845 if selected.includes_work() {
846 let work = take_detached_or_owned_handle(handles.take_work(), |registry| {
847 registry.take_work_handle(identity)
848 })?;
849 if let Some(work) = work {
850 clear_provider_handle(work);
851 }
852 }
853 restore_provider_handles(handles)
854 }
855 RegistryEffect::DispatchWatchdog { successor, .. } => {
856 if let Some(wakeup) = handles.take_wakeup() {
857 clear_provider_handle(wakeup);
858 }
859 let replaced_work = take_detached_or_owned_handle(handles.take_work(), |registry| {
860 registry.take_work_handle(successor.identity())
861 })?;
862 if let Some(replaced_work) = replaced_work {
863 clear_provider_handle(replaced_work);
864 }
865 dispatch_watchdog_effect(effect)
866 }
867 }
868}
869
870fn take_detached_or_owned_handle(
871 detached: Option<ProviderHandle>,
872 take_owned: impl FnOnce(&mut TimerRegistry) -> Option<ProviderHandle>,
873) -> Result<Option<ProviderHandle>, TimerError> {
874 detached.map_or_else(
875 || with_registry_mut(|registry| Ok(take_owned(registry))),
876 |handle| Ok(Some(handle)),
877 )
878}
879
880fn arm_wakeup(effect: &RegistryEffect) -> Result<(), TimerError> {
881 let RegistryEffect::ArmWakeup {
882 token, delay_ns, ..
883 } = effect
884 else {
885 return Err(TimerError::OwnershipInvariant);
886 };
887 let task_token = token.clone();
888 let handle = platform::set_timer(Duration::from_nanos(*delay_ns), async move {
889 dispatch_wakeup(task_token).await;
890 });
891 if let Err((error, handle)) = install_provider_handle(token, handle) {
892 platform::clear_timer(handle);
893 return Err(error);
894 }
895 if let Err(error) = confirm_effect(effect) {
896 let handle = with_registry_mut(|registry| {
897 registry
898 .take_wakeup_handle(token.identity())
899 .ok_or(TimerError::OwnershipInvariant)
900 })?;
901 clear_provider_handle(handle);
902 return Err(error);
903 }
904 Ok(())
905}
906
907fn dispatch_watchdog_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
908 let RegistryEffect::DispatchWatchdog {
909 successor,
910 successor_delay_ns,
911 work,
912 ..
913 } = effect
914 else {
915 return Err(TimerError::OwnershipInvariant);
916 };
917 let successor_token = successor.clone();
918 let successor_handle =
919 platform::set_timer(Duration::from_nanos(*successor_delay_ns), async move {
920 dispatch_watchdog_scheduler(&successor_token);
921 });
922 if let Err((error, handle)) = install_provider_handle(successor, successor_handle) {
923 platform::clear_timer(handle);
924 return Err(error);
925 }
926
927 let work_token = work.clone();
928 let work_handle = platform::set_timer(Duration::ZERO, async move {
929 dispatch_watchdog_work(&work_token);
930 });
931 if let Err((error, handle)) = install_provider_handle(work, work_handle) {
932 platform::clear_timer(handle);
933 clear_entry_provider_handles(successor.identity())?;
934 return Err(error);
935 }
936 if let Err(error) = confirm_effect(effect) {
937 clear_entry_provider_handles(successor.identity())?;
938 return Err(error);
939 }
940 Ok(())
941}
942
943fn install_provider_handle(
944 token: &CallbackToken,
945 handle: TimerHandle,
946) -> Result<(), (TimerError, TimerHandle)> {
947 #[cfg(test)]
948 if take_provider_install_fault() {
949 return Err((TimerError::OwnershipInvariant, handle));
950 }
951 RUNTIME.with(|runtime| {
952 let Ok(mut runtime) = runtime.try_borrow_mut() else {
953 return Err((TimerError::RuntimeBusy, handle));
954 };
955 let Some(registry) = runtime.as_mut() else {
956 return Err((TimerError::NotInitialized, handle));
957 };
958 match registry.install_provider_handle(token, handle) {
959 Ok(()) => Ok(()),
960 Err((error, handle)) => Err((TimerError::from(error), handle)),
961 }
962 })
963}
964
965fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
966 #[cfg(test)]
967 if take_provider_confirmation_fault() {
968 return Err(TimerError::OwnershipInvariant);
969 }
970 with_registry_mut(|registry| {
971 registry
972 .confirm_effect_applied(effect)
973 .map_err(TimerError::from)
974 })
975}
976
977fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
978 let wakeup_failure = handles
981 .take_wakeup()
982 .and_then(|handle| restore_provider_handle(handle).err());
983 let work_failure = handles
984 .take_work()
985 .and_then(|handle| restore_provider_handle(handle).err());
986 wakeup_failure.or(work_failure).map_or(Ok(()), Err)
987}
988
989fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
990 let (token, handle) = handle.into_parts();
991 match install_provider_handle(&token, handle) {
992 Ok(()) => Ok(()),
993 Err((error, handle)) => {
994 platform::clear_timer(handle);
995 Err(error)
996 }
997 }
998}
999
1000fn clear_provider_handle(handle: ProviderHandle) {
1001 let (_, handle) = handle.into_parts();
1002 platform::clear_timer(handle);
1003}
1004
1005fn clear_provider_handles(mut handles: ProviderHandles) {
1006 if let Some(wakeup) = handles.take_wakeup() {
1007 clear_provider_handle(wakeup);
1008 }
1009 if let Some(work) = handles.take_work() {
1010 clear_provider_handle(work);
1011 }
1012}
1013
1014fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
1015 let handles = with_registry_mut(|registry| {
1016 Ok(ProviderHandles::from_parts(
1017 registry.take_wakeup_handle(identity),
1018 registry.take_work_handle(identity),
1019 ))
1020 })?;
1021 clear_provider_handles(handles);
1022 Ok(())
1023}
1024
1025#[allow(clippy::future_not_send)] async fn dispatch_wakeup(token: CallbackToken) {
1027 match token.role() {
1028 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
1029 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
1030 CallbackRole::WatchdogWork => {}
1031 }
1032}
1033
1034#[allow(clippy::future_not_send)] async fn dispatch_ordinary(token: CallbackToken) {
1036 let measurement = CallbackMeasurementStart::capture();
1037 let accepted = with_registry_mut(|registry| {
1038 registry.consume_provider_handle(&token);
1039 Ok(registry.begin_ordinary(&token))
1040 });
1041 match accepted {
1042 Ok(CallbackAcceptance::Accepted) => {}
1043 Ok(CallbackAcceptance::Stale) => return,
1044 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
1045 }
1046
1047 let callback = match with_registry(|registry| {
1048 registry.ordinary_callback(&token).map_err(TimerError::from)
1049 }) {
1050 Ok(callback) => callback,
1051 Err(TimerError::OwnershipInvariant) => {
1052 fail_ordinary_dispatch(&token);
1053 return;
1054 }
1055 Err(error) => trap_callback_failure("ordinary callback lookup", &error),
1056 };
1057 let future = {
1058 let Ok(mut callback) = callback.try_borrow_mut() else {
1059 fail_ordinary_dispatch(&token);
1060 return;
1061 };
1062 callback(token.clone())
1063 };
1064 let result = future.await;
1065 let transition = with_registry_mut(|registry| {
1066 registry
1067 .complete_ordinary(&token, platform::time_ns(), result)
1068 .map_err(TimerError::from)
1069 });
1070 let transition = transition
1071 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
1072 finish_callback_transition(&token, transition, ProviderHandles::default());
1073 record_callback_measurements(&token, measurement.finish());
1074}
1075
1076fn fail_ordinary_dispatch(token: &CallbackToken) {
1077 let transition = with_registry_mut(|registry| {
1078 registry
1079 .complete_ordinary(
1080 token,
1081 platform::time_ns(),
1082 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
1083 )
1084 .map_err(TimerError::from)
1085 });
1086 let transition = transition.unwrap_or_else(|error| {
1087 trap_callback_failure("ordinary invariant-failure completion", &error)
1088 });
1089 finish_callback_transition(token, transition, ProviderHandles::default());
1090}
1091
1092fn dispatch_watchdog_scheduler(token: &CallbackToken) {
1093 let measurement = CallbackMeasurementStart::capture();
1094 let transition = with_registry_mut(|registry| {
1095 registry.consume_provider_handle(token);
1096 Ok(registry.begin_watchdog_scheduler(token, platform::time_ns()))
1097 });
1098 let transition = transition
1099 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
1100 let accepted = !matches!(transition.effect(), RegistryEffect::None);
1101 finish_callback_transition(token, transition, ProviderHandles::default());
1102 if accepted {
1103 record_callback_measurements(token, measurement.finish());
1104 }
1105}
1106
1107fn dispatch_watchdog_work(token: &CallbackToken) {
1108 let measurement = CallbackMeasurementStart::capture();
1109 let accepted = with_registry_mut(|registry| {
1110 registry.consume_provider_handle(token);
1111 Ok(registry.begin_watchdog_work(token))
1112 });
1113 match accepted {
1114 Ok(CallbackAcceptance::Accepted) => {}
1115 Ok(CallbackAcceptance::Stale) => return,
1116 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
1117 }
1118
1119 let callback =
1120 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
1121 {
1122 Ok(callback) => callback,
1123 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
1124 };
1125 let result = {
1126 let Ok(mut callback) = callback.try_borrow_mut() else {
1127 trap_callback_failure(
1128 "watchdog callback ownership",
1129 &TimerError::OwnershipInvariant,
1130 );
1131 };
1132 callback(token.clone())
1133 };
1134 finish_watchdog_dispatch(token, result);
1135 record_callback_measurements(token, measurement.finish());
1136}
1137
1138fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
1139 let claim = RegistrationClaim::from_callback(token);
1140 let completed = with_registry_mut(|registry| {
1145 let handles = registry
1146 .take_provider_handles_for_claim(&claim)
1147 .map_err(TimerError::from)?;
1148 #[cfg(test)]
1149 {
1150 if take_watchdog_completion_fault() {
1151 return Err(TimerError::OwnershipInvariant);
1152 }
1153 }
1154 let transition = registry
1155 .complete_watchdog_work(token, platform::time_ns(), result)
1156 .map_err(TimerError::from)?;
1157 Ok((transition, handles))
1158 });
1159 let (transition, handles) =
1160 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
1161 finish_callback_transition(token, transition, handles);
1162}
1163
1164fn finish_callback_transition(
1165 token: &CallbackToken,
1166 transition: RegistryTransition,
1167 handles: ProviderHandles,
1168) {
1169 match finish_transition(transition, handles) {
1170 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
1171 Err(
1172 error @ (TimerError::NotInitialized
1173 | TimerError::RuntimeBusy
1174 | TimerError::Register(_)
1175 | TimerError::Schedule(_)
1176 | TimerError::RegistrationExpired
1177 | TimerError::OwnershipInvariant
1178 | TimerError::ReconciliationConflict),
1179 ) => {
1180 if token.role() == CallbackRole::WatchdogWork {
1181 trap_callback_failure("watchdog provider-handle completion", &error);
1182 }
1183 fail_provider_binding(token).unwrap_or_else(|binding_error| {
1184 trap_callback_failure("provider-binding failure cleanup", &binding_error)
1185 });
1186 }
1187 }
1188}
1189
1190fn fail_provider_binding(token: &CallbackToken) -> Result<(), TimerError> {
1191 let claim = RegistrationClaim::from_callback(token);
1192 fail_claim_provider_binding(&claim)
1193}
1194
1195fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
1196 let failed = with_registry_mut(|registry| {
1197 registry
1198 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
1199 .map_err(TimerError::from)
1200 });
1201 clear_provider_handles(failed?);
1202 Ok(())
1203}
1204
1205#[derive(Clone, Copy)]
1206struct CallbackMeasurementStart {
1207 instructions_before: u64,
1208 memory_start: platform::MemoryPages,
1209}
1210
1211impl CallbackMeasurementStart {
1212 fn capture() -> Self {
1213 let memory_start = platform::memory_pages();
1215 let instructions_before = platform::instruction_counter();
1216 Self {
1217 instructions_before,
1218 memory_start,
1219 }
1220 }
1221
1222 fn finish(self) -> CallbackMeasurement {
1223 let instructions = platform::instruction_counter().saturating_sub(self.instructions_before);
1225 let memory_end = platform::memory_pages();
1226 CallbackMeasurement {
1227 instructions,
1228 memory_start: self.memory_start,
1229 memory_end,
1230 }
1231 }
1232}
1233
1234#[derive(Clone, Copy)]
1235struct CallbackMeasurement {
1236 instructions: u64,
1237 memory_start: platform::MemoryPages,
1238 memory_end: platform::MemoryPages,
1239}
1240
1241fn record_callback_measurements(token: &CallbackToken, measurement: CallbackMeasurement) {
1242 with_registry_mut(|registry| {
1243 registry
1244 .record_callback_measurements(
1245 token,
1246 measurement.instructions,
1247 measurement.memory_start,
1248 measurement.memory_end,
1249 )
1250 .map_err(TimerError::from)
1251 })
1252 .unwrap_or_else(|error| trap_callback_failure("callback measurement accounting", &error));
1253}
1254
1255fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
1256 platform::trap(&format!("ic-timers {context} failed: {error}"))
1257}
1258
1259fn with_registry<T>(
1260 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
1261) -> Result<T, TimerError> {
1262 RUNTIME.with(|runtime| {
1263 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1264 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1265 operation(registry)
1266 })
1267}
1268
1269fn with_registry_mut<T>(
1270 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1271) -> Result<T, TimerError> {
1272 RUNTIME.with(|runtime| {
1273 let mut runtime = runtime
1274 .try_borrow_mut()
1275 .map_err(|_| TimerError::RuntimeBusy)?;
1276 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1277 operation(registry)
1278 })
1279}
1280
1281#[cfg(test)]
1282fn reset_for_test(now_ns: u64, canister_version: u64) {
1283 platform::reset(now_ns, canister_version);
1284 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1285 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(None));
1286 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(false));
1287 RUNTIME.with(|runtime| {
1288 *runtime.borrow_mut() = None;
1289 });
1290}
1291
1292#[cfg(test)]
1293thread_local! {
1294 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1295 static PROVIDER_INSTALL_FAULT_AFTER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1296 static PROVIDER_CONFIRMATION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1297}
1298
1299#[cfg(test)]
1300fn inject_watchdog_completion_fault() {
1301 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1302}
1303
1304#[cfg(test)]
1305fn take_watchdog_completion_fault() -> bool {
1306 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1307}
1308
1309#[cfg(test)]
1310fn inject_provider_install_fault() {
1311 inject_provider_install_fault_after(0);
1312}
1313
1314#[cfg(test)]
1315fn inject_provider_install_fault_after(successful_installs: u64) {
1316 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(Some(successful_installs)));
1317}
1318
1319#[cfg(test)]
1320fn take_provider_install_fault() -> bool {
1321 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| match fault.get() {
1322 Some(0) => {
1323 fault.set(None);
1324 true
1325 }
1326 Some(remaining) => {
1327 fault.set(Some(remaining - 1));
1328 false
1329 }
1330 None => false,
1331 })
1332}
1333
1334#[cfg(test)]
1335fn inject_provider_confirmation_fault() {
1336 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(true));
1337}
1338
1339#[cfg(test)]
1340fn take_provider_confirmation_fault() -> bool {
1341 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.replace(false))
1342}
1343
1344#[cfg(test)]
1345mod tests;