1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// 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;