Expand description
§Pausable Clock
This crate provides a clock that can be paused … (duh?). The provided
struct PausableClock
allows you to get the current time in a way that
respects the atomic state and history of the clock. Put more simply, a
pausable clock’s elapsed time increases at the same as real time but only
when the clock is resumed.
§Features
- Thread-Safe: (
Send
/Sync
) All operations on the clock are atomic or use std mutexes - Resume Notification: the
wait_for_resume
method will block until the clock is resumed (if the clock is paused) - Resume Notification: the
wait_for_pause
method will block until the clock is paused (if the clock is running) - Guarantees: Just like
std::time::Instant::now()
guarantees that time always increases,PausableClock
guarantees that the time returned byclock.now()
while the clock is paused is >= any other instant returned before the clock was paused. - Unpausable Tasks: We provide methods called
run_unpausable
andrun_if_resumed
that allow tasks to be run that can prevent the clock from being paused while they are still running. - Unresumable Tasks: We provide a methods
run_unresumable
andrun_if_paused
that allow tasks to be run that can prevent the clock from being resumed while they are still running. - There is a significant amount of weakly-ordered atomic operation going on
in this library to make sure the calls to now and unpausable task don’t
require any locks. I can’t claim that it is provably correct, but it has
been tested to high degree of certainty on x86_64 processors. Tests on
weakly ordered systems are forthcoming as are
loom
-based tests.
§Example
let clock = Arc::new(PausableClock::default());
// With the default parameters, there should be no difference
// between the real time and the clock's time
assert!(Instant::from(clock.now()).elapsed().as_millis() == 0);
// Pause the clock right after creation
clock.pause();
// Clone the arc of the clock to pass to a new thread
let clock_clone = clock.clone();
let t = thread::spawn(move || {
// In the new thread, just wait for resume
clock_clone.wait_for_resume();
});
// Sleep for a sec, then resume the clock
let sleep_start = Instant::now();
thread::sleep(Duration::from_secs(1));
let slept = sleep_start.elapsed().as_secs_f64();
clock.resume();
// Wait for the spawned thread to unblock
t.join().unwrap();
// After being paused for a second, the clock is now a second behind
assert!((Instant::from(clock.now()).elapsed().as_secs_f64() - slept).abs() <= 0.001);
§Caveats
- We use an
AtomicU64
to contain the entire state of the pausable clock, so the granularity of the instant’s produced by the clock is milliseconds. This means the maximum time the timer can handle is on the order of hundreds of thousands of years. - Reads of the pause state for
PausableClock::is_paused
is done atomically withOrdering::Relaxed
. That allows the call to be slightly faster, but it means you shouldn’t think it as fencing a operations
Structs§
- Pausable
Clock - Source of time information that can be paused and resumed. At its heart it is a reference instant in real time, a record of elapsed time, and an atomic state stored in a u64
- Pausable
Instant - Native representation of a paused instant that contains the reference time and the elapsed time on the clock from the reference time.