coordinode-lsm-tree 7.4.2

Embedded LSM-tree storage engine in pure Rust, no C/C++ dependency. MVCC snapshots, BuRR filters, zstd dictionary compression, columnar PAX blocks, AES-256-GCM at rest, self-healing per-block ECC, compaction on a near-full disk, no_std support.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024-present, fjall-rs
// Copyright (c) 2026-present, Dmitry Prudnikov

use core::time::Duration;

/// A source of wall-clock time.
///
/// The engine reads wall-clock time (for the `created_at` stamp on tables and
/// blob files, and for FIFO TTL expiry) through this trait. A tree reads the
/// clock its config names ([`Config::with_clock`](crate::Config::with_clock)),
/// and the process clock without one: under `std` the built-in
/// [`SystemClock`], under `no_std`, where there is no ambient system clock,
/// the one a consumer (e.g. a WASM host exposing `Date.now()`) registers once
/// via `set_clock` before opening a tree.
///
/// # Examples
///
/// ```
/// # use lsm_tree::Clock;
/// # use core::time::Duration;
/// struct FixedClock(Duration);
/// impl Clock for FixedClock {
///     fn unix_time(&self) -> Duration {
///         self.0
///     }
/// }
/// let clock = FixedClock(Duration::from_secs(1_700_000_000));
/// assert_eq!(clock.unix_time().as_secs(), 1_700_000_000);
/// ```
#[diagnostic::on_unimplemented(
    message = "`{Self}` is not a wall-clock source",
    label = "this type does not implement `Clock`",
    note = "implement `Clock` to inject a wall-clock under `no_std`, or enable the `std` feature to use the built-in `SystemClock`"
)]
pub trait Clock: Send + Sync {
    /// Wall-clock time elapsed since the Unix epoch.
    ///
    /// A clock with no real time source should return [`Duration::ZERO`] (the
    /// epoch), which disables TTL expiry rather than expiring everything.
    fn unix_time(&self) -> Duration;
}

/// The system wall-clock, backed by [`std::time::SystemTime`].
///
/// The default [`Clock`] under `feature = "std"`; consumers never need to
/// register it explicitly.
#[cfg(feature = "std")]
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;

#[cfg(feature = "std")]
impl Clock for SystemClock {
    fn unix_time(&self) -> Duration {
        #[expect(
            clippy::expect_used,
            reason = "the system clock predates the Unix epoch only on a grossly misconfigured host"
        )]
        std::time::SystemTime::now()
            .duration_since(std::time::SystemTime::UNIX_EPOCH)
            .expect("system time is before the Unix epoch")
    }
}

/// Gets the unix timestamp as a duration (wall-clock time since the epoch).
///
/// Reads through the active [`Clock`]: the built-in [`SystemClock`] under
/// `std`, or the caller-registered clock under `no_std` (see `set_clock`).
/// Until a `no_std` clock is registered the value is [`Duration::ZERO`]
/// (epoch), which disables TTL expiry rather than expiring everything.
pub fn unix_timestamp() -> Duration {
    #[cfg(test)]
    #[allow(clippy::significant_drop_in_scrutinee, clippy::expect_used)]
    {
        if let Some(cell) = NOW_OVERRIDE.get()
            && let Some(override_val) = *cell.lock().expect("lock is poisoned")
        {
            return override_val;
        }
    }

    #[cfg(feature = "std")]
    {
        SystemClock.unix_time()
    }

    #[cfg(not(feature = "std"))]
    {
        nostd_clock::now()
    }
}

/// The time on `clock`, a tree's own clock, or on the process clock
/// ([`unix_timestamp`]) when the tree has none.
pub fn now_on(clock: Option<&alloc::sync::Arc<dyn Clock>>) -> Duration {
    clock.map_or_else(unix_timestamp, |clock| clock.unix_time())
}

/// Monotonic instant used for elapsed-time logging on the compaction / flush
/// paths.
///
/// Under `std` this is a re-export of `std::time::Instant`. Under `no_std`
/// there is no ambient monotonic clock, so this is a zero-sized stub whose
/// `elapsed` always reports `core::time::Duration::ZERO`
/// — the timing logs degrade to `0ns` rather than failing to compile.
// no-std: wire a caller-provided monotonic Clock hook (mirroring the
// `unix_timestamp` wall-clock hook) if real elapsed timing is needed.
#[cfg(feature = "std")]
pub use std::time::Instant;

/// See the `std` variant — a no-op monotonic-instant stub for `no_std`.
#[cfg(not(feature = "std"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Instant;

#[cfg(not(feature = "std"))]
impl Instant {
    /// Returns the (stub) current instant.
    #[must_use]
    pub const fn now() -> Self {
        Self
    }

    /// Always [`core::time::Duration::ZERO`] under `no_std` (no monotonic clock).
    #[must_use]
    pub const fn elapsed(&self) -> core::time::Duration {
        core::time::Duration::ZERO
    }
}

/// Registers the [`Clock`] used by [`unix_timestamp`] under `no_std` (e.g. a
/// WASM host's `Date.now()`). Idempotent: the first registration wins, later
/// calls are ignored. Only present under `no_std`: under `std` the built-in
/// [`SystemClock`] is always available, so no registration is needed.
#[cfg(not(feature = "std"))]
pub fn set_clock(clock: alloc::boxed::Box<dyn Clock>) {
    nostd_clock::set(clock);
}

#[cfg(not(feature = "std"))]
mod nostd_clock {
    use super::Clock;
    use alloc::boxed::Box;
    use core::time::Duration;
    use once_cell::race::OnceBox;

    // Caller-injected wall-clock. Lock-free (atomic pointer), set once.
    static CLOCK: OnceBox<Box<dyn Clock>> = OnceBox::new();

    pub fn set(clock: Box<dyn Clock>) {
        let _ = CLOCK.set(Box::new(clock));
    }

    pub fn now() -> Duration {
        CLOCK
            .get()
            .map_or(Duration::ZERO, |clock| clock.unix_time())
    }
}

#[cfg(test)]
use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError};

#[cfg(test)]
static NOW_OVERRIDE: OnceLock<Mutex<Option<std::time::Duration>>> = OnceLock::new();

/// Held for a whole test that overrides the clock, so two such tests in one
/// process never see each other's override.
#[cfg(test)]
static CLOCK_OWNER: Mutex<()> = Mutex::new(());

#[cfg(test)]
#[allow(clippy::expect_used)]
fn set_unix_timestamp_for_test(value: Option<std::time::Duration>) {
    let cell = NOW_OVERRIDE.get_or_init(|| Mutex::new(None));
    *cell.lock().expect("lock is poisoned") = value;
}

/// Handle to the test clock override, lent by [`with_test_clock`].
#[cfg(test)]
pub struct TestClock {
    _lent: (),
}

#[cfg(test)]
impl TestClock {
    /// Pins [`unix_timestamp`] at `secs` since the epoch.
    #[expect(
        clippy::unused_self,
        reason = "the receiver proves the caller holds the clock"
    )]
    pub fn set_secs(&self, secs: u64) {
        set_unix_timestamp_for_test(Some(Duration::from_secs(secs)));
    }
}

/// The clock lock, held for one [`with_test_clock`] call; dropping it clears
/// the override.
#[cfg(test)]
struct HeldClock {
    _owner: MutexGuard<'static, ()>,
}

#[cfg(test)]
impl Drop for HeldClock {
    fn drop(&mut self) {
        set_unix_timestamp_for_test(None);
    }
}

/// Runs `f` with exclusive use of the test clock override.
///
/// The override is process-wide, and `cargo test` runs a crate's tests as
/// threads of one process, so every test that sets it, and every test whose
/// result depends on the clock it reads, runs inside this call: other clock
/// tests wait until it returns, and the override starts and ends unset, even
/// when `f` returns early or panics. A test that only reads leaves it unset
/// and sees the real clock.
#[cfg(test)]
pub fn with_test_clock<R>(f: impl FnOnce(&TestClock) -> R) -> R {
    // A test that panicked while holding the clock poisons the lock; the
    // override it left is cleared below, so the lock is still usable.
    let held = HeldClock {
        _owner: CLOCK_OWNER.lock().unwrap_or_else(PoisonError::into_inner),
    };
    set_unix_timestamp_for_test(None);
    let result = f(&TestClock { _lent: () });
    drop(held);
    result
}

#[cfg(test)]
mod tests;