Skip to main content

qubit_clock/timer/
std_timer.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines a standard-library timer with one shared scheduler worker.
9
10use std::sync::Arc;
11use std::time::Duration;
12use std::time::Instant;
13
14use crate::MonotonicClock;
15use crate::MonotonicInstant;
16use crate::StdMonotonicClock;
17use crate::TimeError;
18use crate::Timer;
19use crate::TimerFuture;
20use crate::timer::internal::std_timer_future::StdTimerFuture;
21use crate::timer::internal::std_timer_scheduler::StdTimerScheduler;
22use crate::timer::internal::std_timer_waiter::StdTimerWaiter;
23
24/// A real-time asynchronous timer backed by [`std::time::Instant`].
25///
26/// Every standard Timer in the process shares one scheduler worker. The worker
27/// starts lazily with the first future registration and remains parked while
28/// idle so later registrations do not need to create another native thread.
29/// If that worker exits unexpectedly, its active futures are awakened and
30/// return [`TimeError::TimerUnavailable`] with
31/// [`TimerUnavailableError::SchedulerWorkerTerminated`](crate::TimerUnavailableError::SchedulerWorkerTerminated)
32/// instead of remaining pending or reporting false deadline completion. A
33/// later registration starts a replacement worker generation.
34pub struct StdTimer {
35    /// Private clock handle retaining the source domain and native origin.
36    clock: StdMonotonicClock,
37    /// Process-wide scheduler shared by every standard Timer registration.
38    scheduler: Arc<StdTimerScheduler>,
39}
40
41impl StdTimer {
42    /// Creates a timer backed by a new standard monotonic clock.
43    ///
44    /// # Returns
45    ///
46    /// A timer with a fresh clock domain and the process-wide scheduler.
47    #[must_use]
48    #[inline]
49    pub fn new() -> Self {
50        let clock = StdMonotonicClock::new();
51        Self::from_clock(&clock)
52    }
53
54    /// Creates a timer sharing the supplied standard clock's exact domain.
55    ///
56    /// # Parameters
57    ///
58    /// * `clock` - Standard clock whose domain and origin drive this timer.
59    ///
60    /// # Returns
61    ///
62    /// A timer retaining the process-wide lazy scheduler.
63    #[must_use]
64    #[inline]
65    pub fn from_clock(clock: &StdMonotonicClock) -> Self {
66        Self {
67            clock: clock.same_domain_handle(),
68            scheduler: StdTimerScheduler::shared(),
69        }
70    }
71
72    /// Converts a domain-scoped deadline to its native standard instant.
73    ///
74    /// # Parameters
75    ///
76    /// * `deadline` - Absolute deadline in the source clock domain.
77    ///
78    /// # Returns
79    ///
80    /// The corresponding native instant.
81    ///
82    /// # Errors
83    ///
84    /// Returns [`TimeError::ClockDomainMismatch`] for a foreign deadline and
85    /// [`TimeError::InstantOverflow`] when conversion overflows.
86    fn native_deadline(&self, deadline: MonotonicInstant) -> Result<Instant, TimeError> {
87        deadline.validate_domain(self.clock.domain())?;
88        self.clock
89            .origin()
90            .checked_add(deadline.elapsed_since_origin())
91            .ok_or(TimeError::InstantOverflow)
92    }
93
94    /// Registers a native deadline with the shared scheduler.
95    ///
96    /// # Parameters
97    ///
98    /// * `deadline` - Native deadline to register.
99    /// * `now` - Native instant sampled for this registration.
100    ///
101    /// # Returns
102    ///
103    /// A cancellation-safe future whose registration is already active, or an
104    /// immediately ready future when the deadline has been reached.
105    ///
106    /// # Errors
107    ///
108    /// Returns a scheduler startup error before returning a future.
109    ///
110    /// # Panics
111    ///
112    /// Panics when scheduler registration identifiers or worker generations
113    /// are exhausted, or an internal scheduler index invariant is violated.
114    #[inline]
115    fn schedule(&self, deadline: Instant, now: Instant) -> Result<TimerFuture, TimeError> {
116        if deadline <= now {
117            return Ok(Box::pin(std::future::ready(Ok(()))));
118        }
119        let waiter = Arc::new(StdTimerWaiter::new());
120        let waiter_id = self.scheduler.register(deadline, Arc::clone(&waiter))?;
121        Ok(Box::pin(StdTimerFuture::new(
122            Arc::clone(&self.scheduler),
123            waiter_id,
124            waiter,
125        )))
126    }
127}
128
129impl Default for StdTimer {
130    /// Creates a standard timer with a fresh clock domain.
131    ///
132    /// # Returns
133    ///
134    /// A standard timer with a newly allocated clock domain.
135    ///
136    /// # Panics
137    ///
138    /// Panics if all process-wide clock-domain identifiers are exhausted.
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144impl std::fmt::Debug for StdTimer {
145    /// Formats the retained clock without exposing scheduler internals.
146    ///
147    /// # Parameters
148    ///
149    /// * `formatter` - Destination formatter.
150    ///
151    /// # Returns
152    ///
153    /// `Ok(())` when formatting succeeds.
154    ///
155    /// # Errors
156    ///
157    /// Returns [`std::fmt::Error`] when the destination rejects output.
158    #[inline]
159    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        formatter
161            .debug_struct("StdTimer")
162            .field("clock", &self.clock)
163            .finish_non_exhaustive()
164    }
165}
166
167impl Timer for StdTimer {
168    /// Returns the private same-domain standard clock handle.
169    ///
170    /// # Returns
171    ///
172    /// The monotonic clock driving this timer.
173    #[inline(always)]
174    fn clock(&self) -> &dyn MonotonicClock {
175        &self.clock
176    }
177
178    /// Eagerly registers an absolute deadline with the shared scheduler.
179    ///
180    /// # Parameters
181    ///
182    /// * `deadline` - Deadline in this timer's clock domain.
183    ///
184    /// # Returns
185    ///
186    /// A cancellation-safe future whose registration is already active, or an
187    /// immediately ready future for a reached deadline. The future returns
188    /// [`TimeError::TimerUnavailable`] with
189    /// [`TimerUnavailableError::SchedulerWorkerTerminated`](crate::TimerUnavailableError::SchedulerWorkerTerminated)
190    /// when its scheduler worker exits unexpectedly.
191    ///
192    /// # Errors
193    ///
194    /// Returns a domain mismatch, native-instant overflow, or scheduler startup
195    /// error before returning a future.
196    ///
197    /// # Panics
198    ///
199    /// Panics when scheduler registration identifiers or worker generations
200    /// are exhausted, or an internal scheduler index invariant is violated.
201    fn at(&self, deadline: MonotonicInstant) -> Result<TimerFuture, TimeError> {
202        let deadline = self.native_deadline(deadline)?;
203        self.schedule(deadline, Instant::now())
204    }
205
206    /// Eagerly registers a relative deadline with the shared scheduler.
207    ///
208    /// This implementation samples the native instant once, avoiding the
209    /// monotonic-domain conversion required by the default trait method.
210    ///
211    /// # Parameters
212    ///
213    /// * `duration` - Delay before the future becomes ready.
214    ///
215    /// # Returns
216    ///
217    /// A cancellation-safe future whose registration is already active, or an
218    /// immediately ready future for a zero duration.
219    ///
220    /// # Errors
221    ///
222    /// Returns [`TimeError::InstantOverflow`] when the native deadline cannot
223    /// be represented, or a scheduler startup error before returning a future.
224    ///
225    /// # Panics
226    ///
227    /// Panics when scheduler registration identifiers or worker generations
228    /// are exhausted, or an internal scheduler index invariant is violated.
229    fn after(&self, duration: Duration) -> Result<TimerFuture, TimeError> {
230        let now = Instant::now();
231        let deadline = now.checked_add(duration).ok_or(TimeError::InstantOverflow)?;
232        self.schedule(deadline, now)
233    }
234}