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

This commit is contained in:
glm-5.3-flash committed 2026-10-10 11:27:52 +00:00
1 parent 6f9cfb5778
commit 6632dcce1f
10 files changed
+938 -8

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
//! The guarded per-mechanism state core — the store instance *is* the
//! state (engine-mem.md §Architecture): per-mechanism tables owned by
//! the engine instance behind the family's locking discipline
//! (short-held synchronous internal locks; tokio primitives for
//! cross-await coordination — [`crate::wakes`]' channels and the
//! scheduler's boundary waits arrive with their tasks).
//!
//! The idiom: each mechanism's committed state sits behind one
//! short-held [`std::sync::Mutex`] section, accessed through
//! [`Guard::guard`]. Guards are never held across an `.await`, never
//! across user-provided code (delivery closures, payload callbacks) —
//! staging happens engine-side, so the mutex cannot observe a panic
//! while held and poisoning is structurally unreachable; the
//! [`Guard::guard`] fold into the inner value keeps the type honest
//! anyway.
//!
//! This module owns the shared scaffolding only: the container, the
//! section shapes' names, and the guard idiom. Each mechanism's table
//! shape is owned by its own wave-6 task (`mem-engine-queues`,
//! `mem-engine-streams`, `mem-engine-locks`,
//! `mem-engine-scheduler-outbox`) — the sections here start empty and
//! grow there.
use std::sync::Mutex;
use std::sync::MutexGuard;
/// A short-held synchronous internal lock: `guard()` is the one access
/// idiom (see the module docs for the discipline).
pub(crate) trait Guard<T> {
fn guard(&self) -> MutexGuard<'_, T>;
}
impl<T> Guard<T> for Mutex<T> {
fn guard(&self) -> MutexGuard<'_, T> {
self.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
/// The queue mechanism's committed section: the jobs table and the
/// dead-letter storage (`mem-engine-queues` owns the shapes).
#[derive(Debug, Default)]
pub(crate) struct QueueState {}
/// The stream mechanism's committed section: event logs and
/// per-consumer offsets (`mem-engine-streams` owns the shapes).
#[derive(Debug, Default)]
pub(crate) struct StreamState {}
/// The named-locks mechanism's committed section: the TTL registry
/// (`mem-engine-locks` owns the shapes).
#[derive(Debug, Default)]
pub(crate) struct LockState {}
/// The scheduler mechanism's committed section: the schedule table
/// (`mem-engine-scheduler-outbox` owns the shapes; the outbox rides
/// queues).
#[derive(Debug, Default)]
pub(crate) struct ScheduleState {}
/// The store's committed state: one guarded section per mechanism.
/// Sections are independent — a mechanism task locks only its own.
// The sections are read by their owning wave-6 mechanism tasks; the
// foundations build alone sees them as dead.
#[allow(dead_code)]
pub(crate) struct State {
pub(crate) queues: Mutex<QueueState>,
pub(crate) streams: Mutex<StreamState>,
pub(crate) locks: Mutex<LockState>,
pub(crate) schedules: Mutex<ScheduleState>,
}
impl State {
pub(crate) fn new() -> Self {
Self {
queues: Mutex::new(QueueState::default()),
streams: Mutex::new(StreamState::default()),
locks: Mutex::new(LockState::default()),
schedules: Mutex::new(ScheduleState::default()),
}
}
}
impl std::fmt::Debug for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("State").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sections_are_per_mechanism_and_independent() {
let state = State::new();
let queued_ptr = {
let mut queues = state.queues.guard();
std::ptr::from_mut(&mut *queues)
};
let streams_ptr = {
let mut streams = state.streams.guard();
std::ptr::from_mut(&mut *streams)
};
let queues_ptr_again = {
let mut queues = state.queues.guard();
std::ptr::from_mut(&mut *queues)
};
assert_eq!(queued_ptr, queues_ptr_again, "the same section relocks");
assert_ne!(
queued_ptr.cast::<()>(),
streams_ptr.cast::<()>(),
"sections are distinct storage"
);
}
#[test]
fn sections_relock_after_guard_drop() {
let state = std::sync::Arc::new(State::new());
std::thread::scope(|scope| {
let held = scope.spawn(|| {
for _ in 0..100 {
let _guard = state.streams.guard();
std::thread::yield_now();
}
});
for _ in 0..100 {
let _guard = state.queues.guard();
std::thread::yield_now();
}
held.join().expect("the lock churn task never panics");
});
}
}