use std::fmt::Debug;
#[cfg(test)]
use std::sync::Arc;
#[cfg(test)]
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
pub trait MonotonicClock: Debug + Send + Sync {
fn now_nanos(&self) -> u64;
}
#[derive(Debug, Clone, Copy)]
pub struct SystemClock {
epoch: Instant,
}
impl SystemClock {
#[must_use]
pub fn new() -> Self {
Self {
epoch: Instant::now(),
}
}
}
impl Default for SystemClock {
fn default() -> Self {
Self::new()
}
}
impl MonotonicClock for SystemClock {
#[inline]
fn now_nanos(&self) -> u64 {
u64::try_from(self.epoch.elapsed().as_nanos()).unwrap_or(u64::MAX)
}
}
#[derive(Debug, Default)]
#[cfg(test)]
pub struct ManualClock {
nanos: AtomicU64,
}
#[cfg(test)]
impl ManualClock {
#[must_use]
pub fn new(nanos: u64) -> Self {
Self {
nanos: AtomicU64::new(nanos),
}
}
pub fn advance(&self, nanos: u64) {
let _ = self.nanos.fetch_add(nanos, Ordering::Relaxed);
}
}
#[cfg(test)]
impl MonotonicClock for ManualClock {
#[inline]
fn now_nanos(&self) -> u64 {
self.nanos.load(Ordering::Relaxed)
}
}
#[derive(Debug, Clone)]
pub enum AdmissionClock {
System(SystemClock),
#[cfg(test)]
Manual(Arc<ManualClock>),
}
impl AdmissionClock {
#[must_use]
pub fn system() -> Self {
Self::System(SystemClock::new())
}
#[inline]
#[must_use]
pub fn now_nanos(&self) -> u64 {
match self {
Self::System(clock) => clock.now_nanos(),
#[cfg(test)]
Self::Manual(clock) => clock.now_nanos(),
}
}
}
impl Default for AdmissionClock {
fn default() -> Self {
Self::system()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manual_clock_advances_only_when_told() {
let clock = ManualClock::new(0);
assert_eq!(clock.now_nanos(), 0);
clock.advance(1_500);
assert_eq!(clock.now_nanos(), 1_500);
clock.advance(500);
assert_eq!(clock.now_nanos(), 2_000);
}
#[test]
fn manual_clock_readings_are_shared_across_threads() {
let clock = Arc::new(ManualClock::new(42));
let observed: Vec<u64> = std::thread::scope(|scope| {
let handles: Vec<_> = (0..4)
.map(|_| {
let clock = Arc::clone(&clock);
scope.spawn(move || clock.now_nanos())
})
.collect();
handles
.into_iter()
.map(|handle| handle.join().expect("clock reader thread"))
.collect()
});
assert_eq!(observed, vec![42; 4]);
}
#[test]
fn system_clock_is_monotonic() {
let clock = AdmissionClock::system();
let first = clock.now_nanos();
let second = clock.now_nanos();
assert!(second >= first, "{second} < {first}");
}
}