tor_rtcompat/coarse_time.rs
1//! Wrapper for coarsetime.
2//!
3// (Note that this is the doc comment for a private module,
4// not public API docs.
5// So this describes the implementation and rationale.)
6//
7//! We want to be able to mock coarsetime in tor-rtmock,
8//! so we need coarse time provision to to be part of a `Runtime`.
9//!
10//! (We can't just use `coarsetime`'s mocking facilities,
11//! because they still use a process-wide global for the current time.)
12//!
13//! We use [`coarsetime::Instant::now`],
14//! which in turn calls the OS's
15//! `CLOCK_MONOTONIC_COARSE`, `CLOCK_MONOTONIC_FAST`, or similar.
16//!
17//! We don't use the non-updating coarsetime methods
18//! such as `coarsetime::Instant:: now_without_cache_update`.
19//! So, nor do we start a [`coarsetime::Updater`] update thread
20//! (that would wake up frequently).
21//!
22//! We don't use (or expose any way to use) `coarsetime::Clock`;
23//! we don't think that's a useful thing.
24//!
25//! ### Future possibilities
26//!
27//! If we ever need to mix-and-match coarse time values
28//! from low-level crates like tor-proto,
29//! with the wrapped-up coarsetime we have here,
30//! we have the following options:
31//!
32//! a. move much of this (perhaps the whole module) to a lower-layer crate
33//! (let's call it tor-coarsetime) and have everyone use that.
34//! Even the `CoarseTimeProvider` trait could be moved down,
35//! since it doesn't depend on anything else from tor-rtcompat.
36//!
37//! b1. semver-expose coarsetime here in tor-rtcompat,
38//! perhaps optionally, by exposing a conversions
39//! with coarsetime::Instant.
40//!
41//! b2. abolish the newtypes and instead make the types
42//! here aliases for coarsetime
43
44use std::time;
45
46use derive_more::{Add, AddAssign, Sub, SubAssign};
47use paste::paste;
48
49use crate::traits::CoarseTimeProvider;
50
51/// A duration with reduced precision, and, in the future, saturating arithmetic
52///
53/// This type represents a (nonnegative) period
54/// between two [`CoarseInstant`]s.
55///
56/// This is (slightly lossily) interconvertible with `std::time::Duration`.
57///
58/// ### Range and precision
59///
60/// A `CoarseDuration` can represent at least 2^31 seconds,
61/// at a granularity of at least 1 second.
62// We may want to promise a better precision; that would be fine.
63///
64/// ### Panics
65///
66/// Currently, operations on `CoarseDuration` (including conversions)
67/// can panic on under/overflow.
68/// We regard this as a bug.
69/// The intent is that all operations will saturate.
70//
71// Currently this type's API is a bit anaemic.
72// If that turns out to be annoying, we might want to add
73// methods like `from_secs`, `as_secs` etc.
74#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] //
75#[derive(Add, Sub, AddAssign, SubAssign)]
76pub struct CoarseDuration(coarsetime::Duration);
77
78/// A monotonic timestamp with reduced precision, and, in the future, saturating arithmetic
79///
80/// Like `std::time::Instant`, but:
81///
82/// - [`RealCoarseTimeProvider::now_coarse()`] is cheap on all platforms,
83/// unlike `std::time::Instant::now`.
84///
85/// - **Not true yet**: Arithmetic is saturating (so, it's panic-free).
86///
87/// - Precision and accuracy are reduced.
88///
89/// - *Cannot* be compared with, or converted to/from, `std::time::Instant`.
90/// It has a completely different timescale to `Instant`.
91///
92/// You can obtain this (only) from `CoarseTimeProvider::now_coarse`.
93///
94/// ### Range and precision
95///
96/// The range of a `CoarseInstant` is not directly visible,
97/// since the absolute value isn't.
98/// `CoarseInstant`s are valid only within the context of one program execution (process).
99///
100/// Correct behaviour with processes that run for more than 2^31 seconds (about 30 years)
101/// is not guaranteed.
102///
103/// The precision is no worse than 1 second.
104// We may want to promise a better precision; that would be fine.
105///
106/// ### Panics
107///
108/// Currently, operations on `CoarseInstant` and `CoarseDuration`
109/// can panic on under/overflow.
110/// We regard this as a bug.
111/// The intent is that all operations will saturate.
112#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] //
113pub struct CoarseInstant(coarsetime::Instant);
114
115impl From<time::Duration> for CoarseDuration {
116 fn from(td: time::Duration) -> CoarseDuration {
117 CoarseDuration(td.into())
118 }
119}
120impl From<CoarseDuration> for time::Duration {
121 fn from(cd: CoarseDuration) -> time::Duration {
122 cd.0.into()
123 }
124}
125/// implement `$AddSub<CoarseDuration> for CoarseInstant`, and `*Assign`
126macro_rules! impl_add_sub { { $($AddSub:ident),* $(,)? } => { paste! { $(
127 impl std::ops::$AddSub<CoarseDuration> for CoarseInstant {
128 type Output = CoarseInstant;
129 fn [< $AddSub:lower >](self, rhs: CoarseDuration) -> CoarseInstant {
130 CoarseInstant(self.0. [< $AddSub:lower >]( rhs.0 ))
131 }
132 }
133 impl std::ops::[< $AddSub Assign >]<CoarseDuration> for CoarseInstant {
134 fn [< $AddSub:lower _assign >](&mut self, rhs: CoarseDuration) {
135 use std::ops::$AddSub;
136 *self = self.[< $AddSub:lower >](rhs);
137 }
138 }
139)* } } }
140impl_add_sub!(Add, Sub);
141
142/// Provider of reduced-precision timestamps using the real OS clock
143///
144/// This is a ZST.
145#[derive(Default, Clone, Debug)]
146#[non_exhaustive]
147pub struct RealCoarseTimeProvider {}
148
149impl RealCoarseTimeProvider {
150 /// Returns a new `RealCoarseTimeProvider`
151 ///
152 /// All `RealCoarseTimeProvider`s are equivalent.
153 #[inline]
154 pub fn new() -> Self {
155 RealCoarseTimeProvider::default()
156 }
157}
158
159impl CoarseTimeProvider for RealCoarseTimeProvider {
160 fn now_coarse(&self) -> CoarseInstant {
161 CoarseInstant(coarsetime::Instant::now())
162 }
163}
164
165#[cfg(not(miri))] // TODO coarse_time subtracts with overflow!
166#[cfg(test)]
167mod test {
168 // @@ begin test lint list maintained by maint/add_warning @@
169 #![allow(clippy::bool_assert_comparison)]
170 #![allow(clippy::clone_on_copy)]
171 #![allow(clippy::dbg_macro)]
172 #![allow(clippy::mixed_attributes_style)]
173 #![allow(clippy::print_stderr)]
174 #![allow(clippy::print_stdout)]
175 #![allow(clippy::single_char_pattern)]
176 #![allow(clippy::unwrap_used)]
177 #![allow(clippy::unchecked_duration_subtraction)]
178 #![allow(clippy::useless_vec)]
179 #![allow(clippy::needless_pass_by_value)]
180 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
181 #![allow(clippy::erasing_op)]
182 use super::*;
183
184 #[test]
185 fn basic() {
186 let t1 = RealCoarseTimeProvider::new().now_coarse();
187 let t2 = t1 + CoarseDuration::from(time::Duration::from_secs(10));
188 let t0 = t1 - CoarseDuration::from(time::Duration::from_secs(10));
189
190 assert!(t0 < t1);
191 assert!(t0 < t2);
192 assert!(t1 < t2);
193 }
194}