feat(channel): migrate Inner to RawMutex; two-class lock order (Leaf -> Channel)
Phase 2 surfaced the hole this closes: channel guards were held with preemption enabled, so a timeslice switch inside a channel critical section could release the pthread mutex from a different OS thread (UB). RawMutex disables preemption for the guard span and is cross-thread-release sound by construction; poison goes away with it. The strict single-class leaf rule cannot survive the migration: finalize clones the supervisor/trap senders and monitor() clones the Down sender, all under a cold lock, and those senders live in the slot - the nesting is structural. The leaf check is therefore generalized to two classes: Leaf (cold locks, free list, stack pool) and Channel. Order is Leaf -> Channel, at most one of each; both directions of violation are debug-asserted at the acquisition site. Channel critical sections call only the lock-free unpark protocol, so the order is acyclic. Tests: class-ordering unit tests in raw_mutex (allowed nesting + all three rejected shapes), plus a multi-scheduler integration test driving channels through monitor churn and actor death so any ordering regression trips the debug assert instead of deadlocking.
This commit is contained in:
+5
-4
@@ -109,10 +109,11 @@ pub struct Monitor {
|
||||
pub fn monitor(target: Pid) -> Monitor {
|
||||
let (tx, rx) = channel::<Down>();
|
||||
|
||||
// Register under the target's cold lock. `tx.clone()` only touches the
|
||||
// channel's own mutex, never any runtime lock, so it is safe here — but
|
||||
// we must not *send* under the lock, as `Sender::send` can unpark a
|
||||
// parked receiver, and there's no reason to nest that.
|
||||
// Register under the target's cold lock. `tx.clone()` takes the channel's
|
||||
// own lock — a Channel-class RawMutex, explicitly permitted *under* a Leaf
|
||||
// (cold) lock by the lock order (see raw_mutex.rs). We must still not
|
||||
// *send* under the lock, as `Sender::send` can unpark a parked receiver,
|
||||
// and there's no reason to nest that.
|
||||
let (id, registered) = with_runtime(|inner| {
|
||||
let id = inner.alloc_monitor_id();
|
||||
let registered = match inner.slot_at(target) {
|
||||
|
||||
Reference in New Issue
Block a user