scheduler: spawn_monitor / spawn_monitor_with — monitor registered on the child's slot before publish, so the Down always carries the real reason (spawn-then-monitor could race to NoProc); tests; channel test uses it

This commit is contained in:
claude-asm-audit
2026-08-21 12:23:46 +00:00
parent ea2b222cff
commit bc0a5e8656
6 changed files with 127 additions and 16 deletions
+47 -2
View File
@@ -362,7 +362,7 @@ pub fn spawn_under_with<A>(
let pid = with_runtime(|inner| {
let idx = inner.allocate_slot(); // panics loudly on slab exhaustion
crate::runtime::install_actor(inner, idx, sp, stack, supervisor, closure)
crate::runtime::install_actor(inner, idx, sp, stack, supervisor, closure, None)
});
JoinHandle {
@@ -371,6 +371,51 @@ pub fn spawn_under_with<A>(
}
}
/// [`spawn`] and [`monitor`](crate::monitor) the child in one step, with no
/// window in which the child can die unobserved.
///
/// `spawn` followed by `monitor(h.pid())` races: on a multi-scheduler
/// runtime the child can run to completion before the monitor registers, and
/// a monitor on a dead pid delivers [`DownReason::NoProc`](crate::DownReason::NoProc)
/// — the real reason (Exit vs Panic) is lost. Here the monitor is registered
/// on the child's slot *before* the child is published to any run queue, so
/// the `Down` always carries the child's actual termination reason. This is
/// Erlang's `spawn_monitor/1`.
pub fn spawn_monitor(f: impl FnOnce() + Send + 'static) -> (JoinHandle, crate::monitor::Monitor) {
spawn_monitor_with(SpawnOpts::default(), f)
}
/// [`spawn_monitor`] with per-actor stack shape overrides (RFC 019).
pub fn spawn_monitor_with(
opts: SpawnOpts,
f: impl FnOnce() + Send + 'static,
) -> (JoinHandle, crate::monitor::Monitor) {
let parent = current_pid().unwrap_or_else(|| with_runtime(|_| crate::runtime::ROOT_PID));
let (tx, rx) = crate::channel::channel::<crate::monitor::Down>();
let stack = with_runtime(|inner| crate::runtime::acquire_stack(inner, opts));
let sp = init_actor_stack(stack.top(), crate::actor::trampoline);
let closure: crate::runtime::Closure = Box::new(f);
let (pid, id) = with_runtime(|inner| {
let idx = inner.allocate_slot(); // panics loudly on slab exhaustion
let id = inner.alloc_monitor_id();
let pid = crate::runtime::install_actor(inner, idx, sp, stack, parent, closure, Some((id, tx)));
(pid, id)
});
(
JoinHandle {
pid,
consumed: false,
},
crate::monitor::Monitor {
id,
target: pid,
rx,
},
)
}
/// [`spawn`] that reports a full actor slab instead of panicking.
///
/// Behaviour parity with [`spawn`] in every case except one: when the fixed
@@ -429,7 +474,7 @@ pub fn try_spawn_under_with<A>(
claimed.0 = None; // install_actor takes ownership of the slot from here
let pid = with_runtime(|inner| {
crate::runtime::install_actor(inner, idx, sp, stack, supervisor, closure)
crate::runtime::install_actor(inner, idx, sp, stack, supervisor, closure, None)
});
Ok(JoinHandle {