feat(park): scheduler coordination layer — parkers, idle mask, wake protocol (RFC 018)

Schedulers get an IO-agnostic sleep/wake primitive of their own: one
futex Parker per scheduler thread (permit semantics, std::thread::park
shaped — closes the check-then-park race), an AtomicU64 idle mask with a
set-bit → re-check → wait park protocol, wake_one (highest-bit LIFO,
CAS-clear before unpark: exactly one wakeup per call by construction),
wake_all for the terminal path, and the timekeeper role — at most one
parked scheduler holds the timer deadline, with an atomic armed-deadline
snapshot for the busy-path due-check and an insert-side re-arm wake.

Deadlines travel as nanosecond timespecs end to end; the wake pipe's
as_millis truncation is unrepresentable here. The Dekker publish/re-check
shape is resolved by the same-location-RMW handshake (AcqRel), not SeqCst
loads; loom verifies exactly this in four models (no-lost-wake, chain
propagation, timekeeper handoff, termination), run with
LOOM_MAX_PREEMPTIONS=3 — unbounded exploration is impractical for the
looped models. Loom/non-Linux builds park on a Mutex+Condvar via
sync_shim.

Standalone until the runtime swap (next commit): nothing outside tests
constructs a Coordinator yet, hence the temporary dead_code allow in
lib.rs.
This commit is contained in:
smarm
2026-07-24 09:12:12 +02:00
parent 006a3283e7
commit 7b026cfe56
3 changed files with 864 additions and 0 deletions
+4
View File
@@ -32,6 +32,10 @@ pub mod introspect;
#[cfg(feature = "observer")]
pub mod observer;
pub mod runtime;
// TEMPORARY dead_code allow: park is standalone until the RFC 018 runtime
// swap (next commit) wires it into schedule_loop/enqueue; the allow dies there.
#[allow(dead_code)]
pub(crate) mod park;
pub(crate) mod raw_mutex;
pub(crate) mod slot_state;
pub(crate) mod sync_shim;