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 has_armed_wakeup(&self) -> Result<bool, TimerError> {
161 has_armed_wakeup_claim(&self.claim)
162 }
163
164 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
166 ensure_once_claim(&self.claim, None, schedule)
167 }
168
169 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
173 reconcile_ordinary_claim(&self.claim, None, schedule)
174 }
175
176 pub fn cancel(&self) -> Result<(), TimerError> {
178 cancel_claim(&self.claim, None)
179 }
180
181 pub fn unregister(self) -> Result<(), TimerError> {
183 unregister_claim(self.claim)
184 }
185}
186
187#[must_use = "retain the registration claim so the timer remains controllable"]
189pub struct AfterCompletionRegistration {
190 claim: RegistrationClaim,
191}
192
193#[must_use = "retain the registration claim so the timer remains controllable"]
195pub struct WatchdogRegistration {
196 claim: RegistrationClaim,
197}
198
199#[derive(Clone, Copy, Debug, Eq, PartialEq)]
201pub enum TimerReconcileState {
202 Inactive,
204 Scheduled,
206}
207
208impl WatchdogRegistration {
209 #[must_use]
211 pub const fn identity(&self) -> &TimerIdentity {
212 self.claim.identity()
213 }
214
215 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
222 has_armed_wakeup_claim(&self.claim)
223 }
224
225 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
227 ensure_recurring_claim(&self.claim, None)
228 }
229
230 pub fn cancel(&self) -> Result<(), TimerError> {
232 cancel_claim(&self.claim, None)
233 }
234
235 pub fn unregister(self) -> Result<(), TimerError> {
237 unregister_claim(self.claim)
238 }
239}
240
241impl AfterCompletionRegistration {
242 #[must_use]
244 pub const fn identity(&self) -> &TimerIdentity {
245 self.claim.identity()
246 }
247
248 pub fn has_armed_wakeup(&self) -> Result<bool, TimerError> {
254 has_armed_wakeup_claim(&self.claim)
255 }
256
257 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
259 ensure_recurring_claim(&self.claim, None)
260 }
261
262 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
266 reconcile_ordinary_claim(&self.claim, None, schedule)
267 }
268
269 pub fn cancel(&self) -> Result<(), TimerError> {
271 cancel_claim(&self.claim, None)
272 }
273
274 pub fn unregister(self) -> Result<(), TimerError> {
276 unregister_claim(self.claim)
277 }
278}
279
280pub fn register_once<F, Fut>(
282 identity: TimerIdentity,
283 lifetime: DeclarationLifetime,
284 mut callback: F,
285) -> Result<OnceRegistration, TimerError>
286where
287 F: FnMut(TimerContext) -> Fut + 'static,
288 Fut: Future<Output = TimerRunResult> + 'static,
289{
290 let callback: OrdinaryCallback = Rc::new(RefCell::new(Box::new(move |context| {
291 Box::pin(callback(context))
292 })));
293 let claim = with_registry_mut(|registry| {
294 registry
295 .register_once_with_callback(identity, lifetime, callback)
296 .map_err(TimerError::from)
297 })?;
298 Ok(OnceRegistration { claim })
299}
300
301pub fn register_after_completion<F, Fut>(
303 identity: TimerIdentity,
304 cadence: TimerCadence,
305 lifetime: DeclarationLifetime,
306 mut callback: F,
307) -> Result<AfterCompletionRegistration, TimerError>
308where
309 F: FnMut(TimerContext) -> Fut + 'static,
310 Fut: Future<Output = TimerRunResult> + 'static,
311{
312 let callback: OrdinaryCallback = Rc::new(RefCell::new(Box::new(move |context| {
313 Box::pin(callback(context))
314 })));
315 let claim = with_registry_mut(|registry| {
316 registry
317 .register_after_completion_with_callback(identity, cadence, lifetime, callback)
318 .map_err(TimerError::from)
319 })?;
320 Ok(AfterCompletionRegistration { claim })
321}
322
323pub fn register_watchdog<F>(
329 identity: TimerIdentity,
330 cadence: TimerCadence,
331 lifetime: DeclarationLifetime,
332 callback: F,
333) -> Result<WatchdogRegistration, TimerError>
334where
335 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
336{
337 let callback: WatchdogCallback = Rc::new(RefCell::new(Box::new(callback)));
338 let claim = with_registry_mut(|registry| {
339 registry
340 .register_watchdog_with_callback(identity, cadence, lifetime, callback)
341 .map_err(TimerError::from)
342 })?;
343 Ok(WatchdogRegistration { claim })
344}
345
346pub fn reconcile_once<F, Fut>(
354 registration: &mut Option<OnceRegistration>,
355 identity: &TimerIdentity,
356 desired: Option<TimerSchedule>,
357 callback: F,
358) -> Result<(), TimerError>
359where
360 F: FnMut(TimerContext) -> Fut + 'static,
361 Fut: Future<Output = TimerRunResult> + 'static,
362{
363 if registration.is_none() {
364 *registration = Some(register_once(
365 identity.clone(),
366 DeclarationLifetime::Retained,
367 callback,
368 )?);
369 }
370 verify_declaration(
371 registration.as_ref().map(OnceRegistration::identity),
372 identity,
373 crate::TimerPolicy::Once,
374 )?;
375 registration
376 .as_ref()
377 .ok_or(TimerError::ReconciliationConflict)?
378 .reconcile_schedule(desired)
379}
380
381pub fn reconcile_after_completion<F, Fut>(
390 registration: &mut Option<AfterCompletionRegistration>,
391 identity: &TimerIdentity,
392 cadence: TimerCadence,
393 desired: TimerReconcileState,
394 callback: F,
395) -> Result<(), TimerError>
396where
397 F: FnMut(TimerContext) -> Fut + 'static,
398 Fut: Future<Output = TimerRunResult> + 'static,
399{
400 if registration.is_none() {
401 *registration = Some(register_after_completion(
402 identity.clone(),
403 cadence,
404 DeclarationLifetime::Retained,
405 callback,
406 )?);
407 }
408 verify_declaration(
409 registration
410 .as_ref()
411 .map(AfterCompletionRegistration::identity),
412 identity,
413 crate::TimerPolicy::AfterCompletion { cadence },
414 )?;
415 let registration = registration
416 .as_ref()
417 .ok_or(TimerError::ReconciliationConflict)?;
418 match desired {
419 TimerReconcileState::Inactive => registration.cancel(),
420 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
421 }
422}
423
424pub fn reconcile_watchdog<F>(
431 registration: &mut Option<WatchdogRegistration>,
432 identity: &TimerIdentity,
433 cadence: TimerCadence,
434 desired: TimerReconcileState,
435 callback: F,
436) -> Result<(), TimerError>
437where
438 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
439{
440 if registration.is_none() {
441 *registration = Some(register_watchdog(
442 identity.clone(),
443 cadence,
444 DeclarationLifetime::Retained,
445 callback,
446 )?);
447 }
448 verify_declaration(
449 registration.as_ref().map(WatchdogRegistration::identity),
450 identity,
451 crate::TimerPolicy::Watchdog { cadence },
452 )?;
453 let registration = registration
454 .as_ref()
455 .ok_or(TimerError::ReconciliationConflict)?;
456 match desired {
457 TimerReconcileState::Inactive => registration.cancel(),
458 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
459 }
460}
461
462fn verify_declaration(
463 claimed_identity: Option<&TimerIdentity>,
464 identity: &TimerIdentity,
465 policy: crate::TimerPolicy,
466) -> Result<(), TimerError> {
467 if claimed_identity != Some(identity) {
468 return Err(TimerError::ReconciliationConflict);
469 }
470 let snapshot = timer_snapshot(identity)?.ok_or(TimerError::RegistrationExpired)?;
471 if snapshot.policy() != policy || snapshot.lifetime() != DeclarationLifetime::Retained {
472 return Err(TimerError::ReconciliationConflict);
473 }
474 Ok(())
475}
476
477pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
479 with_registry(|registry| Ok(registry.snapshot(identity)))
480}
481
482pub fn timer_snapshots() -> Result<Vec<TimerSnapshot>, TimerError> {
484 with_registry(|registry| Ok(registry.snapshots()))
485}
486
487pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
489 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
490}
491
492fn has_armed_wakeup_claim(claim: &RegistrationClaim) -> Result<bool, TimerError> {
493 with_registry(|registry| registry.has_armed_wakeup(claim).map_err(TimerError::from))
494}
495
496fn ensure_once_claim(
497 claim: &RegistrationClaim,
498 context: Option<&CallbackToken>,
499 schedule: TimerSchedule,
500) -> Result<(), TimerError> {
501 let transition = with_registry_mut(|registry| {
502 validate_context(registry, context)?;
503 registry
504 .ensure_once(claim, platform::time_ns(), schedule)
505 .map_err(TimerError::from)
506 })?;
507 finish_claim_transition(claim, transition, ProviderHandles::default())
508}
509
510fn reconcile_ordinary_claim(
511 claim: &RegistrationClaim,
512 context: Option<&CallbackToken>,
513 schedule: Option<TimerSchedule>,
514) -> Result<(), TimerError> {
515 if schedule.is_none() {
516 let (handles, transition) = with_registry_mut(|registry| {
517 validate_context(registry, context)?;
518 registry
519 .validate_ordinary_claim(claim)
520 .map_err(TimerError::from)?;
521 let handles = registry
522 .take_provider_handles_for_claim(claim)
523 .map_err(TimerError::from)?;
524 let transition = registry
525 .reconcile_ordinary(claim, platform::time_ns(), None)
526 .map_err(TimerError::from)?;
527 Ok((handles, transition))
528 })?;
529 return finish_claim_transition(claim, transition, handles);
530 }
531 let transition = with_registry_mut(|registry| {
532 validate_context(registry, context)?;
533 registry
534 .reconcile_ordinary(claim, platform::time_ns(), schedule)
535 .map_err(TimerError::from)
536 })?;
537 finish_claim_transition(claim, transition, ProviderHandles::default())
538}
539
540fn ensure_recurring_claim(
541 claim: &RegistrationClaim,
542 context: Option<&CallbackToken>,
543) -> Result<(), TimerError> {
544 let transition = with_registry_mut(|registry| {
545 validate_context(registry, context)?;
546 registry
547 .ensure_recurring(claim, platform::time_ns())
548 .map_err(TimerError::from)
549 })?;
550 finish_claim_transition(claim, transition, ProviderHandles::default())
551}
552
553fn cancel_claim(
554 claim: &RegistrationClaim,
555 context: Option<&CallbackToken>,
556) -> Result<(), TimerError> {
557 let (handles, transition) = with_registry_mut(|registry| {
558 validate_context(registry, context)?;
559 let handles = registry
560 .take_provider_handles_for_claim(claim)
561 .map_err(TimerError::from)?;
562 let transition = registry.cancel(claim).map_err(TimerError::from)?;
563 Ok((handles, transition))
564 })?;
565 finish_claim_transition(claim, transition, handles)
566}
567
568fn validate_context(
569 registry: &TimerRegistry,
570 context: Option<&CallbackToken>,
571) -> Result<(), TimerError> {
572 context.map_or(Ok(()), |token| {
573 registry
574 .validate_running_context(token)
575 .map_err(TimerError::from)
576 })
577}
578
579fn unregister_claim(claim: RegistrationClaim) -> Result<(), TimerError> {
580 let cleanup_claim =
581 RegistrationClaim::delegated(claim.identity().clone(), claim.claim_generation());
582 let (handles, transition) = with_registry_mut(|registry| {
583 let handles = registry
584 .take_provider_handles_for_claim(&claim)
585 .map_err(TimerError::from)?;
586 let transition = registry.unregister(claim).map_err(TimerError::from)?;
587 Ok((handles, transition))
588 })?;
589 finish_claim_transition(&cleanup_claim, transition, handles)
590}
591
592fn finish_claim_transition(
593 claim: &RegistrationClaim,
594 transition: RegistryTransition,
595 handles: ProviderHandles,
596) -> Result<(), TimerError> {
597 match finish_transition(transition, handles) {
598 result @ (Ok(()) | Err(TimerError::ControlFailure(_))) => result,
599 Err(error) => match fail_claim_provider_binding(claim) {
600 Ok(()) | Err(TimerError::RegistrationExpired) => Err(error),
601 Err(cleanup_error) => Err(cleanup_error),
602 },
603 }
604}
605
606fn finish_transition(
607 transition: RegistryTransition,
608 handles: ProviderHandles,
609) -> Result<(), TimerError> {
610 let failure = transition.failure();
611 let effect = transition.into_effect();
612 apply_effect(&effect, handles)?;
613 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
614}
615
616fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
617 match effect {
618 RegistryEffect::None => restore_provider_handles(handles),
619 RegistryEffect::ArmWakeup {
620 token,
621 delay_ns,
622 replace,
623 ..
624 } => {
625 let detached_wakeup = handles.take_wakeup();
626 if *replace {
627 let replaced = match detached_wakeup {
628 Some(handle) => Some(handle),
629 None => with_registry_mut(|registry| {
630 Ok(registry.take_wakeup_handle(token.identity()))
631 })?,
632 };
633 if let Some(replaced) = replaced {
634 clear_provider_handle(replaced);
635 }
636 } else if let Some(detached_wakeup) = detached_wakeup {
637 restore_provider_handle(detached_wakeup)?;
638 }
639 if let Some(work) = handles.take_work() {
640 restore_provider_handle(work)?;
641 }
642 arm_wakeup(token, *delay_ns, effect)
643 }
644 RegistryEffect::ClearCallbacks {
645 identity,
646 clear_wakeup,
647 clear_work,
648 } => {
649 let detached_wakeup = handles.take_wakeup();
650 if *clear_wakeup {
651 let wakeup = match detached_wakeup {
652 Some(handle) => Some(handle),
653 None => {
654 with_registry_mut(|registry| Ok(registry.take_wakeup_handle(identity)))?
655 }
656 };
657 if let Some(wakeup) = wakeup {
658 clear_provider_handle(wakeup);
659 }
660 } else if let Some(wakeup) = detached_wakeup {
661 restore_provider_handle(wakeup)?;
662 }
663 let detached_work = handles.take_work();
664 if *clear_work {
665 let work = match detached_work {
666 Some(handle) => Some(handle),
667 None => with_registry_mut(|registry| Ok(registry.take_work_handle(identity)))?,
668 };
669 if let Some(work) = work {
670 clear_provider_handle(work);
671 }
672 } else if let Some(work) = detached_work {
673 restore_provider_handle(work)?;
674 }
675 restore_provider_handles(handles)
676 }
677 RegistryEffect::DispatchWatchdog {
678 successor,
679 successor_delay_ns,
680 work,
681 ..
682 } => {
683 if let Some(wakeup) = handles.take_wakeup() {
684 clear_provider_handle(wakeup);
685 }
686 let replaced_work = handles.take_work().or(with_registry_mut(|registry| {
687 Ok(registry.take_work_handle(successor.identity()))
688 })?);
689 if let Some(replaced_work) = replaced_work {
690 clear_provider_handle(replaced_work);
691 }
692 dispatch_watchdog_effect(successor, *successor_delay_ns, work, effect)
693 }
694 }
695}
696
697fn arm_wakeup(
698 token: &CallbackToken,
699 delay_ns: u64,
700 effect: &RegistryEffect,
701) -> Result<(), TimerError> {
702 let task_token = token.clone();
703 let handle = platform::set_timer(Duration::from_nanos(delay_ns), async move {
704 dispatch_wakeup(task_token).await;
705 });
706 if let Err((error, handle)) = install_provider_handle(token, handle) {
707 platform::clear_timer(handle);
708 return Err(error);
709 }
710 if let Err(error) = confirm_effect(effect) {
711 let handle = with_registry_mut(|registry| {
712 registry
713 .take_wakeup_handle(token.identity())
714 .ok_or(TimerError::OwnershipInvariant)
715 })?;
716 clear_provider_handle(handle);
717 return Err(error);
718 }
719 Ok(())
720}
721
722fn dispatch_watchdog_effect(
723 successor: &CallbackToken,
724 successor_delay_ns: u64,
725 work: &CallbackToken,
726 effect: &RegistryEffect,
727) -> Result<(), TimerError> {
728 let successor_token = successor.clone();
729 let successor_handle =
730 platform::set_timer(Duration::from_nanos(successor_delay_ns), async move {
731 dispatch_watchdog_scheduler(&successor_token);
732 });
733 if let Err((error, handle)) = install_provider_handle(successor, successor_handle) {
734 platform::clear_timer(handle);
735 return Err(error);
736 }
737
738 let work_token = work.clone();
739 let work_handle = platform::set_timer(Duration::ZERO, async move {
740 dispatch_watchdog_work(&work_token);
741 });
742 if let Err((error, handle)) = install_provider_handle(work, work_handle) {
743 platform::clear_timer(handle);
744 clear_entry_provider_handles(successor.identity())?;
745 return Err(error);
746 }
747 if let Err(error) = confirm_effect(effect) {
748 clear_entry_provider_handles(successor.identity())?;
749 return Err(error);
750 }
751 Ok(())
752}
753
754fn install_provider_handle(
755 token: &CallbackToken,
756 handle: TimerHandle,
757) -> Result<(), (TimerError, TimerHandle)> {
758 #[cfg(test)]
759 if take_provider_install_fault() {
760 return Err((TimerError::OwnershipInvariant, handle));
761 }
762 RUNTIME.with(|runtime| {
763 let Ok(mut runtime) = runtime.try_borrow_mut() else {
764 return Err((TimerError::RuntimeBusy, handle));
765 };
766 let Some(registry) = runtime.as_mut() else {
767 return Err((TimerError::NotInitialized, handle));
768 };
769 match registry.install_provider_handle(token, handle) {
770 Ok(()) => Ok(()),
771 Err((error, handle)) => Err((TimerError::from(error), handle)),
772 }
773 })
774}
775
776fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
777 #[cfg(test)]
778 if take_provider_confirmation_fault() {
779 return Err(TimerError::OwnershipInvariant);
780 }
781 with_registry_mut(|registry| {
782 registry
783 .confirm_effect_applied(effect)
784 .map_err(TimerError::from)
785 })
786}
787
788fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
789 if let Some(wakeup) = handles.take_wakeup() {
790 restore_provider_handle(wakeup)?;
791 }
792 if let Some(work) = handles.take_work() {
793 restore_provider_handle(work)?;
794 }
795 Ok(())
796}
797
798fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
799 let (token, handle) = handle.into_parts();
800 match install_provider_handle(&token, handle) {
801 Ok(()) => Ok(()),
802 Err((error, handle)) => {
803 platform::clear_timer(handle);
804 Err(error)
805 }
806 }
807}
808
809fn clear_provider_handle(handle: ProviderHandle) {
810 let (_, handle) = handle.into_parts();
811 platform::clear_timer(handle);
812}
813
814fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
815 let mut handles = with_registry_mut(|registry| {
816 Ok(ProviderHandles::from_parts(
817 registry.take_wakeup_handle(identity),
818 registry.take_work_handle(identity),
819 ))
820 })?;
821 if let Some(wakeup) = handles.take_wakeup() {
822 clear_provider_handle(wakeup);
823 }
824 if let Some(work) = handles.take_work() {
825 clear_provider_handle(work);
826 }
827 Ok(())
828}
829
830#[allow(clippy::future_not_send)] async fn dispatch_wakeup(token: CallbackToken) {
832 match token.role() {
833 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
834 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
835 CallbackRole::WatchdogWork => {}
836 }
837}
838
839#[allow(clippy::future_not_send)] async fn dispatch_ordinary(token: CallbackToken) {
841 let instructions_before = platform::instruction_counter();
842 let accepted = with_registry_mut(|registry| {
843 registry.consume_provider_handle(&token);
844 Ok(registry.begin_ordinary(&token))
845 });
846 match accepted {
847 Ok(CallbackAcceptance::Accepted) => {}
848 Ok(CallbackAcceptance::Stale) => return,
849 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
850 }
851
852 let callback = match with_registry(|registry| {
853 registry.ordinary_callback(&token).map_err(TimerError::from)
854 }) {
855 Ok(callback) => callback,
856 Err(TimerError::OwnershipInvariant) => {
857 fail_ordinary_dispatch(&token);
858 return;
859 }
860 Err(error) => trap_callback_failure("ordinary callback lookup", &error),
861 };
862 let context = TimerContext::new(token.clone());
863 let future = {
864 let Ok(mut callback) = callback.try_borrow_mut() else {
865 fail_ordinary_dispatch(&token);
866 return;
867 };
868 callback(context)
869 };
870 let result = future.await;
871 let transition = with_registry_mut(|registry| {
872 registry
873 .complete_ordinary(&token, platform::time_ns(), result)
874 .map_err(TimerError::from)
875 });
876 let transition = transition
877 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
878 finish_callback_transition(&token, transition, ProviderHandles::default());
879 record_work_instructions(&token, instructions_before);
880}
881
882fn fail_ordinary_dispatch(token: &CallbackToken) {
883 let transition = with_registry_mut(|registry| {
884 registry
885 .complete_ordinary(
886 token,
887 platform::time_ns(),
888 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
889 )
890 .map_err(TimerError::from)
891 });
892 let transition = transition.unwrap_or_else(|error| {
893 trap_callback_failure("ordinary invariant-failure completion", &error)
894 });
895 finish_callback_transition(token, transition, ProviderHandles::default());
896}
897
898fn dispatch_watchdog_scheduler(token: &CallbackToken) {
899 let instructions_before = platform::instruction_counter();
900 let transition = with_registry_mut(|registry| {
901 registry.consume_provider_handle(token);
902 Ok(registry.begin_watchdog_scheduler(token, platform::time_ns()))
903 });
904 let transition = transition
905 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
906 let accepted = !matches!(transition.effect(), RegistryEffect::None);
907 finish_callback_transition(token, transition, ProviderHandles::default());
908 if accepted {
909 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
910 with_registry_mut(|registry| {
911 registry.record_scheduler_instructions(token, instructions);
912 Ok(())
913 })
914 .unwrap_or_else(|error| {
915 trap_callback_failure("watchdog scheduler instruction accounting", &error)
916 });
917 }
918}
919
920fn dispatch_watchdog_work(token: &CallbackToken) {
921 let instructions_before = platform::instruction_counter();
922 let accepted = with_registry_mut(|registry| {
923 registry.consume_provider_handle(token);
924 Ok(registry.begin_watchdog_work(token))
925 });
926 match accepted {
927 Ok(CallbackAcceptance::Accepted) => {}
928 Ok(CallbackAcceptance::Stale) => return,
929 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
930 }
931
932 let callback =
933 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
934 {
935 Ok(callback) => callback,
936 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
937 };
938 let context = TimerContext::new(token.clone());
939 let result = {
940 let Ok(mut callback) = callback.try_borrow_mut() else {
941 trap_callback_failure(
942 "watchdog callback ownership",
943 &TimerError::OwnershipInvariant,
944 );
945 };
946 callback(context)
947 };
948 finish_watchdog_dispatch(token, result);
949 record_work_instructions(token, instructions_before);
950}
951
952fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
953 let claim = RegistrationClaim::delegated(token.identity().clone(), token.claim_generation());
954 let completed = with_registry_mut(|registry| {
955 let handles = registry
956 .take_provider_handles_for_claim(&claim)
957 .map_err(TimerError::from)?;
958 #[cfg(test)]
959 {
960 if take_watchdog_completion_fault() {
961 return Err(TimerError::OwnershipInvariant);
962 }
963 }
964 let transition = registry
965 .complete_watchdog_work(token, platform::time_ns(), result)
966 .map_err(TimerError::from)?;
967 Ok((transition, handles))
968 });
969 let (transition, handles) =
970 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
971 finish_callback_transition(token, transition, handles);
972}
973
974fn finish_callback_transition(
975 token: &CallbackToken,
976 transition: RegistryTransition,
977 handles: ProviderHandles,
978) {
979 match finish_transition(transition, handles) {
980 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
981 Err(
982 error @ (TimerError::NotInitialized
983 | TimerError::RuntimeBusy
984 | TimerError::Register(_)
985 | TimerError::Schedule(_)
986 | TimerError::RegistrationExpired
987 | TimerError::WrongPolicy
988 | TimerError::OwnershipInvariant
989 | TimerError::ReconciliationConflict),
990 ) => {
991 if token.role() == CallbackRole::WatchdogWork {
992 trap_callback_failure("watchdog provider-handle completion", &error);
993 }
994 fail_provider_binding(token).unwrap_or_else(|binding_error| {
995 trap_callback_failure("provider-binding failure cleanup", &binding_error)
996 });
997 }
998 }
999}
1000
1001fn fail_provider_binding(token: &CallbackToken) -> Result<(), TimerError> {
1002 let claim = RegistrationClaim::delegated(token.identity().clone(), token.claim_generation());
1003 fail_claim_provider_binding(&claim)
1004}
1005
1006fn fail_claim_provider_binding(claim: &RegistrationClaim) -> Result<(), TimerError> {
1007 let failed = with_registry_mut(|registry| {
1008 registry
1009 .fail_registration(claim, TimerControlFailure::ProviderBindingFailed)
1010 .map_err(TimerError::from)
1011 });
1012 let mut handles = failed?;
1013 if let Some(wakeup) = handles.take_wakeup() {
1014 clear_provider_handle(wakeup);
1015 }
1016 if let Some(work) = handles.take_work() {
1017 clear_provider_handle(work);
1018 }
1019 Ok(())
1020}
1021
1022fn record_work_instructions(token: &CallbackToken, instructions_before: u64) {
1023 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
1024 with_registry_mut(|registry| {
1025 registry.record_work_instructions(token, instructions);
1026 Ok(())
1027 })
1028 .unwrap_or_else(|error| trap_callback_failure("work instruction accounting", &error));
1029}
1030
1031fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
1032 platform::trap(&format!("ic-timers {context} failed: {error}"))
1033}
1034
1035fn with_registry<T>(
1036 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
1037) -> Result<T, TimerError> {
1038 RUNTIME.with(|runtime| {
1039 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
1040 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
1041 operation(registry)
1042 })
1043}
1044
1045fn with_registry_mut<T>(
1046 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
1047) -> Result<T, TimerError> {
1048 RUNTIME.with(|runtime| {
1049 let mut runtime = runtime
1050 .try_borrow_mut()
1051 .map_err(|_| TimerError::RuntimeBusy)?;
1052 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
1053 operation(registry)
1054 })
1055}
1056
1057#[cfg(test)]
1058fn reset_for_test(now_ns: u64, canister_version: u64) {
1059 platform::reset(now_ns, canister_version);
1060 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
1061 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(None));
1062 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(false));
1063 RUNTIME.with(|runtime| {
1064 *runtime.borrow_mut() = None;
1065 });
1066}
1067
1068#[cfg(test)]
1069thread_local! {
1070 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1071 static PROVIDER_INSTALL_FAULT_AFTER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
1072 static PROVIDER_CONFIRMATION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1073}
1074
1075#[cfg(test)]
1076fn inject_watchdog_completion_fault() {
1077 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
1078}
1079
1080#[cfg(test)]
1081fn take_watchdog_completion_fault() -> bool {
1082 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
1083}
1084
1085#[cfg(test)]
1086fn inject_provider_install_fault() {
1087 inject_provider_install_fault_after(0);
1088}
1089
1090#[cfg(test)]
1091fn inject_provider_install_fault_after(successful_installs: u64) {
1092 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| fault.set(Some(successful_installs)));
1093}
1094
1095#[cfg(test)]
1096fn take_provider_install_fault() -> bool {
1097 PROVIDER_INSTALL_FAULT_AFTER.with(|fault| match fault.get() {
1098 Some(0) => {
1099 fault.set(None);
1100 true
1101 }
1102 Some(remaining) => {
1103 fault.set(Some(remaining - 1));
1104 false
1105 }
1106 None => false,
1107 })
1108}
1109
1110#[cfg(test)]
1111fn inject_provider_confirmation_fault() {
1112 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.set(true));
1113}
1114
1115#[cfg(test)]
1116fn take_provider_confirmation_fault() -> bool {
1117 PROVIDER_CONFIRMATION_FAULT.with(|fault| fault.replace(false))
1118}
1119
1120#[cfg(test)]
1121mod tests;