use crate::store::StoreError;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, OnceLock};
use std::time::Instant;
pub trait Clock: Send + Sync {
fn now_us(&self) -> i64;
fn now_wall_ns(&self) -> i64;
fn now_mono_ns(&self) -> i64;
fn process_boot_ns(&self) -> u64;
}
fn system_now_us() -> i64 {
let micros = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros();
i64::try_from(micros).unwrap_or(i64::MAX)
}
pub(crate) fn wall_ms_from_timestamp_us(timestamp_us: i64) -> Result<u64, StoreError> {
if timestamp_us < 0 {
return Err(StoreError::InvalidClock {
timestamp_us,
reason: "timestamp_us must be >= 0 microseconds since Unix epoch".into(),
});
}
Ok((timestamp_us / 1000).cast_unsigned())
}
fn system_now_wall_ns_saturating() -> i64 {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
i64::try_from(nanos).unwrap_or(i64::MAX)
}
pub(crate) struct MonotonicAnchor {
anchor_instant: Instant,
anchor_boot_ns: u64,
}
impl MonotonicAnchor {
fn get() -> &'static Self {
static ANCHOR: OnceLock<MonotonicAnchor> = OnceLock::new();
ANCHOR.get_or_init(|| {
let wall_ns = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let anchor_boot_ns = u64::try_from(wall_ns).unwrap_or(u64::MAX);
MonotonicAnchor {
anchor_instant: Instant::now(),
anchor_boot_ns,
}
})
}
fn now_mono_ns(&self) -> i64 {
let elapsed = self.anchor_instant.elapsed().as_nanos();
i64::try_from(elapsed).unwrap_or(i64::MAX)
}
}
#[derive(Clone)]
pub struct SystemClock {
anchor: &'static MonotonicAnchor,
}
impl SystemClock {
pub fn new() -> Self {
Self {
anchor: MonotonicAnchor::get(),
}
}
}
impl Default for SystemClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for SystemClock {
fn now_us(&self) -> i64 {
system_now_us()
}
fn now_wall_ns(&self) -> i64 {
system_now_wall_ns_saturating()
}
fn now_mono_ns(&self) -> i64 {
self.anchor.now_mono_ns()
}
fn process_boot_ns(&self) -> u64 {
self.anchor.anchor_boot_ns
}
}
struct FnClock {
inner: Arc<dyn Fn() -> i64 + Send + Sync>,
anchor: &'static MonotonicAnchor,
}
impl FnClock {
fn new(inner: Arc<dyn Fn() -> i64 + Send + Sync>) -> Self {
Self {
inner,
anchor: MonotonicAnchor::get(),
}
}
}
impl Clock for FnClock {
fn now_us(&self) -> i64 {
(self.inner)()
}
fn now_wall_ns(&self) -> i64 {
self.now_us().saturating_mul(1000)
}
fn now_mono_ns(&self) -> i64 {
self.anchor.now_mono_ns()
}
fn process_boot_ns(&self) -> u64 {
self.anchor.anchor_boot_ns
}
}
pub(crate) fn clock_from_fn(inner: Arc<dyn Fn() -> i64 + Send + Sync>) -> Arc<dyn Clock> {
Arc::new(FnClock::new(inner))
}
#[derive(Clone)]
pub(crate) struct MonotonicClock {
inner: Arc<dyn Clock>,
last: Arc<AtomicI64>,
}
impl MonotonicClock {
pub(crate) fn wrap(inner: Arc<dyn Clock>) -> Self {
Self {
inner,
last: Arc::new(AtomicI64::new(i64::MIN)),
}
}
pub(crate) fn now_us(&self) -> i64 {
let raw = self.inner.now_us();
loop {
let prev = self.last.load(Ordering::Acquire);
if raw >= prev {
match self
.last
.compare_exchange(prev, raw, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => return raw,
Err(_) => continue, }
} else {
tracing::error!("user clock regressed: prev={} new={}", prev, raw);
return prev;
}
}
}
}
impl Clock for MonotonicClock {
fn now_us(&self) -> i64 {
MonotonicClock::now_us(self)
}
fn now_wall_ns(&self) -> i64 {
self.inner.now_wall_ns()
}
fn now_mono_ns(&self) -> i64 {
self.inner.now_mono_ns()
}
fn process_boot_ns(&self) -> u64 {
self.inner.process_boot_ns()
}
}
#[cfg(test)]
mod tests {
use super::{Clock, FnClock};
use std::sync::Arc;
#[test]
fn fn_clock_preserves_negative_wall_values_but_not_monotonic_time() {
let clock = FnClock::new(Arc::new(|| -7));
assert_eq!(
clock.now_us(),
-7,
"PROPERTY: FnClock must expose malformed caller wall time for validation"
);
assert_eq!(
clock.now_wall_ns(),
-7_000,
"PROPERTY: wall nanoseconds come from the caller wall clock, not the monotonic anchor"
);
assert!(
clock.now_mono_ns() >= 0,
"PROPERTY: process-local monotonic evidence must not echo a negative caller wall clock"
);
}
}