diem_time_service/lib.rs
1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8#![forbid(unsafe_code)]
9
10//! Abstract time service
11
12use enum_dispatch::enum_dispatch;
13#[cfg(any(test, feature = "async"))]
14use pin_project::pin_project;
15use std::{
16 fmt::Debug,
17 time::{Duration, Instant},
18};
19#[cfg(any(test, feature = "async"))]
20use std::{
21 future::Future,
22 pin::Pin,
23 task::{Context, Poll},
24};
25
26#[cfg(any(test, feature = "async"))]
27pub mod interval;
28#[cfg(any(test, feature = "testing"))]
29pub mod mock;
30pub mod real;
31#[cfg(any(test, feature = "async"))]
32pub mod timeout;
33
34#[cfg(any(test, feature = "testing"))]
35pub use crate::mock::{MockSleep, MockTimeService};
36pub use crate::real::RealTimeService;
37#[cfg(any(test, feature = "async"))]
38pub use crate::{interval::Interval, real::RealSleep, timeout::Timeout};
39
40// TODO(philiphayes): use Duration constants when those stabilize.
41#[cfg(any(test, feature = "async"))]
42const ZERO_DURATION: Duration = Duration::from_nanos(0);
43
44/// `TimeService` abstracts all time-related operations in one place that can be
45/// easily mocked-out and controlled in tests or delegated to the actual
46/// underlying runtime (usually tokio). It's provided as an enum so we don't
47/// have to infect everything with a generic tag.
48///
49/// `TimeService` is async-focused: the `sleep`, `interval`, and `timeout`
50/// methods all return `Future`s and `Stream`s. That said, `TimeService`
51/// supports non-async clients as well; simply use the `sleep_blocking` method
52/// instead of `sleep`. Note that the blocking call will actually block the
53/// current thread until the sleep time has elapsed.
54///
55/// `TimeService` tries to mirror the API provided by `tokio::time` to an
56/// extent. The primary difference is that all time is expressed in relative
57/// [`Duration`]s. In other words, "sleep for 5s" vs "sleep until unix time
58/// 1607734460". Absolute time is provided by [`TimeService::now`] which returns
59/// the current unix time.
60///
61/// Note: you must also include the [`TimeServiceTrait`] to use the actual
62/// time-related functionality.
63///
64/// Note: we have to provide our own `Timeout` and `Interval` types that
65/// use the `Sleep` future, since tokio's implementations are coupled to its
66/// internal Sleep future.
67///
68/// Note: `TimeService`'s should be free (or very cheap) to clone and send
69/// around between threads. In production (without test features), this enum is
70/// a zero-sized type.
71#[enum_dispatch(TimeServiceTrait)]
72#[derive(Clone, Debug)]
73pub enum TimeService {
74 RealTimeService(RealTimeService),
75
76 #[cfg(any(test, feature = "testing"))]
77 MockTimeService(MockTimeService),
78}
79
80impl TimeService {
81 /// Create a new real, production time service that actually uses the
82 /// systemtime.
83 ///
84 /// See [`RealTimeService`].
85 pub fn real() -> Self { RealTimeService::new().into() }
86
87 /// Create a mock, simulated time service that does not query the system
88 /// time and allows fine-grained control over advancing time and waking
89 /// sleeping tasks.
90 ///
91 /// See [`MockTimeService`].
92 #[cfg(any(test, feature = "testing"))]
93 pub fn mock() -> Self { MockTimeService::new().into() }
94
95 #[cfg(any(test, feature = "testing"))]
96 pub fn into_mock(self) -> MockTimeService {
97 match self {
98 TimeService::MockTimeService(inner) => inner,
99 ts => panic!(
100 "Unexpected TimeService, expected MockTimeService: {:?}",
101 ts
102 ),
103 }
104 }
105}
106
107impl Default for TimeService {
108 fn default() -> Self { Self::real() }
109}
110
111#[enum_dispatch]
112pub trait TimeServiceTrait: Send + Sync + Clone + Debug {
113 /// Query a monotonically nondecreasing clock. Returns an opaque type that
114 /// can only be compared to other [`Instant`]s, i.e., this is a monotonic
115 /// relative time whereas [`now_unix_time`](#method.now_unix_time) is a
116 /// non-monotonic absolute time.
117 ///
118 /// On Linux, this is equivalent to
119 /// [`clock_gettime(CLOCK_MONOTONIC, _)`](https://linux.die.net/man/3/clock_gettime)
120 ///
121 /// See [`Instant`] for more details.
122 fn now(&self) -> Instant;
123
124 /// Query the current unix timestamp as a [`Duration`].
125 ///
126 /// When used on a `TimeService::real()`, this is equivalent to
127 /// `SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)`.
128 ///
129 /// Note: the [`Duration`] returned from this function is _NOT_ guaranteed
130 /// to be monotonic. Use [`now`](#method.now) if you need monotonicity.
131 ///
132 /// From the [`SystemTime`] docs:
133 ///
134 /// > Distinct from the [`Instant`] type, this time measurement is
135 /// > not monotonic. This means that you can save a file to the file system,
136 /// > then save another file to the file system, and the second file has a
137 /// > [`SystemTime`] measurement earlier than the first. In other words, an
138 /// > operation that happens after another operation in real time may have
139 /// > an earlier SystemTime!
140 ///
141 /// For example, the system administrator could [`clock_settime`] into the
142 /// past, breaking clock time monotonicity.
143 ///
144 /// On Linux, this is equivalent to
145 /// [`clock_gettime(CLOCK_REALTIME, _)`](https://linux.die.net/man/3/clock_gettime).
146 ///
147 /// [`Duration`]: std::time::Duration
148 /// [`Instant`]: std::time::Instant
149 /// [`SystemTime`]: std::time::SystemTime
150 /// [`clock_settime`]: https://linux.die.net/man/3/clock_settime
151 fn now_unix_time(&self) -> Duration;
152
153 /// Query the current unix timestamp in seconds.
154 ///
155 /// Equivalent to `self.now_unix_time().as_secs()`.
156 /// See [`now_unix_time`](#method.now_unix_time).
157 fn now_secs(&self) -> u64 { self.now_unix_time().as_secs() }
158
159 /// Return a [`Future`] that waits until `duration` has passed.
160 ///
161 /// No work is performed while awaiting on the sleep future to complete.
162 /// `Sleep` operates at millisecond granularity and should not be used
163 /// for tasks that require high-resolution timers.
164 ///
165 /// # Cancelation
166 ///
167 /// Canceling a sleep instance is done by dropping the returned future. No
168 /// additional cleanup work is required.
169 #[cfg(any(test, feature = "async"))]
170 fn sleep(&self, duration: Duration) -> Sleep;
171
172 /// Return a [`Future`] that waits until the `deadline`.
173 ///
174 /// If the deadline is in the past, the Sleep will trigger as soons as it's
175 /// polled.
176 ///
177 /// See [`sleep`](#method.sleep) for more details.
178 #[cfg(any(test, feature = "async"))]
179 fn sleep_until(&self, deadline: Instant) -> Sleep {
180 let duration = deadline.saturating_duration_since(self.now());
181 self.sleep(duration)
182 }
183
184 /// Blocks the current thread until `duration` time has passed.
185 fn sleep_blocking(&self, duration: Duration);
186
187 /// Creates a new [`Interval`] that yields with interval of `period`. The
188 /// first tick completes immediately. An interval will tick indefinitely.
189 ///
190 /// # Cancelation
191 ///
192 /// At any time, the [`Interval`] value can be dropped. This cancels the
193 /// interval.
194 ///
195 /// # Panics
196 ///
197 /// This function panics if `period` is zero.
198 #[cfg(any(test, feature = "async"))]
199 fn interval(&self, period: Duration) -> Interval {
200 let delay = self.sleep(ZERO_DURATION);
201 Interval::new(delay, period)
202 }
203
204 /// Creates a new [`Interval`] that yields with interval of `period`. The
205 /// first tick completes after the `start` deadline. An interval will tick
206 /// indefinitely.
207 ///
208 /// See [`interval`](#method.interval) for more details.
209 #[cfg(any(test, feature = "async"))]
210 fn interval_at(&self, start: Instant, period: Duration) -> Interval {
211 let delay = self.sleep_until(start);
212 Interval::new(delay, period)
213 }
214
215 /// Require a [`Future`] to complete before the specified duration has
216 /// elapsed.
217 ///
218 /// If the future completes before the duration has elapsed, then the
219 /// completed value is returned. Otherwise, `Err(Elapsed)` is returned
220 /// and the future is canceled.
221 ///
222 /// # Cancelation
223 ///
224 /// Cancelling a timeout is done by dropping the future. No additional
225 /// cleanup or other work is required.
226 ///
227 /// The original future may be obtained by calling [`Timeout::into_inner`].
228 /// This consumes the [`Timeout`].
229 #[cfg(any(test, feature = "async"))]
230 fn timeout<F: Future>(&self, duration: Duration, future: F) -> Timeout<F> {
231 let delay = self.sleep(duration);
232 Timeout::new(future, delay)
233 }
234
235 /// Require a [`Future`] to complete before the `deadline`.
236 ///
237 /// If the future completes before the duration has elapsed, then the
238 /// completed value is returned. Otherwise, `Err(Elapsed)` is returned
239 /// and the future is canceled.
240 ///
241 /// See [`timeout`](#method.timeout) for more details.
242 #[cfg(any(test, feature = "async"))]
243 fn timeout_at<F: Future>(
244 &self, deadline: Instant, future: F,
245 ) -> Timeout<F> {
246 let delay = self.sleep_until(deadline);
247 Timeout::new(future, delay)
248 }
249}
250
251/// A [`Future`] that resolves after some time has elapsed (either real or
252/// simulated, depending on the parent [`TimeService`]).
253///
254/// `Sleep` is modeled after [`tokio::time::Sleep`].
255#[pin_project(project = SleepProject)]
256#[derive(Debug)]
257#[cfg(any(test, feature = "async"))]
258pub enum Sleep {
259 RealSleep(#[pin] RealSleep),
260
261 #[cfg(any(test, feature = "testing"))]
262 MockSleep(MockSleep),
263}
264
265#[cfg(any(test, feature = "async"))]
266impl From<RealSleep> for Sleep {
267 fn from(sleep: RealSleep) -> Self { Sleep::RealSleep(sleep) }
268}
269
270#[cfg(any(test, feature = "fuzzing", feature = "testing"))]
271impl From<MockSleep> for Sleep {
272 fn from(sleep: MockSleep) -> Self { Sleep::MockSleep(sleep) }
273}
274
275#[cfg(any(test, feature = "async"))]
276impl Future for Sleep {
277 type Output = ();
278
279 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
280 match self.project() {
281 SleepProject::RealSleep(inner) => inner.poll(cx),
282 #[cfg(any(test, feature = "testing"))]
283 SleepProject::MockSleep(inner) => Pin::new(inner).poll(cx),
284 }
285 }
286}
287
288#[cfg(any(test, feature = "async"))]
289pub trait SleepTrait: Future<Output = ()> + Send + Sync + Debug {
290 /// Returns `true` if this `Sleep`'s requested wait duration has elapsed.
291 fn is_elapsed(&self) -> bool;
292
293 /// Resets this `Sleep` to wait again for `duration`.
294 fn reset(self: Pin<&mut Self>, duration: Duration);
295
296 /// Reset this `Sleep` to wait again until the `deadline`.
297 fn reset_until(self: Pin<&mut Self>, deadline: Instant);
298}
299
300#[cfg(any(test, feature = "async"))]
301impl SleepTrait for Sleep {
302 fn is_elapsed(&self) -> bool {
303 match self {
304 Sleep::RealSleep(inner) => SleepTrait::is_elapsed(inner),
305 #[cfg(any(test, feature = "fuzzing", feature = "testing"))]
306 Sleep::MockSleep(inner) => SleepTrait::is_elapsed(inner),
307 }
308 }
309
310 fn reset(self: Pin<&mut Self>, duration: Duration) {
311 match self.project() {
312 SleepProject::RealSleep(inner) => {
313 SleepTrait::reset(inner, duration)
314 }
315 #[cfg(any(test, feature = "fuzzing", feature = "testing"))]
316 SleepProject::MockSleep(inner) => {
317 SleepTrait::reset(Pin::new(inner), duration)
318 }
319 }
320 }
321
322 fn reset_until(self: Pin<&mut Self>, deadline: Instant) {
323 match self.project() {
324 SleepProject::RealSleep(inner) => {
325 SleepTrait::reset_until(inner, deadline)
326 }
327 #[cfg(any(test, feature = "fuzzing", feature = "testing"))]
328 SleepProject::MockSleep(inner) => {
329 SleepTrait::reset_until(Pin::new(inner), deadline)
330 }
331 }
332 }
333}