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}