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}