feat(monitor): unidirectional one-shot process monitors
`monitor(pid) -> Receiver<Down>` lets any actor watch any other and
receive a single Down{pid, reason} when it terminates. Generalizes the
existing single supervisor_channel (which is just a hard-wired monitor
the parent installs at spawn).
- src/monitor.rs: Down / DownReason{Exit,Panic,NoProc} + monitor().
Payload-free: a panic payload has one owner and goes to the joiner via
JoinError, so monitors learn only that it panicked. Monitoring an
already-gone pid yields NoProc immediately.
- runtime: Slot grows `monitors: Vec<Sender<Down>>`; finalize_actor
drains and notifies them outside the shared lock (send can unpark a
receiver, which re-takes the non-reentrant mutex). Cleared in
reclaim_slot and on slot reuse at spawn.
- Registration and finalize both serialize on the shared mutex, so a
target alive at registration always delivers a real Down (no race
between liveness check and registration).
tests/monitor.rs: Exit, Panic, NoProc-on-dead-target, and fan-out to
multiple monitors. Full suite green.
This commit is contained in:
+20
-4
@@ -37,6 +37,7 @@ use crate::actor::{
|
||||
use crate::channel::Sender;
|
||||
use crate::context::{get_actor_sp, set_actor_sp, switch_to_actor};
|
||||
use crate::io::IoThread;
|
||||
use crate::monitor::{Down, DownReason};
|
||||
use crate::pid::Pid;
|
||||
use crate::preempt::PREEMPTION_ENABLED;
|
||||
use crate::supervisor::Signal;
|
||||
@@ -223,6 +224,10 @@ pub(crate) struct Slot {
|
||||
pub(crate) waiters: Vec<Pid>,
|
||||
pub(crate) outcome: Option<Outcome>,
|
||||
pub(crate) supervisor_channel: Option<Sender<Signal>>,
|
||||
/// Watchers registered via `monitor()`. Each receives one `Down` when this
|
||||
/// actor terminates (drained in `finalize_actor`). Distinct from
|
||||
/// `supervisor_channel`, which is the parent's single funnel.
|
||||
pub(crate) monitors: Vec<Sender<Down>>,
|
||||
pub(crate) outstanding_handles: u32,
|
||||
pub(crate) pending_io_result: Option<crate::io::IoResult>,
|
||||
/// Set by `unpark()` when the actor is still running (not yet Parked).
|
||||
@@ -240,6 +245,7 @@ impl Slot {
|
||||
waiters: Vec::new(),
|
||||
outcome: None,
|
||||
supervisor_channel: None,
|
||||
monitors: Vec::new(),
|
||||
outstanding_handles: 0,
|
||||
pending_io_result: None,
|
||||
pending_unpark: false,
|
||||
@@ -516,6 +522,7 @@ pub(crate) fn reclaim_slot(s: &mut SharedState, pid: Pid) {
|
||||
slot.outcome = None;
|
||||
slot.waiters.clear();
|
||||
slot.supervisor_channel = None;
|
||||
slot.monitors.clear();
|
||||
slot.state = State::Done;
|
||||
slot.outstanding_handles = 0;
|
||||
slot.pending_unpark = false;
|
||||
@@ -528,15 +535,16 @@ pub(crate) fn reclaim_slot(s: &mut SharedState, pid: Pid) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn finalize_actor(inner: &Arc<RuntimeInner>, pid: Pid, outcome: Outcome) {
|
||||
let (joiner_outcome, sup_signal) = match outcome {
|
||||
Outcome::Exit => (Outcome::Exit, Signal::Exit(pid)),
|
||||
let (joiner_outcome, sup_signal, down_reason) = match outcome {
|
||||
Outcome::Exit => (Outcome::Exit, Signal::Exit(pid), DownReason::Exit),
|
||||
Outcome::Panic(payload) => (
|
||||
Outcome::Panic(payload),
|
||||
Signal::Panic(pid, Box::new(()) as Box<dyn std::any::Any + Send>),
|
||||
DownReason::Panic,
|
||||
),
|
||||
};
|
||||
|
||||
let (waiters, supervisor_pid, recycled_stack) = inner.with_shared(|s| {
|
||||
let (waiters, supervisor_pid, monitors, recycled_stack) = inner.with_shared(|s| {
|
||||
let slot = s.slot_mut(pid).expect("finalize_actor: slot vanished");
|
||||
let sup = slot.actor.as_ref().map(|a| a.supervisor);
|
||||
// Extract the stack before clearing the actor so we can recycle it
|
||||
@@ -544,7 +552,8 @@ fn finalize_actor(inner: &Arc<RuntimeInner>, pid: Pid, outcome: Outcome) {
|
||||
let stack = slot.actor.take().map(|a| a.stack);
|
||||
slot.outcome = Some(joiner_outcome);
|
||||
slot.state = State::Done;
|
||||
(std::mem::take(&mut slot.waiters), sup, stack)
|
||||
let monitors = std::mem::take(&mut slot.monitors);
|
||||
(std::mem::take(&mut slot.waiters), sup, monitors, stack)
|
||||
});
|
||||
|
||||
// Return the stack to the pool outside the shared lock. The pool grows
|
||||
@@ -568,6 +577,13 @@ fn finalize_actor(inner: &Arc<RuntimeInner>, pid: Pid, outcome: Outcome) {
|
||||
}
|
||||
}
|
||||
|
||||
// Notify monitors. Sent outside the shared lock for the same reason as the
|
||||
// supervisor signal: `send` may unpark a parked receiver, which re-takes
|
||||
// the shared mutex.
|
||||
for m in monitors {
|
||||
let _ = m.send(Down { pid, reason: down_reason });
|
||||
}
|
||||
|
||||
// Unpark joiners.
|
||||
for joiner in waiters {
|
||||
crate::scheduler::unpark(joiner);
|
||||
|
||||
Reference in New Issue
Block a user