feat(safety): phase 5 — loom model checking + invariant audit/assertion sweep
State machine extracted to src/slot_state.rs as a standalone unit (StateWord: publish_queued / try_claim / yield_return / park_return / unpark / set_done / reclaim, plus the Status view for cold paths). runtime.rs keeps the protocol rationale and consumes the mechanism; every transition self-asserts its precondition per the assert-the-invariants rule. src/sync_shim.rs: std vs loom indirection (atomics + UnsafeCell with the with/with_mut access API) for the two loom-modeled modules. Loom models run the PRODUCTION code, not a replica: - slot_state: no-lost-wakeup (park vs unpark), two-unparkers-one-enqueue, stale-unpark-never-hits-reused-slot (the ABA theorem), unpark-vs-claim. - run_queue: mpmc exactly-once through a lap wraparound, push/pop race, striped two-producer drain. RUSTFLAGS="--cfg loom" cargo test --lib --release — 7 models, all pass. RawMutex deliberately not loom-modeled (futexes can't be; textbook mutex3 with stress + unwind coverage). Assertion sweep (invariants now checked at the point of reliance): - enqueue debug-asserts the word reads EXACTLY (gen, Queued) — the at-most-once-enqueued invariant the ring capacity proof leans on. - RawMutex enforces the leaf rule mechanically: debug-build thread-local held-count, panics at the acquisition that violates it. - live_actors underflow (double finalize) asserted. - StateWord transition preconditions asserted (yield/park/done/reclaim/ publish/claim). Audit: with_runtime, RawMutex guards, run-queue ops, and trace::record all gate preemption (and thereby the stop sentinel) for their span; trace was already self-gating. Sole remaining exception is the channel std MutexGuard — the documented first post-v0.5 fast-follow. Review fixes to the branched-in work: - slot_state::status_for mapped (matching gen, Vacant) to Stale instead of unreachable!: Pid::new is public, so a forged/never-issued pid (e.g. monitor(Pid::new(5,0)) on a fresh slab) could reach it — "no such actor" is the correct total answer; issued pids still can't get there. - Tightened enqueue's assert from Status::Live to the exact (gen, Queued) word its own comment argues for. Validated: 22 suites in debug (all asserts + leaf counter live) for rq-mutex/rq-mpmc/rq-striped, release for rq-mutex, smarm-trace build + stress, loom 7/7.
This commit is contained in:
@@ -45,6 +45,35 @@ const CONTENDED: u32 = 2;
|
||||
/// sender), so a short spin almost always avoids the syscall.
|
||||
const SPIN_LIMIT: u32 = 64;
|
||||
|
||||
// The leaf rule, mechanically enforced (debug builds): slot cold locks, the
|
||||
// free list, and the stack pool — i.e. every RawMutex — are mutual leaves.
|
||||
// Holding two at once is a deadlock waiting for the right interleaving, so
|
||||
// fail at the acquisition that violates it, not in the eventual hang.
|
||||
#[cfg(debug_assertions)]
|
||||
thread_local! {
|
||||
static RAW_MUTEXES_HELD: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn leaf_check_acquire() {
|
||||
#[cfg(debug_assertions)]
|
||||
RAW_MUTEXES_HELD.with(|c| {
|
||||
debug_assert_eq!(
|
||||
c.get(),
|
||||
0,
|
||||
"leaf rule violated: acquiring a RawMutex while already holding one \
|
||||
(cold locks / free list / stack pool are mutual leaves)"
|
||||
);
|
||||
c.set(c.get() + 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn leaf_check_release() {
|
||||
#[cfg(debug_assertions)]
|
||||
RAW_MUTEXES_HELD.with(|c| c.set(c.get() - 1));
|
||||
}
|
||||
|
||||
pub(crate) struct RawMutex<T> {
|
||||
state: AtomicU32,
|
||||
data: UnsafeCell<T>,
|
||||
@@ -69,6 +98,7 @@ impl<T> RawMutex<T> {
|
||||
// Enter NoPreempt *before* acquiring, so a preemption can't fire
|
||||
// between acquisition and guard construction.
|
||||
let prev_preempt = crate::preempt::PREEMPTION_ENABLED.with(|c| c.replace(false));
|
||||
leaf_check_acquire();
|
||||
if self
|
||||
.state
|
||||
.compare_exchange(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed)
|
||||
@@ -142,6 +172,7 @@ impl<T> Drop for RawMutexGuard<'_, T> {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
self.m.unlock();
|
||||
leaf_check_release();
|
||||
// Restore preemption only after the lock is released.
|
||||
crate::preempt::PREEMPTION_ENABLED.with(|c| c.set(self.prev_preempt));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user