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}