use std::sync::Mutex;
use std::time::Duration;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Instant(u64);
impl Instant {
#[must_use]
pub fn saturating_duration_since(self, earlier: Instant) -> Duration {
Duration::from_nanos(self.0.saturating_sub(earlier.0))
}
#[must_use]
pub fn saturating_add(self, delta: Duration) -> Instant {
let nanos = u64::try_from(delta.as_nanos()).unwrap_or(u64::MAX);
Instant(self.0.saturating_add(nanos))
}
}
pub trait Clock: Send + Sync {
fn now(&self) -> Instant;
fn unix_nanos(&self) -> u64;
}
#[derive(Clone, Copy, Default, Debug)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> Instant {
static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
#[allow(
clippy::disallowed_methods,
reason = "the one sanctioned site reading the OS monotonic clock (docs/12)"
)]
let (raw, epoch) = (
std::time::Instant::now(),
*EPOCH.get_or_init(std::time::Instant::now),
);
Instant(u64::try_from(raw.saturating_duration_since(epoch).as_nanos()).unwrap_or(u64::MAX))
}
fn unix_nanos(&self) -> u64 {
#[allow(
clippy::disallowed_methods,
reason = "the one sanctioned site reading the OS wall clock (docs/12)"
)]
let since_epoch = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH);
since_epoch.map_or(0, |d| u64::try_from(d.as_nanos()).unwrap_or(u64::MAX))
}
}
#[derive(Debug, Default)]
pub struct ManualClock {
nanos: Mutex<u64>,
}
impl ManualClock {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn advance(&self, delta: Duration) {
let add = u64::try_from(delta.as_nanos()).unwrap_or(u64::MAX);
if let Ok(mut nanos) = self.nanos.lock() {
*nanos = nanos.saturating_add(add);
}
}
}
impl Clock for ManualClock {
fn now(&self) -> Instant {
Instant(self.nanos.lock().map_or(0, |n| *n))
}
fn unix_nanos(&self) -> u64 {
self.nanos.lock().map_or(0, |n| *n)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manual_clock_is_frozen_until_advanced() {
let clock = ManualClock::new();
let t0 = clock.now();
assert_eq!(clock.now(), t0, "clock must not advance on its own");
clock.advance(Duration::from_millis(250));
let t1 = clock.now();
assert_eq!(t1.saturating_duration_since(t0), Duration::from_millis(250));
}
#[test]
fn instant_arithmetic_saturates_and_does_not_panic() {
let clock = ManualClock::new();
let t0 = clock.now();
let later = t0.saturating_add(Duration::from_secs(5));
assert_eq!(later.saturating_duration_since(t0), Duration::from_secs(5));
assert_eq!(t0.saturating_duration_since(later), Duration::ZERO);
}
#[test]
fn system_clock_is_monotonic() {
let clock = SystemClock;
let a = clock.now();
let b = clock.now();
assert!(b >= a, "monotonic clock must not go backwards");
}
}