111 lines
2.8 KiB
Rust
111 lines
2.8 KiB
Rust
//! The engine's clock — every timestamp the mem engine produces (unix
|
|
//! seconds stamps, visibility deadlines, TTLs, scheduler boundaries)
|
|
//! reads this one accessor ([`Clock::unix_now`]); the process clock is
|
|
//! all there is (engine-mem.md: no server clock to defer to).
|
|
//!
|
|
//! The single point exists so tests can observe timestamps
|
|
//! deterministically the way the SQL engines observe row shapes: a
|
|
//! `#[cfg(test)]` freeze/advance seam pins a wall value and every
|
|
//! accessor call returns it (see the unit pins below). Production
|
|
//! reads `std::time::SystemTime`.
|
|
|
|
#[cfg(test)]
|
|
use std::sync::Mutex;
|
|
|
|
#[cfg(test)]
|
|
use crate::state::Guard;
|
|
|
|
// Mechanism task code stamps every timestamp through this accessor —
|
|
// scaffolding reads as dead until the wave-6 mechanism tasks wire it.
|
|
#[allow(dead_code)]
|
|
fn process_unix_now() -> i64 {
|
|
std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.map(|since| since.as_secs() as i64)
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
/// The engine's single clock.
|
|
#[derive(Debug)]
|
|
pub(crate) struct Clock {
|
|
#[cfg(test)]
|
|
frozen: Mutex<Option<i64>>,
|
|
}
|
|
|
|
impl Default for Clock {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl Clock {
|
|
pub(crate) fn new() -> Self {
|
|
Self {
|
|
#[cfg(test)]
|
|
frozen: Mutex::new(None),
|
|
}
|
|
}
|
|
|
|
/// Unix-seconds "now" — the one timestamp source of the engine.
|
|
#[allow(dead_code)]
|
|
pub(crate) fn unix_now(&self) -> i64 {
|
|
#[cfg(test)]
|
|
let frozen = { *self.frozen.guard() };
|
|
#[cfg(test)]
|
|
if let Some(unix) = frozen {
|
|
return unix;
|
|
}
|
|
process_unix_now()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn freeze(&self, at_unix: i64) {
|
|
*self.frozen.guard() = Some(at_unix);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn advance(&self, seconds: i64) {
|
|
let mut guard = self.frozen.guard();
|
|
*guard = Some(frozen_now(&guard).saturating_add(seconds));
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn unfreeze(&self) {
|
|
*self.frozen.guard() = None;
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn frozen_now(frozen: &Option<i64>) -> i64 {
|
|
frozen.unwrap_or_else(process_unix_now)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn unfrozen_reads_a_plausible_process_time() {
|
|
let clock = Clock::new();
|
|
let now = clock.unix_now();
|
|
assert!(
|
|
now > 1_700_000_000,
|
|
"the accessor reads the process clock when unfrozen"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn frozen_reads_deterministically_and_advances() {
|
|
let clock = Clock::new();
|
|
clock.freeze(1_234_567);
|
|
assert_eq!(clock.unix_now(), 1_234_567);
|
|
clock.advance(10);
|
|
assert_eq!(clock.unix_now(), 1_234_577);
|
|
clock.unfreeze();
|
|
assert!(
|
|
clock.unix_now() > 1_700_000_000,
|
|
"unfreeze restores the process clock"
|
|
);
|
|
}
|
|
}
|