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

            
148
use std::cell::Cell;
149
use std::cmp::Ordering;
150
use std::time::{Duration, Instant, SystemTime};
151

            
152
use derive_deftly::{define_derive_deftly, Deftly};
153
use futures::{future, select_biased, FutureExt as _};
154
use itertools::chain;
155

            
156
use tor_rtcompat::{SleepProvider, SleepProviderExt as _};
157

            
158
//========== derive-deftly macros, which must come first ==========
159

            
160
define_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
746
        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
716
        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
120
        pub fn get_now_untracked(&self) -> $NOW {
194
            self.now
195
        }
196

            
197
        /// Core of a tracked update: updates `earliest` with `maybe_earlier`
198
1064
        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
1324
        fn update_conditional(
210
1324
            o: Ordering,
211
1324
            earliest: &Cell<Option<$TRACK>>,
212
1324
            maybe_earlier: $TRACK,
213
1324
        ) {
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

            
224
define_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
4
        pub fn new( $(
235
4
            $fname: $NOW,
236
4
        ) ) -> $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
358
        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
128
        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
100
        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
4
        fn update(&self, t: $NOW) {
265
            self.$fname.update(t);
266
        }
267
    }
268

            
269
    define_PartialOrd_via_cmp! { $ttype, $NOW, .$fname }
270
  )
271
}
272

            
273
define_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
858
        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()`
304
macro_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
24
        fn eq(&self, t: &$NOW) -> bool {
312
24
            self $($field)* .cmp(*t) == Ordering::Equal
313
24
        }
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
1378
        fn partial_cmp(&self, t: &$NOW) -> Option<std::cmp::Ordering> {
321
1378
            Some(self $($field)* .cmp(*t))
322
1378
        }
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
12
        fn eq(&self, t: &$ttype) -> bool {
330
12
            t.eq(self)
331
12
        }
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
84
        fn partial_cmp(&self, t: &$ttype) -> Option<std::cmp::Ordering> {
339
126
            t.partial_cmp(self).map(|o| o.reverse())
340
84
        }
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")]
359
pub 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`
369
type 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")]
390
pub 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)]
416
pub 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)]
429
pub 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
441
pub 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
459
mod sealed {
460
    /// Sealed
461
    pub trait Sealed {}
462
}
463
use sealed::*;
464

            
465
//========== implementations, organised by theme ==========
466

            
467
//----- earliest accessor ----
468

            
469
impl TrackingSystemTimeNow {
470
    /// Return the earliest future `SystemTime` with which this has been compared
471
10
    pub fn earliest(self) -> Option<SystemTime> {
472
10
        self.earliest.into_inner()
473
10
    }
474

            
475
    /// Return the shortest `Duration` until any future `SystemTime` with which this has been compared
476
112
    pub fn shortest(self) -> Option<Duration> {
477
112
        self.earliest
478
112
            .into_inner()
479
121
            .map(|earliest| earliest.duration_since(self.now).unwrap_or(Duration::ZERO))
480
112
    }
481
}
482

            
483
impl TrackingInstantNow {
484
    /// Return the shortest `Duration` until any future `Instant` with which this has been compared
485
118
    pub fn shortest(self) -> Option<Duration> {
486
118
        self.earliest.into_inner()
487
118
    }
488
}
489

            
490
//----- manual update functions ----
491

            
492
impl Update<SystemTime> for TrackingSystemTimeNow {
493
6
    fn update(&self, t: SystemTime) {
494
6
        Self::update_unconditional(&self.earliest, t);
495
6
    }
496
}
497

            
498
impl Update<Instant> for TrackingInstantNow {
499
6
    fn update(&self, t: Instant) {
500
6
        self.update(t.checked_duration_since(self.now).unwrap_or_default());
501
6
    }
502
}
503

            
504
impl Update<Duration> for TrackingInstantNow {
505
10
    fn update(&self, d: Duration) {
506
10
        Self::update_unconditional(&self.earliest, d);
507
10
    }
508
}
509

            
510
impl Sealed for TrackingNow {}
511

            
512
impl Update<Duration> for TrackingNow {
513
2
    fn update(&self, d: Duration) {
514
2
        self.instant().update(d);
515
2
    }
516
}
517

            
518
//----- cmp and PartialOrd implementation ----
519

            
520
impl 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
48
    fn cmp(&self, t: SystemTime) -> std::cmp::Ordering {
525
48
        let o = self.now.cmp(&t);
526
48
        Self::update_conditional(o, &self.earliest, t);
527
48
        o
528
48
    }
529
}
530
define_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`.
536
1360
fn instant_cmp(earliest: &InstantEarliest, threshold: Instant, t: Instant) -> Ordering {
537
1360
    let Some(d) = t.checked_duration_since(threshold) else {
538
84
        return Ordering::Greater;
539
    };
540

            
541
1276
    let o = Duration::ZERO.cmp(&d);
542
1276
    TrackingInstantNow::update_conditional(o, earliest, d);
543
1276
    o
544
1360
}
545

            
546
impl 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
1302
    fn cmp(&self, t: Instant) -> std::cmp::Ordering {
551
1302
        instant_cmp(&self.earliest, self.now, t)
552
1302
    }
553
}
554
define_PartialOrd_via_cmp! { TrackingInstantNow, Instant, }
555

            
556
impl<'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
58
    fn cmp(&self, t: Instant) -> std::cmp::Ordering {
566
58
        instant_cmp(self.earliest, self.threshold, t)
567
58
    }
568
}
569
define_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

            
575
impl 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
22
    pub fn checked_sub(&self, offset: Duration) -> Option<TrackingInstantOffsetNow> {
586
22
        let threshold = self.now.checked_sub(offset)?;
587
22
        Some(TrackingInstantOffsetNow {
588
22
            threshold,
589
22
            earliest: &self.earliest,
590
22
        })
591
22
    }
592
}
593

            
594
impl 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
18
    pub fn checked_sub(&self, offset: Duration) -> Option<TrackingInstantOffsetNow> {
610
18
        self.instant.checked_sub(offset)
611
18
    }
612
}
613

            
614
#[cfg(test)]
615
mod 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
}