qubit_clock/timer/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 the asynchronous timer capability.
9
10use crate::{
11 MonotonicClock,
12 MonotonicInstant,
13 TimeError,
14 TimerFuture,
15};
16use std::time::Duration;
17
18/// Creates asynchronous notifications in one monotonic clock domain.
19///
20/// Calling [`at()`](Self::at) or [`after()`](Self::after) fixes the logical
21/// deadline and cancellation ownership before returning. The returned future
22/// waits for that fixed deadline. If the deadline is reached before the first
23/// poll, that first poll returns ready. As with every [`Future`], callers must
24/// not poll it again after it first returns ready. A backend may defer
25/// enrollment with its native scheduler until the future is polled. Dropping an
26/// incomplete future cancels the outstanding notification.
27///
28/// Every call to [`clock()`](Self::clock) on one Timer must report the same
29/// clock domain for the Timer's lifetime. Implementations must reject deadlines
30/// from a different domain with [`TimeError::ClockDomainMismatch`].
31/// [`MonotonicInstant::validate_domain`] provides the canonical validation and
32/// error construction for custom Timer implementations.
33///
34/// Timer failures have two stages: the outer [`Result`] reports registration
35/// failures, while the returned [`TimerFuture`] reports failures observed after
36/// registration, such as an unavailable scheduler worker or a Tokio runtime
37/// that shut down. Custom implementations may document additional lifecycle
38/// preconditions and panic conditions.
39pub trait Timer: Send + Sync {
40 /// Returns the monotonic clock whose domain this timer uses.
41 ///
42 /// Successive calls may return different handles, but every returned clock
43 /// must report the same domain for this Timer's lifetime.
44 ///
45 /// # Returns
46 ///
47 /// The clock retained by this timer.
48 ///
49 /// # Examples
50 ///
51 /// Discarding the retained clock is diagnosed when unused results are
52 /// denied:
53 ///
54 /// ```compile_fail
55 /// #![deny(unused_must_use)]
56 /// use qubit_clock::{MonotonicClock, StdMonotonicClock, Timer};
57 ///
58 /// let timer = StdMonotonicClock::new().new_timer();
59 /// timer.clock();
60 /// ```
61 #[must_use = "the Timer clock should be used to sample or validate deadlines"]
62 fn clock(&self) -> &dyn MonotonicClock;
63
64 /// Returns the current monotonic instant in this timer's clock domain.
65 ///
66 /// # Returns
67 ///
68 /// The current instant sampled from this timer's clock.
69 #[must_use = "the current timer instant should be used to measure or validate deadlines"]
70 #[inline(always)]
71 fn now(&self) -> MonotonicInstant {
72 self.clock().now()
73 }
74
75 /// Fixes an absolute deadline relative to the current timer instant.
76 ///
77 /// This method samples the timer clock while it runs and returns the
78 /// resulting deadline without registering a Timer waiter. Callers can
79 /// carry the returned deadline through lock acquisition or other
80 /// non-interruptible work while preserving one operation-wide budget.
81 /// It does not guarantee that such work returns before the deadline.
82 ///
83 /// # Parameters
84 ///
85 /// * duration - Duration from the current monotonic instant.
86 ///
87 /// # Returns
88 ///
89 /// The fixed deadline in this timer's clock domain.
90 ///
91 /// # Errors
92 ///
93 /// Returns TimeError::InstantOverflow when the deadline cannot be
94 /// represented.
95 #[inline(always)]
96 fn deadline_after(
97 &self,
98 duration: Duration,
99 ) -> Result<MonotonicInstant, TimeError> {
100 self.now().checked_add(duration)
101 }
102
103 /// Creates a notification for an absolute monotonic deadline.
104 ///
105 /// The deadline is fixed before this method returns. A deadline at or
106 /// before the current time produces a future that is already ready.
107 ///
108 /// # Parameters
109 ///
110 /// * `deadline` - Absolute deadline in this timer's clock domain.
111 ///
112 /// # Returns
113 ///
114 /// A future that returns `Ok(())` when `deadline` is reached. The future
115 /// returns a [`TimeError`] if the backend fails after registration.
116 ///
117 /// # Errors
118 ///
119 /// Returns [`TimeError::ClockDomainMismatch`] when `deadline` belongs to a
120 /// different clock domain. Returns another [`TimeError`] when the
121 /// notification cannot be created.
122 fn at(&self, deadline: MonotonicInstant) -> Result<TimerFuture, TimeError>;
123
124 /// Registers a notification after a relative duration.
125 ///
126 /// The deadline is fixed by sampling [`clock()`](Self::clock) during this
127 /// call, not when the returned future is first polled.
128 ///
129 /// # Parameters
130 ///
131 /// * `duration` - Duration from the current monotonic instant.
132 ///
133 /// # Returns
134 ///
135 /// A future that returns `Ok(())` when the fixed deadline is reached. The
136 /// future returns a [`TimeError`] if the backend later fails.
137 ///
138 /// # Errors
139 ///
140 /// Returns [`TimeError::InstantOverflow`] when the deadline cannot be
141 /// represented. Returns any error produced while creating the notification
142 /// for that deadline.
143 #[inline]
144 fn after(&self, duration: Duration) -> Result<TimerFuture, TimeError> {
145 let deadline = self.deadline_after(duration)?;
146 self.at(deadline)
147 }
148}
149
150impl<T> Timer for std::sync::Arc<T>
151where
152 T: Timer + ?Sized,
153{
154 /// Delegates access to the shared timer's clock.
155 ///
156 /// # Returns
157 ///
158 /// The clock exposed by the wrapped timer.
159 #[inline(always)]
160 fn clock(&self) -> &dyn MonotonicClock {
161 self.as_ref().clock()
162 }
163
164 /// Delegates absolute deadline registration to the shared timer.
165 ///
166 /// # Parameters
167 ///
168 /// * `deadline` - Absolute deadline in the wrapped timer's clock domain.
169 ///
170 /// # Returns
171 ///
172 /// The wrapped timer's cancellation-safe completion future.
173 ///
174 /// # Errors
175 ///
176 /// Returns any registration error reported by the wrapped timer.
177 #[inline(always)]
178 fn at(&self, deadline: MonotonicInstant) -> Result<TimerFuture, TimeError> {
179 self.as_ref().at(deadline)
180 }
181
182 /// Delegates relative deadline registration to the shared timer.
183 ///
184 /// # Parameters
185 ///
186 /// * `duration` - Duration from the wrapped timer's current instant.
187 ///
188 /// # Returns
189 ///
190 /// The wrapped timer's cancellation-safe completion future.
191 ///
192 /// # Errors
193 ///
194 /// Returns any registration error reported by the wrapped timer.
195 #[inline(always)]
196 fn after(&self, duration: Duration) -> Result<TimerFuture, TimeError> {
197 self.as_ref().after(duration)
198 }
199}
200
201impl<T> Timer for Box<T>
202where
203 T: Timer + ?Sized,
204{
205 /// Delegates access to the boxed timer's clock.
206 ///
207 /// # Returns
208 ///
209 /// The clock exposed by the wrapped timer.
210 #[inline(always)]
211 fn clock(&self) -> &dyn MonotonicClock {
212 self.as_ref().clock()
213 }
214
215 /// Delegates absolute deadline registration to the boxed timer.
216 ///
217 /// # Parameters
218 ///
219 /// * `deadline` - Absolute deadline in the wrapped timer's clock domain.
220 ///
221 /// # Returns
222 ///
223 /// The wrapped timer's cancellation-safe completion future.
224 ///
225 /// # Errors
226 ///
227 /// Returns any registration error reported by the wrapped timer.
228 #[inline(always)]
229 fn at(&self, deadline: MonotonicInstant) -> Result<TimerFuture, TimeError> {
230 self.as_ref().at(deadline)
231 }
232
233 /// Delegates relative deadline registration to the boxed timer.
234 ///
235 /// # Parameters
236 ///
237 /// * `duration` - Duration from the wrapped timer's current instant.
238 ///
239 /// # Returns
240 ///
241 /// The wrapped timer's cancellation-safe completion future.
242 ///
243 /// # Errors
244 ///
245 /// Returns any registration error reported by the wrapped timer.
246 #[inline(always)]
247 fn after(&self, duration: Duration) -> Result<TimerFuture, TimeError> {
248 self.as_ref().after(duration)
249 }
250}