tor_rtcompat/dyn_time.rs
1//! type-erased time provider
2
3use std::future::Future;
4use std::mem::{self, MaybeUninit};
5use std::pin::Pin;
6use std::time::{Duration, Instant, SystemTime};
7
8use dyn_clone::DynClone;
9use educe::Educe;
10use paste::paste;
11
12use crate::{CoarseInstant, CoarseTimeProvider, SleepProvider};
13
14//-------------------- handle PreferredRuntime maybe not existing ----------
15
16// TODO use this more widely, eg in tor-rtcompat/lib.rs
17
18/// See the other implementation
19#[allow(unused_macros)] // Will be redefined if there *is* a preferred runtime
20macro_rules! if_preferred_runtime {{ [$($y:tt)*] [$($n:tt)*] } => { $($n)* }}
21#[cfg(all(
22 any(feature = "native-tls", feature = "rustls"),
23 any(feature = "async-std", feature = "tokio")
24))]
25/// `if_preferred_runtime!{[ Y ] [ N ]}` expands to `Y` (if there's `PreferredRuntime`) or `N`
26macro_rules! if_preferred_runtime {{ [$($y:tt)*] [$($n:tt)*] } => { $($y)* }}
27
28if_preferred_runtime! {[
29 use crate::PreferredRuntime;
30] [
31 /// Dummy value that makes the variant uninhabited
32 #[derive(Clone, Debug)]
33 enum PreferredRuntime {}
34]}
35/// `with_preferred_runtime!( R; EXPR )` expands to `EXPR`, or to `match *R {}`.
36macro_rules! with_preferred_runtime {{ $p:ident; $($then:tt)* } => {
37 if_preferred_runtime!([ $($then)* ] [ match *$p {} ])
38}}
39
40//---------- principal types ----------
41
42/// Convenience alias for a boxed sleep future
43type DynSleepFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
44
45/// Object-safe version of `SleepProvider` and `CoarseTimeProvider`
46///
47/// The methods mirror those in `SleepProvider` and `CoarseTimeProvider`
48#[allow(clippy::missing_docs_in_private_items)]
49trait DynProvider: DynClone + Send + Sync + 'static {
50 // SleepProvider principal methods
51 fn dyn_now(&self) -> Instant;
52 fn dyn_wallclock(&self) -> SystemTime;
53 fn dyn_sleep(&self, duration: Duration) -> DynSleepFuture;
54
55 // SleepProvider testing stuff
56 fn dyn_block_advance(&self, reason: String);
57 fn dyn_release_advance(&self, _reason: String);
58 fn dyn_allow_one_advance(&self, duration: Duration);
59
60 // CoarseTimeProvider
61 fn dyn_now_coarse(&self) -> CoarseInstant;
62}
63
64dyn_clone::clone_trait_object!(DynProvider);
65
66/// Type-erased `SleepProvider` and `CoarseTimeProvider`
67///
68/// Useful where time is needed, but we don't want a runtime type parameter.
69#[derive(Clone, Debug)]
70pub struct DynTimeProvider(Impl);
71
72/// Actual contents of a `DynTimeProvider`
73///
74/// We optimise the `PreferredRuntime` case.
75/// We *could*, instead, just use `Box<dyn DynProvider>` here.
76///
77/// The reason for doing it this way is that we expect this to be on many hot paths.
78/// Putting a message in a queue is extremely common, and we'd like to save a dyn dispatch,
79/// and reference to a further heap entry (which might be distant in the cache).
80///
81/// (Also, it's nice to avoid boxing when we crate new types that use this,
82/// including our memory-quota-tracked mpsc streams, see `tor-memquota::mq_queue`.
83///
84/// The downside is that this means:
85/// * This enum instead of a simple type
86/// * The `unsafe` inside `downcast_value`.
87/// * `match` statements in method shims
88#[derive(Clone, Educe)]
89#[educe(Debug)]
90enum Impl {
91 /// Just (a handle to) the preferred runtime
92 Preferred(PreferredRuntime),
93 /// Some other runtime
94 Dyn(#[educe(Debug(ignore))] Box<dyn DynProvider>),
95}
96
97impl DynTimeProvider {
98 /// Create a new `DynTimeProvider` from a concrete runtime type
99 pub fn new<R: SleepProvider + CoarseTimeProvider>(runtime: R) -> Self {
100 // Try casting to a `DynTimeProvider` directly first, to avoid possibly creating a
101 // `DynTimeProvider` containing another `DynTimeProvider`.
102 let runtime = match downcast_value(runtime) {
103 Ok(x) => return x,
104 Err(x) => x,
105 };
106 // Try casting to a `PreferredRuntime`.
107 let imp = match downcast_value(runtime) {
108 Ok(preferred) => Impl::Preferred(preferred),
109 Err(other) => Impl::Dyn(Box::new(other) as _),
110 };
111 DynTimeProvider(imp)
112 }
113}
114
115//---------- impl DynProvider for any SleepProvider + CoarseTimeProvider ----------
116
117/// Define ordinary methods in `impl DynProvider`
118///
119/// This macro exists mostly to avoid copypaste mistakes where we (for example)
120/// implement `block_advance` by calling `release_advance`.
121macro_rules! dyn_impl_methods { { $(
122 fn $name:ident(
123 ,
124 $( $param:ident: $ptype:ty ),*
125 ) -> $ret:ty;
126)* } => { paste! { $(
127 fn [<dyn_ $name>](
128 &self,
129 $( $param: $ptype, )*
130 )-> $ret {
131 self.$name( $($param,)* )
132 }
133)* } } }
134
135impl<R: SleepProvider + CoarseTimeProvider> DynProvider for R {
136 dyn_impl_methods! {
137 fn now(,) -> Instant;
138 fn wallclock(,) -> SystemTime;
139
140 fn block_advance(, reason: String) -> ();
141 fn release_advance(, reason: String) -> ();
142 fn allow_one_advance(, duration: Duration) -> ();
143
144 fn now_coarse(,) -> CoarseInstant;
145 }
146
147 fn dyn_sleep(&self, duration: Duration) -> DynSleepFuture {
148 Box::pin(self.sleep(duration))
149 }
150}
151
152//---------- impl SleepProvider and CoarseTimeProvider for DynTimeProvider ----------
153
154/// Define ordinary methods in `impl .. for DynTimeProvider`
155///
156/// This macro exists mostly to avoid copypaste mistakes where we (for example)
157/// implement `block_advance` by calling `release_advance`.
158macro_rules! pub_impl_methods { { $(
159 fn $name:ident $( [ $($generics:tt)* ] )? (
160 ,
161 $( $param:ident: $ptype:ty ),*
162 ) -> $ret:ty;
163)* } => { paste! { $(
164 fn $name $( < $($generics)* > )?(
165 &self,
166 $( $param: $ptype, )*
167 )-> $ret {
168 match &self.0 {
169 Impl::Preferred(p) => with_preferred_runtime!(p; p.$name( $($param,)* )),
170 Impl::Dyn(p) => p.[<dyn_ $name>]( $($param .into() ,)? ),
171 }
172 }
173)* } } }
174
175impl SleepProvider for DynTimeProvider {
176 pub_impl_methods! {
177 fn now(,) -> Instant;
178 fn wallclock(,) -> SystemTime;
179
180 fn block_advance[R: Into<String>](, reason: R) -> ();
181 fn release_advance[R: Into<String>](, reason: R) -> ();
182 fn allow_one_advance(, duration: Duration) -> ();
183 }
184
185 type SleepFuture = DynSleepFuture;
186
187 fn sleep(&self, duration: Duration) -> DynSleepFuture {
188 match &self.0 {
189 Impl::Preferred(p) => with_preferred_runtime!(p; Box::pin(p.sleep(duration))),
190 Impl::Dyn(p) => p.dyn_sleep(duration),
191 }
192 }
193}
194
195impl CoarseTimeProvider for DynTimeProvider {
196 pub_impl_methods! {
197 fn now_coarse(,) -> CoarseInstant;
198 }
199}
200
201//---------- downcast_value ----------
202
203// TODO expose this, maybe in tor-basic-utils ?
204
205/// Try to cast `I` (which is presumably a TAIT) to `O` (presumably a concrete type)
206///
207/// We use runtime casting, but typically the answer is known at compile time.
208///
209/// Astonishingly, this isn't in any of the following:
210/// * `std`
211/// * `match-downcast`
212/// * `better_any` (`downcast:move` comes close but doesn't give you your `self` back)
213/// * `castaway`
214/// * `mopa`
215/// * `as_any`
216fn downcast_value<I: std::any::Any, O: Sized + 'static>(input: I) -> Result<O, I> {
217 // `MaybeUninit` makes it possible to to use `downcast_mut`
218 // and, if it's successful, *move* out of the reference.
219 //
220 // It might be possible to write this function using `mme::transmute` instead.
221 // That might be simpler on the surface, but `mem:transmute` is a very big hammer,
222 // and doing it that way would make it quite easy to accidentally
223 // use the wrong type for the dynamic type check, or mess up lifetimes in I or O.
224 // (Also if we try to transmute the *value*, it might not be possible to
225 // persuade the compiler that the two layouts were necessarily the same.)
226 //
227 // The technique we use is:
228 // * Put the input into `MaybeUninit`, giving us manual control of `I`'s ownership.
229 // * Try to downcast `&mut I` (from the `MaybeUninit`) to `&mut O`.
230 // * If the downcast is successful, move out of the `&mut O`;
231 // this invalidates the `MaybeUninit` (making it uninitialised).
232 // * If the downcast is unsuccessful, reocver the original `I`,
233 // which hasn't in fact have invalidated.
234
235 let mut input = MaybeUninit::new(input);
236 // SAFETY: the MaybeUninit is initialised just above
237 let mut_ref: &mut I = unsafe { input.assume_init_mut() };
238 match <dyn std::any::Any>::downcast_mut(mut_ref) {
239 Some::<&mut O>(output) => {
240 let output = output as *mut O;
241 // SAFETY:
242 // output is properly aligned and points to a properly initialised
243 // O, because it came from a mut reference
244 // Reading this *invalidates* the MaybeUninit, since the value isn't Copy.
245 // It also invalidates mut_ref, which we therefore mustn't use again.
246 let output: O = unsafe { output.read() };
247 // Prove that the MaybeUninit is live up to here, and then isn't used any more
248 #[allow(clippy::drop_non_drop)] // Yes, we know
249 mem::drop::<MaybeUninit<I>>(input);
250 Ok(output)
251 }
252 None => Err(
253 // SAFETY: Indeed, it was just initialised, and downcast_mut didn't change that
254 unsafe { input.assume_init() },
255 ),
256 }
257}
258
259#[cfg(test)]
260mod test {
261 // @@ begin test lint list maintained by maint/add_warning @@
262 #![allow(clippy::bool_assert_comparison)]
263 #![allow(clippy::clone_on_copy)]
264 #![allow(clippy::dbg_macro)]
265 #![allow(clippy::mixed_attributes_style)]
266 #![allow(clippy::print_stderr)]
267 #![allow(clippy::print_stdout)]
268 #![allow(clippy::single_char_pattern)]
269 #![allow(clippy::unwrap_used)]
270 #![allow(clippy::unchecked_duration_subtraction)]
271 #![allow(clippy::useless_vec)]
272 #![allow(clippy::needless_pass_by_value)]
273 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
274 #![allow(clippy::useless_format)]
275 use super::*;
276
277 use std::fmt::{Debug, Display};
278 use std::hint::black_box;
279
280 fn try_downcast_string<S: Display + Debug + 'static>(x: S) -> Result<String, S> {
281 black_box(downcast_value(black_box(x)))
282 }
283
284 #[test]
285 fn check_downcast_value() {
286 // This and the one in check_downcast_dropcount are not combined, with generics,
287 // so that the types of everything are as clear as they can be.
288 assert_eq!(try_downcast_string(format!("hi")).unwrap(), format!("hi"));
289 assert_eq!(try_downcast_string("hi").unwrap_err().to_string(), "hi");
290 }
291
292 #[test]
293 fn check_downcast_dropcount() {
294 #[derive(Debug, derive_more::Display)]
295 #[display("{self:?}")]
296 struct DropCounter(u32);
297
298 fn try_downcast_dc(x: impl Debug + 'static) -> Result<DropCounter, impl Debug + 'static> {
299 black_box(downcast_value(black_box(x)))
300 }
301
302 impl Drop for DropCounter {
303 fn drop(&mut self) {
304 let _: u32 = self.0.checked_sub(1).unwrap();
305 }
306 }
307
308 let dc = DropCounter(0);
309 let mut dc: DropCounter = try_downcast_dc(dc).unwrap();
310 assert_eq!(dc.0, 0);
311 dc.0 = 1;
312
313 let dc = DropCounter(0);
314 let mut dc: DropCounter = try_downcast_string(dc).unwrap_err();
315 assert_eq!(dc.0, 0);
316 dc.0 = 1;
317 }
318
319 if_preferred_runtime! {[
320 #[test]
321 fn dyn_time_provider_from_dyn_time_provider() {
322 // A new `DynTimeProvider(Impl::PreferredRuntime(_))`.
323 let x = DynTimeProvider::new(PreferredRuntime::create().unwrap());
324
325 // Cast `x` as a generic `SleepProvider + CoarseTimeProvider` and wrap in a new
326 // `DynTimeProvider`.
327 fn new_provider<R: SleepProvider + CoarseTimeProvider>(runtime: R) -> DynTimeProvider {
328 DynTimeProvider::new(runtime)
329 }
330 let x = new_provider(x);
331
332 // Ensure that `x` didn't end up as a `DynTimeProvider(Impl::Dyn(_))`.
333 assert!(matches!(x, DynTimeProvider(Impl::Preferred(_))));
334 }
335 ] [
336 // no test if there is no preferred runtime
337 ]}
338}