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>(
324 registration: &mut Option<OnceRegistration>,
325 identity: &TimerIdentity,
326 lifetime: DeclarationLifetime,
327 desired: Option<TimerSchedule>,
328 callback: F,
329) -> Result<(), TimerError>
330where
331 F: FnMut(TimerContext) -> Fut + 'static,
332 Fut: Future<Output = TimerRunResult> + 'static,
333{
334 if registration.is_none() {
335 *registration = Some(register_once(identity.clone(), lifetime, callback)?);
336 }
337 verify_declaration(
338 registration.as_ref().map(OnceRegistration::identity),
339 identity,
340 crate::TimerPolicy::Once,
341 lifetime,
342 )?;
343 registration
344 .as_ref()
345 .ok_or(TimerError::ReconciliationConflict)?
346 .reconcile_schedule(desired)
347}
348
349pub fn reconcile_after_completion<F, Fut>(
356 registration: &mut Option<AfterCompletionRegistration>,
357 identity: &TimerIdentity,
358 cadence: TimerCadence,
359 lifetime: DeclarationLifetime,
360 desired: TimerReconcileState,
361 callback: F,
362) -> Result<(), TimerError>
363where
364 F: FnMut(TimerContext) -> Fut + 'static,
365 Fut: Future<Output = TimerRunResult> + 'static,
366{
367 if registration.is_none() {
368 *registration = Some(register_after_completion(
369 identity.clone(),
370 cadence,
371 lifetime,
372 callback,
373 )?);
374 }
375 verify_declaration(
376 registration
377 .as_ref()
378 .map(AfterCompletionRegistration::identity),
379 identity,
380 crate::TimerPolicy::AfterCompletion { cadence },
381 lifetime,
382 )?;
383 let registration = registration
384 .as_ref()
385 .ok_or(TimerError::ReconciliationConflict)?;
386 match desired {
387 TimerReconcileState::Inactive => registration.cancel(),
388 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
389 }
390}
391
392pub fn reconcile_watchdog<F>(
398 registration: &mut Option<WatchdogRegistration>,
399 identity: &TimerIdentity,
400 cadence: TimerCadence,
401 lifetime: DeclarationLifetime,
402 desired: TimerReconcileState,
403 callback: F,
404) -> Result<(), TimerError>
405where
406 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
407{
408 if registration.is_none() {
409 *registration = Some(register_watchdog(
410 identity.clone(),
411 cadence,
412 lifetime,
413 callback,
414 )?);
415 }
416 verify_declaration(
417 registration.as_ref().map(WatchdogRegistration::identity),
418 identity,
419 crate::TimerPolicy::Watchdog { cadence },
420 lifetime,
421 )?;
422 let registration = registration
423 .as_ref()
424 .ok_or(TimerError::ReconciliationConflict)?;
425 match desired {
426 TimerReconcileState::Inactive => registration.cancel(),
427 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
428 }
429}
430
431fn verify_declaration(
432 claimed_identity: Option<&TimerIdentity>,
433 identity: &TimerIdentity,
434 policy: crate::TimerPolicy,
435 lifetime: DeclarationLifetime,
436) -> Result<(), TimerError> {
437 if claimed_identity != Some(identity) {
438 return Err(TimerError::ReconciliationConflict);
439 }
440 let snapshot = timer_snapshot(identity)?.ok_or(TimerError::RegistrationExpired)?;
441 if snapshot.policy() != policy || snapshot.lifetime() != lifetime {
442 return Err(TimerError::ReconciliationConflict);
443 }
444 Ok(())
445}
446
447pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
449 with_registry(|registry| Ok(registry.snapshot(identity)))
450}
451
452pub fn timer_snapshots() -> Result<Vec<TimerSnapshot>, TimerError> {
454 with_registry(|registry| Ok(registry.snapshots()))
455}
456
457pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
459 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
460}
461
462fn ensure_once_claim(
463 claim: &RegistrationClaim,
464 context: Option<&CallbackToken>,
465 schedule: TimerSchedule,
466) -> Result<(), TimerError> {
467 let transition = with_registry_mut(|registry| {
468 validate_context(registry, context)?;
469 registry
470 .ensure_once(claim, platform::time_ns(), schedule)
471 .map_err(TimerError::from)
472 })?;
473 finish_claim_transition(claim, transition, ProviderHandles::default())
474}
475
476fn reconcile_ordinary_claim(
477 claim: &RegistrationClaim,
478 context: Option<&CallbackToken>,
479 schedule: Option<TimerSchedule>,
480) -> Result<(), TimerError> {
481 if schedule.is_none() {
482 let (handles, transition) = with_registry_mut(|registry| {
483 validate_context(registry, context)?;
484 registry
485 .validate_ordinary_claim(claim)
486 .map_err(TimerError::from)?;
487 let handles = registry
488 .take_provider_handles_for_claim(claim)
489 .map_err(TimerError::from)?;
490 let transition = registry
491 .reconcile_ordinary(claim, platform::time_ns(), None)
492 .map_err(TimerError::from)?;
493 Ok((handles, transition))
494 })?;
495 return finish_claim_transition(claim, transition, handles);
496 }
497 let transition = with_registry_mut(|registry| {
498 validate_context(registry, context)?;
499 registry
500 .reconcile_ordinary(claim, platform::time_ns(), schedule)
501 .map_err(TimerError::from)
502 })?;
503 finish_claim_transition(claim, transition, ProviderHandles::default())
504}
505
506fn ensure_recurring_claim(
507 claim: &RegistrationClaim,
508 context: Option<&CallbackToken>,
509) -> Result<(), TimerError> {
510 let transition = with_registry_mut(|registry| {
511 validate_context(registry, context)?;
512 registry
513 .ensure_recurring(claim, platform::time_ns())
514 .map_err(TimerError::from)
515 })?;
516 finish_claim_transition(claim, transition, ProviderHandles::default())
517}
518
519fn cancel_claim(
520 claim: &RegistrationClaim,
521 context: Option<&CallbackToken>,
522) -> Result<(), TimerError> {
523 let (handles, transition) = with_registry_mut(|registry| {
524 validate_context(registry, context)?;
525 let handles = registry
526 .take_provider_handles_for_claim(claim)
527 .map_err(TimerError::from)?;
528 let transition = registry.cancel(claim).map_err(TimerError::from)?;
529 Ok((handles, transition))
530 })?;
531 finish_claim_transition(claim, transition, handles)
532}
533
534fn validate_context(
535 registry: &TimerRegistry,
536 context: Option<&CallbackToken>,
537) -> Result<(), TimerError> {
538 context.map_or(Ok(()), |token| {
539 registry
540 .validate_running_context(token)
541 .map_err(TimerError::from)
542 })
543}
544
545fn unregister_claim(claim: RegistrationClaim) -> Result<(), TimerError> {
546 let cleanup_claim =
547 RegistrationClaim::delegated(claim.identity().clone(), claim.claim_generation());
548 let (handles, transition) = with_registry_mut(|registry| {
549 let handles = registry
550 .take_provider_handles_for_claim(&claim)
551 .map_err(TimerError::from)?;
552 let transition = registry.unregister(claim).map_err(TimerError::from)?;
553 Ok((handles, transition))
554 })?;
555 finish_claim_transition(&cleanup_claim, transition, handles)
556}
557
558fn finish_claim_transition(
559 claim: &RegistrationClaim,
560 transition: RegistryTransition,
561 handles: ProviderHandles,
562) -> Result<(), TimerError> {
563 match finish_transition(transition, handles) {
564 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
565 Err(error) => match fail_claim_provider_binding(claim) {
566 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
567 Err(cleanup_error) => Err(cleanup_error),
568 },
569 }
570}
571
572fn finish_transition(
573 transition: RegistryTransition,
574 handles: ProviderHandles,
575) -> Result<(), TimerError> {
576 let failure = transition.failure();
577 let effect = transition.into_effect();
578 apply_effect(&effect, handles)?;
579 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
580}
581
582fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
583 match effect {
584 RegistryEffect::None => restore_provider_handles(handles),
585 RegistryEffect::ArmWakeup {
586 token,
587 delay_ns,
588 replace,
589 ..
590 } => {
591 let detached_wakeup = handles.take_wakeup();
592 if *replace {
593 let replaced = match detached_wakeup {
594 Some(handle) => Some(handle),
595 None => with_registry_mut(|registry| {
596 Ok(registry.take_wakeup_handle(token.identity()))
597 })?,
598 };
599 if let Some(replaced) = replaced {
600 clear_provider_handle(replaced);
601 }
602 } else if let Some(detached_wakeup) = detached_wakeup {
603 restore_provider_handle(detached_wakeup)?;
604 }
605 if let Some(work) = handles.take_work() {
606 restore_provider_handle(work)?;
607 }
608 arm_wakeup(token, *delay_ns, effect)
609 }
610 RegistryEffect::ClearCallbacks {
611 identity,
612 clear_wakeup,
613 clear_work,
614 } => {
615 let detached_wakeup = handles.take_wakeup();
616 if *clear_wakeup {
617 let wakeup = match detached_wakeup {
618 Some(handle) => Some(handle),
619 None => {
620 with_registry_mut(|registry| Ok(registry.take_wakeup_handle(identity)))?
621 }
622 };
623 if let Some(wakeup) = wakeup {
624 clear_provider_handle(wakeup);
625 }
626 } else if let Some(wakeup) = detached_wakeup {
627 restore_provider_handle(wakeup)?;
628 }
629 let detached_work = handles.take_work();
630 if *clear_work {
631 let work = match detached_work {
632 Some(handle) => Some(handle),
633 None => with_registry_mut(|registry| Ok(registry.take_work_handle(identity)))?,
634 };
635 if let Some(work) = work {
636 clear_provider_handle(work);
637 }
638 } else if let Some(work) = detached_work {
639 restore_provider_handle(work)?;
640 }
641 restore_provider_handles(handles)
642 }
643 RegistryEffect::DispatchWatchdog {
644 successor,
645 successor_delay_ns,
646 work,
647 ..
648 } => {
649 if let Some(wakeup) = handles.take_wakeup() {
650 clear_provider_handle(wakeup);
651 }
652 let replaced_work = handles.take_work().or(with_registry_mut(|registry| {
653 Ok(registry.take_work_handle(successor.identity()))
654 })?);
655 if let Some(replaced_work) = replaced_work {
656 clear_provider_handle(replaced_work);
657 }
658 dispatch_watchdog_effect(successor, *successor_delay_ns, work, effect)
659 }
660 }
661}
662
663fn arm_wakeup(
664 token: &CallbackToken,
665 delay_ns: u64,
666 effect: &RegistryEffect,
667) -> Result<(), TimerError> {
668 let task_token = token.clone();
669 let handle = platform::set_timer(Duration::from_nanos(delay_ns), async move {
670 dispatch_wakeup(task_token).await;
671 });
672 if let Err((error, handle)) = install_provider_handle(token, handle) {
673 platform::clear_timer(handle);
674 return Err(error);
675 }
676 if let Err(error) = confirm_effect(effect) {
677 let handle = with_registry_mut(|registry| {
678 registry
679 .take_wakeup_handle(token.identity())
680 .ok_or(TimerError::OwnershipInvariant)
681 })?;
682 clear_provider_handle(handle);
683 return Err(error);
684 }
685 Ok(())
686}
687
688fn dispatch_watchdog_effect(
689 successor: &CallbackToken,
690 successor_delay_ns: u64,
691 work: &CallbackToken,
692 effect: &RegistryEffect,
693) -> Result<(), TimerError> {
694 let successor_token = successor.clone();
695 let successor_handle =
696 platform::set_timer(Duration::from_nanos(successor_delay_ns), async move {
697 dispatch_watchdog_scheduler(&successor_token);
698 });
699 if let Err((error, handle)) = install_provider_handle(successor, successor_handle) {
700 platform::clear_timer(handle);
701 return Err(error);
702 }
703
704 let work_token = work.clone();
705 let work_handle = platform::set_timer(Duration::ZERO, async move {
706 dispatch_watchdog_work(&work_token);
707 });
708 if let Err((error, handle)) = install_provider_handle(work, work_handle) {
709 platform::clear_timer(handle);
710 clear_entry_provider_handles(successor.identity())?;
711 return Err(error);
712 }
713 if let Err(error) = confirm_effect(effect) {
714 clear_entry_provider_handles(successor.identity())?;
715 return Err(error);
716 }
717 Ok(())
718}
719
720fn install_provider_handle(
721 token: &CallbackToken,
722 handle: TimerHandle,
723) -> Result<(), (TimerError, TimerHandle)> {
724 #[cfg(test)]
725 if take_provider_install_fault() {
726 return Err((TimerError::OwnershipInvariant, handle));
727 }
728 RUNTIME.with(|runtime| {
729 let Ok(mut runtime) = runtime.try_borrow_mut() else {
730 return Err((TimerError::RuntimeBusy, handle));
731 };
732 let Some(registry) = runtime.as_mut() else {
733 return Err((TimerError::NotInitialized, handle));
734 };
735 match registry.install_provider_handle(token, handle) {
736 Ok(()) => Ok(()),
737 Err((error, handle)) => Err((TimerError::from(error), handle)),
738 }
739 })
740}
741
742fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
743 with_registry_mut(|registry| {
744 registry
745 .confirm_effect_applied(effect)
746 .map_err(TimerError::from)
747 })
748}
749
750fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
751 if let Some(wakeup) = handles.take_wakeup() {
752 restore_provider_handle(wakeup)?;
753 }
754 if let Some(work) = handles.take_work() {
755 restore_provider_handle(work)?;
756 }
757 Ok(())
758}
759
760fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
761 let (token, handle) = handle.into_parts();
762 match install_provider_handle(&token, handle) {
763 Ok(()) => Ok(()),
764 Err((error, handle)) => {
765 platform::clear_timer(handle);
766 Err(error)
767 }
768 }
769}
770
771fn clear_provider_handle(handle: ProviderHandle) {
772 let (_, handle) = handle.into_parts();
773 platform::clear_timer(handle);
774}
775
776fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
777 let mut handles = with_registry_mut(|registry| {
778 Ok(ProviderHandles::from_parts(
779 registry.take_wakeup_handle(identity),
780 registry.take_work_handle(identity),
781 ))
782 })?;
783 if let Some(wakeup) = handles.take_wakeup() {
784 clear_provider_handle(wakeup);
785 }
786 if let Some(work) = handles.take_work() {
787 clear_provider_handle(work);
788 }
789 Ok(())
790}
791
792#[allow(clippy::future_not_send)] async fn dispatch_wakeup(token: CallbackToken) {
794 match token.role() {
795 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
796 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
797 CallbackRole::WatchdogWork => {}
798 }
799}
800
801#[allow(clippy::future_not_send)] async fn dispatch_ordinary(token: CallbackToken) {
803 let instructions_before = platform::instruction_counter();
804 let accepted = with_registry_mut(|registry| {
805 registry.consume_provider_handle(&token);
806 Ok(registry.begin_ordinary(&token))
807 });
808 match accepted {
809 Ok(CallbackAcceptance::Accepted) => {}
810 Ok(CallbackAcceptance::Stale) => return,
811 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
812 }
813
814 let callback = match with_registry(|registry| {
815 registry.ordinary_callback(&token).map_err(TimerError::from)
816 }) {
817 Ok(callback) => callback,
818 Err(TimerError::OwnershipInvariant) => {
819 fail_ordinary_dispatch(&token);
820 return;
821 }
822 Err(error) => trap_callback_failure("ordinary callback lookup", &error),
823 };
824 let context = TimerContext::new(token.clone());
825 let future = {
826 let Ok(mut callback) = callback.try_borrow_mut() else {
827 fail_ordinary_dispatch(&token);
828 return;
829 };
830 callback(context)
831 };
832 let result = future.await;
833 let transition = with_registry_mut(|registry| {
834 registry
835 .complete_ordinary(&token, platform::time_ns(), result)
836 .map_err(TimerError::from)
837 });
838 let transition = transition
839 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
840 finish_callback_transition(&token, transition, ProviderHandles::default());
841 record_work_instructions(&token, instructions_before);
842}
843
844fn fail_ordinary_dispatch(token: &CallbackToken) {
845 let transition = with_registry_mut(|registry| {
846 registry
847 .complete_ordinary(
848 token,
849 platform::time_ns(),
850 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
851 )
852 .map_err(TimerError::from)
853 });
854 let transition = transition.unwrap_or_else(|error| {
855 trap_callback_failure("ordinary invariant-failure completion", &error)
856 });
857 finish_callback_transition(token, transition, ProviderHandles::default());
858}
859
860fn dispatch_watchdog_scheduler(token: &CallbackToken) {
861 let instructions_before = platform::instruction_counter();
862 let transition = with_registry_mut(|registry| {
863 registry.consume_provider_handle(token);
864 Ok(registry.begin_watchdog_scheduler(token, platform::time_ns()))
865 });
866 let transition = transition
867 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
868 let accepted = !matches!(transition.effect(), RegistryEffect::None);
869 finish_callback_transition(token, transition, ProviderHandles::default());
870 if accepted {
871 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
872 with_registry_mut(|registry| {
873 registry.record_scheduler_instructions(token, instructions);
874 Ok(())
875 })
876 .unwrap_or_else(|error| {
877 trap_callback_failure("watchdog scheduler instruction accounting", &error)
878 });
879 }
880}
881
882fn dispatch_watchdog_work(token: &CallbackToken) {
883 let instructions_before = platform::instruction_counter();
884 let accepted = with_registry_mut(|registry| {
885 registry.consume_provider_handle(token);
886 Ok(registry.begin_watchdog_work(token))
887 });
888 match accepted {
889 Ok(CallbackAcceptance::Accepted) => {}
890 Ok(CallbackAcceptance::Stale) => return,
891 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
892 }
893
894 let callback =
895 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
896 {
897 Ok(callback) => callback,
898 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
899 };
900 let context = TimerContext::new(token.clone());
901 let result = {
902 let Ok(mut callback) = callback.try_borrow_mut() else {
903 trap_callback_failure(
904 "watchdog callback ownership",
905 &TimerError::OwnershipInvariant,
906 );
907 };
908 callback(context)
909 };
910 finish_watchdog_dispatch(token, result);
911 record_work_instructions(token, instructions_before);
912}
913
914fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
915 let claim = RegistrationClaim::delegated(token.identity().clone(), token.claim_generation());
916 let completed = with_registry_mut(|registry| {
917 let handles = registry
918 .take_provider_handles_for_claim(&claim)
919 .map_err(TimerError::from)?;
920 #[cfg(test)]
921 {
922 if take_watchdog_completion_fault() {
923 return Err(TimerError::OwnershipInvariant);
924 }
925 }
926 let transition = registry
927 .complete_watchdog_work(token, platform::time_ns(), result)
928 .map_err(TimerError::from)?;
929 Ok((transition, handles))
930 });
931 let (transition, handles) =
932 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
933 finish_callback_transition(token, transition, handles);
934}
935
936fn finish_callback_transition(
937 token: &CallbackToken,
938 transition: RegistryTransition,
939 handles: ProviderHandles,
940) {
941 match finish_transition(transition, handles) {
942 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
943 Err(
944 error @ (TimerError::NotInitialized
945 | TimerError::RuntimeBusy
946 | TimerError::Register(_)
947 | TimerError::Schedule(_)
948 | TimerError::RegistrationExpired
949 | TimerError::WrongPolicy
950 | TimerError::OwnershipInvariant
951 | TimerError::ReconciliationConflict),
952 ) => {
953 if token.role() == CallbackRole::WatchdogWork {
954 trap_callback_failure("watchdog provider-handle completion", &error);
955 }
956 fail_provider_binding(token).unwrap_or_else(|binding_error| {
957 trap_callback_failure("provider-binding failure cleanup", &binding_error)
958 });
959 }
960 }
961}
962
963fn fail_provider_binding(token: &CallbackToken) -> Result<(), TimerError> {
964 let claim = RegistrationClaim::delegated(token.identity().clone(), token.claim_generation());
965 fail_claim_provider_binding(&claim)
966}
967
968fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
969 let failed = with_registry_mut(|registry| {
970 registry
971 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
972 .map_err(TimerError::from)
973 });
974 let mut handles = failed?;
975 if let Some(wakeup) = handles.take_wakeup() {
976 clear_provider_handle(wakeup);
977 }
978 if let Some(work) = handles.take_work() {
979 clear_provider_handle(work);
980 }
981 Ok(())
982}
983
984fn record_work_instructions(token: &CallbackToken, instructions_before: u64) {
985 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
986 with_registry_mut(|registry| {
987 registry.record_work_instructions(token, instructions);
988 Ok(())
989 })
990 .unwrap_or_else(|error| trap_callback_failure("work instruction accounting", &error));
991}
992
993fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
994 platform::trap(&format!("ic-timers {context} failed: {error}"))
995}
996
997fn with_registry<T>(
998 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
999) -> Result<T, TimerError> {
1000 RUNTIME.with(|runtime| {
1001 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1002 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1003 operation(registry)
1004 })
1005}
1006
1007fn with_registry_mut<T>(
1008 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1009) -> Result<T, TimerError> {
1010 RUNTIME.with(|runtime| {
1011 let mut runtime = runtime
1012 .try_borrow_mut()
1013 .map_err(|_| TimerError::RuntimeBusy)?;
1014 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1015 operation(registry)
1016 })
1017}
1018
1019#[cfg(test)]
1020fn reset_for_test(now_ns: u64, canister_version: u64) {
1021 platform::reset(now_ns, canister_version);
1022 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1023 PROVIDER_INSTALL_FAULT.with(|fault| fault.set(false));
1024 RUNTIME.with(|runtime| {
1025 *runtime.borrow_mut() = None;
1026 });
1027}
1028
1029#[cfg(test)]
1030thread_local! {
1031 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1032 static PROVIDER_INSTALL_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1033}
1034
1035#[cfg(test)]
1036fn inject_watchdog_completion_fault() {
1037 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1038}
1039
1040#[cfg(test)]
1041fn take_watchdog_completion_fault() -> bool {
1042 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1043}
1044
1045#[cfg(test)]
1046fn inject_provider_install_fault() {
1047 PROVIDER_INSTALL_FAULT.with(|fault| fault.set(true));
1048}
1049
1050#[cfg(test)]
1051fn take_provider_install_fault() -> bool {
1052 PROVIDER_INSTALL_FAULT.with(|fault| fault.replace(false))
1053}
1054
1055#[cfg(test)]
1056mod tests;