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 #[inline(always)]
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156impl std::fmt::Debug for StdTimer {
157 /// Formats the retained clock without exposing scheduler internals.
158 ///
159 /// # Parameters
160 ///
161 /// * `formatter` - Destination formatter.
162 ///
163 /// # Returns
164 ///
165 /// `Ok(())` when formatting succeeds.
166 ///
167 /// # Errors
168 ///
169 /// Returns [`std::fmt::Error`] when the destination rejects output.
170 #[inline]
171 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 formatter
173 .debug_struct("StdTimer")
174 .field("clock", &self.clock)
175 .finish_non_exhaustive()
176 }
177}
178
179impl Timer for StdTimer {
180 /// Returns the private same-domain standard clock handle.
181 ///
182 /// # Returns
183 ///
184 /// The monotonic clock driving this timer.
185 #[inline(always)]
186 fn clock(&self) -> &dyn MonotonicClock {
187 &self.clock
188 }
189
190 /// Eagerly registers an absolute deadline with the shared scheduler.
191 ///
192 /// # Parameters
193 ///
194 /// * `deadline` - Deadline in this timer's clock domain.
195 ///
196 /// # Returns
197 ///
198 /// A cancellation-safe future whose registration is already active, or an
199 /// immediately ready future for a reached deadline. The future returns
200 /// [`TimeError::TimerUnavailable`] with
201 /// [`TimerUnavailableError::SchedulerWorkerTerminated`](crate::TimerUnavailableError::SchedulerWorkerTerminated)
202 /// when its scheduler worker exits unexpectedly.
203 ///
204 /// # Errors
205 ///
206 /// Returns a domain mismatch, native-instant overflow, or scheduler startup
207 /// error before returning a future.
208 ///
209 /// # Panics
210 ///
211 /// Panics when scheduler registration identifiers or worker generations
212 /// are exhausted, or an internal scheduler index invariant is violated.
213 fn at(&self, deadline: MonotonicInstant) -> Result<TimerFuture, TimeError> {
214 let deadline = self.native_deadline(deadline)?;
215 self.schedule(deadline, Instant::now())
216 }
217
218 /// Eagerly registers a relative deadline with the shared scheduler.
219 ///
220 /// This implementation samples the native instant once, avoiding the
221 /// monotonic-domain conversion required by the default trait method.
222 ///
223 /// # Parameters
224 ///
225 /// * `duration` - Delay before the future becomes ready.
226 ///
227 /// # Returns
228 ///
229 /// A cancellation-safe future whose registration is already active, or an
230 /// immediately ready future for a zero duration.
231 ///
232 /// # Errors
233 ///
234 /// Returns [`TimeError::InstantOverflow`] when the native deadline cannot
235 /// be represented, or a scheduler startup error before returning a future.
236 ///
237 /// # Panics
238 ///
239 /// Panics when scheduler registration identifiers or worker generations
240 /// are exhausted, or an internal scheduler index invariant is violated.
241 fn after(&self, duration: Duration) -> Result<TimerFuture, TimeError> {
242 let now = Instant::now();
243 let deadline = now
244 .checked_add(duration)
245 .ok_or(TimeError::InstantOverflow)?;
246 self.schedule(deadline, now)
247 }
248}