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 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_inventory() -> Result<TimerInventorySnapshot, TimerError> {
655 with_registry(|registry| Ok(registry.inventory()))
656}
657
658pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
660 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
661}
662
663fn has_armed_wakeup_claim(claim: &RegistrationClaim) -> Result<bool, TimerError> {
664 with_registry(|registry| registry.has_armed_wakeup(claim).map_err(TimerError::from))
665}
666
667fn erase_ordinary_callback<Context: 'static, F, Fut>(
668 mut callback: F,
669 context: fn(CallbackToken) -> Context,
670) -> OrdinaryCallback
671where
672 F: FnMut(Context) -> Fut + 'static,
673 Fut: Future<Output = TimerRunResult> + 'static,
674{
675 Rc::new(RefCell::new(Box::new(move |token| {
676 Box::pin(callback(context(token)))
677 })))
678}
679
680fn apply_claim_transition(
681 claim: &RegistrationClaim,
682 context: Option<&CallbackToken>,
683 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
684) -> Result<(), TimerError> {
685 let transition = with_registry_mut(|registry| {
686 validate_context(registry, context)?;
687 operation(registry).map_err(TimerError::from)
688 })?;
689 finish_claim_transition(claim, transition, ProviderHandles::default())
690}
691
692fn ensure_once_claim(
693 claim: &RegistrationClaim,
694 context: Option<&CallbackToken>,
695 schedule: TimerSchedule,
696) -> Result<(), TimerError> {
697 apply_claim_transition(claim, context, |registry| {
698 registry.ensure_once(claim, platform::time_ns(), schedule)
699 })
700}
701
702fn reconcile_ordinary_claim(
703 claim: &RegistrationClaim,
704 context: Option<&CallbackToken>,
705 schedule: Option<TimerSchedule>,
706) -> Result<(), TimerError> {
707 if schedule.is_none() {
708 let (handles, transition) = with_registry_mut(|registry| {
709 validate_context(registry, context)?;
710 registry
711 .validate_ordinary_claim(claim)
712 .map_err(TimerError::from)?;
713 let handles = registry
714 .take_provider_handles_for_claim(claim)
715 .map_err(TimerError::from)?;
716 let transition = registry
717 .reconcile_ordinary(claim, platform::time_ns(), None)
718 .map_err(TimerError::from);
719 Ok((handles, transition))
720 })?;
721 return finish_detached_claim_transition(claim, handles, transition);
722 }
723 apply_claim_transition(claim, context, |registry| {
724 registry.reconcile_ordinary(claim, platform::time_ns(), schedule)
725 })
726}
727
728fn ensure_recurring_claim(
729 claim: &RegistrationClaim,
730 context: Option<&CallbackToken>,
731) -> Result<(), TimerError> {
732 apply_claim_transition(claim, context, |registry| {
733 registry.ensure_recurring(claim, platform::time_ns())
734 })
735}
736
737fn cancel_claim(
738 claim: &RegistrationClaim,
739 context: Option<&CallbackToken>,
740) -> Result<(), TimerError> {
741 apply_detached_claim_transition(claim, context, |registry| registry.cancel(claim))
742}
743
744fn validate_context(
745 registry: &TimerRegistry,
746 context: Option<&CallbackToken>,
747) -> Result<(), TimerError> {
748 context.map_or(Ok(()), |token| {
749 registry
750 .validate_running_context(token)
751 .map_err(TimerError::from)
752 })
753}
754
755fn unregister_claim(claim: &RegistrationClaim) -> Result<(), TimerError> {
756 apply_detached_claim_transition(claim, None, |registry| registry.unregister(claim))
757}
758
759fn apply_detached_claim_transition(
760 claim: &RegistrationClaim,
761 context: Option<&CallbackToken>,
762 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
763) -> Result<(), TimerError> {
764 let (handles, transition) = with_registry_mut(|registry| {
765 validate_context(registry, context)?;
766 let handles = registry
767 .take_provider_handles_for_claim(claim)
768 .map_err(TimerError::from)?;
769 let transition = operation(registry).map_err(TimerError::from);
770 Ok((handles, transition))
771 })?;
772 finish_detached_claim_transition(claim, handles, transition)
773}
774
775fn finish_detached_claim_transition(
776 claim: &RegistrationClaim,
777 handles: ProviderHandles,
778 transition: Result<RegistryTransition, TimerError>,
779) -> Result<(), TimerError> {
780 match transition {
781 Ok(transition) => finish_claim_transition(claim, transition, handles),
782 Err(error) => match restore_provider_handles(handles) {
783 Ok(()) => Err(error),
784 Err(restoration_error) => retire_failed_claim(claim, restoration_error),
785 },
786 }
787}
788
789fn finish_claim_transition(
790 claim: &RegistrationClaim,
791 transition: RegistryTransition,
792 handles: ProviderHandles,
793) -> Result<(), TimerError> {
794 match finish_transition(transition, handles) {
795 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
796 Err(error) => retire_failed_claim(claim, error),
797 }
798}
799
800fn retire_failed_claim(claim: &RegistrationClaim, error: TimerError) -> Result<(), TimerError> {
801 match fail_claim_provider_binding(claim) {
802 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
803 Err(cleanup_error) => Err(cleanup_error),
804 }
805}
806
807fn finish_transition(
808 transition: RegistryTransition,
809 handles: ProviderHandles,
810) -> Result<(), TimerError> {
811 let failure = transition.failure();
812 let effect = transition.into_effect();
813 apply_effect(&effect, handles)?;
814 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
815}
816
817fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
818 if !effect.has_valid_shape() {
819 clear_provider_handles(handles);
820 return Err(TimerError::OwnershipInvariant);
821 }
822 match effect {
823 RegistryEffect::None => restore_provider_handles(handles),
824 RegistryEffect::ArmWakeup { token, arm, .. } => {
825 if arm.replaces_existing() {
826 let replaced = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
827 registry.take_wakeup_handle(token.identity())
828 })?;
829 if let Some(replaced) = replaced {
830 clear_provider_handle(replaced);
831 }
832 }
833 restore_provider_handles(handles)?;
834 arm_wakeup(effect)
835 }
836 RegistryEffect::ClearCallbacks {
837 identity,
838 handles: selected,
839 } => {
840 if selected.includes_wakeup() {
841 let wakeup = take_detached_or_owned_handle(handles.take_wakeup(), |registry| {
842 registry.take_wakeup_handle(identity)
843 })?;
844 if let Some(wakeup) = wakeup {
845 clear_provider_handle(wakeup);
846 }
847 }
848 if selected.includes_work() {
849 let work = take_detached_or_owned_handle(handles.take_work(), |registry| {
850 registry.take_work_handle(identity)
851 })?;
852 if let Some(work) = work {
853 clear_provider_handle(work);
854 }
855 }
856 restore_provider_handles(handles)
857 }
858 RegistryEffect::DispatchWatchdog { successor, .. } => {
859 if let Some(wakeup) = handles.take_wakeup() {
860 clear_provider_handle(wakeup);
861 }
862 let replaced_work = take_detached_or_owned_handle(handles.take_work(), |registry| {
863 registry.take_work_handle(successor.identity())
864 })?;
865 if let Some(replaced_work) = replaced_work {
866 clear_provider_handle(replaced_work);
867 }
868 dispatch_watchdog_effect(effect)
869 }
870 }
871}
872
873fn take_detached_or_owned_handle(
874 detached: Option<ProviderHandle>,
875 take_owned: impl FnOnce(&mut TimerRegistry) -> Option<ProviderHandle>,
876) -> Result<Option<ProviderHandle>, TimerError> {
877 detached.map_or_else(
878 || with_registry_mut(|registry| Ok(take_owned(registry))),
879 |handle| Ok(Some(handle)),
880 )
881}
882
883fn arm_wakeup(effect: &RegistryEffect) -> Result<(), TimerError> {
884 let RegistryEffect::ArmWakeup {
885 token, delay_ns, ..
886 } = effect
887 else {
888 return Err(TimerError::OwnershipInvariant);
889 };
890 let task_token = token.clone();
891 let handle = platform::set_timer(Duration::from_nanos(*delay_ns), async move {
892 dispatch_wakeup(task_token).await;
893 });
894 if let Err((error, handle)) = install_provider_handle(token, handle) {
895 platform::clear_timer(handle);
896 return Err(error);
897 }
898 if let Err(error) = confirm_effect(effect) {
899 let handle = with_registry_mut(|registry| {
900 registry
901 .take_wakeup_handle(token.identity())
902 .ok_or(TimerError::OwnershipInvariant)
903 })?;
904 clear_provider_handle(handle);
905 return Err(error);
906 }
907 Ok(())
908}
909
910fn dispatch_watchdog_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
911 let RegistryEffect::DispatchWatchdog {
912 successor,
913 successor_delay_ns,
914 work,
915 ..
916 } = effect
917 else {
918 return Err(TimerError::OwnershipInvariant);
919 };
920 let successor_token = successor.clone();
921 let successor_handle =
922 platform::set_timer(Duration::from_nanos(*successor_delay_ns), async move {
923 dispatch_watchdog_scheduler(&successor_token);
924 });
925 if let Err((error, handle)) = install_provider_handle(successor, successor_handle) {
926 platform::clear_timer(handle);
927 return Err(error);
928 }
929
930 let work_token = work.clone();
931 let work_handle = platform::set_timer(Duration::ZERO, async move {
932 dispatch_watchdog_work(&work_token);
933 });
934 if let Err((error, handle)) = install_provider_handle(work, work_handle) {
935 platform::clear_timer(handle);
936 clear_entry_provider_handles(successor.identity())?;
937 return Err(error);
938 }
939 if let Err(error) = confirm_effect(effect) {
940 clear_entry_provider_handles(successor.identity())?;
941 return Err(error);
942 }
943 Ok(())
944}
945
946fn install_provider_handle(
947 token: &CallbackToken,
948 handle: TimerHandle,
949) -> Result<(), (TimerError, TimerHandle)> {
950 #[cfg(test)]
951 if take_provider_install_fault() {
952 return Err((TimerError::OwnershipInvariant, handle));
953 }
954 RUNTIME.with(|runtime| {
955 let Ok(mut runtime) = runtime.try_borrow_mut() else {
956 return Err((TimerError::RuntimeBusy, handle));
957 };
958 let Some(registry) = runtime.as_mut() else {
959 return Err((TimerError::NotInitialized, handle));
960 };
961 match registry.install_provider_handle(token, handle) {
962 Ok(()) => Ok(()),
963 Err((error, handle)) => Err((TimerError::from(error), handle)),
964 }
965 })
966}
967
968fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
969 #[cfg(test)]
970 if take_provider_confirmation_fault() {
971 return Err(TimerError::OwnershipInvariant);
972 }
973 with_registry_mut(|registry| {
974 registry
975 .confirm_effect_applied(effect)
976 .map_err(TimerError::from)
977 })
978}
979
980fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
981 let wakeup_failure = handles
984 .take_wakeup()
985 .and_then(|handle| restore_provider_handle(handle).err());
986 let work_failure = handles
987 .take_work()
988 .and_then(|handle| restore_provider_handle(handle).err());
989 wakeup_failure.or(work_failure).map_or(Ok(()), Err)
990}
991
992fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
993 let (token, handle) = handle.into_parts();
994 match install_provider_handle(&token, handle) {
995 Ok(()) => Ok(()),
996 Err((error, handle)) => {
997 platform::clear_timer(handle);
998 Err(error)
999 }
1000 }
1001}
1002
1003fn clear_provider_handle(handle: ProviderHandle) {
1004 let (_, handle) = handle.into_parts();
1005 platform::clear_timer(handle);
1006}
1007
1008fn clear_provider_handles(mut handles: ProviderHandles) {
1009 if let Some(wakeup) = handles.take_wakeup() {
1010 clear_provider_handle(wakeup);
1011 }
1012 if let Some(work) = handles.take_work() {
1013 clear_provider_handle(work);
1014 }
1015}
1016
1017fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
1018 let handles = with_registry_mut(|registry| {
1019 Ok(ProviderHandles::from_parts(
1020 registry.take_wakeup_handle(identity),
1021 registry.take_work_handle(identity),
1022 ))
1023 })?;
1024 clear_provider_handles(handles);
1025 Ok(())
1026}
1027
1028#[allow(clippy::future_not_send)] async fn dispatch_wakeup(token: CallbackToken) {
1030 match token.role() {
1031 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
1032 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
1033 CallbackRole::WatchdogWork => {}
1034 }
1035}
1036
1037#[allow(clippy::future_not_send)] async fn dispatch_ordinary(token: CallbackToken) {
1039 let measurement = CallbackMeasurementStart::capture();
1040 let accepted = with_registry_mut(|registry| {
1041 registry.consume_provider_handle(&token);
1042 Ok(registry.begin_ordinary(&token))
1043 });
1044 match accepted {
1045 Ok(CallbackAcceptance::Accepted) => {}
1046 Ok(CallbackAcceptance::Stale) => return,
1047 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
1048 }
1049
1050 let callback = match with_registry(|registry| {
1051 registry.ordinary_callback(&token).map_err(TimerError::from)
1052 }) {
1053 Ok(callback) => callback,
1054 Err(TimerError::OwnershipInvariant) => {
1055 fail_ordinary_dispatch(&token);
1056 return;
1057 }
1058 Err(error) => trap_callback_failure("ordinary callback lookup", &error),
1059 };
1060 let future = {
1061 let Ok(mut callback) = callback.try_borrow_mut() else {
1062 fail_ordinary_dispatch(&token);
1063 return;
1064 };
1065 callback(token.clone())
1066 };
1067 let result = future.await;
1068 let transition = with_registry_mut(|registry| {
1069 registry
1070 .complete_ordinary(&token, platform::time_ns(), result)
1071 .map_err(TimerError::from)
1072 });
1073 let transition = transition
1074 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
1075 finish_callback_transition(&token, transition, ProviderHandles::default());
1076 record_callback_measurements(&token, measurement.finish());
1077}
1078
1079fn fail_ordinary_dispatch(token: &CallbackToken) {
1080 let transition = with_registry_mut(|registry| {
1081 registry
1082 .complete_ordinary(
1083 token,
1084 platform::time_ns(),
1085 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
1086 )
1087 .map_err(TimerError::from)
1088 });
1089 let transition = transition.unwrap_or_else(|error| {
1090 trap_callback_failure("ordinary invariant-failure completion", &error)
1091 });
1092 finish_callback_transition(token, transition, ProviderHandles::default());
1093}
1094
1095fn dispatch_watchdog_scheduler(token: &CallbackToken) {
1096 let measurement = CallbackMeasurementStart::capture();
1097 let transition = with_registry_mut(|registry| {
1098 registry.consume_provider_handle(token);
1099 Ok(registry.begin_watchdog_scheduler(token, platform::time_ns()))
1100 });
1101 let transition = transition
1102 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
1103 let accepted = !matches!(transition.effect(), RegistryEffect::None);
1104 finish_callback_transition(token, transition, ProviderHandles::default());
1105 if accepted {
1106 record_callback_measurements(token, measurement.finish());
1107 }
1108}
1109
1110fn dispatch_watchdog_work(token: &CallbackToken) {
1111 let measurement = CallbackMeasurementStart::capture();
1112 let accepted = with_registry_mut(|registry| {
1113 registry.consume_provider_handle(token);
1114 Ok(registry.begin_watchdog_work(token))
1115 });
1116 match accepted {
1117 Ok(CallbackAcceptance::Accepted) => {}
1118 Ok(CallbackAcceptance::Stale) => return,
1119 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
1120 }
1121
1122 let callback =
1123 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
1124 {
1125 Ok(callback) => callback,
1126 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
1127 };
1128 let result = {
1129 let Ok(mut callback) = callback.try_borrow_mut() else {
1130 trap_callback_failure(
1131 "watchdog callback ownership",
1132 &TimerError::OwnershipInvariant,
1133 );
1134 };
1135 callback(token.clone())
1136 };
1137 finish_watchdog_dispatch(token, result);
1138 record_callback_measurements(token, measurement.finish());
1139}
1140
1141fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
1142 let claim = RegistrationClaim::from_callback(token);
1143 let completed = with_registry_mut(|registry| {
1148 let handles = registry
1149 .take_provider_handles_for_claim(&claim)
1150 .map_err(TimerError::from)?;
1151 #[cfg(test)]
1152 {
1153 if take_watchdog_completion_fault() {
1154 return Err(TimerError::OwnershipInvariant);
1155 }
1156 }
1157 let transition = registry
1158 .complete_watchdog_work(token, platform::time_ns(), result)
1159 .map_err(TimerError::from)?;
1160 Ok((transition, handles))
1161 });
1162 let (transition, handles) =
1163 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
1164 finish_callback_transition(token, transition, handles);
1165}
1166
1167fn finish_callback_transition(
1168 token: &CallbackToken,
1169 transition: RegistryTransition,
1170 handles: ProviderHandles,
1171) {
1172 match finish_transition(transition, handles) {
1173 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
1174 Err(
1175 error @ (TimerError::NotInitialized
1176 | TimerError::RuntimeBusy
1177 | TimerError::Register(_)
1178 | TimerError::Schedule(_)
1179 | TimerError::RegistrationExpired
1180 | TimerError::OwnershipInvariant
1181 | TimerError::ReconciliationConflict),
1182 ) => {
1183 if token.role() == CallbackRole::WatchdogWork {
1184 trap_callback_failure("watchdog provider-handle completion", &error);
1185 }
1186 fail_provider_binding(token).unwrap_or_else(|binding_error| {
1187 trap_callback_failure("provider-binding failure cleanup", &binding_error)
1188 });
1189 }
1190 }
1191}
1192
1193fn fail_provider_binding(token: &CallbackToken) -> Result<(), TimerError> {
1194 let claim = RegistrationClaim::from_callback(token);
1195 fail_claim_provider_binding(&claim)
1196}
1197
1198fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
1199 let failed = with_registry_mut(|registry| {
1200 registry
1201 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
1202 .map_err(TimerError::from)
1203 });
1204 clear_provider_handles(failed?);
1205 Ok(())
1206}
1207
1208#[derive(Clone, Copy)]
1209struct CallbackMeasurementStart {
1210 instructions_before: u64,
1211 memory_start: platform::MemoryPages,
1212}
1213
1214impl CallbackMeasurementStart {
1215 fn capture() -> Self {
1216 let memory_start = platform::memory_pages();
1218 let instructions_before = platform::instruction_counter();
1219 Self {
1220 instructions_before,
1221 memory_start,
1222 }
1223 }
1224
1225 fn finish(self) -> CallbackMeasurement {
1226 let instructions = platform::instruction_counter().saturating_sub(self.instructions_before);
1228 let memory_end = platform::memory_pages();
1229 CallbackMeasurement {
1230 instructions,
1231 memory_start: self.memory_start,
1232 memory_end,
1233 }
1234 }
1235}
1236
1237#[derive(Clone, Copy)]
1238struct CallbackMeasurement {
1239 instructions: u64,
1240 memory_start: platform::MemoryPages,
1241 memory_end: platform::MemoryPages,
1242}
1243
1244fn record_callback_measurements(token: &CallbackToken, measurement: CallbackMeasurement) {
1245 with_registry_mut(|registry| {
1246 registry
1247 .record_callback_measurements(
1248 token,
1249 measurement.instructions,
1250 measurement.memory_start,
1251 measurement.memory_end,
1252 )
1253 .map_err(TimerError::from)
1254 })
1255 .unwrap_or_else(|error| trap_callback_failure("callback measurement accounting", &error));
1256}
1257
1258fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
1259 platform::trap(&format!("ic-timers {context} failed: {error}"))
1260}
1261
1262fn with_registry<T>(
1263 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
1264) -> Result<T, TimerError> {
1265 RUNTIME.with(|runtime| {
1266 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1267 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1268 operation(registry)
1269 })
1270}
1271
1272fn with_registry_mut<T>(
1273 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1274) -> Result<T, TimerError> {
1275 RUNTIME.with(|runtime| {
1276 let mut runtime = runtime
1277 .try_borrow_mut()
1278 .map_err(|_| TimerError::RuntimeBusy)?;
1279 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1280 operation(registry)
1281 })
1282}
1283
1284#[cfg(test)]
1285fn reset_for_test(now_ns: u64, canister_version: u64) {
1286 platform::reset(now_ns, canister_version);
1287 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1288 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(None));
1289 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(false));
1290 RUNTIME.with(|runtime| {
1291 *runtime.borrow_mut() = None;
1292 });
1293}
1294
1295#[cfg(test)]
1296thread_local! {
1297 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1298 static PROVIDER_INSTALL_FAULT_AFTER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1299 static PROVIDER_CONFIRMATION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1300}
1301
1302#[cfg(test)]
1303fn inject_watchdog_completion_fault() {
1304 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1305}
1306
1307#[cfg(test)]
1308fn take_watchdog_completion_fault() -> bool {
1309 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1310}
1311
1312#[cfg(test)]
1313fn inject_provider_install_fault() {
1314 inject_provider_install_fault_after(0);
1315}
1316
1317#[cfg(test)]
1318fn inject_provider_install_fault_after(successful_installs: u64) {
1319 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(Some(successful_installs)));
1320}
1321
1322#[cfg(test)]
1323fn take_provider_install_fault() -> bool {
1324 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| match fault.get() {
1325 Some(0) => {
1326 fault.set(None);
1327 true
1328 }
1329 Some(remaining) => {
1330 fault.set(Some(remaining - 1));
1331 false
1332 }
1333 None => false,
1334 })
1335}
1336
1337#[cfg(test)]
1338fn inject_provider_confirmation_fault() {
1339 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(true));
1340}
1341
1342#[cfg(test)]
1343fn take_provider_confirmation_fault() -> bool {
1344 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.replace(false))
1345}
1346
1347#[cfg(test)]
1348mod tests;