tor_hsservice/
timeout_track.rs

1//! Utilities to track and compare times and timeouts
2//!
3//! Contains [`TrackingNow`], and variants.
4//!
5//! Each one records the current time,
6//! and can be used to see if prospective timeouts have expired yet,
7//! via the [`PartialOrd`] implementations.
8//!
9//! Each can be compared with a prospective wakeup time via a `.cmp()` method,
10//! and via implementations of [`PartialOrd`] (including via `<` operators etc.)
11//!
12//! Each tracks every such comparison,
13//! and can yield the earliest *unexpired* timeout that was asked about.
14//!
15//! I.e., the timeout tracker tells you when (in the future)
16//! any of the comparisons you have made, might produce different answers.
17//! So, that can be used to know how long to sleep for when waiting for timeout(s).
18//!
19//! This approach means you must be sure to actually perform the timeout action
20//! whenever a comparison tells you the relevant period has elapsed.
21//! If you fail to do so, the timeout tracker will still disregard the event
22//! for the purposes of calculating how to wait, since it is in the past.
23//! So if you use the timeout tracker to decide how long to sleep,
24//! you won't be woken up until *something else* occurs.
25//! (When the timeout has *exactly* elapsed, you should eagerly perform the action.
26//! Otherwise the timeout tracker will calculate a zero timeout and you'll spin.)
27//!
28//! Each tracker has interior mutability,
29//! which is necessary because `PartialOrd` (`<=` etc.) only passes immutable references.
30//! Most are `Send`, none are `Sync`,
31//! so use in thread-safe async code is somewhat restricted.
32//! (Recommended use is to do all work influencing timeout calculations synchronously;
33//! otherwise, in any case, you risk the time advancing mid-calculations.)
34//!
35//! `Clone` gives you a *copy*, not a handle onto the same tracker.
36//! Comparisons done with the clone do not update the original.
37//! (Exception: `TrackingInstantOffsetNow::clone`.)
38//!
39//! The types are:
40//!
41//!  * [`TrackingNow`]: tracks timeouts based on both [`SystemTime`] and [`Instant`],
42//!  * [`TrackingSystemTimeNow`]: tracks timeouts based on [`SystemTime`]
43//!  * [`TrackingInstantNow`]: tracks timeouts based on [`Instant`]
44//!  * [`TrackingInstantOffsetNow`]: `InstantTrackingNow` but with an offset applied
45//!
46//! # Advantages, disadvantages, and alternatives
47//!
48//! Using `TrackingNow` allows time-dependent code to be written
49//! in a natural, imperative, style,
50//! even if the timeout calculations are complex.
51//! Simply test whether it is time yet to do each thing, and if so do it.
52//!
53//! This can conveniently be combined with an idempotent imperative style
54//! for handling non-time-based inputs:
55//! for each possible action, you can decide in one place whether it needs doing.
56//! (Use a `select_biased!`, with [`wait_for_earliest`](TrackingNow::wait_for_earliest)
57//! as one of the branches.)
58//!
59//! This approach makes it harder to write bugs where
60//! some of the consequences of events are forgotten.
61//! Each timeout calculation is always done afresh from all its inputs.
62//! There is only ever one place where each action is considered,
63//! and the consideration is always redone from first principles.
64//!
65//! However, this is not necessarily the most performant approach.
66//! Each iteration of the event loop doesn't know *why* it has woken up,
67//! so must simply re-test all of the things that might need to be done.
68//!
69//! When higher performance is needed, consider maintaining timeouts as state:
70//! either as wakeup times or durations, or as actual `Future`s,
71//! depending on how frequently they are going to occur,
72//! and how much they need to be modified.
73//! With this approach you must remember to update or recalculate the timeout,
74//! on every change to any of the inputs to each timeout calculation.
75//! You must write code to check (or perform) each action,
76//! in the handler for each event that might trigger it.
77//! Omitting a call is easy,
78//! can result in mysterious ordering-dependent "stuckess" bugs,
79//! and is often detectable only by very comprehensive testing.
80//!
81//! # Example
82//!
83//! ```
84//! use std::sync::{Arc, Mutex};
85//! use std::time::Duration;
86//! use futures::task::SpawnExt as _;
87//! use tor_rtcompat::{ToplevelBlockOn as _, SleepProvider as _};
88//!
89//! # use tor_hsservice::timeout_track_for_doctests_unstable_no_semver_guarantees as timeout_track;
90//! # #[cfg(all)] // works like #[cfg(FALSE)].  Instead, we have this workaround ^.
91//! use crate::timeout_track;
92//! use timeout_track::TrackingInstantNow;
93//!
94//! // Test harness
95//! let runtime = tor_rtmock::MockRuntime::new();
96//! let actions = Arc::new(Mutex::new("".to_string())); // initial letters of performed actions
97//! let perform_action = {
98//!     let actions = actions.clone();
99//!     move |s: &str| actions.lock().unwrap().extend(s.chars().take(1))
100//! };
101//!
102//! runtime.spawn({
103//!     let runtime = runtime.clone();
104//!
105//!     // Example program which models cooking a stir-fry
106//!     async move {
107//!         perform_action("add ingredients");
108//!         let started = runtime.now();
109//!         let mut last_stirred = started;
110//!         loop {
111//!             let now_track = TrackingInstantNow::now(&runtime);
112//!
113//!             const STIR_EVERY: Duration = Duration::from_secs(25);
114//!             // In production, we might avoid panics:  .. >= last_stirred.checked_add(..)
115//!             if now_track >= last_stirred + STIR_EVERY {
116//!                 perform_action("stir");
117//!                 last_stirred = now_track.get_now_untracked();
118//!                 continue;
119//!             }
120//!
121//!             const COOK_FOR: Duration = Duration::from_secs(3 * 60);
122//!             if now_track >= started + COOK_FOR {
123//!                 break;
124//!             }
125//!
126//!             now_track.wait_for_earliest(&runtime).await;
127//!         }
128//!         perform_action("dish up");
129//!     }
130//! }).unwrap();
131//!
132//! // Do a test run
133//! runtime.block_on(async {
134//!     runtime.advance_by(Duration::from_secs(1 * 60)).await;
135//!     assert_eq!(*actions.lock().unwrap(), "ass");
136//!     runtime.advance_by(Duration::from_secs(2 * 60)).await;
137//!     assert_eq!(*actions.lock().unwrap(), "asssssssd");
138//! });
139//! ```
140
141// TODO - eventually we hope this will become pub, in another crate
142
143// Rustdoc complains that we link to these private docs from these docs which are
144// themselves only formatted with --document-private-items.
145// TODO - Remove when this is actually public
146#![allow(rustdoc::private_intra_doc_links)]
147
148use std::cell::Cell;
149use std::cmp::Ordering;
150use std::time::{Duration, Instant, SystemTime};
151
152use derive_deftly::{define_derive_deftly, Deftly};
153use futures::{future, select_biased, FutureExt as _};
154use itertools::chain;
155
156use tor_rtcompat::{SleepProvider, SleepProviderExt as _};
157
158//========== derive-deftly macros, which must come first ==========
159
160define_derive_deftly! {
161    /// Defines methods and types which are common to trackers for `Instant` and `SystemTime`
162    SingleTimeoutTracker for struct, expect items:
163
164    // type of the `now` field, ie the absolute time type
165    ${define NOW $(
166        ${when approx_equal($fname, now)}
167        $ftype
168    ) }
169
170    // type that we track, ie the inner contents of the `Cell<Option<...>>`
171    ${define TRACK ${tmeta(track) as ty}}
172
173    // TODO maybe some of this should be a trait?  But that would probably include
174    // wait_for_earliest, which would be an async trait method and quite annoying.
175    impl $ttype {
176        /// Creates a new timeout tracker, given a value for the current time
177        pub fn new(now: $NOW) -> Self {
178            Self {
179                now,
180                earliest: None.into(),
181            }
182        }
183
184        /// Creates a new timeout tracker from the current time as seen by a runtime
185        pub fn now(r: &impl SleepProvider) -> Self {
186            let now = r.${tmeta(from_runtime) as ident}();
187            Self::new(now)
188        }
189
190        /// Return the "current time" value in use
191        ///
192        /// If you do comparisons with this, they won't be tracked, obviously.
193        pub fn get_now_untracked(&self) -> $NOW {
194            self.now
195        }
196
197        /// Core of a tracked update: updates `earliest` with `maybe_earlier`
198        fn update_unconditional(earliest: &Cell<Option<$TRACK>>, maybe_earlier: $TRACK) {
199            earliest.set(chain!(
200                earliest.take(),
201                [maybe_earlier],
202            ).min())
203        }
204
205        /// Core of a tracked update: updates `earliest` with `maybe_earlier`, if necessary
206        ///
207        /// `o` is what we are about to return:
208        /// `Less` if the current time hasn't reached `maybe_earlier` yet.
209        fn update_conditional(
210            o: Ordering,
211            earliest: &Cell<Option<$TRACK>>,
212            maybe_earlier: $TRACK,
213        ) {
214            match o {
215                Ordering::Greater | Ordering::Equal => {},
216                Ordering::Less => Self::update_unconditional(earliest, maybe_earlier),
217            }
218        }
219    }
220
221    impl Sealed for $ttype {}
222}
223
224define_derive_deftly! {
225    /// Impls for `TrackingNow`, the combined tracker
226    ///
227    /// Defines just the methods which want to abstract over fields
228    CombinedTimeoutTracker for struct, expect items:
229
230    ${define NOW ${fmeta(now) as ty}}
231
232    impl $ttype {
233        /// Creates a new combined timeout tracker, given values for the current time
234        pub fn new( $(
235            $fname: $NOW,
236        ) ) -> $ttype {
237            $ttype { $( $fname: $ftype::new($fname), ) }
238        }
239
240        /// Creates a new timeout tracker from the current times as seen by a runtime
241        pub fn now(r: &impl SleepProvider) -> Self {
242            $ttype { $(
243                $fname: $ftype::now(r),
244            ) }
245        }
246
247      $(
248        #[doc = concat!("Access the specific timeout tracker for [`", stringify!($NOW), "`]")]
249        pub fn $fname(&self) -> &$ftype {
250            &self.$fname
251        }
252      )
253
254        /// Return the shortest `Duration` until any future time with which this has been compared
255        pub fn shortest(self) -> Option<Duration> {
256            chain!( $(
257                self.$fname.shortest(),
258            ) ).min()
259        }
260    }
261
262  $(
263    impl Update<$NOW> for TrackingNow {
264        fn update(&self, t: $NOW) {
265            self.$fname.update(t);
266        }
267    }
268
269    define_PartialOrd_via_cmp! { $ttype, $NOW, .$fname }
270  )
271}
272
273define_derive_deftly! {
274    /// Defines `wait_for_earliest`
275    ///
276    /// Combined into this macro mostly so we only have to write the docs once
277    WaitForEarliest for struct, expect items:
278
279    impl $ttype {
280        /// Wait for the earliest timeout implied by any of the comparisons
281        ///
282        /// Waits until the earliest future time at which any of the comparisons performed
283        /// might change their answer.
284        ///
285        /// If there were no comparisons there are no timeouts, so we wait forever.
286        pub async fn wait_for_earliest(self, runtime: &impl SleepProvider) {
287            ${if tmeta(runtime_sleep) {
288                // tracker for a single kind of time
289                match self.earliest.into_inner() {
290                    None => future::pending().await,
291                    Some(earliest) => runtime.${tmeta(runtime_sleep) as ident}(earliest).await,
292                }
293            } else {
294                // combined tracker, wait for earliest of any kind of timeout
295                select_biased! { $(
296                    () = self.$fname.wait_for_earliest(runtime).fuse() => {},
297                ) }
298            }}
299        }
300    }
301}
302
303/// `impl PartialOrd<$NOW> for $ttype` in terms of `...$field.cmp()`
304macro_rules! define_PartialOrd_via_cmp { {
305    $ttype:ty, $NOW:ty, $( $field:tt )*
306} => {
307    /// Check if time `t` has been reached yet (and if not, remember that we want to wake up then)
308    ///
309    /// Always returns `Some`.
310    impl PartialEq<$NOW> for $ttype {
311        fn eq(&self, t: &$NOW) -> bool {
312            self $($field)* .cmp(*t) == Ordering::Equal
313        }
314    }
315
316    /// Check if time `t` has been reached yet (and if not, remember that we want to wake up then)
317    ///
318    /// Always returns `Some`.
319    impl PartialOrd<$NOW> for $ttype {
320        fn partial_cmp(&self, t: &$NOW) -> Option<std::cmp::Ordering> {
321            Some(self $($field)* .cmp(*t))
322        }
323    }
324
325    /// Check if we have reached time `t` yet (and if not, remember that we want to wake up then)
326    ///
327    /// Always returns `Some`.
328    impl PartialEq<$ttype> for $NOW {
329        fn eq(&self, t: &$ttype) -> bool {
330            t.eq(self)
331        }
332    }
333
334    /// Check if we have reached time `t` yet (and if not, remember that we want to wake up then)
335    ///
336    /// Always returns `Some`.
337    impl PartialOrd<$ttype> for $NOW {
338        fn partial_cmp(&self, t: &$ttype) -> Option<std::cmp::Ordering> {
339            t.partial_cmp(self).map(|o| o.reverse())
340        }
341    }
342} }
343
344//========== data structures ==========
345
346/// Utility to track timeouts based on [`SystemTime`] (wall clock time)
347///
348/// Represents the current `SystemTime` (from when it was created).
349/// See the [module-level documentation](self) for the general overview.
350///
351/// To operate a timeout,
352/// you should calculate the `SystemTime` at which you should time out,
353/// and compare that future planned wakeup time with this `TrackingSystemTimeNow`
354/// (via [`.cmp()`](Self::cmp) or inequality operators and [`PartialOrd`]).
355#[derive(Clone, Debug, Deftly)]
356#[derive_deftly(SingleTimeoutTracker, WaitForEarliest)]
357#[deftly(track = "SystemTime")]
358#[deftly(from_runtime = "wallclock", runtime_sleep = "sleep_until_wallclock")]
359pub struct TrackingSystemTimeNow {
360    /// Current time
361    now: SystemTime,
362    /// Earliest time at which we should wake up
363    earliest: Cell<Option<SystemTime>>,
364}
365
366/// Earliest timeout at which an [`Instant`] based timeout should occur, as duration from now
367///
368/// The actual tracker, found via `TrackingInstantNow` or `TrackingInstantOffsetNow`
369type InstantEarliest = Cell<Option<Duration>>;
370
371/// Utility to track timeouts based on [`Instant`] (monotonic time)
372///
373/// Represents the current `Instant` (from when it was created).
374/// See the [module-level documentation](self) for the general overview.
375///
376/// To calculate and check a timeout,
377/// you can
378/// calculate the future `Instant` at which you wish to wake up,
379/// and compare it with a `TrackingInstantNow`,
380/// via [`.cmp()`](Self::cmp) or inequality operators and [`PartialOrd`].
381///
382/// Or you can
383/// use
384/// [`.checked_sub()`](TrackingInstantNow::checked_sub)
385/// to obtain a [`TrackingInstantOffsetNow`].
386#[derive(Clone, Debug, Deftly)]
387#[derive_deftly(SingleTimeoutTracker, WaitForEarliest)]
388#[deftly(track = "Duration")]
389#[deftly(from_runtime = "now", runtime_sleep = "sleep")]
390pub struct TrackingInstantNow {
391    /// Current time
392    now: Instant,
393    /// Duration until earliest time we should wake up
394    earliest: InstantEarliest,
395}
396
397/// Current minus an offset, for [`Instant`]-based timeout checks
398///
399/// Returned by
400/// [`TrackingNow::checked_sub()`]
401/// and
402/// [`TrackingInstantNow::checked_sub()`].
403///
404/// You can compare this with an interesting fixed `Instant`,
405/// via [`.cmp()`](Self::cmp) or inequality operators and [`PartialOrd`].
406///
407/// Borrows from its parent `TrackingInstantNow`;
408/// multiple different `TrackingInstantOffsetNow`'s can exist
409/// for the same parent tracker,
410/// and they'll all update it.
411///
412/// (There is no corresponding call for `SystemTime`;
413/// see the [docs for `TrackingNow::checked_sub()`](TrackingNow::checked_sub)
414/// for why.)
415#[derive(Debug)]
416pub struct TrackingInstantOffsetNow<'i> {
417    /// Value to compare with
418    threshold: Instant,
419    /// Comparison tracker
420    earliest: &'i InstantEarliest,
421}
422
423/// Timeout tracker that can handle both `Instant`s and `SystemTime`s
424///
425/// Internally, the two kinds of timeouts are tracked separately:
426/// this contains a [`TrackingInstantNow`] and a [`TrackingSystemTimeNow`].
427#[derive(Clone, Debug, Deftly)]
428#[derive_deftly(CombinedTimeoutTracker, WaitForEarliest)]
429pub struct TrackingNow {
430    /// For `Instant`s
431    #[deftly(now = "Instant")]
432    instant: TrackingInstantNow,
433    /// For `SystemTime`s
434    #[deftly(now = "SystemTime")]
435    system_time: TrackingSystemTimeNow,
436}
437
438//========== trait for providing `update` method ==========
439
440/// Trait providing the `update` method on timeout trackers
441pub trait Update<T>: Sealed {
442    /// Update the "earliest timeout" notion, to ensure it's at least as early as `t`
443    ///
444    /// This is an *unconditional* update.
445    /// Usually, `t` should be (strictly) in the future.
446    ///
447    /// Implemented for [`TrackingNow`], [`TrackingInstantNow`],
448    /// and [`TrackingSystemTimeNow`].
449    /// `t` can be a `SystemTime`, `Instant`, or `Duration`,
450    /// (depending on the type of `self`).
451    ///
452    /// `Update<Duration>` is not implemented for [`TrackingSystemTimeNow`]
453    /// because tracking of relative times should be done via `Instant`,
454    /// as the use of the monotonic clock is more reliable.
455    fn update(&self, t: T);
456}
457
458/// Sealed
459mod sealed {
460    /// Sealed
461    pub trait Sealed {}
462}
463use sealed::*;
464
465//========== implementations, organised by theme ==========
466
467//----- earliest accessor ----
468
469impl TrackingSystemTimeNow {
470    /// Return the earliest future `SystemTime` with which this has been compared
471    pub fn earliest(self) -> Option<SystemTime> {
472        self.earliest.into_inner()
473    }
474
475    /// Return the shortest `Duration` until any future `SystemTime` with which this has been compared
476    pub fn shortest(self) -> Option<Duration> {
477        self.earliest
478            .into_inner()
479            .map(|earliest| earliest.duration_since(self.now).unwrap_or(Duration::ZERO))
480    }
481}
482
483impl TrackingInstantNow {
484    /// Return the shortest `Duration` until any future `Instant` with which this has been compared
485    pub fn shortest(self) -> Option<Duration> {
486        self.earliest.into_inner()
487    }
488}
489
490//----- manual update functions ----
491
492impl Update<SystemTime> for TrackingSystemTimeNow {
493    fn update(&self, t: SystemTime) {
494        Self::update_unconditional(&self.earliest, t);
495    }
496}
497
498impl Update<Instant> for TrackingInstantNow {
499    fn update(&self, t: Instant) {
500        self.update(t.checked_duration_since(self.now).unwrap_or_default());
501    }
502}
503
504impl Update<Duration> for TrackingInstantNow {
505    fn update(&self, d: Duration) {
506        Self::update_unconditional(&self.earliest, d);
507    }
508}
509
510impl Sealed for TrackingNow {}
511
512impl Update<Duration> for TrackingNow {
513    fn update(&self, d: Duration) {
514        self.instant().update(d);
515    }
516}
517
518//----- cmp and PartialOrd implementation ----
519
520impl TrackingSystemTimeNow {
521    /// Check if time `t` has been reached yet (and remember that we want to wake up then)
522    ///
523    /// Also available via [`PartialOrd`]
524    fn cmp(&self, t: SystemTime) -> std::cmp::Ordering {
525        let o = self.now.cmp(&t);
526        Self::update_conditional(o, &self.earliest, t);
527        o
528    }
529}
530define_PartialOrd_via_cmp! { TrackingSystemTimeNow, SystemTime, }
531
532/// Check `t` against a now-based `threshold` (and remember for wakeup)
533///
534/// Common code for `TrackingInstantNow` and `TrackingInstantOffsetNow`'s
535/// `cmp`.
536fn instant_cmp(earliest: &InstantEarliest, threshold: Instant, t: Instant) -> Ordering {
537    let Some(d) = t.checked_duration_since(threshold) else {
538        return Ordering::Greater;
539    };
540
541    let o = Duration::ZERO.cmp(&d);
542    TrackingInstantNow::update_conditional(o, earliest, d);
543    o
544}
545
546impl TrackingInstantNow {
547    /// Check if time `t` has been reached yet (and remember that we want to wake up then)
548    ///
549    /// Also available via [`PartialOrd`]
550    fn cmp(&self, t: Instant) -> std::cmp::Ordering {
551        instant_cmp(&self.earliest, self.now, t)
552    }
553}
554define_PartialOrd_via_cmp! { TrackingInstantNow, Instant, }
555
556impl<'i> TrackingInstantOffsetNow<'i> {
557    /// Check if the offset current time has advanced to `t` yet (and remember for wakeup)
558    ///
559    /// Also available via [`PartialOrd`]
560    ///
561    /// ### Alternative description
562    ///
563    /// Checks if the current time has advanced to `offset` *after* `t`,
564    /// where `offset` was passed to `TrackingInstantNow::checked_sub`.
565    fn cmp(&self, t: Instant) -> std::cmp::Ordering {
566        instant_cmp(self.earliest, self.threshold, t)
567    }
568}
569define_PartialOrd_via_cmp! { TrackingInstantOffsetNow<'_>, Instant, }
570
571// Combined TrackingNow cmp and PartialOrd impls done via derive-deftly
572
573//----- checked_sub (constructor for Instant offset tracker) -----
574
575impl TrackingInstantNow {
576    /// Return a tracker representing a specific offset before the current time
577    ///
578    /// You can use this to pre-calculate an offset from the current time,
579    /// to compare other `Instant`s with.
580    ///
581    /// This can be convenient to avoid repetition;
582    /// also,
583    /// when working with checked time arithmetic,
584    /// this can helpfully centralise the out-of-bounds error handling site.
585    pub fn checked_sub(&self, offset: Duration) -> Option<TrackingInstantOffsetNow> {
586        let threshold = self.now.checked_sub(offset)?;
587        Some(TrackingInstantOffsetNow {
588            threshold,
589            earliest: &self.earliest,
590        })
591    }
592}
593
594impl TrackingNow {
595    /// Return a tracker representing an `Instant` a specific offset before the current time
596    ///
597    /// See [`TrackingInstantNow::checked_sub()`] for more details.
598    ///
599    /// ### `Instant`-only
600    ///
601    /// The returned tracker handles only `Instant`s,
602    /// for reasons relating to clock warps:
603    /// broadly, waiting for a particular `SystemTime` must always be done
604    /// by working with the future `SystemTime` at which to wake up;
605    /// whereas, waiting for a particular `Instant` can be done by calculating `Durations`s.
606    ///
607    /// For the same reason there is no
608    /// `.checked_sub()` method on [`TrackingSystemTimeNow`].
609    pub fn checked_sub(&self, offset: Duration) -> Option<TrackingInstantOffsetNow> {
610        self.instant.checked_sub(offset)
611    }
612}
613
614#[cfg(test)]
615mod test {
616    // @@ begin test lint list maintained by maint/add_warning @@
617    #![allow(clippy::bool_assert_comparison)]
618    #![allow(clippy::clone_on_copy)]
619    #![allow(clippy::dbg_macro)]
620    #![allow(clippy::mixed_attributes_style)]
621    #![allow(clippy::print_stderr)]
622    #![allow(clippy::print_stdout)]
623    #![allow(clippy::single_char_pattern)]
624    #![allow(clippy::unwrap_used)]
625    #![allow(clippy::unchecked_duration_subtraction)]
626    #![allow(clippy::useless_vec)]
627    #![allow(clippy::needless_pass_by_value)]
628    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
629
630    #![allow(clippy::needless_pass_by_value)] // TODO hoist into standard lint block
631
632    use super::*;
633    use futures::poll;
634    use oneshot_fused_workaround as oneshot;
635    use std::future::Future;
636    use std::task::Poll;
637    use tor_rtcompat::ToplevelBlockOn;
638    use tor_rtmock::MockRuntime;
639
640    fn parse_rfc3339(s: &str) -> SystemTime {
641        humantime::parse_rfc3339(s).unwrap()
642    }
643
644    fn earliest_systemtime() -> SystemTime {
645        parse_rfc3339("1993-11-01T00:00:00Z")
646    }
647
648    fn check_orderings<TT, T>(tt: &TT, earliest: T, middle: T, later: T)
649    where
650        TT: PartialOrd<T>,
651        T: PartialOrd<TT>,
652    {
653        assert!(*tt > earliest);
654        assert!(*tt >= earliest);
655        assert!(earliest < *tt);
656        assert!(earliest <= *tt);
657        assert!(*tt == middle);
658        assert!(middle == *tt);
659        assert!(*tt < later);
660        assert!(*tt <= later);
661        assert!(later > *tt);
662        assert!(later >= *tt);
663    }
664
665    fn test_systemtimes() -> (SystemTime, SystemTime, SystemTime) {
666        (
667            earliest_systemtime(),
668            parse_rfc3339("1994-11-01T00:00:00Z"),
669            parse_rfc3339("1995-11-01T00:00:00Z"),
670        )
671    }
672
673    fn secs(s: u64) -> Duration {
674        Duration::from_secs(s)
675    }
676    fn days(d: u64) -> Duration {
677        Duration::from_secs(86400 * d)
678    }
679
680    #[test]
681    fn arith_systemtime() {
682        let (earliest, middle, later) = test_systemtimes();
683
684        {
685            let tt = TrackingSystemTimeNow::new(middle);
686            assert_eq!(tt.earliest(), None);
687        }
688        {
689            let tt = TrackingSystemTimeNow::new(middle);
690            assert_eq!(tt.cmp(earliest), Ordering::Greater);
691            assert_eq!(tt.clone().shortest(), None);
692            assert_eq!(tt.earliest(), None);
693        }
694        {
695            let tt = TrackingSystemTimeNow::new(middle);
696            assert_eq!(tt.cmp(later), Ordering::Less);
697            assert_eq!(tt.clone().shortest(), Some(days(365)));
698            assert_eq!(tt.earliest(), Some(later));
699        }
700        {
701            let tt = TrackingSystemTimeNow::new(middle);
702            check_orderings(&tt, earliest, middle, later);
703            assert_eq!(tt.clone().shortest(), Some(days(365)));
704            assert_eq!(tt.earliest(), Some(later));
705        }
706        {
707            let tt = TrackingSystemTimeNow::new(middle);
708            // Use this notation so we can see that all the Update impls are tested
709            <TrackingSystemTimeNow as Update<SystemTime>>::update(&tt, later);
710            assert_eq!(tt.clone().shortest(), Some(days(365)));
711            // Test the underflow edge case (albeit that this would probably be a caller bug)
712            <TrackingSystemTimeNow as Update<SystemTime>>::update(&tt, earliest);
713            assert_eq!(tt.shortest(), Some(days(0)));
714        }
715    }
716
717    #[test]
718    fn arith_instant_combined() {
719        // Adding 1Ms gives us some headroom, since we don't want to underflow
720        let earliest = Instant::now() + secs(1000000);
721        let middle_d = secs(200);
722        let middle = earliest + middle_d;
723        let later_d = secs(300);
724        let later = middle + later_d;
725
726        {
727            let tt = TrackingInstantNow::new(middle);
728            assert_eq!(tt.shortest(), None);
729        }
730        {
731            let tt = TrackingInstantNow::new(middle);
732            assert_eq!(tt.cmp(earliest), Ordering::Greater);
733            assert_eq!(tt.shortest(), None);
734        }
735        {
736            let tt = TrackingInstantNow::new(middle);
737            check_orderings(&tt, earliest, middle, later);
738            assert_eq!(tt.shortest(), Some(secs(300)));
739        }
740        {
741            let tt = TrackingInstantNow::new(middle);
742            let off = tt.checked_sub(secs(700)).expect("underflow");
743            assert!(off < earliest); // (200-700) vs 0
744            assert_eq!(tt.shortest(), Some(secs(500)));
745        }
746        {
747            let tt = TrackingInstantNow::new(middle);
748            let off = tt.checked_sub(Duration::ZERO).unwrap();
749            check_orderings(&off, earliest, middle, later);
750            assert_eq!(tt.shortest(), Some(secs(300)));
751        }
752        {
753            let tt = TrackingInstantNow::new(middle);
754            <TrackingInstantNow as Update<Instant>>::update(&tt, later);
755            assert_eq!(tt.clone().shortest(), Some(secs(300)));
756            <TrackingInstantNow as Update<Duration>>::update(&tt, secs(100));
757            assert_eq!(tt.clone().shortest(), Some(secs(100)));
758            // Test the underflow edge case (albeit that this would probably be a caller bug)
759            <TrackingInstantNow as Update<Instant>>::update(&tt, earliest);
760            assert_eq!(tt.shortest(), Some(secs(0)));
761        }
762
763        let (earliest_st, middle_st, later_st) = test_systemtimes();
764        {
765            let tt = TrackingNow::new(middle, middle_st);
766            let off = tt.checked_sub(Duration::ZERO).unwrap();
767            check_orderings(&tt, earliest, middle, later);
768            check_orderings(&off, earliest, middle, later);
769            check_orderings(&tt, earliest_st, middle_st, later_st);
770            assert_eq!(tt.clone().shortest(), Some(secs(300)));
771            assert_eq!(tt.instant().clone().shortest(), Some(secs(300)));
772            assert_eq!(tt.system_time().clone().shortest(), Some(days(365)));
773            assert_eq!(tt.system_time().clone().earliest(), Some(later_st));
774        }
775        let (_earliest_st, middle_st, later_st) = test_systemtimes();
776        {
777            let tt = TrackingNow::new(middle, middle_st);
778            <TrackingNow as Update<SystemTime>>::update(&tt, later_st);
779            assert_eq!(tt.clone().shortest(), Some(days(365)));
780            <TrackingNow as Update<Duration>>::update(&tt, days(10));
781            assert_eq!(tt.clone().shortest(), Some(days(10)));
782            <TrackingNow as Update<Instant>>::update(&tt, middle + days(5));
783            assert_eq!(tt.shortest(), Some(days(5)));
784            // No need to test edge cases, as our Update impls are just delegations
785        }
786    }
787
788    fn test_sleeper<WF>(
789        expected_wait: Option<Duration>,
790        wait_for_timeout: impl FnOnce(MockRuntime) -> WF + Send + 'static,
791    ) where
792        WF: Future<Output = ()> + Send + 'static,
793    {
794        let runtime = MockRuntime::new();
795        runtime.clone().block_on(async move {
796            // prevent underflow of Instant in case we started very recently
797            // (just jump the clock)
798            runtime.advance_by(secs(1000000)).await;
799            // set SystemTime to a known value
800            runtime.jump_wallclock(earliest_systemtime());
801
802            let (tx, rx) = oneshot::channel();
803
804            runtime.mock_task().spawn_identified("timeout task", {
805                let runtime = runtime.clone();
806                async move {
807                    wait_for_timeout(runtime.clone()).await;
808                    tx.send(()).unwrap();
809                }
810            });
811
812            runtime.mock_task().progress_until_stalled().await;
813
814            if expected_wait == Some(Duration::ZERO) {
815                assert_eq!(poll!(rx), Poll::Ready(Ok(())));
816            } else {
817                let actual_wait = runtime.time_until_next_timeout();
818                assert_eq!(actual_wait, expected_wait);
819            }
820        });
821    }
822
823    fn test_sleeper_combined(
824        expected_wait: Option<Duration>,
825        update_tt: impl FnOnce(&MockRuntime, &TrackingNow) + Send + 'static,
826    ) {
827        test_sleeper(expected_wait, |rt| async move {
828            let tt = TrackingNow::now(&rt);
829            update_tt(&rt, &tt);
830            tt.wait_for_earliest(&rt).await;
831        });
832    }
833
834    #[test]
835    fn sleeps() {
836        let s = earliest_systemtime();
837        let d = secs(42);
838
839        test_sleeper_combined(None, |_rt, _tt| {});
840        test_sleeper_combined(None, move |_rt, tt| {
841            assert!(*tt > (s - d));
842        });
843        test_sleeper_combined(Some(d), move |_rt, tt| {
844            assert!(*tt < (s + d));
845        });
846
847        test_sleeper_combined(None, move |rt, tt| {
848            let i = rt.now();
849            assert!(*tt > (i - d));
850        });
851        test_sleeper_combined(Some(d), move |rt, tt| {
852            let i = rt.now();
853            assert!(*tt < (i + d));
854        });
855    }
856}