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