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