Files
alkstore/alkstore-mem/src/clock.rs
T
glm-5.3-flash 6632dcce1f mem-engine-foundations — the mem engine's foundations land (alkstore-mem, wave 6 task 1 of the ADR-024 chain): fourth workspace member with the manifest surface ADR-024 §1 pins — alkstore versioned path pin (ADR-017 §4.1) plus tokio restricted to the wasm-supported subset (sync + time; dev-deps rt/macros for the current-thread-flavor tests), no driver deps, no parking_lot (the description's prose named it, but the ADR + acceptance criterion pin the manifest tighter — short-held internal guards are std::sync::Mutex behind a state::Guard trait that folds poisoning away structurally, guards never held across awaits or user code, tokio mpsc the cross-await primitive). Five modules re-exported from lib.rs whose crate docs carry the ADR-016 posture statement (honest-ephemeral, full contract v1, wasm32-compile-clean identity, architecture sketch with the wave-6 construction line). MemStore::new() is the engine-native isolated constructor (ADR-024 §4's documented open-prose exception): fresh guarded state core (state.rs — empty named per-mechanism sections QueueState/StreamState/LockState/ScheduleState, shapes owned by the mechanism tasks), fresh wake bus, fresh clock per open; teardown is drop driving WakeBus::close() — no explicit close form. Wake substrate (wakes.rs): WakeBus::attach → WakeFeed (one unbounded mpsc feed per subscriber, detach-on-drop, attach under the registry guard so attach-after-commit no-replay is structural), fire_commit_wakes(channels) as the commit-ordered wake source's entry point (guard makes watermark bump + per-channel fanout one operation — per-subscriber FIFO by commit order, never coalesced), close empties senders so engine drop disconnects every feed; commit_watermark is a cfg(test) ordering observable only. Clock (clock.rs): one accessor Clock::unix_now over SystemTime, freeze/advance/unfreeze as the cfg(test) deterministic-seam; MemStore clock/wakes cfg(test) accessors. Validation (validation.rs) carries no new rule — pin module only (empty/whitespace InvalidName, prefix ReservedName, RESERVED_LISTENER_RECONNECTED rejected though unused here); mechanisms route core's validate_shared_name/local_name directly. Targeted allow(dead_code) on not-yet-wired scaffold surfaces so clippy -D warnings stays green pre-consumer (mechanism tasks own exhausting the allows); tokio time pinned unexercised (lock-TTL/scheduler consumers arrive with their tasks). Unit pins: 17 mem tests — two-instances-share-nothing (Arc pointer identity + behavioral: frozen clock isolation, cross-instance wake silence), state section independence/relock, wake routing (channel-scoped + foreign silence, per-notify never coalesced, watermark-ordered FIFO, no-replay, close disconnects all + post-close inert, dropped feed detaches), engine-drop closes feeds, clock determinism, validation pin row. Task file: status completed, Notes (decisions of record: parking_lot rejection, empty sections, substrate API shape, no-close, scaffold allow posture, unexercised time feature) + Summary filled. Verified: cargo build; workspace cargo test green (pg 124/25/9, sqlite 191/25, core 25, suite 3, mem 17); clippy --all-targets -D warnings clean; cargo fmt --check clean; cargo check --target wasm32-unknown-unknown -p alkstore-mem clean — the wave-6 wasm gate starts green from this task onward
2026-10-10 11:27:52 +00:00

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"
);
}
}