Expand description
Utilities to track and compare times and timeouts
Contains TrackingNow
, and variants.
Each one records the current time,
and can be used to see if prospective timeouts have expired yet,
via the PartialOrd
implementations.
Each can be compared with a prospective wakeup time via a .cmp()
method,
and via implementations of PartialOrd
(including via <
operators etc.)
Each tracks every such comparison, and can yield the earliest unexpired timeout that was asked about.
I.e., the timeout tracker tells you when (in the future) any of the comparisons you have made, might produce different answers. So, that can be used to know how long to sleep for when waiting for timeout(s).
This approach means you must be sure to actually perform the timeout action whenever a comparison tells you the relevant period has elapsed. If you fail to do so, the timeout tracker will still disregard the event for the purposes of calculating how to wait, since it is in the past. So if you use the timeout tracker to decide how long to sleep, you won’t be woken up until something else occurs. (When the timeout has exactly elapsed, you should eagerly perform the action. Otherwise the timeout tracker will calculate a zero timeout and you’ll spin.)
Each tracker has interior mutability,
which is necessary because PartialOrd
(<=
etc.) only passes immutable references.
Most are Send
, none are Sync
,
so use in thread-safe async code is somewhat restricted.
(Recommended use is to do all work influencing timeout calculations synchronously;
otherwise, in any case, you risk the time advancing mid-calculations.)
Clone
gives you a copy, not a handle onto the same tracker.
Comparisons done with the clone do not update the original.
(Exception: TrackingInstantOffsetNow::clone
.)
The types are:
TrackingNow
: tracks timeouts based on bothSystemTime
andInstant
,TrackingSystemTimeNow
: tracks timeouts based onSystemTime
TrackingInstantNow
: tracks timeouts based onInstant
TrackingInstantOffsetNow
:InstantTrackingNow
but with an offset applied
§Advantages, disadvantages, and alternatives
Using TrackingNow
allows time-dependent code to be written
in a natural, imperative, style,
even if the timeout calculations are complex.
Simply test whether it is time yet to do each thing, and if so do it.
This can conveniently be combined with an idempotent imperative style
for handling non-time-based inputs:
for each possible action, you can decide in one place whether it needs doing.
(Use a select_biased!
, with wait_for_earliest
as one of the branches.)
This approach makes it harder to write bugs where some of the consequences of events are forgotten. Each timeout calculation is always done afresh from all its inputs. There is only ever one place where each action is considered, and the consideration is always redone from first principles.
However, this is not necessarily the most performant approach. Each iteration of the event loop doesn’t know why it has woken up, so must simply re-test all of the things that might need to be done.
When higher performance is needed, consider maintaining timeouts as state:
either as wakeup times or durations, or as actual Future
s,
depending on how frequently they are going to occur,
and how much they need to be modified.
With this approach you must remember to update or recalculate the timeout,
on every change to any of the inputs to each timeout calculation.
You must write code to check (or perform) each action,
in the handler for each event that might trigger it.
Omitting a call is easy,
can result in mysterious ordering-dependent “stuckess” bugs,
and is often detectable only by very comprehensive testing.
§Example
use std::sync::{Arc, Mutex};
use std::time::Duration;
use futures::task::SpawnExt as _;
use tor_rtcompat::{ToplevelBlockOn as _, SleepProvider as _};
use crate::timeout_track;
use timeout_track::TrackingInstantNow;
// Test harness
let runtime = tor_rtmock::MockRuntime::new();
let actions = Arc::new(Mutex::new("".to_string())); // initial letters of performed actions
let perform_action = {
let actions = actions.clone();
move |s: &str| actions.lock().unwrap().extend(s.chars().take(1))
};
runtime.spawn({
let runtime = runtime.clone();
// Example program which models cooking a stir-fry
async move {
perform_action("add ingredients");
let started = runtime.now();
let mut last_stirred = started;
loop {
let now_track = TrackingInstantNow::now(&runtime);
const STIR_EVERY: Duration = Duration::from_secs(25);
// In production, we might avoid panics: .. >= last_stirred.checked_add(..)
if now_track >= last_stirred + STIR_EVERY {
perform_action("stir");
last_stirred = now_track.get_now_untracked();
continue;
}
const COOK_FOR: Duration = Duration::from_secs(3 * 60);
if now_track >= started + COOK_FOR {
break;
}
now_track.wait_for_earliest(&runtime).await;
}
perform_action("dish up");
}
}).unwrap();
// Do a test run
runtime.block_on(async {
runtime.advance_by(Duration::from_secs(1 * 60)).await;
assert_eq!(*actions.lock().unwrap(), "ass");
runtime.advance_by(Duration::from_secs(2 * 60)).await;
assert_eq!(*actions.lock().unwrap(), "asssssssd");
});
Modules§
- sealed 🔒
- Sealed
Macros§
- define_
Partial 🔒Ord_ via_ cmp impl PartialOrd<$NOW> for $ttype
in terms of...$field.cmp()
- derive_
deftly_ 🔒template_ Combined Timeout Tracker - Impls for
TrackingNow
, the combined tracker - derive_
deftly_ 🔒template_ Single Timeout Tracker - Defines methods and types which are common to trackers for
Instant
andSystemTime
- derive_
deftly_ 🔒template_ Wait ForEarliest - Defines
wait_for_earliest
Structs§
- Tracking
Instant Now - Utility to track timeouts based on
Instant
(monotonic time) - Tracking
Instant Offset Now - Current minus an offset, for
Instant
-based timeout checks - Tracking
Now - Timeout tracker that can handle both
Instant
s andSystemTime
s - Tracking
System Time Now - Utility to track timeouts based on
SystemTime
(wall clock time)
Traits§
- Update
- Trait providing the
update
method on timeout trackers
Functions§
- instant_
cmp 🔒 - Check
t
against a now-basedthreshold
(and remember for wakeup)
Type Aliases§
- Instant
Earliest 🔒 - Earliest timeout at which an
Instant
based timeout should occur, as duration from now