minerva 0.2.0

Causal ordering for distributed systems
//! Production `TimeSource` implementations backed by the operating system.
//!
//! `chronos` is the adapter layer for real, std-backed physical time. The
//! `kairos` core owns the [`TimeSource`] seam and
//! depends only on that trait; this module plugs concrete OS sources into it.
//! The whole module requires the `std` feature. The `no_std` core never enables
//! it, and the deterministic [`TickCounter`](crate::kairos::TickCounter) stays in
//! `kairos` precisely because it is `no_std`.
//!
//! # Choosing a source
//!
//! [`SystemTimeSource`] reads the wall clock
//! (nanoseconds since the Unix epoch). It is the right default for a
//! *distributed* clock: wall time is a shared frame of reference across
//! machines, so the `physical` component of a `Kairos` keeps its cross-node
//! meaning and [`Clock::after`](crate::kairos::Clock::after) can fold a remote
//! stamp in against a comparable scale. The wall clock can step backward: an
//! NTP correction, or an operator setting the clock. That is safe here. The
//! HLC never emits a decreasing stamp, because it takes the max of its last
//! physical and the reading. It records the regression as backward skew rather
//! than correcting it silently.
//!
//! [`MonotonicTimeSource`] reads a
//! per-process monotonic clock (nanoseconds since construction). It never steps
//! backward, but its zero point is process-local,
//! so its readings are *not* comparable across machines. Prefer it for
//! single-node ordering or for measuring elapsed time, not as the physical
//! component of a clock whose stamps cross a network.

extern crate std;

use crate::kairos::TimeSource;
use std::time::{Instant, SystemTime, UNIX_EPOCH};

/// Saturating nanoseconds-to-`u64`. A `u64` of nanoseconds spans ~584 years;
/// past that both sources pin at the ceiling rather than wrapping, so a physical
/// reading never jumps backward on overflow.
fn saturating_nanos(nanos: u128) -> u64 {
    u64::try_from(nanos).unwrap_or(u64::MAX)
}

/// Wall-clock [`TimeSource`]: nanoseconds since the Unix epoch.
///
/// The cross-node-comparable default for a distributed HLC. See the module docs
/// for why wall time, not a monotonic clock, is the right physical component when
/// stamps travel between machines.
#[derive(Debug, Default, Clone, Copy)]
pub struct SystemTimeSource;

impl SystemTimeSource {
    /// Creates a wall-clock time source.
    #[must_use]
    pub const fn new() -> Self {
        Self
    }
}

impl TimeSource for SystemTimeSource {
    /// Nanoseconds since the Unix epoch, saturating at the `u64` ceiling. A clock
    /// set before 1970 (so the reading would precede the epoch) reads as `0`
    /// rather than failing; the HLC then treats it as a very old reading and its
    /// own last-physical floor carries ordering forward.
    fn now(&self) -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_or(0, |elapsed| saturating_nanos(elapsed.as_nanos()))
    }
}

/// Per-process monotonic [`TimeSource`]: nanoseconds since this source was
/// constructed.
///
/// Never steps backward, but its zero point is process-local, so readings are not
/// comparable across machines. See the module docs for when to prefer this over
/// [`SystemTimeSource`].
#[derive(Debug, Clone, Copy)]
pub struct MonotonicTimeSource {
    base: Instant,
}

impl MonotonicTimeSource {
    /// Creates a monotonic source whose zero point is the moment of this call.
    #[must_use]
    pub fn new() -> Self {
        Self {
            base: Instant::now(),
        }
    }
}

impl Default for MonotonicTimeSource {
    fn default() -> Self {
        Self::new()
    }
}

impl TimeSource for MonotonicTimeSource {
    /// Nanoseconds since construction, saturating at the `u64` ceiling. The
    /// zero point resets on every process restart, so a clock driven by this
    /// source regresses across restarts; persist and re-observe the last
    /// stamp if ordering must survive one.
    fn now(&self) -> u64 {
        saturating_nanos(self.base.elapsed().as_nanos())
    }
}

#[cfg(test)]
mod tests;