Skip to main content

ic_timers/runtime/
mod.rs

1//! Canister-local owner and live timer execution.
2
3use 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
17/// Erased future returned by an ordinary timer callback.
18pub type TimerFuture = Pin<Box<dyn Future<Output = TimerRunResult>>>;
19
20thread_local! {
21    static RUNTIME: RefCell<Option<TimerRegistry>> = const { RefCell::new(None) };
22}
23
24/// Failure from the canister-local timer runtime API.
25#[non_exhaustive]
26#[derive(Debug, Error)]
27pub enum TimerError {
28    /// The lifecycle owner has not initialized the volatile runtime.
29    #[error("timer runtime is not initialized")]
30    NotInitialized,
31    /// A nested internal borrow indicates unsupported re-entrancy.
32    #[error("timer runtime is already borrowed")]
33    RuntimeBusy,
34    /// Registration failed before any provider callback was armed.
35    #[error(transparent)]
36    Register(#[from] RegisterError),
37    /// Cadence or deadline validation failed.
38    #[error(transparent)]
39    Schedule(#[from] ScheduleError),
40    /// The logical registration was removed or superseded.
41    #[error("timer registration is no longer authoritative")]
42    RegistrationExpired,
43    /// An operation was attempted through the wrong policy-specific API.
44    #[error("timer operation does not match its registered policy")]
45    WrongPolicy,
46    /// Pure checked control reached a terminal failure after effects were applied.
47    #[error("timer control failed: {0:?}")]
48    ControlFailure(TimerControlFailure),
49    /// Canonical callback or provider-handle ownership was internally inconsistent.
50    #[error("timer runtime ownership invariant failed")]
51    OwnershipInvariant,
52    /// A retained lifecycle claim no longer matches its canonical declaration.
53    #[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
72/// Initialize the volatile canister-local runtime once for this Wasm instance.
73///
74/// Repeated calls are idempotent and return the original epoch. This function
75/// exports no lifecycle hook; the canister's existing lifecycle owner calls it.
76pub 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
90/// Delegated control capability scoped to one exact consumer-work attempt.
91///
92/// Identity remains inspectable after work returns, but mutation methods then
93/// return [`TimerError::RegistrationExpired`]. Retain the policy-specific
94/// registration capability for longer-lived ownership.
95pub 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    /// Return the logical timer identity executing this work.
109    #[must_use]
110    pub const fn identity(&self) -> &TimerIdentity {
111        self.token.identity()
112    }
113
114    /// Schedule a `Once` declaration while this exact work attempt is active.
115    ///
116    /// A context retained after its callback completes is expired and returns
117    /// [`TimerError::RegistrationExpired`].
118    pub fn ensure_once(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
119        ensure_once_claim(&self.claim(), Some(&self.token), schedule)
120    }
121
122    /// Reconcile the executing ordinary declaration to one exact schedule.
123    ///
124    /// `None` cancels live work while retaining callback authority. Watchdog
125    /// declarations reject this operation. A retained context cannot mutate
126    /// the registration after its exact work attempt ends.
127    pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
128        reconcile_ordinary_claim(&self.claim(), Some(&self.token), schedule)
129    }
130
131    /// Ensure recurrence while this exact work attempt is active.
132    pub fn ensure_recurring(&self) -> Result<(), TimerError> {
133        ensure_recurring_claim(&self.claim(), Some(&self.token))
134    }
135
136    /// Request cancellation while this exact work attempt is active.
137    pub fn cancel(&self) -> Result<(), TimerError> {
138        cancel_claim(&self.claim(), Some(&self.token))
139    }
140}
141
142/// Opaque non-clone claim for one registered `Once` callback.
143#[must_use = "retain the registration claim so the timer remains controllable"]
144pub struct OnceRegistration {
145    claim: RegistrationClaim,
146}
147
148impl OnceRegistration {
149    /// Return the claimed logical identity.
150    #[must_use]
151    pub const fn identity(&self) -> &TimerIdentity {
152        self.claim.identity()
153    }
154
155    /// Synchronously ensure one callback is scheduled.
156    pub fn ensure_scheduled(&self, schedule: TimerSchedule) -> Result<(), TimerError> {
157        ensure_once_claim(&self.claim, None, schedule)
158    }
159
160    /// Reconcile to one exact desired schedule, replacing a later or earlier
161    /// live deadline as necessary. `None` retains the declaration but cancels
162    /// its live callback.
163    pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
164        reconcile_ordinary_claim(&self.claim, None, schedule)
165    }
166
167    /// Cancel the current schedule while retaining callback authority when configured.
168    pub fn cancel(&self) -> Result<(), TimerError> {
169        cancel_claim(&self.claim, None)
170    }
171
172    /// Consume the claim and unregister its callback authority.
173    pub fn unregister(self) -> Result<(), TimerError> {
174        unregister_claim(self.claim)
175    }
176}
177
178/// Opaque non-clone claim for one after-completion callback.
179#[must_use = "retain the registration claim so the timer remains controllable"]
180pub struct AfterCompletionRegistration {
181    claim: RegistrationClaim,
182}
183
184/// Opaque non-clone claim for one pre-armed watchdog callback.
185#[must_use = "retain the registration claim so the timer remains controllable"]
186pub struct WatchdogRegistration {
187    claim: RegistrationClaim,
188}
189
190/// Desired volatile scheduling state during synchronous lifecycle reconciliation.
191#[derive(Clone, Copy, Debug, Eq, PartialEq)]
192pub enum TimerReconcileState {
193    /// Retain an existing declaration but clear its live callbacks.
194    Inactive,
195    /// Ensure one authoritative wake-up exists.
196    Scheduled,
197}
198
199impl WatchdogRegistration {
200    /// Return the claimed logical identity.
201    #[must_use]
202    pub const fn identity(&self) -> &TimerIdentity {
203        self.claim.identity()
204    }
205
206    /// Synchronously ensure one watchdog scheduler wake-up is authoritative.
207    pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
208        ensure_recurring_claim(&self.claim, None)
209    }
210
211    /// Cancel the scheduler and any dispatched work callback.
212    pub fn cancel(&self) -> Result<(), TimerError> {
213        cancel_claim(&self.claim, None)
214    }
215
216    /// Consume the claim and unregister its callback authority.
217    pub fn unregister(self) -> Result<(), TimerError> {
218        unregister_claim(self.claim)
219    }
220}
221
222impl AfterCompletionRegistration {
223    /// Return the claimed logical identity.
224    #[must_use]
225    pub const fn identity(&self) -> &TimerIdentity {
226        self.claim.identity()
227    }
228
229    /// Synchronously ensure one callback is scheduled at the configured cadence.
230    pub fn ensure_scheduled(&self) -> Result<(), TimerError> {
231        ensure_recurring_claim(&self.claim, None)
232    }
233
234    /// Reconcile to one exact desired schedule without changing the configured
235    /// after-completion cadence. `None` retains the declaration but cancels its
236    /// live callback.
237    pub fn reconcile_schedule(&self, schedule: Option<TimerSchedule>) -> Result<(), TimerError> {
238        reconcile_ordinary_claim(&self.claim, None, schedule)
239    }
240
241    /// Cancel the current schedule while retaining callback authority when configured.
242    pub fn cancel(&self) -> Result<(), TimerError> {
243        cancel_claim(&self.claim, None)
244    }
245
246    /// Consume the claim and unregister its callback authority.
247    pub fn unregister(self) -> Result<(), TimerError> {
248        unregister_claim(self.claim)
249    }
250}
251
252/// Register one asynchronous `Once` callback without scheduling it.
253pub 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
273/// Register one asynchronous fixed-cadence after-completion callback.
274pub 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
295/// Register one synchronous pre-armed watchdog callback without scheduling it.
296///
297/// The callback cannot be async: it runs only in the work message after a
298/// separate scheduler message has armed the next cadence successor. Its
299/// `WatchdogDecision` either retains or clears that committed successor.
300pub 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
318/// Reconstruct or reconcile one `Once` declaration synchronously.
319///
320/// `Some(schedule)` is authoritative and may move an existing deadline in
321/// either direction. `None` retains an inactive declaration in the canonical
322/// inventory, including on a fresh heap.
323pub 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
349/// Reconstruct or reconcile one after-completion declaration synchronously.
350///
351/// The consumer owns `registration` in volatile state. A fresh Wasm heap has
352/// `None`, so this function installs callback authority before reconciling it
353/// active or inactive. A repeated call reuses the exact claim and does not
354/// replace its callback.
355pub 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
392/// Reconstruct or reconcile one watchdog declaration synchronously.
393///
394/// Durable readiness remains consumer-owned. Fresh inactive authority still
395/// installs an observable retained declaration. This helper owns no lifecycle
396/// export and persists no policy, generation, provider handle, or callback.
397pub 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
447/// Return one coherent inert snapshot by identity.
448pub fn timer_snapshot(identity: &TimerIdentity) -> Result<Option<TimerSnapshot>, TimerError> {
449    with_registry(|registry| Ok(registry.snapshot(identity)))
450}
451
452/// Return the complete bounded inventory in deterministic identity order.
453pub fn timer_snapshots() -> Result<Vec<TimerSnapshot>, TimerError> {
454    with_registry(|registry| Ok(registry.snapshots()))
455}
456
457/// Return functional expected-failure state by identity without a full inventory.
458pub 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)] // IC callbacks and canister-local state are single-threaded.
793async 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)] // IC callbacks and canister-local state are single-threaded.
802async 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;