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:
1 parent
6f9cfb5778
commit
6632dcce1f
10 files changed
+938
-8
No files matched your search
@@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user