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 {
92 identity: TimerIdentity,
93 claim_generation: u64,
94}
95
96impl TimerContext {
97 const fn new(identity: TimerIdentity, claim_generation: u64) -> Self {
98 Self {
99 identity,
100 claim_generation,
101 }
102 }
103
104 fn claim(&self) -> RegistrationClaim {
105 RegistrationClaim::delegated(self.identity.clone(), self.claim_generation)
106 }
107
108 #[must_use]
110 pub const fn identity(&self) -> &TimerIdentity {
111 &self.identity
112 }
113
114 pub fn ensure_once(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
116 ensure_once_claim(&self.claim(), schedule)
117 }
118
119 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
124 reconcile_ordinary_claim(&self.claim(), schedule)
125 }
126
127 pub fn ensure_recurring(&self) -> Result<(), TimerError> {
129 ensure_recurring_claim(&self.claim())
130 }
131
132 pub fn cancel(&self) -> Result<(), TimerError> {
134 cancel_claim(&self.claim())
135 }
136}
137
138#[must_use = "retain the registration claim so the timer remains controllable"]
140pub struct OnceRegistration {
141 claim: RegistrationClaim,
142}
143
144impl OnceRegistration {
145 #[must_use]
147 pub const fn identity(&self) -> &TimerIdentity {
148 self.claim.identity()
149 }
150
151 pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
153 ensure_once_claim(&self.claim, schedule)
154 }
155
156 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
160 reconcile_ordinary_claim(&self.claim, schedule)
161 }
162
163 pub fn cancel(&self) -> Result<(), TimerError> {
165 cancel_claim(&self.claim)
166 }
167
168 pub fn unregister(self) -> Result<(), TimerError> {
170 unregister_claim(self.claim)
171 }
172}
173
174#[must_use = "retain the registration claim so the timer remains controllable"]
176pub struct AfterCompletionRegistration {
177 claim: RegistrationClaim,
178}
179
180#[must_use = "retain the registration claim so the timer remains controllable"]
182pub struct WatchdogRegistration {
183 claim: RegistrationClaim,
184}
185
186#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188pub enum TimerReconcileState {
189 Inactive,
191 Scheduled,
193}
194
195impl WatchdogRegistration {
196 #[must_use]
198 pub const fn identity(&self) -> &TimerIdentity {
199 self.claim.identity()
200 }
201
202 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
204 ensure_recurring_claim(&self.claim)
205 }
206
207 pub fn cancel(&self) -> Result<(), TimerError> {
209 cancel_claim(&self.claim)
210 }
211
212 pub fn unregister(self) -> Result<(), TimerError> {
214 unregister_claim(self.claim)
215 }
216}
217
218impl AfterCompletionRegistration {
219 #[must_use]
221 pub const fn identity(&self) -> &TimerIdentity {
222 self.claim.identity()
223 }
224
225 pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
227 ensure_recurring_claim(&self.claim)
228 }
229
230 pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
234 reconcile_ordinary_claim(&self.claim, schedule)
235 }
236
237 pub fn cancel(&self) -> Result<(), TimerError> {
239 cancel_claim(&self.claim)
240 }
241
242 pub fn unregister(self) -> Result<(), TimerError> {
244 unregister_claim(self.claim)
245 }
246}
247
248pub fn register_once<F, Fut>(
250 identity: TimerIdentity,
251 lifetime: DeclarationLifetime,
252 mut callback: F,
253) -> Result<OnceRegistration, TimerError>
254where
255 F: FnMut(TimerContext) -> Fut + 'static,
256 Fut: Future<Output = TimerRunResult> + 'static,
257{
258 let callback: OrdinaryCallback = Rc::new(RefCell::new(Box::new(move |context| {
259 Box::pin(callback(context))
260 })));
261 let claim = with_registry_mut(|registry| {
262 registry
263 .register_once_with_callback(identity, lifetime, callback)
264 .map_err(TimerError::from)
265 })?;
266 Ok(OnceRegistration { claim })
267}
268
269pub fn register_after_completion<F, Fut>(
271 identity: TimerIdentity,
272 cadence: TimerCadence,
273 lifetime: DeclarationLifetime,
274 mut callback: F,
275) -> Result<AfterCompletionRegistration, TimerError>
276where
277 F: FnMut(TimerContext) -> Fut + 'static,
278 Fut: Future<Output = TimerRunResult> + 'static,
279{
280 let callback: OrdinaryCallback = Rc::new(RefCell::new(Box::new(move |context| {
281 Box::pin(callback(context))
282 })));
283 let claim = with_registry_mut(|registry| {
284 registry
285 .register_after_completion_with_callback(identity, cadence, lifetime, callback)
286 .map_err(TimerError::from)
287 })?;
288 Ok(AfterCompletionRegistration { claim })
289}
290
291pub fn register_watchdog<F>(
297 identity: TimerIdentity,
298 cadence: TimerCadence,
299 lifetime: DeclarationLifetime,
300 callback: F,
301) -> Result<WatchdogRegistration, TimerError>
302where
303 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
304{
305 let callback: WatchdogCallback = Rc::new(RefCell::new(Box::new(callback)));
306 let claim = with_registry_mut(|registry| {
307 registry
308 .register_watchdog_with_callback(identity, cadence, lifetime, callback)
309 .map_err(TimerError::from)
310 })?;
311 Ok(WatchdogRegistration { claim })
312}
313
314pub fn reconcile_once<F, Fut>(
320 registration: &mut Option<OnceRegistration>,
321 identity: &TimerIdentity,
322 lifetime: DeclarationLifetime,
323 desired: Option<TimerSchedule>,
324 callback: F,
325) -> Result<(), TimerError>
326where
327 F: FnMut(TimerContext) -> Fut + 'static,
328 Fut: Future<Output = TimerRunResult> + 'static,
329{
330 if registration.is_none() {
331 *registration = Some(register_once(identity.clone(), lifetime, callback)?);
332 }
333 verify_declaration(
334 registration.as_ref().map(OnceRegistration::identity),
335 identity,
336 crate::TimerPolicy::Once,
337 lifetime,
338 )?;
339 registration
340 .as_ref()
341 .ok_or(TimerError::ReconciliationConflict)?
342 .reconcile_schedule(desired)
343}
344
345pub fn reconcile_after_completion<F, Fut>(
352 registration: &mut Option<AfterCompletionRegistration>,
353 identity: &TimerIdentity,
354 cadence: TimerCadence,
355 lifetime: DeclarationLifetime,
356 desired: TimerReconcileState,
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_after_completion(
365 identity.clone(),
366 cadence,
367 lifetime,
368 callback,
369 )?);
370 }
371 verify_declaration(
372 registration
373 .as_ref()
374 .map(AfterCompletionRegistration::identity),
375 identity,
376 crate::TimerPolicy::AfterCompletion { cadence },
377 lifetime,
378 )?;
379 let registration = registration
380 .as_ref()
381 .ok_or(TimerError::ReconciliationConflict)?;
382 match desired {
383 TimerReconcileState::Inactive => registration.cancel(),
384 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
385 }
386}
387
388pub fn reconcile_watchdog<F>(
394 registration: &mut Option<WatchdogRegistration>,
395 identity: &TimerIdentity,
396 cadence: TimerCadence,
397 lifetime: DeclarationLifetime,
398 desired: TimerReconcileState,
399 callback: F,
400) -> Result<(), TimerError>
401where
402 F: FnMut(TimerContext) -> WatchdogRunResult + 'static,
403{
404 if registration.is_none() {
405 *registration = Some(register_watchdog(
406 identity.clone(),
407 cadence,
408 lifetime,
409 callback,
410 )?);
411 }
412 verify_declaration(
413 registration.as_ref().map(WatchdogRegistration::identity),
414 identity,
415 crate::TimerPolicy::Watchdog { cadence },
416 lifetime,
417 )?;
418 let registration = registration
419 .as_ref()
420 .ok_or(TimerError::ReconciliationConflict)?;
421 match desired {
422 TimerReconcileState::Inactive => registration.cancel(),
423 TimerReconcileState::Scheduled => registration.ensure_scheduled(),
424 }
425}
426
427fn verify_declaration(
428 claimed_identity: Option<&TimerIdentity>,
429 identity: &TimerIdentity,
430 policy: crate::TimerPolicy,
431 lifetime: DeclarationLifetime,
432) -> Result<(), TimerError> {
433 if claimed_identity != Some(identity) {
434 return Err(TimerError::ReconciliationConflict);
435 }
436 let snapshot = timer_snapshot(identity)?.ok_or(TimerError::RegistrationExpired)?;
437 if snapshot.policy() != policy || snapshot.lifetime() != lifetime {
438 return Err(TimerError::ReconciliationConflict);
439 }
440 Ok(())
441}
442
443pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
445 with_registry(|registry| Ok(registry.snapshot(identity)))
446}
447
448pub fn timer_snapshots() -> Result<Vec<TimerSnapshot>, TimerError> {
450 with_registry(|registry| Ok(registry.snapshots()))
451}
452
453pub fn consecutive_expected_failures(identity: &TimerIdentity) -> Result<Option<u64>, TimerError> {
455 with_registry(|registry| Ok(registry.consecutive_expected_failures(identity)))
456}
457
458fn ensure_once_claim(claim: &RegistrationClaim, schedule: TimerSchedule) -> Result<(), TimerError> {
459 let transition = with_registry_mut(|registry| {
460 registry
461 .ensure_once(claim, platform::time_ns(), schedule)
462 .map_err(TimerError::from)
463 })?;
464 finish_transition(transition, ProviderHandles::default())
465}
466
467fn reconcile_ordinary_claim(
468 claim: &RegistrationClaim,
469 schedule: Option<TimerSchedule>,
470) -> Result<(), TimerError> {
471 if schedule.is_none() {
472 let (handles, transition) = with_registry_mut(|registry| {
473 registry
474 .validate_ordinary_claim(claim)
475 .map_err(TimerError::from)?;
476 let handles = registry
477 .take_provider_handles_for_claim(claim)
478 .map_err(TimerError::from)?;
479 let transition = registry
480 .reconcile_ordinary(claim, platform::time_ns(), None)
481 .map_err(TimerError::from)?;
482 Ok((handles, transition))
483 })?;
484 return finish_transition(transition, handles);
485 }
486 let transition = with_registry_mut(|registry| {
487 registry
488 .reconcile_ordinary(claim, platform::time_ns(), schedule)
489 .map_err(TimerError::from)
490 })?;
491 finish_transition(transition, ProviderHandles::default())
492}
493
494fn ensure_recurring_claim(claim: &RegistrationClaim) -> Result<(), TimerError> {
495 let transition = with_registry_mut(|registry| {
496 registry
497 .ensure_recurring(claim, platform::time_ns())
498 .map_err(TimerError::from)
499 })?;
500 finish_transition(transition, ProviderHandles::default())
501}
502
503fn cancel_claim(claim: &RegistrationClaim) -> Result<(), TimerError> {
504 let (handles, transition) = with_registry_mut(|registry| {
505 let handles = registry
506 .take_provider_handles_for_claim(claim)
507 .map_err(TimerError::from)?;
508 let transition = registry.cancel(claim).map_err(TimerError::from)?;
509 Ok((handles, transition))
510 })?;
511 finish_transition(transition, handles)
512}
513
514fn unregister_claim(claim: RegistrationClaim) -> Result<(), TimerError> {
515 let (handles, transition) = with_registry_mut(|registry| {
516 let handles = registry
517 .take_provider_handles_for_claim(&claim)
518 .map_err(TimerError::from)?;
519 let transition = registry.unregister(claim).map_err(TimerError::from)?;
520 Ok((handles, transition))
521 })?;
522 finish_transition(transition, handles)
523}
524
525fn finish_transition(
526 transition: RegistryTransition,
527 handles: ProviderHandles,
528) -> Result<(), TimerError> {
529 let failure = transition.failure();
530 let effect = transition.into_effect();
531 apply_effect(&effect, handles)?;
532 failure.map_or(Ok(()), |failure| Err(TimerError::ControlFailure(failure)))
533}
534
535fn apply_effect(effect: &RegistryEffect, mut handles: ProviderHandles) -> Result<(), TimerError> {
536 match effect {
537 RegistryEffect::None => restore_provider_handles(handles),
538 RegistryEffect::ArmWakeup {
539 token,
540 delay_ns,
541 replace,
542 ..
543 } => {
544 let detached_wakeup = handles.take_wakeup();
545 if *replace {
546 let replaced = match detached_wakeup {
547 Some(handle) => Some(handle),
548 None => with_registry_mut(|registry| {
549 Ok(registry.take_wakeup_handle(token.identity()))
550 })?,
551 };
552 if let Some(replaced) = replaced {
553 clear_provider_handle(replaced);
554 }
555 } else if let Some(detached_wakeup) = detached_wakeup {
556 restore_provider_handle(detached_wakeup)?;
557 }
558 if let Some(work) = handles.take_work() {
559 restore_provider_handle(work)?;
560 }
561 arm_wakeup(token, *delay_ns, effect)
562 }
563 RegistryEffect::ClearCallbacks {
564 identity,
565 clear_wakeup,
566 clear_work,
567 } => {
568 let detached_wakeup = handles.take_wakeup();
569 if *clear_wakeup {
570 let wakeup = match detached_wakeup {
571 Some(handle) => Some(handle),
572 None => {
573 with_registry_mut(|registry| Ok(registry.take_wakeup_handle(identity)))?
574 }
575 };
576 if let Some(wakeup) = wakeup {
577 clear_provider_handle(wakeup);
578 }
579 } else if let Some(wakeup) = detached_wakeup {
580 restore_provider_handle(wakeup)?;
581 }
582 let detached_work = handles.take_work();
583 if *clear_work {
584 let work = match detached_work {
585 Some(handle) => Some(handle),
586 None => with_registry_mut(|registry| Ok(registry.take_work_handle(identity)))?,
587 };
588 if let Some(work) = work {
589 clear_provider_handle(work);
590 }
591 } else if let Some(work) = detached_work {
592 restore_provider_handle(work)?;
593 }
594 restore_provider_handles(handles)
595 }
596 RegistryEffect::DispatchWatchdog {
597 successor,
598 successor_delay_ns,
599 work,
600 ..
601 } => {
602 if let Some(wakeup) = handles.take_wakeup() {
603 clear_provider_handle(wakeup);
604 }
605 let replaced_work = handles.take_work().or(with_registry_mut(|registry| {
606 Ok(registry.take_work_handle(successor.identity()))
607 })?);
608 if let Some(replaced_work) = replaced_work {
609 clear_provider_handle(replaced_work);
610 }
611 dispatch_watchdog_effect(successor, *successor_delay_ns, work, effect)
612 }
613 }
614}
615
616fn arm_wakeup(
617 token: &CallbackToken,
618 delay_ns: u64,
619 effect: &RegistryEffect,
620) -> Result<(), TimerError> {
621 let task_token = token.clone();
622 let handle = platform::set_timer(Duration::from_nanos(delay_ns), async move {
623 dispatch_wakeup(task_token).await;
624 });
625 if let Err((error, handle)) = install_provider_handle(token, handle) {
626 platform::clear_timer(handle);
627 return Err(error);
628 }
629 if let Err(error) = confirm_effect(effect) {
630 let handle = with_registry_mut(|registry| {
631 registry
632 .take_wakeup_handle(token.identity())
633 .ok_or(TimerError::OwnershipInvariant)
634 })?;
635 clear_provider_handle(handle);
636 return Err(error);
637 }
638 Ok(())
639}
640
641fn dispatch_watchdog_effect(
642 successor: &CallbackToken,
643 successor_delay_ns: u64,
644 work: &CallbackToken,
645 effect: &RegistryEffect,
646) -> Result<(), TimerError> {
647 let successor_token = successor.clone();
648 let successor_handle =
649 platform::set_timer(Duration::from_nanos(successor_delay_ns), async move {
650 dispatch_watchdog_scheduler(&successor_token);
651 });
652 if let Err((error, handle)) = install_provider_handle(successor, successor_handle) {
653 platform::clear_timer(handle);
654 return Err(error);
655 }
656
657 let work_token = work.clone();
658 let work_handle = platform::set_timer(Duration::ZERO, async move {
659 dispatch_watchdog_work(&work_token);
660 });
661 if let Err((error, handle)) = install_provider_handle(work, work_handle) {
662 platform::clear_timer(handle);
663 clear_entry_provider_handles(successor.identity())?;
664 return Err(error);
665 }
666 if let Err(error) = confirm_effect(effect) {
667 clear_entry_provider_handles(successor.identity())?;
668 return Err(error);
669 }
670 Ok(())
671}
672
673fn install_provider_handle(
674 token: &CallbackToken,
675 handle: TimerHandle,
676) -> Result<(), (TimerError, TimerHandle)> {
677 RUNTIME.with(|runtime| {
678 let Ok(mut runtime) = runtime.try_borrow_mut() else {
679 return Err((TimerError::RuntimeBusy, handle));
680 };
681 let Some(registry) = runtime.as_mut() else {
682 return Err((TimerError::NotInitialized, handle));
683 };
684 match registry.install_provider_handle(token, handle) {
685 Ok(()) => Ok(()),
686 Err((error, handle)) => Err((TimerError::from(error), handle)),
687 }
688 })
689}
690
691fn confirm_effect(effect: &RegistryEffect) -> Result<(), TimerError> {
692 with_registry_mut(|registry| {
693 registry
694 .confirm_effect_applied(effect)
695 .map_err(TimerError::from)
696 })
697}
698
699fn restore_provider_handles(mut handles: ProviderHandles) -> Result<(), TimerError> {
700 if let Some(wakeup) = handles.take_wakeup() {
701 restore_provider_handle(wakeup)?;
702 }
703 if let Some(work) = handles.take_work() {
704 restore_provider_handle(work)?;
705 }
706 Ok(())
707}
708
709fn restore_provider_handle(handle: ProviderHandle) -> Result<(), TimerError> {
710 let (token, handle) = handle.into_parts();
711 match install_provider_handle(&token, handle) {
712 Ok(()) => Ok(()),
713 Err((error, handle)) => {
714 platform::clear_timer(handle);
715 Err(error)
716 }
717 }
718}
719
720fn clear_provider_handle(handle: ProviderHandle) {
721 let (_, handle) = handle.into_parts();
722 platform::clear_timer(handle);
723}
724
725fn clear_entry_provider_handles(identity: &TimerIdentity) -> Result<(), TimerError> {
726 let mut handles = with_registry_mut(|registry| {
727 Ok(ProviderHandles::from_parts(
728 registry.take_wakeup_handle(identity),
729 registry.take_work_handle(identity),
730 ))
731 })?;
732 if let Some(wakeup) = handles.take_wakeup() {
733 clear_provider_handle(wakeup);
734 }
735 if let Some(work) = handles.take_work() {
736 clear_provider_handle(work);
737 }
738 Ok(())
739}
740
741#[allow(clippy::future_not_send)] async fn dispatch_wakeup(token: CallbackToken) {
743 match token.role() {
744 CallbackRole::OrdinaryWork => dispatch_ordinary(token).await,
745 CallbackRole::WatchdogScheduler => dispatch_watchdog_scheduler(&token),
746 CallbackRole::WatchdogWork => {}
747 }
748}
749
750#[allow(clippy::future_not_send)] async fn dispatch_ordinary(token: CallbackToken) {
752 let instructions_before = platform::instruction_counter();
753 let accepted = with_registry_mut(|registry| {
754 registry.consume_provider_handle(&token);
755 Ok(registry.begin_ordinary(&token))
756 });
757 match accepted {
758 Ok(CallbackAcceptance::Accepted) => {}
759 Ok(CallbackAcceptance::Stale) => return,
760 Err(error) => trap_callback_failure("ordinary callback acceptance", &error),
761 }
762
763 let callback = match with_registry(|registry| {
764 registry.ordinary_callback(&token).map_err(TimerError::from)
765 }) {
766 Ok(callback) => callback,
767 Err(TimerError::OwnershipInvariant) => {
768 fail_ordinary_dispatch(&token);
769 return;
770 }
771 Err(error) => trap_callback_failure("ordinary callback lookup", &error),
772 };
773 let context = TimerContext::new(token.identity().clone(), token.claim_generation());
774 let future = {
775 let Ok(mut callback) = callback.try_borrow_mut() else {
776 fail_ordinary_dispatch(&token);
777 return;
778 };
779 callback(context)
780 };
781 let result = future.await;
782 let transition = with_registry_mut(|registry| {
783 registry
784 .complete_ordinary(&token, platform::time_ns(), result)
785 .map_err(TimerError::from)
786 });
787 let transition = transition
788 .unwrap_or_else(|error| trap_callback_failure("ordinary callback completion", &error));
789 finish_callback_transition(&token, transition, ProviderHandles::default());
790 record_work_instructions(&token, instructions_before);
791}
792
793fn fail_ordinary_dispatch(token: &CallbackToken) {
794 let transition = with_registry_mut(|registry| {
795 registry
796 .complete_ordinary(
797 token,
798 platform::time_ns(),
799 TimerRunResult::new(TimerCompletion::invariant_failure(0), TimerDirective::Stop),
800 )
801 .map_err(TimerError::from)
802 });
803 let transition = transition.unwrap_or_else(|error| {
804 trap_callback_failure("ordinary invariant-failure completion", &error)
805 });
806 finish_callback_transition(token, transition, ProviderHandles::default());
807}
808
809fn dispatch_watchdog_scheduler(token: &CallbackToken) {
810 let instructions_before = platform::instruction_counter();
811 let transition = with_registry_mut(|registry| {
812 registry.consume_provider_handle(token);
813 Ok(registry.begin_watchdog_scheduler(token, platform::time_ns()))
814 });
815 let transition = transition
816 .unwrap_or_else(|error| trap_callback_failure("watchdog scheduler transition", &error));
817 let accepted = !matches!(transition.effect(), RegistryEffect::None);
818 finish_callback_transition(token, transition, ProviderHandles::default());
819 if accepted {
820 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
821 with_registry_mut(|registry| {
822 registry.record_scheduler_instructions(token, instructions);
823 Ok(())
824 })
825 .unwrap_or_else(|error| {
826 trap_callback_failure("watchdog scheduler instruction accounting", &error)
827 });
828 }
829}
830
831fn dispatch_watchdog_work(token: &CallbackToken) {
832 let instructions_before = platform::instruction_counter();
833 let accepted = with_registry_mut(|registry| {
834 registry.consume_provider_handle(token);
835 Ok(registry.begin_watchdog_work(token))
836 });
837 match accepted {
838 Ok(CallbackAcceptance::Accepted) => {}
839 Ok(CallbackAcceptance::Stale) => return,
840 Err(error) => trap_callback_failure("watchdog work acceptance", &error),
841 }
842
843 let callback =
844 match with_registry(|registry| registry.watchdog_callback(token).map_err(TimerError::from))
845 {
846 Ok(callback) => callback,
847 Err(error) => trap_callback_failure("watchdog callback lookup", &error),
848 };
849 let context = TimerContext::new(token.identity().clone(), token.claim_generation());
850 let result = {
851 let Ok(mut callback) = callback.try_borrow_mut() else {
852 trap_callback_failure(
853 "watchdog callback ownership",
854 &TimerError::OwnershipInvariant,
855 );
856 };
857 callback(context)
858 };
859 finish_watchdog_dispatch(token, result);
860 record_work_instructions(token, instructions_before);
861}
862
863fn finish_watchdog_dispatch(token: &CallbackToken, result: WatchdogRunResult) {
864 let claim = RegistrationClaim::delegated(token.identity().clone(), token.claim_generation());
865 let completed = with_registry_mut(|registry| {
866 let handles = registry
867 .take_provider_handles_for_claim(&claim)
868 .map_err(TimerError::from)?;
869 #[cfg(test)]
870 {
871 if take_watchdog_completion_fault() {
872 return Err(TimerError::OwnershipInvariant);
873 }
874 }
875 let transition = registry
876 .complete_watchdog_work(token, platform::time_ns(), result)
877 .map_err(TimerError::from)?;
878 Ok((transition, handles))
879 });
880 let (transition, handles) =
881 completed.unwrap_or_else(|error| trap_callback_failure("watchdog work completion", &error));
882 finish_callback_transition(token, transition, handles);
883}
884
885fn finish_callback_transition(
886 token: &CallbackToken,
887 transition: RegistryTransition,
888 handles: ProviderHandles,
889) {
890 match finish_transition(transition, handles) {
891 Ok(()) | Err(TimerError::ControlFailure(_)) => {}
892 Err(
893 error @ (TimerError::NotInitialized
894 | TimerError::RuntimeBusy
895 | TimerError::Register(_)
896 | TimerError::Schedule(_)
897 | TimerError::RegistrationExpired
898 | TimerError::WrongPolicy
899 | TimerError::OwnershipInvariant
900 | TimerError::ReconciliationConflict),
901 ) => {
902 if token.role() == CallbackRole::WatchdogWork {
903 trap_callback_failure("watchdog provider-handle completion", &error);
904 }
905 fail_provider_binding(token).unwrap_or_else(|binding_error| {
906 trap_callback_failure("provider-binding failure cleanup", &binding_error)
907 });
908 }
909 }
910}
911
912fn fail_provider_binding(token: &CallbackToken) -> Result<(), TimerError> {
913 let claim = RegistrationClaim::delegated(token.identity().clone(), token.claim_generation());
914 let failed = with_registry_mut(|registry| {
915 registry
916 .fail_registration(&claim, TimerControlFailure::ProviderBindingFailed)
917 .map_err(TimerError::from)
918 });
919 let mut handles = failed?;
920 if let Some(wakeup) = handles.take_wakeup() {
921 clear_provider_handle(wakeup);
922 }
923 if let Some(work) = handles.take_work() {
924 clear_provider_handle(work);
925 }
926 Ok(())
927}
928
929fn record_work_instructions(token: &CallbackToken, instructions_before: u64) {
930 let instructions = platform::instruction_counter().saturating_sub(instructions_before);
931 with_registry_mut(|registry| {
932 registry.record_work_instructions(token, instructions);
933 Ok(())
934 })
935 .unwrap_or_else(|error| trap_callback_failure("work instruction accounting", &error));
936}
937
938fn trap_callback_failure(context: &str, error: &TimerError) -> ! {
939 platform::trap(&format!("ic-timers {context} failed: {error}"))
940}
941
942fn with_registry<T>(
943 operation: impl FnOnce(&TimerRegistry) -> Result<T, TimerError>,
944) -> Result<T, TimerError> {
945 RUNTIME.with(|runtime| {
946 let runtime = runtime.try_borrow().map_err(|_| TimerError::RuntimeBusy)?;
947 let registry = runtime.as_ref().ok_or(TimerError::NotInitialized)?;
948 operation(registry)
949 })
950}
951
952fn with_registry_mut<T>(
953 operation: impl FnOnce(&mut TimerRegistry) -> Result<T, TimerError>,
954) -> Result<T, TimerError> {
955 RUNTIME.with(|runtime| {
956 let mut runtime = runtime
957 .try_borrow_mut()
958 .map_err(|_| TimerError::RuntimeBusy)?;
959 let registry = runtime.as_mut().ok_or(TimerError::NotInitialized)?;
960 operation(registry)
961 })
962}
963
964#[cfg(test)]
965fn reset_for_test(now_ns: u64, canister_version: u64) {
966 platform::reset(now_ns, canister_version);
967 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(false));
968 RUNTIME.with(|runtime| {
969 *runtime.borrow_mut() = None;
970 });
971}
972
973#[cfg(test)]
974thread_local! {
975 static WATCHDOG_COMPLETION_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
976}
977
978#[cfg(test)]
979fn inject_watchdog_completion_fault() {
980 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.set(true));
981}
982
983#[cfg(test)]
984fn take_watchdog_completion_fault() -> bool {
985 WATCHDOG_COMPLETION_FAULT.with(|fault| fault.replace(false))
986}
987
988#[cfg(test)]
989mod tests;