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
+60
View File
@@ -161,3 +161,63 @@ fn demonitor_after_fire_is_none() {
let _ = h.join();
});
}
// ---------------------------------------------------------------------------
// spawn_monitor: registration precedes publish, so a child that dies before
// the parent gets another instruction in still reports its real reason.
// ---------------------------------------------------------------------------
#[test]
fn spawn_monitor_never_reports_noproc_multi_thread() {
// 4 schedulers, 200 instantly-dying children. With spawn+monitor this
// observes NoProc a few percent of the time (the child finishes on
// another scheduler before monitor() registers); with spawn_monitor it
// must be Exit every time.
let noproc = Arc::new(AtomicUsize::new(0));
let exit = Arc::new(AtomicUsize::new(0));
let (n, e) = (noproc.clone(), exit.clone());
smarm::init(smarm::Config::exact(4)).run(move || {
let mut hs = Vec::new();
for _ in 0..200 {
let (h, m) = smarm::spawn_monitor(|| {});
let d = m.rx.recv().expect("Down");
assert_eq!(d.pid, h.pid());
match d.reason {
DownReason::Exit => e.fetch_add(1, Ordering::Relaxed),
DownReason::NoProc => n.fetch_add(1, Ordering::Relaxed),
other => panic!("unexpected {other:?}"),
};
hs.push(h);
}
for h in hs {
h.join().unwrap();
}
});
assert_eq!(noproc.load(Ordering::Relaxed), 0);
assert_eq!(exit.load(Ordering::Relaxed), 200);
}
#[test]
fn spawn_monitor_sees_panic_and_demonitor_works() {
let ok = Arc::new(AtomicBool::new(false));
let o = ok.clone();
run(move || {
let (h, m) = smarm::spawn_monitor(|| panic!("boom"));
let d = m.rx.recv().expect("Down");
assert_eq!(d.pid, h.pid());
assert!(matches!(d.reason, DownReason::Panic));
let _ = h.join();
// demonitor before the child runs: no Down ever arrives.
let (h2, m2) = smarm::spawn_monitor(|| {});
demonitor(&m2);
h2.join().unwrap();
// Last sender gone with nothing sent: the channel is closed and empty.
assert!(
!matches!(m2.rx.try_recv(), Ok(Some(_))),
"demonitored spawn_monitor still delivered"
);
o.store(true, Ordering::SeqCst);
});
assert!(ok.load(Ordering::SeqCst));
}