1use crate::{
4 callback::{AfterCompletionRunResult, OnceRunResult, OrdinaryRunResult},
5 platform::{self, TimerHandle},
6 registry::{
7 CallbackRole, CallbackToken, OrdinaryCallback, ProviderHandle, ProviderHandles,
8 RegisterError, RegistrationClaim, RegistryEffect, RegistryError, RegistryTransition,
9 TimerRegistry, WakeupToken, WatchdogCallback,
10 },
11 schedule::{OrdinaryDirective, ScheduleError, TimerCadence, TimerSchedule},
12 snapshot::{
13 DeclarationLifetime, MemoryPageExtent, TimerCompletion, TimerControlFailure, TimerEpoch,
14 TimerIdentity, TimerInventorySnapshot, TimerPolicy, TimerSnapshot, WatchdogRunResult,
15 },
16};
17use std::{cell::RefCell, future::Future, rc::Rc, time::Duration};
18use thiserror::Error;
19
20thread_local! {
21 static RUNTIME: RefCell<Option<TimerRegistry>> = const { RefCell::new(None) };
22}
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum TimerStart {
38 Retained {
40 initial: Option<TimerSchedule>,
42 },
43 RemoveWhenStopped {
45 initial: TimerSchedule,
47 },
48}
49
50impl TimerStart {
51 const fn into_parts(self) -> (DeclarationLifetime, Option<TimerSchedule>) {
52 match self {
53 Self::Retained { initial } => (DeclarationLifetime::Retained, initial),
54 Self::RemoveWhenStopped { initial } => {
55 (DeclarationLifetime::RemoveWhenStopped, Some(initial))
56 }
57 }
58 }
59}
60
61#[non_exhaustive]
63#[derive(Debug, Error)]
64pub enum TimerError {
65 #[error("timer runtime is not initialized")]
67 NotInitialized,
68 #[error("timer runtime is already borrowed")]
70 RuntimeBusy,
71 #[error(transparent)]
73 Register(#[from] RegisterError),
74 #[error(transparent)]
76 Schedule(#[from] ScheduleError),
77 #[error("timer registration is no longer authoritative")]
79 RegistrationExpired,
80 #[error("timer control failed: {0:?}")]
82 ControlFailure(TimerControlFailure),
83 #[error("timer runtime ownership invariant failed")]
85 OwnershipInvariant,
86 #[error("timer backend pending capacity exhausted")]
88 BackendCapacity,
89 #[error("timer backend callback identities exhausted")]
91 BackendIdentityExhausted,
92 #[error("timer lifecycle reconciliation conflicts with the canonical declaration")]
94 ReconciliationConflict,
95}
96
97impl From<platform::AdmissionError> for TimerError {
98 fn from(error: platform::AdmissionError) -> Self {
99 match error {
100 platform::AdmissionError::Capacity => Self::BackendCapacity,
101 platform::AdmissionError::IdentityExhausted => Self::BackendIdentityExhausted,
102 platform::AdmissionError::DeadlineOverflow => {
103 Self::Schedule(ScheduleError::DeadlineOverflow)
104 }
105 }
106 }
107}
108
109impl From<RegistryError> for TimerError {
110 fn from(value: RegistryError) -> Self {
111 match value {
112 RegistryError::UnknownRegistration
113 | RegistryError::StaleRegistration
114 | RegistryError::StaleCallback => Self::RegistrationExpired,
115 RegistryError::PolicyMismatch { .. } | RegistryError::ProviderHandleAlreadyOwned => {
116 Self::OwnershipInvariant
117 }
118 RegistryError::Schedule(error) => Self::Schedule(error),
119 }
120 }
121}
122
123pub fn initialize_runtime() -> Result<TimerEpoch, TimerError> {
128 RUNTIME.with(|runtime| {
129 let mut runtime = runtime
130 .try_borrow_mut()
131 .map_err(|_| TimerError::RuntimeBusy)?;
132 let registry = runtime.get_or_insert_with(|| {
133 TimerRegistry::new(TimerEpoch::new(
134 platform::canister_version(),
135 platform::time_ns(),
136 ))
137 });
138 Ok(registry.epoch())
139 })
140}
141
142pub struct OnceContext {
148 token: CallbackToken,
149}
150
151impl OnceContext {
152 const fn new(token: CallbackToken) -> Self {
153 Self { token }
154 }
155
156 #[must_use]
158 pub const fn identity(&self) -> &TimerIdentity {
159 self.token.identity()
160 }
161
162 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
167 ensure_once_claim(self.token.claim(), Some(&self.token), schedule)
168 }
169
170 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
180 reconcile_ordinary_claim(self.token.claim(), Some(&self.token), schedule)
181 }
182
183 pub fn cancel(&self) -> Result<(), TimerError> {
191 cancel_claim(self.token.claim(), Some(&self.token))
192 }
193}
194
195pub struct AfterCompletionContext {
202 token: CallbackToken,
203}
204
205impl AfterCompletionContext {
206 const fn new(token: CallbackToken) -> Self {
207 Self { token }
208 }
209
210 #[must_use]
212 pub const fn identity(&self) -> &TimerIdentity {
213 self.token.identity()
214 }
215
216 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
218 ensure_recurring_claim(self.token.claim(), Some(&self.token))
219 }
220
221 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
232 reconcile_ordinary_claim(self.token.claim(), Some(&self.token), schedule)
233 }
234
235 pub fn cancel(&self) -> Result<(), TimerError> {
243 cancel_claim(self.token.claim(), Some(&self.token))
244 }
245}
246
247pub struct WatchdogContext {
252 token: CallbackToken,
253}
254
255impl WatchdogContext {
256 const fn new(token: CallbackToken) -> Self {
257 Self { token }
258 }
259
260 #[must_use]
262 pub const fn identity(&self) -> &TimerIdentity {
263 self.token.identity()
264 }
265
266 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
272 ensure_recurring_claim(self.token.claim(), Some(&self.token))
273 }
274
275 pub fn ensure_scheduled_immediately(&self) -> Result<(), TimerError> {
282 ensure_watchdog_immediately_claim(self.token.claim(), Some(&self.token))
283 }
284
285 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
291 reconcile_watchdog_claim(self.token.claim(), Some(&self.token), schedule)
292 }
293
294 pub fn cancel(&self) -> Result<(), TimerError> {
302 cancel_claim(self.token.claim(), Some(&self.token))
303 }
304}
305
306#[must_use = "retain the registration claim so the timer remains controllable"]
308pub struct OnceRegistration {
309 claim: RegistrationClaim,
310}
311
312impl OnceRegistration {
313 #[must_use]
315 pub const fn identity(&self) -> &TimerIdentity {
316 self.claim.identity()
317 }
318
319 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
325 has_armed_wakeup_claim(&self.claim)
326 }
327
328 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
330 ensure_once_claim(&self.claim, None, schedule)
331 }
332
333 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
343 reconcile_ordinary_claim(&self.claim, None, schedule)
344 }
345
346 pub fn cancel(&self) -> Result<(), TimerError> {
353 cancel_claim(&self.claim, None)
354 }
355
356 pub fn unregister(self) -> Result<(), TimerError> {
361 unregister_claim(&self.claim)
362 }
363}
364
365#[must_use = "retain the registration claim so the timer remains controllable"]
368pub struct AfterCompletionRegistration {
369 claim: RegistrationClaim,
370}
371
372#[must_use = "retain the registration claim so the timer remains controllable"]
374pub struct WatchdogRegistration {
375 claim: RegistrationClaim,
376}
377
378trait RegistrationClaimOwner {
379 fn registration_claim(&self) -> &RegistrationClaim;
380}
381
382impl RegistrationClaimOwner for OnceRegistration {
383 fn registration_claim(&self) -> &RegistrationClaim {
384 &self.claim
385 }
386}
387
388impl RegistrationClaimOwner for AfterCompletionRegistration {
389 fn registration_claim(&self) -> &RegistrationClaim {
390 &self.claim
391 }
392}
393
394impl RegistrationClaimOwner for WatchdogRegistration {
395 fn registration_claim(&self) -> &RegistrationClaim {
396 &self.claim
397 }
398}
399
400#[derive(Clone, Copy, Debug, Eq, PartialEq)]
402pub enum TimerReconcileState {
403 Inactive,
405 Scheduled,
407}
408
409#[derive(Clone, Copy, Debug, Eq, PartialEq)]
411pub enum WatchdogReconcileState {
412 Inactive,
414 Scheduled,
416 ScheduledImmediately,
421 ScheduledAt(u64),
423}
424
425impl WatchdogRegistration {
426 #[must_use]
428 pub const fn identity(&self) -> &TimerIdentity {
429 self.claim.identity()
430 }
431
432 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
439 has_armed_wakeup_claim(&self.claim)
440 }
441
442 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
446 ensure_recurring_claim(&self.claim, None)
447 }
448
449 pub fn ensure_scheduled_immediately(&self) -> Result<(), TimerError> {
457 ensure_watchdog_immediately_claim(&self.claim, None)
458 }
459
460 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
468 reconcile_watchdog_claim(&self.claim, None, schedule)
469 }
470
471 pub fn cancel(&self) -> Result<(), TimerError> {
479 cancel_claim(&self.claim, None)
480 }
481
482 pub fn unregister(self) -> Result<(), TimerError> {
487 unregister_claim(&self.claim)
488 }
489}
490
491impl AfterCompletionRegistration {
492 #[must_use]
494 pub const fn identity(&self) -> &TimerIdentity {
495 self.claim.identity()
496 }
497
498 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
504 has_armed_wakeup_claim(&self.claim)
505 }
506
507 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
510 ensure_recurring_claim(&self.claim, None)
511 }
512
513 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
522 reconcile_ordinary_claim(&self.claim, None, schedule)
523 }
524
525 pub fn cancel(&self) -> Result<(), TimerError> {
532 cancel_claim(&self.claim, None)
533 }
534
535 pub fn unregister(self) -> Result<(), TimerError> {
540 unregister_claim(&self.claim)
541 }
542}
543
544pub fn register_once<F, Fut>(
564 identity: TimerIdentity,
565 start: TimerStart,
566 callback: F,
567) -> Result<OnceRegistration, TimerError>
568where
569 F: FnMut(OnceContext) -> Fut + 'static,
570 Fut: Future<Output = OnceRunResult> + 'static,
571{
572 let callback = erase_ordinary_callback(callback, OnceContext::new, OrdinaryRunResult::from);
573 let claim = register_initial(start, |registry, lifetime| {
574 registry.register_once_with_callback(identity, lifetime, Rc::clone(&callback))
575 });
576 drop(callback);
578 Ok(OnceRegistration { claim: claim? })
579}
580
581pub fn register_after_completion<F, Fut>(
599 identity: TimerIdentity,
600 cadence: TimerCadence,
601 start: TimerStart,
602 callback: F,
603) -> Result<AfterCompletionRegistration, TimerError>
604where
605 F: FnMut(AfterCompletionContext) -> Fut + 'static,
606 Fut: Future<Output = AfterCompletionRunResult> + 'static,
607{
608 let callback = erase_ordinary_callback(
609 callback,
610 AfterCompletionContext::new,
611 OrdinaryRunResult::from,
612 );
613 let claim = register_initial(start, |registry, lifetime| {
614 registry.register_after_completion_with_callback(
615 identity,
616 cadence,
617 lifetime,
618 Rc::clone(&callback),
619 )
620 });
621 drop(callback);
623 Ok(AfterCompletionRegistration { claim: claim? })
624}
625
626pub fn register_watchdog<F>(
637 identity: TimerIdentity,
638 cadence: TimerCadence,
639 start: TimerStart,
640 callback: F,
641) -> Result<WatchdogRegistration, TimerError>
642where
643 F: FnMut(WatchdogContext) -> WatchdogRunResult + 'static,
644{
645 let mut callback = callback;
646 let callback: WatchdogCallback = Rc::new(RefCell::new(move |token| {
647 callback(WatchdogContext::new(token))
648 }));
649 let claim = register_initial(start, |registry, lifetime| {
650 registry.register_watchdog_with_callback(identity, cadence, lifetime, Rc::clone(&callback))
651 });
652 drop(callback);
654 Ok(WatchdogRegistration { claim: claim? })
655}
656
657fn register_initial(
660 start: TimerStart,
661 register: impl FnOnce(
662 &mut TimerRegistry,
663 DeclarationLifetime,
664 ) -> Result<RegistrationClaim, RegisterError>,
665) -> Result<RegistrationClaim, TimerError> {
666 let (lifetime, initial) = start.into_parts();
667 register_initialized(
668 |registry| {
669 let now_ns = platform::time_ns();
670 let requested = initial
671 .map(|schedule| schedule.resolve(now_ns))
672 .transpose()?;
673 Ok((register(registry, lifetime)?, (requested, now_ns)))
674 },
675 |registry, claim, (requested, now_ns)| {
676 requested
677 .map(|schedule| {
678 registry
679 .transition_claim(claim, |registry| {
680 registry.reconcile_initial(claim, now_ns, schedule)
681 })
682 .map_err(TimerError::from)
683 })
684 .transpose()
685 },
686 )
687}
688
689fn register_initialized<Prepared>(
692 register: impl FnOnce(&mut TimerRegistry) -> Result<(RegistrationClaim, Prepared), TimerError>,
693 initialize: impl FnOnce(
694 &mut TimerRegistry,
695 &RegistrationClaim,
696 Prepared,
697 ) -> Result<Option<(RegistryTransition, ProviderHandles)>, TimerError>,
698) -> Result<RegistrationClaim, TimerError> {
699 let (claim, initialized) = with_registry_mut(|registry| {
700 let (claim, prepared) = register(registry)?;
701 let initialized = initialize(registry, &claim, prepared);
702 Ok((claim, initialized))
703 })?;
704 let applied = initialized.and_then(|transition| {
705 transition.map_or(Ok(()), |(transition, handles)| {
706 finish_transition(transition, handles)
707 })
708 });
709 applied.map_err(|error| cleanup_creation_error(&claim, error))?;
710 Ok(claim)
711}
712
713fn cleanup_creation_error(claim: &RegistrationClaim, error: TimerError) -> TimerError {
714 match unregister_claim(claim) {
715 Ok(()) | Err(TimerError::RegistrationExpired) => error,
716 Err(cleanup_error) => cleanup_error,
717 }
718}
719
720pub fn reconcile_once<F, Fut>(
743 registration: &mut Option<OnceRegistration>,
744 identity: &TimerIdentity,
745 desired: Option<TimerSchedule>,
746 callback: F,
747) -> Result<(), TimerError>
748where
749 F: FnMut(OnceContext) -> Fut + 'static,
750 Fut: Future<Output = OnceRunResult> + 'static,
751{
752 reconcile_registration(
753 registration,
754 identity,
755 TimerPolicy::Once,
756 || {
757 let callback =
758 erase_ordinary_callback(callback, OnceContext::new, OrdinaryRunResult::from);
759 let claim = register_initialized(
760 |registry| {
761 Ok((
762 registry.register_once_with_callback(
763 identity.clone(),
764 DeclarationLifetime::Retained,
765 Rc::clone(&callback),
766 )?,
767 (),
768 ))
769 },
770 |registry, claim, ()| {
771 registry
772 .transition_claim(claim, |registry| {
773 registry.reconcile_ordinary(claim, platform::time_ns(), desired)
774 })
775 .map(Some)
776 .map_err(TimerError::from)
777 },
778 );
779 drop(callback);
780 Ok(OnceRegistration { claim: claim? })
781 },
782 |registration| registration.reconcile_schedule(desired),
783 )
784}
785
786pub fn reconcile_after_completion<F, Fut>(
811 registration: &mut Option<AfterCompletionRegistration>,
812 identity: &TimerIdentity,
813 cadence: TimerCadence,
814 desired: TimerReconcileState,
815 callback: F,
816) -> Result<(), TimerError>
817where
818 F: FnMut(AfterCompletionContext) -> Fut + 'static,
819 Fut: Future<Output = AfterCompletionRunResult> + 'static,
820{
821 reconcile_registration(
822 registration,
823 identity,
824 TimerPolicy::AfterCompletion { cadence },
825 || {
826 let callback = erase_ordinary_callback(
827 callback,
828 AfterCompletionContext::new,
829 OrdinaryRunResult::from,
830 );
831 let claim = register_initialized(
832 |registry| {
833 Ok((
834 registry.register_after_completion_with_callback(
835 identity.clone(),
836 cadence,
837 DeclarationLifetime::Retained,
838 Rc::clone(&callback),
839 )?,
840 (),
841 ))
842 },
843 |registry, claim, ()| {
844 registry
845 .transition_claim(claim, |registry| match desired {
846 TimerReconcileState::Inactive => registry.cancel(claim),
847 TimerReconcileState::Scheduled => {
848 registry.ensure_recurring(claim, platform::time_ns())
849 }
850 })
851 .map(Some)
852 .map_err(TimerError::from)
853 },
854 );
855 drop(callback);
856 Ok(AfterCompletionRegistration { claim: claim? })
857 },
858 |registration| match desired {
859 TimerReconcileState::Inactive => registration.cancel(),
860 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
861 },
862 )
863}
864
865pub fn reconcile_watchdog<F>(
878 registration: &mut Option<WatchdogRegistration>,
879 identity: &TimerIdentity,
880 cadence: TimerCadence,
881 desired: WatchdogReconcileState,
882 callback: F,
883) -> Result<(), TimerError>
884where
885 F: FnMut(WatchdogContext) -> WatchdogRunResult + 'static,
886{
887 reconcile_registration(
888 registration,
889 identity,
890 TimerPolicy::Watchdog { cadence },
891 || {
892 let mut callback = callback;
893 let callback: WatchdogCallback = Rc::new(RefCell::new(move |token| {
894 callback(WatchdogContext::new(token))
895 }));
896 let claim = register_initialized(
897 |registry| {
898 Ok((
899 registry.register_watchdog_with_callback(
900 identity.clone(),
901 cadence,
902 DeclarationLifetime::Retained,
903 Rc::clone(&callback),
904 )?,
905 (),
906 ))
907 },
908 |registry, claim, ()| {
909 registry
910 .transition_claim(claim, |registry| match desired {
911 WatchdogReconcileState::Inactive => registry.cancel(claim),
912 WatchdogReconcileState::Scheduled => {
913 registry.ensure_recurring(claim, platform::time_ns())
914 }
915 WatchdogReconcileState::ScheduledImmediately => {
916 registry.ensure_watchdog_immediately(claim, platform::time_ns())
917 }
918 WatchdogReconcileState::ScheduledAt(deadline_ns) => registry
919 .reconcile_watchdog_schedule(
920 claim,
921 platform::time_ns(),
922 Some(TimerSchedule::At(deadline_ns)),
923 ),
924 })
925 .map(Some)
926 .map_err(TimerError::from)
927 },
928 );
929 drop(callback);
930 Ok(WatchdogRegistration { claim: claim? })
931 },
932 |registration| match desired {
933 WatchdogReconcileState::Inactive => registration.cancel(),
934 WatchdogReconcileState::Scheduled => registration.ensure_scheduled(),
935 WatchdogReconcileState::ScheduledImmediately => {
936 registration.ensure_scheduled_immediately()
937 }
938 WatchdogReconcileState::ScheduledAt(deadline_ns) => {
939 registration.reconcile_schedule(Some(TimerSchedule::At(deadline_ns)))
940 }
941 },
942 )
943}
944
945fn reconcile_registration<Registration>(
946 registration: &mut Option<Registration>,
947 identity: &TimerIdentity,
948 policy: TimerPolicy,
949 register: impl FnOnce() -> Result<Registration, TimerError>,
950 reconcile: impl FnOnce(&Registration) -> Result<(), TimerError>,
951) -> Result<(), TimerError>
952where
953 Registration: RegistrationClaimOwner,
954{
955 let Some(current) = registration.as_ref() else {
956 *registration = Some(register()?);
957 return Ok(());
958 };
959 let claim = current.registration_claim();
960 if claim.identity() != identity {
961 return Err(TimerError::ReconciliationConflict);
962 }
963 with_registry(|registry| {
964 registry
965 .declaration_matches(claim, policy, DeclarationLifetime::Retained)
966 .map_err(TimerError::from)?
967 .then_some(())
968 .ok_or(TimerError::ReconciliationConflict)
969 })?;
970 drop(register);
973 reconcile(current)
974}
975
976pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
978 with_registry(|registry| Ok(registry.snapshot(identity)))
979}
980
981pub fn timer_inventory() -> Result<TimerInventorySnapshot, TimerError> {
986 with_registry(|registry| Ok(registry.inventory()))
987}
988
989pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
991 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
992}
993
994fn has_armed_wakeup_claim(claim: &RegistrationClaim) -> Result<bool, TimerError> {
995 with_registry(|registry| registry.has_armed_wakeup(claim).map_err(TimerError::from))
996}
997
998fn erase_ordinary_callback<Context: 'static, Output: 'static, F, Fut>(
999 mut callback: F,
1000 context: fn(CallbackToken) -> Context,
1001 erase_result: fn(Output) -> OrdinaryRunResult,
1002) -> OrdinaryCallback
1003where
1004 F: FnMut(Context) -> Fut + 'static,
1005 Fut: Future<Output = Output> + 'static,
1006{
1007 Rc::new(RefCell::new(move |token| {
1008 let future = callback(context(token));
1009 Box::pin(async move { erase_result(future.await) }) as _
1010 }))
1011}
1012
1013fn apply_claim_transition(
1014 claim: &RegistrationClaim,
1015 context: Option<&CallbackToken>,
1016 operation: impl FnOnce(&mut TimerRegistry) -> Result<RegistryTransition, RegistryError>,
1017) -> Result<(), TimerError> {
1018 let (transition, handles) = with_registry_mut(|registry| {
1019 if let Some(token) = context {
1020 registry
1021 .validate_running_context(token)
1022 .map_err(TimerError::from)?;
1023 }
1024 registry
1025 .transition_claim(claim, operation)
1026 .map_err(TimerError::from)
1027 })?;
1028 match finish_transition(transition, handles) {
1029 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
1030 Err(error) => retire_failed_claim(claim, error),
1031 }
1032}
1033
1034fn ensure_once_claim(
1035 claim: &RegistrationClaim,
1036 context: Option<&CallbackToken>,
1037 schedule: TimerSchedule,
1038) -> Result<(), TimerError> {
1039 apply_claim_transition(claim, context, |registry| {
1040 registry.ensure_once(claim, platform::time_ns(), schedule)
1041 })
1042}
1043
1044fn reconcile_ordinary_claim(
1045 claim: &RegistrationClaim,
1046 context: Option<&CallbackToken>,
1047 schedule: Option<TimerSchedule>,
1048) -> Result<(), TimerError> {
1049 apply_claim_transition(claim, context, |registry| {
1050 registry.reconcile_ordinary(claim, platform::time_ns(), schedule)
1051 })
1052}
1053
1054fn ensure_recurring_claim(
1055 claim: &RegistrationClaim,
1056 context: Option<&CallbackToken>,
1057) -> Result<(), TimerError> {
1058 apply_claim_transition(claim, context, |registry| {
1059 registry.ensure_recurring(claim, platform::time_ns())
1060 })
1061}
1062
1063fn ensure_watchdog_immediately_claim(
1064 claim: &RegistrationClaim,
1065 context: Option<&CallbackToken>,
1066) -> Result<(), TimerError> {
1067 apply_claim_transition(claim, context, |registry| {
1068 registry.ensure_watchdog_immediately(claim, platform::time_ns())
1069 })
1070}
1071
1072fn reconcile_watchdog_claim(
1073 claim: &RegistrationClaim,
1074 context: Option<&CallbackToken>,
1075 schedule: Option<TimerSchedule>,
1076) -> Result<(), TimerError> {
1077 apply_claim_transition(claim, context, |registry| {
1078 registry.reconcile_watchdog_schedule(claim, platform::time_ns(), schedule)
1079 })
1080}
1081
1082fn cancel_claim(
1083 claim: &RegistrationClaim,
1084 context: Option<&CallbackToken>,
1085) -> Result<(), TimerError> {
1086 apply_claim_transition(claim, context, |registry| registry.cancel(claim))
1087}
1088
1089fn unregister_claim(claim: &RegistrationClaim) -> Result<(), TimerError> {
1090 apply_claim_transition(claim, None, |registry| registry.unregister(claim))
1091}
1092
1093fn retire_failed_claim(claim: &RegistrationClaim, error: TimerError) -> Result<(), TimerError> {
1094 match fail_claim_provider_binding(claim) {
1095 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
1096 Err(cleanup_error) => Err(cleanup_error),
1097 }
1098}
1099
1100fn finish_transition(
1101 transition: RegistryTransition,
1102 handles: ProviderHandles,
1103) -> Result<(), TimerError> {
1104 let failure = transition.failure();
1105 let applied = apply_effect(transition.effect(), handles);
1106 drop(transition);
1109 applied?;
1110 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
1111}
1112
1113fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
1114 match effect {
1115 RegistryEffect::None => restore_provider_handles(handles),
1116 RegistryEffect::ArmWakeup {
1117 token,
1118 delay_ns,
1119 arm,
1120 } => {
1121 if arm.replaces_existing()
1122 && let Some(replaced) = handles.take_wakeup()
1123 {
1124 clear_provider_handle(replaced);
1125 }
1126 restore_provider_handles(handles)?;
1127 let handle = platform::try_set_timer(
1128 Duration::from_nanos(*delay_ns),
1129 dispatch_wakeup(token.clone()),
1130 )?;
1131 bind_provider_handle(token.token(), handle, Some(*delay_ns))
1132 }
1133 RegistryEffect::ClearCallbacks { handles: selected } => {
1134 if selected.includes_wakeup()
1135 && let Some(wakeup) = handles.take_wakeup()
1136 {
1137 clear_provider_handle(wakeup);
1138 }
1139 if selected.includes_work()
1140 && let Some(work) = handles.take_work()
1141 {
1142 clear_provider_handle(work);
1143 }
1144 restore_provider_handles(handles)
1145 }
1146 RegistryEffect::DispatchWatchdog {
1147 dispatch,
1148 successor_delay_ns,
1149 } => {
1150 let successor = dispatch.successor();
1151 let work = dispatch.work();
1152 if let Some(wakeup) = handles.take_wakeup() {
1153 clear_provider_handle(wakeup);
1154 }
1155 if let Some(replaced_work) = handles.take_work() {
1156 clear_provider_handle(replaced_work);
1157 }
1158 let successor_token = successor.clone();
1159 let successor_handle =
1160 platform::try_set_timer(Duration::from_nanos(*successor_delay_ns), async move {
1161 dispatch_watchdog_scheduler(&successor_token);
1162 })?;
1163 bind_provider_handle(successor, successor_handle, None)?;
1164
1165 let work_token = work.clone();
1166 let work_handle = platform::try_set_timer(Duration::ZERO, async move {
1167 dispatch_watchdog_work(&work_token);
1168 })?;
1169 if let Err(error) = bind_provider_handle(&work, work_handle, Some(*successor_delay_ns))
1170 {
1171 clear_entry_provider_handles(successor.identity())?;
1172 return Err(error);
1173 }
1174 Ok(())
1175 }
1176 }
1177}
1178
1179fn bind_provider_handle(
1181 token: &CallbackToken,
1182 handle: TimerHandle,
1183 armed_delay_ns: Option<u64>,
1184) -> Result<(), TimerError> {
1185 RUNTIME
1186 .with(|runtime| {
1187 #[cfg(test)]
1188 if take_provider_install_fault() {
1189 return Err((TimerError::OwnershipInvariant, handle));
1190 }
1191 let Ok(mut runtime) = runtime.try_borrow_mut() else {
1192 return Err((TimerError::RuntimeBusy, handle));
1193 };
1194 let Some(registry) = runtime.as_mut() else {
1195 return Err((TimerError::NotInitialized, handle));
1196 };
1197 match registry.install_provider_handle(token, handle, armed_delay_ns) {
1198 Ok(()) => Ok(()),
1199 Err((error, handle)) => Err((TimerError::from(error), handle)),
1200 }
1201 })
1202 .map_err(|(error, handle)| {
1203 platform::clear_timer(handle);
1205 error
1206 })
1207}
1208
1209fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
1210 let wakeup_failure = handles
1213 .take_wakeup()
1214 .and_then(|handle| restore_provider_handle(handle).err());
1215 let work_failure = handles
1216 .take_work()
1217 .and_then(|handle| restore_provider_handle(handle).err());
1218 wakeup_failure.or(work_failure).map_or(Ok(()), Err)
1219}
1220
1221fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
1222 let (token, handle) = handle.into_parts();
1223 bind_provider_handle(&token, handle, None)
1224}
1225
1226fn clear_provider_handle(handle: ProviderHandle) {
1227 let (_, handle) = handle.into_parts();
1228 platform::clear_timer(handle);
1229}
1230
1231fn clear_provider_handles(mut handles: ProviderHandles) {
1232 if let Some(wakeup) = handles.take_wakeup() {
1233 clear_provider_handle(wakeup);
1234 }
1235 if let Some(work) = handles.take_work() {
1236 clear_provider_handle(work);
1237 }
1238}
1239
1240fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
1241 let handles = with_registry_mut(|registry| Ok(registry.take_provider_handles(identity)))?;
1242 clear_provider_handles(handles);
1243 Ok(())
1244}
1245
1246#[expect(
1247 clippy::future_not_send,
1248 reason = "IC callbacks and canister-local state are single-threaded."
1249)]
1250fn dispatch_wakeup(token: WakeupToken) -> impl Future<Output = ()> {
1251 let delivery = match token {
1254 WakeupToken::Ordinary(token) => WakeupDelivery::Ordinary(OrdinaryDelivery {
1255 token,
1256 completed: false,
1257 }),
1258 WakeupToken::Scheduler(token) => WakeupDelivery::Scheduler(token),
1259 };
1260 async move {
1261 match delivery {
1262 WakeupDelivery::Ordinary(mut delivery) => {
1263 dispatch_ordinary(&delivery.token).await;
1264 delivery.completed = true;
1265 drop(delivery);
1267 }
1268 WakeupDelivery::Scheduler(token) => dispatch_watchdog_scheduler(&token),
1269 }
1270 }
1271}
1272
1273enum WakeupDelivery {
1274 Ordinary(OrdinaryDelivery),
1275 Scheduler(CallbackToken),
1276}
1277
1278struct OrdinaryDelivery {
1279 token: CallbackToken,
1280 completed: bool,
1281}
1282
1283impl Drop for OrdinaryDelivery {
1284 fn drop(&mut self) {
1285 if self.completed {
1286 return;
1287 }
1288 let removed = RUNTIME.with(|runtime| {
1289 let Ok(mut runtime) = runtime.try_borrow_mut() else {
1290 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1294 return if runtime
1295 .as_ref()
1296 .is_some_and(|registry| registry.owns_ordinary_delivery(&self.token))
1297 {
1298 Err(TimerError::RuntimeBusy)
1299 } else {
1300 Ok(None)
1301 };
1302 };
1303 Ok(runtime
1304 .as_mut()
1305 .and_then(|registry| registry.abandon_ordinary(&self.token, platform::time_ns())))
1306 });
1307 let removed = removed
1308 .unwrap_or_else(|error| trap_callback_failure("ordinary delivery abandonment", &error));
1309 drop(removed);
1310 }
1311}
1312
1313#[expect(
1314 clippy::future_not_send,
1315 reason = "IC callbacks and canister-local state are single-threaded."
1316)]
1317async fn dispatch_ordinary(token: &CallbackToken) {
1318 let measurement = CallbackMeasurementStart::capture();
1319 let callback = with_registry_mut(|registry| Ok(registry.begin_ordinary(token)));
1320 let callback = match callback {
1321 Ok(Some(callback)) => callback,
1322 Ok(None) => return,
1323 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
1324 };
1325 let future = {
1326 let Ok(mut callback) = callback.try_borrow_mut() else {
1327 finish_ordinary_callback(
1328 token,
1329 OrdinaryRunResult::new(
1330 TimerCompletion::invariant_failure(0),
1331 OrdinaryDirective::Stop,
1332 ),
1333 );
1334 return;
1335 };
1336 callback(token.clone())
1337 };
1338 let result = future.await;
1339 finish_ordinary_callback(token, result);
1340 measurement.record(token);
1341}
1342
1343fn finish_ordinary_callback(token: &CallbackToken, result: OrdinaryRunResult) {
1344 let transition = with_registry_mut(|registry| {
1345 registry
1346 .complete_ordinary(token, platform::time_ns(), result)
1347 .map_err(TimerError::from)
1348 });
1349 let transition = transition
1350 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
1351 finish_callback_transition(token, transition, ProviderHandles::default());
1352}
1353
1354fn dispatch_watchdog_scheduler(token: &CallbackToken) {
1355 let measurement = CallbackMeasurementStart::capture();
1356 let dispatched = with_registry_mut(|registry| {
1357 let handles = registry
1358 .take_watchdog_scheduler_handles(token)
1359 .map_err(TimerError::from)?;
1360 Ok((
1361 registry.begin_watchdog_scheduler(token, platform::time_ns()),
1362 handles,
1363 ))
1364 });
1365 let (transition, handles) = dispatched
1366 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
1367 let accepted = !matches!(transition.effect(), RegistryEffect::None);
1368 finish_callback_transition(token, transition, handles);
1369 if accepted {
1370 measurement.record(token);
1371 }
1372}
1373
1374fn dispatch_watchdog_work(token: &CallbackToken) {
1375 let measurement = CallbackMeasurementStart::capture();
1376 let callback = with_registry_mut(|registry| Ok(registry.begin_watchdog_work(token)));
1377 let callback = match callback {
1378 Ok(Some(callback)) => callback,
1379 Ok(None) => return,
1380 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
1381 };
1382 let result = {
1383 let Ok(mut callback) = callback.try_borrow_mut() else {
1384 trap_callback_failure(
1385 "watchdog callback ownership",
1386 &TimerError::OwnershipInvariant,
1387 );
1388 };
1389 callback(token.clone())
1390 };
1391 finish_watchdog_dispatch(token, result);
1392 measurement.record(token);
1393}
1394
1395fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
1396 let completed = with_registry_mut(|registry| {
1401 let handles = registry
1402 .take_provider_handles_for_claim(token.claim())
1403 .map_err(TimerError::from)?;
1404 #[cfg(test)]
1405 {
1406 if take_watchdog_completion_fault() {
1407 return Err(TimerError::OwnershipInvariant);
1408 }
1409 }
1410 let transition = registry
1411 .complete_watchdog_work(token, platform::time_ns(), result)
1412 .map_err(TimerError::from)?;
1413 Ok((transition, handles))
1414 });
1415 let (transition, handles) =
1416 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
1417 finish_callback_transition(token, transition, handles);
1418}
1419
1420fn finish_callback_transition(
1421 token: &CallbackToken,
1422 transition: RegistryTransition,
1423 handles: ProviderHandles,
1424) {
1425 match finish_transition(transition, handles) {
1428 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
1429 Err(error) => {
1430 if token.role() == CallbackRole::WatchdogWork {
1431 trap_callback_failure("watchdog provider-handle completion", &error);
1432 }
1433 fail_claim_provider_binding(token.claim()).unwrap_or_else(|binding_error| {
1434 trap_callback_failure("provider-binding failure cleanup", &binding_error)
1435 });
1436 }
1437 }
1438}
1439
1440fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
1441 let failed = with_registry_mut(|registry| {
1442 registry
1443 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
1444 .map_err(TimerError::from)
1445 });
1446 let (handles, transition) = failed?;
1447 clear_provider_handles(handles);
1448 drop(transition);
1449 Ok(())
1450}
1451
1452#[derive(Clone, Copy)]
1453struct CallbackMeasurementStart {
1454 instructions_before: u64,
1455 memory_start: MemoryPageExtent,
1456}
1457
1458impl CallbackMeasurementStart {
1459 fn capture() -> Self {
1460 let memory_start = platform::memory_pages();
1462 let instructions_before = platform::instruction_counter();
1463 Self {
1464 instructions_before,
1465 memory_start,
1466 }
1467 }
1468
1469 fn record(self, token: &CallbackToken) {
1470 let instructions = platform::instruction_counter().saturating_sub(self.instructions_before);
1472 let memory_end = platform::memory_pages();
1473 with_registry_mut(|registry| {
1474 registry
1475 .record_callback_measurements(token, instructions, self.memory_start, memory_end)
1476 .map_err(TimerError::from)
1477 })
1478 .unwrap_or_else(|error| trap_callback_failure("callback measurement accounting", &error));
1479 }
1480}
1481
1482fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
1483 platform::trap(&format!("ic-timers {context} failed: {error}"))
1484}
1485
1486fn with_registry<T>(
1487 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
1488) -> Result<T, TimerError> {
1489 RUNTIME.with(|runtime| {
1490 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1491 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1492 operation(registry)
1493 })
1494}
1495
1496fn with_registry_mut<T>(
1497 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1498) -> Result<T, TimerError> {
1499 RUNTIME.with(|runtime| {
1500 let mut runtime = runtime
1501 .try_borrow_mut()
1502 .map_err(|_| TimerError::RuntimeBusy)?;
1503 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1504 operation(registry)
1505 })
1506}
1507
1508#[cfg(test)]
1509fn reset_for_test(now_ns: u64, canister_version: u64) {
1510 platform::reset(now_ns, canister_version);
1511 platform::ADMISSION_FAULT.set(None);
1512 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1513 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(None));
1514 RUNTIME.with(|runtime| {
1515 *runtime.borrow_mut() = None;
1516 });
1517}
1518
1519#[cfg(test)]
1520thread_local! {
1521 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1522 static PROVIDER_INSTALL_FAULT_AFTER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1523}
1524
1525#[cfg(test)]
1526fn inject_watchdog_completion_fault() {
1527 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1528}
1529
1530#[cfg(test)]
1531fn take_watchdog_completion_fault() -> bool {
1532 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1533}
1534
1535#[cfg(test)]
1536fn inject_provider_install_fault() {
1537 inject_provider_install_fault_after(0);
1538}
1539
1540#[cfg(test)]
1541fn inject_provider_install_fault_after(successful_installs: u64) {
1542 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(Some(successful_installs)));
1543}
1544
1545#[cfg(test)]
1546fn take_provider_install_fault() -> bool {
1547 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| match fault.get() {
1548 Some(0) => {
1549 fault.set(None);
1550 true
1551 }
1552 Some(remaining) => {
1553 fault.set(Some(remaining - 1));
1554 false
1555 }
1556 None => false,
1557 })
1558}
1559
1560#[cfg(test)]
1561mod tests;