1use crate::{
4 DeclarationLifetime, RegisterError, ScheduleError, TimerCadence, TimerCompletion,
5 TimerControlFailure, TimerDirective, TimerEpoch, TimerIdentity, TimerRunResult, TimerSchedule,
6 TimerSnapshot, WatchdogRunResult,
7 platform::{self, TimerHandle},
8 registry::{
9 CallbackAcceptance, CallbackRole, CallbackToken, OrdinaryCallback, ProviderHandle,
10 ProviderHandles, RegistrationClaim, RegistryEffect, RegistryError, RegistryTransition,
11 TimerRegistry, WatchdogCallback,
12 },
13};
14use std::{cell::RefCell, future::Future, pin::Pin, rc::Rc, time::Duration};
15use thiserror::Error;
16
17pub type TimerFuture = Pin<Box<dyn Future<Output = TimerRunResult>>>;
19
20thread_local! {
21 static RUNTIME: RefCell<Option<TimerRegistry>> = const { RefCell::new(None) };
22}
23
24#[non_exhaustive]
26#[derive(Debug, Error)]
27pub enum TimerError {
28 #[error("timer runtime is not initialized")]
30 NotInitialized,
31 #[error("timer runtime is already borrowed")]
33 RuntimeBusy,
34 #[error(transparent)]
36 Register(#[from] RegisterError),
37 #[error(transparent)]
39 Schedule(#[from] ScheduleError),
40 #[error("timer registration is no longer authoritative")]
42 RegistrationExpired,
43 #[error("timer operation does not match its registered policy")]
45 WrongPolicy,
46 #[error("timer control failed: {0:?}")]
48 ControlFailure(TimerControlFailure),
49 #[error("timer runtime ownership invariant failed")]
51 OwnershipInvariant,
52 #[error("timer lifecycle reconciliation conflicts with the canonical declaration")]
54 ReconciliationConflict,
55}
56
57impl From<RegistryError> for TimerError {
58 fn from(value: RegistryError) -> Self {
59 match value {
60 RegistryError::UnknownRegistration
61 | RegistryError::StaleRegistration
62 | RegistryError::StaleCallback => Self::RegistrationExpired,
63 RegistryError::WrongPolicy { .. } => Self::WrongPolicy,
64 RegistryError::Schedule(error) => Self::Schedule(error),
65 RegistryError::MissingCallback | RegistryError::ProviderHandleAlreadyOwned => {
66 Self::OwnershipInvariant
67 }
68 }
69 }
70}
71
72pub fn initialize_runtime() -> Result<TimerEpoch, TimerError> {
77 let epoch = TimerEpoch::new(platform::canister_version(), platform::time_ns());
78 RUNTIME.with(|runtime| {
79 let mut runtime = runtime
80 .try_borrow_mut()
81 .map_err(|_| TimerError::RuntimeBusy)?;
82 if let Some(registry) = runtime.as_ref() {
83 return Ok(registry.epoch());
84 }
85 *runtime = Some(TimerRegistry::new(epoch));
86 Ok(epoch)
87 })
88}
89
90pub struct TimerContext {
96 token: CallbackToken,
97}
98
99impl TimerContext {
100 const fn new(token: CallbackToken) -> Self {
101 Self { token }
102 }
103
104 fn claim(&self) -> RegistrationClaim {
105 RegistrationClaim::delegated(self.token.identity().clone(), self.token.claim_generation())
106 }
107
108 #[must_use]
110 pub const fn identity(&self) -> &TimerIdentity {
111 self.token.identity()
112 }
113
114 pub fn ensure_once(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
119 ensure_once_claim(&self.claim(), Some(&self.token), schedule)
120 }
121
122 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
128 reconcile_ordinary_claim(&self.claim(), Some(&self.token), schedule)
129 }
130
131 pub fn ensure_recurring(&self) -> Result<(), TimerError> {
133 ensure_recurring_claim(&self.claim(), Some(&self.token))
134 }
135
136 pub fn cancel(&self) -> Result<(), TimerError> {
138 cancel_claim(&self.claim(), Some(&self.token))
139 }
140}
141
142#[must_use = "retain the registration claim so the timer remains controllable"]
144pub struct OnceRegistration {
145 claim: RegistrationClaim,
146}
147
148impl OnceRegistration {
149 #[must_use]
151 pub const fn identity(&self) -> &TimerIdentity {
152 self.claim.identity()
153 }
154
155 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
157 ensure_once_claim(&self.claim, None, schedule)
158 }
159
160 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
164 reconcile_ordinary_claim(&self.claim, None, schedule)
165 }
166
167 pub fn cancel(&self) -> Result<(), TimerError> {
169 cancel_claim(&self.claim, None)
170 }
171
172 pub fn unregister(self) -> Result<(), TimerError> {
174 unregister_claim(self.claim)
175 }
176}
177
178#[must_use = "retain the registration claim so the timer remains controllable"]
180pub struct AfterCompletionRegistration {
181 claim: RegistrationClaim,
182}
183
184#[must_use = "retain the registration claim so the timer remains controllable"]
186pub struct WatchdogRegistration {
187 claim: RegistrationClaim,
188}
189
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
192pub enum TimerReconcileState {
193 Inactive,
195 Scheduled,
197}
198
199impl WatchdogRegistration {
200 #[must_use]
202 pub const fn identity(&self) -> &TimerIdentity {
203 self.claim.identity()
204 }
205
206 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
208 ensure_recurring_claim(&self.claim, None)
209 }
210
211 pub fn cancel(&self) -> Result<(), TimerError> {
213 cancel_claim(&self.claim, None)
214 }
215
216 pub fn unregister(self) -> Result<(), TimerError> {
218 unregister_claim(self.claim)
219 }
220}
221
222impl AfterCompletionRegistration {
223 #[must_use]
225 pub const fn identity(&self) -> &TimerIdentity {
226 self.claim.identity()
227 }
228
229 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
231 ensure_recurring_claim(&self.claim, None)
232 }
233
234 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
238 reconcile_ordinary_claim(&self.claim, None, schedule)
239 }
240
241 pub fn cancel(&self) -> Result<(), TimerError> {
243 cancel_claim(&self.claim, None)
244 }
245
246 pub fn unregister(self) -> Result<(), TimerError> {
248 unregister_claim(self.claim)
249 }
250}
251
252pub fn register_once<F, Fut>(
254 identity: TimerIdentity,
255 lifetime: DeclarationLifetime,
256 mut callback: F,
257) -> Result<OnceRegistration, TimerError>
258where
259 F: FnMut(TimerContext) -> Fut + 'static,
260 Fut: Future<Output = TimerRunResult> + 'static,
261{
262 let callback: OrdinaryCallback = Rc::new(RefCell::new(Box::new(move |context| {
263 Box::pin(callback(context))
264 })));
265 let claim = with_registry_mut(|registry| {
266 registry
267 .register_once_with_callback(identity, lifetime, callback)
268 .map_err(TimerError::from)
269 })?;
270 Ok(OnceRegistration { claim })
271}
272
273pub fn register_after_completion<F, Fut>(
275 identity: TimerIdentity,
276 cadence: TimerCadence,
277 lifetime: DeclarationLifetime,
278 mut callback: F,
279) -> Result<AfterCompletionRegistration, TimerError>
280where
281 F: FnMut(TimerContext) -> Fut + 'static,
282 Fut: Future<Output = TimerRunResult> + 'static,
283{
284 let callback: OrdinaryCallback = Rc::new(RefCell::new(Box::new(move |context| {
285 Box::pin(callback(context))
286 })));
287 let claim = with_registry_mut(|registry| {
288 registry
289 .register_after_completion_with_callback(identity, cadence, lifetime, callback)
290 .map_err(TimerError::from)
291 })?;
292 Ok(AfterCompletionRegistration { claim })
293}
294
295pub fn register_watchdog<F>(
301 identity: TimerIdentity,
302 cadence: TimerCadence,
303 lifetime: DeclarationLifetime,
304 callback: F,
305) -> Result<WatchdogRegistration, TimerError>
306where
307 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
308{
309 let callback: WatchdogCallback = Rc::new(RefCell::new(Box::new(callback)));
310 let claim = with_registry_mut(|registry| {
311 registry
312 .register_watchdog_with_callback(identity, cadence, lifetime, callback)
313 .map_err(TimerError::from)
314 })?;
315 Ok(WatchdogRegistration { claim })
316}
317
318pub fn reconcile_once<F, Fut>(
326 registration: &mut Option<OnceRegistration>,
327 identity: &TimerIdentity,
328 desired: Option<TimerSchedule>,
329 callback: F,
330) -> Result<(), TimerError>
331where
332 F: FnMut(TimerContext) -> Fut + 'static,
333 Fut: Future<Output = TimerRunResult> + 'static,
334{
335 if registration.is_none() {
336 *registration = Some(register_once(
337 identity.clone(),
338 DeclarationLifetime::Retained,
339 callback,
340 )?);
341 }
342 verify_declaration(
343 registration.as_ref().map(OnceRegistration::identity),
344 identity,
345 crate::TimerPolicy::Once,
346 )?;
347 registration
348 .as_ref()
349 .ok_or(TimerError::ReconciliationConflict)?
350 .reconcile_schedule(desired)
351}
352
353pub fn reconcile_after_completion<F, Fut>(
362 registration: &mut Option<AfterCompletionRegistration>,
363 identity: &TimerIdentity,
364 cadence: TimerCadence,
365 desired: TimerReconcileState,
366 callback: F,
367) -> Result<(), TimerError>
368where
369 F: FnMut(TimerContext) -> Fut + 'static,
370 Fut: Future<Output = TimerRunResult> + 'static,
371{
372 if registration.is_none() {
373 *registration = Some(register_after_completion(
374 identity.clone(),
375 cadence,
376 DeclarationLifetime::Retained,
377 callback,
378 )?);
379 }
380 verify_declaration(
381 registration
382 .as_ref()
383 .map(AfterCompletionRegistration::identity),
384 identity,
385 crate::TimerPolicy::AfterCompletion { cadence },
386 )?;
387 let registration = registration
388 .as_ref()
389 .ok_or(TimerError::ReconciliationConflict)?;
390 match desired {
391 TimerReconcileState::Inactive => registration.cancel(),
392 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
393 }
394}
395
396pub fn reconcile_watchdog<F>(
403 registration: &mut Option<WatchdogRegistration>,
404 identity: &TimerIdentity,
405 cadence: TimerCadence,
406 desired: TimerReconcileState,
407 callback: F,
408) -> Result<(), TimerError>
409where
410 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
411{
412 if registration.is_none() {
413 *registration = Some(register_watchdog(
414 identity.clone(),
415 cadence,
416 DeclarationLifetime::Retained,
417 callback,
418 )?);
419 }
420 verify_declaration(
421 registration.as_ref().map(WatchdogRegistration::identity),
422 identity,
423 crate::TimerPolicy::Watchdog { cadence },
424 )?;
425 let registration = registration
426 .as_ref()
427 .ok_or(TimerError::ReconciliationConflict)?;
428 match desired {
429 TimerReconcileState::Inactive => registration.cancel(),
430 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
431 }
432}
433
434fn verify_declaration(
435 claimed_identity: Option<&TimerIdentity>,
436 identity: &TimerIdentity,
437 policy: crate::TimerPolicy,
438) -> Result<(), TimerError> {
439 if claimed_identity != Some(identity) {
440 return Err(TimerError::ReconciliationConflict);
441 }
442 let snapshot = timer_snapshot(identity)?.ok_or(TimerError::RegistrationExpired)?;
443 if snapshot.policy() != policy || snapshot.lifetime() != DeclarationLifetime::Retained {
444 return Err(TimerError::ReconciliationConflict);
445 }
446 Ok(())
447}
448
449pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
451 with_registry(|registry| Ok(registry.snapshot(identity)))
452}
453
454pub fn timer_snapshots() -> Result<Vec<TimerSnapshot>, TimerError> {
456 with_registry(|registry| Ok(registry.snapshots()))
457}
458
459pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
461 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
462}
463
464fn ensure_once_claim(
465 claim: &RegistrationClaim,
466 context: Option<&CallbackToken>,
467 schedule: TimerSchedule,
468) -> Result<(), TimerError> {
469 let transition = with_registry_mut(|registry| {
470 validate_context(registry, context)?;
471 registry
472 .ensure_once(claim, platform::time_ns(), schedule)
473 .map_err(TimerError::from)
474 })?;
475 finish_claim_transition(claim, transition, ProviderHandles::default())
476}
477
478fn reconcile_ordinary_claim(
479 claim: &RegistrationClaim,
480 context: Option<&CallbackToken>,
481 schedule: Option<TimerSchedule>,
482) -> Result<(), TimerError> {
483 if schedule.is_none() {
484 let (handles, transition) = with_registry_mut(|registry| {
485 validate_context(registry, context)?;
486 registry
487 .validate_ordinary_claim(claim)
488 .map_err(TimerError::from)?;
489 let handles = registry
490 .take_provider_handles_for_claim(claim)
491 .map_err(TimerError::from)?;
492 let transition = registry
493 .reconcile_ordinary(claim, platform::time_ns(), None)
494 .map_err(TimerError::from)?;
495 Ok((handles, transition))
496 })?;
497 return finish_claim_transition(claim, transition, handles);
498 }
499 let transition = with_registry_mut(|registry| {
500 validate_context(registry, context)?;
501 registry
502 .reconcile_ordinary(claim, platform::time_ns(), schedule)
503 .map_err(TimerError::from)
504 })?;
505 finish_claim_transition(claim, transition, ProviderHandles::default())
506}
507
508fn ensure_recurring_claim(
509 claim: &RegistrationClaim,
510 context: Option<&CallbackToken>,
511) -> Result<(), TimerError> {
512 let transition = with_registry_mut(|registry| {
513 validate_context(registry, context)?;
514 registry
515 .ensure_recurring(claim, platform::time_ns())
516 .map_err(TimerError::from)
517 })?;
518 finish_claim_transition(claim, transition, ProviderHandles::default())
519}
520
521fn cancel_claim(
522 claim: &RegistrationClaim,
523 context: Option<&CallbackToken>,
524) -> Result<(), TimerError> {
525 let (handles, transition) = with_registry_mut(|registry| {
526 validate_context(registry, context)?;
527 let handles = registry
528 .take_provider_handles_for_claim(claim)
529 .map_err(TimerError::from)?;
530 let transition = registry.cancel(claim).map_err(TimerError::from)?;
531 Ok((handles, transition))
532 })?;
533 finish_claim_transition(claim, transition, handles)
534}
535
536fn validate_context(
537 registry: &TimerRegistry,
538 context: Option<&CallbackToken>,
539) -> Result<(), TimerError> {
540 context.map_or(Ok(()), |token| {
541 registry
542 .validate_running_context(token)
543 .map_err(TimerError::from)
544 })
545}
546
547fn unregister_claim(claim: RegistrationClaim) -> Result<(), TimerError> {
548 let cleanup_claim =
549 RegistrationClaim::delegated(claim.identity().clone(), claim.claim_generation());
550 let (handles, transition) = with_registry_mut(|registry| {
551 let handles = registry
552 .take_provider_handles_for_claim(&claim)
553 .map_err(TimerError::from)?;
554 let transition = registry.unregister(claim).map_err(TimerError::from)?;
555 Ok((handles, transition))
556 })?;
557 finish_claim_transition(&cleanup_claim, transition, handles)
558}
559
560fn finish_claim_transition(
561 claim: &RegistrationClaim,
562 transition: RegistryTransition,
563 handles: ProviderHandles,
564) -> Result<(), TimerError> {
565 match finish_transition(transition, handles) {
566 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
567 Err(error) => match fail_claim_provider_binding(claim) {
568 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
569 Err(cleanup_error) => Err(cleanup_error),
570 },
571 }
572}
573
574fn finish_transition(
575 transition: RegistryTransition,
576 handles: ProviderHandles,
577) -> Result<(), TimerError> {
578 let failure = transition.failure();
579 let effect = transition.into_effect();
580 apply_effect(&effect, handles)?;
581 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
582}
583
584fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
585 match effect {
586 RegistryEffect::None => restore_provider_handles(handles),
587 RegistryEffect::ArmWakeup {
588 token,
589 delay_ns,
590 replace,
591 ..
592 } => {
593 let detached_wakeup = handles.take_wakeup();
594 if *replace {
595 let replaced = match detached_wakeup {
596 Some(handle) => Some(handle),
597 None => with_registry_mut(|registry| {
598 Ok(registry.take_wakeup_handle(token.identity()))
599 })?,
600 };
601 if let Some(replaced) = replaced {
602 clear_provider_handle(replaced);
603 }
604 } else if let Some(detached_wakeup) = detached_wakeup {
605 restore_provider_handle(detached_wakeup)?;
606 }
607 if let Some(work) = handles.take_work() {
608 restore_provider_handle(work)?;
609 }
610 arm_wakeup(token, *delay_ns, effect)
611 }
612 RegistryEffect::ClearCallbacks {
613 identity,
614 clear_wakeup,
615 clear_work,
616 } => {
617 let detached_wakeup = handles.take_wakeup();
618 if *clear_wakeup {
619 let wakeup = match detached_wakeup {
620 Some(handle) => Some(handle),
621 None => {
622 with_registry_mut(|registry| Ok(registry.take_wakeup_handle(identity)))?
623 }
624 };
625 if let Some(wakeup) = wakeup {
626 clear_provider_handle(wakeup);
627 }
628 } else if let Some(wakeup) = detached_wakeup {
629 restore_provider_handle(wakeup)?;
630 }
631 let detached_work = handles.take_work();
632 if *clear_work {
633 let work = match detached_work {
634 Some(handle) => Some(handle),
635 None => with_registry_mut(|registry| Ok(registry.take_work_handle(identity)))?,
636 };
637 if let Some(work) = work {
638 clear_provider_handle(work);
639 }
640 } else if let Some(work) = detached_work {
641 restore_provider_handle(work)?;
642 }
643 restore_provider_handles(handles)
644 }
645 RegistryEffect::DispatchWatchdog {
646 successor,
647 successor_delay_ns,
648 work,
649 ..
650 } => {
651 if let Some(wakeup) = handles.take_wakeup() {
652 clear_provider_handle(wakeup);
653 }
654 let replaced_work = handles.take_work().or(with_registry_mut(|registry| {
655 Ok(registry.take_work_handle(successor.identity()))
656 })?);
657 if let Some(replaced_work) = replaced_work {
658 clear_provider_handle(replaced_work);
659 }
660 dispatch_watchdog_effect(successor, *successor_delay_ns, work, effect)
661 }
662 }
663}
664
665fn arm_wakeup(
666 token: &CallbackToken,
667 delay_ns: u64,
668 effect: &RegistryEffect,
669) -> Result<(), TimerError> {
670 let task_token = token.clone();
671 let handle = platform::set_timer(Duration::from_nanos(delay_ns), async move {
672 dispatch_wakeup(task_token).await;
673 });
674 if let Err((error, handle)) = install_provider_handle(token, handle) {
675 platform::clear_timer(handle);
676 return Err(error);
677 }
678 if let Err(error) = confirm_effect(effect) {
679 let handle = with_registry_mut(|registry| {
680 registry
681 .take_wakeup_handle(token.identity())
682 .ok_or(TimerError::OwnershipInvariant)
683 })?;
684 clear_provider_handle(handle);
685 return Err(error);
686 }
687 Ok(())
688}
689
690fn dispatch_watchdog_effect(
691 successor: &CallbackToken,
692 successor_delay_ns: u64,
693 work: &CallbackToken,
694 effect: &RegistryEffect,
695) -> Result<(), TimerError> {
696 let successor_token = successor.clone();
697 let successor_handle =
698 platform::set_timer(Duration::from_nanos(successor_delay_ns), async move {
699 dispatch_watchdog_scheduler(&successor_token);
700 });
701 if let Err((error, handle)) = install_provider_handle(successor, successor_handle) {
702 platform::clear_timer(handle);
703 return Err(error);
704 }
705
706 let work_token = work.clone();
707 let work_handle = platform::set_timer(Duration::ZERO, async move {
708 dispatch_watchdog_work(&work_token);
709 });
710 if let Err((error, handle)) = install_provider_handle(work, work_handle) {
711 platform::clear_timer(handle);
712 clear_entry_provider_handles(successor.identity())?;
713 return Err(error);
714 }
715 if let Err(error) = confirm_effect(effect) {
716 clear_entry_provider_handles(successor.identity())?;
717 return Err(error);
718 }
719 Ok(())
720}
721
722fn install_provider_handle(
723 token: &CallbackToken,
724 handle: TimerHandle,
725) -> Result<(), (TimerError, TimerHandle)> {
726 #[cfg(test)]
727 if take_provider_install_fault() {
728 return Err((TimerError::OwnershipInvariant, handle));
729 }
730 RUNTIME.with(|runtime| {
731 let Ok(mut runtime) = runtime.try_borrow_mut() else {
732 return Err((TimerError::RuntimeBusy, handle));
733 };
734 let Some(registry) = runtime.as_mut() else {
735 return Err((TimerError::NotInitialized, handle));
736 };
737 match registry.install_provider_handle(token, handle) {
738 Ok(()) => Ok(()),
739 Err((error, handle)) => Err((TimerError::from(error), handle)),
740 }
741 })
742}
743
744fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
745 #[cfg(test)]
746 if take_provider_confirmation_fault() {
747 return Err(TimerError::OwnershipInvariant);
748 }
749 with_registry_mut(|registry| {
750 registry
751 .confirm_effect_applied(effect)
752 .map_err(TimerError::from)
753 })
754}
755
756fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
757 if let Some(wakeup) = handles.take_wakeup() {
758 restore_provider_handle(wakeup)?;
759 }
760 if let Some(work) = handles.take_work() {
761 restore_provider_handle(work)?;
762 }
763 Ok(())
764}
765
766fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
767 let (token, handle) = handle.into_parts();
768 match install_provider_handle(&token, handle) {
769 Ok(()) => Ok(()),
770 Err((error, handle)) => {
771 platform::clear_timer(handle);
772 Err(error)
773 }
774 }
775}
776
777fn clear_provider_handle(handle: ProviderHandle) {
778 let (_, handle) = handle.into_parts();
779 platform::clear_timer(handle);
780}
781
782fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
783 let mut handles = with_registry_mut(|registry| {
784 Ok(ProviderHandles::from_parts(
785 registry.take_wakeup_handle(identity),
786 registry.take_work_handle(identity),
787 ))
788 })?;
789 if let Some(wakeup) = handles.take_wakeup() {
790 clear_provider_handle(wakeup);
791 }
792 if let Some(work) = handles.take_work() {
793 clear_provider_handle(work);
794 }
795 Ok(())
796}
797
798#[allow(clippy::future_not_send)] async fn dispatch_wakeup(token: CallbackToken) {
800 match token.role() {
801 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
802 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
803 CallbackRole::WatchdogWork => {}
804 }
805}
806
807#[allow(clippy::future_not_send)] async fn dispatch_ordinary(token: CallbackToken) {
809 let instructions_before = platform::instruction_counter();
810 let accepted = with_registry_mut(|registry| {
811 registry.consume_provider_handle(&token);
812 Ok(registry.begin_ordinary(&token))
813 });
814 match accepted {
815 Ok(CallbackAcceptance::Accepted) => {}
816 Ok(CallbackAcceptance::Stale) => return,
817 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
818 }
819
820 let callback = match with_registry(|registry| {
821 registry.ordinary_callback(&token).map_err(TimerError::from)
822 }) {
823 Ok(callback) => callback,
824 Err(TimerError::OwnershipInvariant) => {
825 fail_ordinary_dispatch(&token);
826 return;
827 }
828 Err(error) => trap_callback_failure("ordinary callback lookup", &error),
829 };
830 let context = TimerContext::new(token.clone());
831 let future = {
832 let Ok(mut callback) = callback.try_borrow_mut() else {
833 fail_ordinary_dispatch(&token);
834 return;
835 };
836 callback(context)
837 };
838 let result = future.await;
839 let transition = with_registry_mut(|registry| {
840 registry
841 .complete_ordinary(&token, platform::time_ns(), result)
842 .map_err(TimerError::from)
843 });
844 let transition = transition
845 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
846 finish_callback_transition(&token, transition, ProviderHandles::default());
847 record_work_instructions(&token, instructions_before);
848}
849
850fn fail_ordinary_dispatch(token: &CallbackToken) {
851 let transition = with_registry_mut(|registry| {
852 registry
853 .complete_ordinary(
854 token,
855 platform::time_ns(),
856 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
857 )
858 .map_err(TimerError::from)
859 });
860 let transition = transition.unwrap_or_else(|error| {
861 trap_callback_failure("ordinary invariant-failure completion", &error)
862 });
863 finish_callback_transition(token, transition, ProviderHandles::default());
864}
865
866fn dispatch_watchdog_scheduler(token: &CallbackToken) {
867 let instructions_before = platform::instruction_counter();
868 let transition = with_registry_mut(|registry| {
869 registry.consume_provider_handle(token);
870 Ok(registry.begin_watchdog_scheduler(token, platform::time_ns()))
871 });
872 let transition = transition
873 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
874 let accepted = !matches!(transition.effect(), RegistryEffect::None);
875 finish_callback_transition(token, transition, ProviderHandles::default());
876 if accepted {
877 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
878 with_registry_mut(|registry| {
879 registry.record_scheduler_instructions(token, instructions);
880 Ok(())
881 })
882 .unwrap_or_else(|error| {
883 trap_callback_failure("watchdog scheduler instruction accounting", &error)
884 });
885 }
886}
887
888fn dispatch_watchdog_work(token: &CallbackToken) {
889 let instructions_before = platform::instruction_counter();
890 let accepted = with_registry_mut(|registry| {
891 registry.consume_provider_handle(token);
892 Ok(registry.begin_watchdog_work(token))
893 });
894 match accepted {
895 Ok(CallbackAcceptance::Accepted) => {}
896 Ok(CallbackAcceptance::Stale) => return,
897 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
898 }
899
900 let callback =
901 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
902 {
903 Ok(callback) => callback,
904 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
905 };
906 let context = TimerContext::new(token.clone());
907 let result = {
908 let Ok(mut callback) = callback.try_borrow_mut() else {
909 trap_callback_failure(
910 "watchdog callback ownership",
911 &TimerError::OwnershipInvariant,
912 );
913 };
914 callback(context)
915 };
916 finish_watchdog_dispatch(token, result);
917 record_work_instructions(token, instructions_before);
918}
919
920fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
921 let claim = RegistrationClaim::delegated(token.identity().clone(), token.claim_generation());
922 let completed = with_registry_mut(|registry| {
923 let handles = registry
924 .take_provider_handles_for_claim(&claim)
925 .map_err(TimerError::from)?;
926 #[cfg(test)]
927 {
928 if take_watchdog_completion_fault() {
929 return Err(TimerError::OwnershipInvariant);
930 }
931 }
932 let transition = registry
933 .complete_watchdog_work(token, platform::time_ns(), result)
934 .map_err(TimerError::from)?;
935 Ok((transition, handles))
936 });
937 let (transition, handles) =
938 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
939 finish_callback_transition(token, transition, handles);
940}
941
942fn finish_callback_transition(
943 token: &CallbackToken,
944 transition: RegistryTransition,
945 handles: ProviderHandles,
946) {
947 match finish_transition(transition, handles) {
948 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
949 Err(
950 error @ (TimerError::NotInitialized
951 | TimerError::RuntimeBusy
952 | TimerError::Register(_)
953 | TimerError::Schedule(_)
954 | TimerError::RegistrationExpired
955 | TimerError::WrongPolicy
956 | TimerError::OwnershipInvariant
957 | TimerError::ReconciliationConflict),
958 ) => {
959 if token.role() == CallbackRole::WatchdogWork {
960 trap_callback_failure("watchdog provider-handle completion", &error);
961 }
962 fail_provider_binding(token).unwrap_or_else(|binding_error| {
963 trap_callback_failure("provider-binding failure cleanup", &binding_error)
964 });
965 }
966 }
967}
968
969fn fail_provider_binding(token: &CallbackToken) -> Result<(), TimerError> {
970 let claim = RegistrationClaim::delegated(token.identity().clone(), token.claim_generation());
971 fail_claim_provider_binding(&claim)
972}
973
974fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
975 let failed = with_registry_mut(|registry| {
976 registry
977 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
978 .map_err(TimerError::from)
979 });
980 let mut handles = failed?;
981 if let Some(wakeup) = handles.take_wakeup() {
982 clear_provider_handle(wakeup);
983 }
984 if let Some(work) = handles.take_work() {
985 clear_provider_handle(work);
986 }
987 Ok(())
988}
989
990fn record_work_instructions(token: &CallbackToken, instructions_before: u64) {
991 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
992 with_registry_mut(|registry| {
993 registry.record_work_instructions(token, instructions);
994 Ok(())
995 })
996 .unwrap_or_else(|error| trap_callback_failure("work instruction accounting", &error));
997}
998
999fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
1000 platform::trap(&format!("ic-timers {context} failed: {error}"))
1001}
1002
1003fn with_registry<T>(
1004 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
1005) -> Result<T, TimerError> {
1006 RUNTIME.with(|runtime| {
1007 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1008 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1009 operation(registry)
1010 })
1011}
1012
1013fn with_registry_mut<T>(
1014 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1015) -> Result<T, TimerError> {
1016 RUNTIME.with(|runtime| {
1017 let mut runtime = runtime
1018 .try_borrow_mut()
1019 .map_err(|_| TimerError::RuntimeBusy)?;
1020 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1021 operation(registry)
1022 })
1023}
1024
1025#[cfg(test)]
1026fn reset_for_test(now_ns: u64, canister_version: u64) {
1027 platform::reset(now_ns, canister_version);
1028 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1029 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(None));
1030 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(false));
1031 RUNTIME.with(|runtime| {
1032 *runtime.borrow_mut() = None;
1033 });
1034}
1035
1036#[cfg(test)]
1037thread_local! {
1038 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1039 static PROVIDER_INSTALL_FAULT_AFTER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1040 static PROVIDER_CONFIRMATION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1041}
1042
1043#[cfg(test)]
1044fn inject_watchdog_completion_fault() {
1045 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1046}
1047
1048#[cfg(test)]
1049fn take_watchdog_completion_fault() -> bool {
1050 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1051}
1052
1053#[cfg(test)]
1054fn inject_provider_install_fault() {
1055 inject_provider_install_fault_after(0);
1056}
1057
1058#[cfg(test)]
1059fn inject_provider_install_fault_after(successful_installs: u64) {
1060 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(Some(successful_installs)));
1061}
1062
1063#[cfg(test)]
1064fn take_provider_install_fault() -> bool {
1065 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| match fault.get() {
1066 Some(0) => {
1067 fault.set(None);
1068 true
1069 }
1070 Some(remaining) => {
1071 fault.set(Some(remaining - 1));
1072 false
1073 }
1074 None => false,
1075 })
1076}
1077
1078#[cfg(test)]
1079fn inject_provider_confirmation_fault() {
1080 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(true));
1081}
1082
1083#[cfg(test)]
1084fn take_provider_confirmation_fault() -> bool {
1085 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.replace(false))
1086}
1087
1088#[cfg(test)]
1089mod tests;