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:
+5
-11
@@ -137,19 +137,13 @@ fn channel_ops_interleaved_with_monitor_churn_multi_thread() {
|
||||
for i in 0..32i64 {
|
||||
let tx = tx.clone();
|
||||
handles.push(spawn(move || {
|
||||
// Short-lived target whose death fires the monitor below. It
|
||||
// waits for `go` so the monitor is registered before it can
|
||||
// die — otherwise a fast target yields an immediate NoProc
|
||||
// Down instead of the finalize-sent Exit this test is about
|
||||
// (pre-existing ~8% flake at 4 threads, independent of the
|
||||
// wake slot).
|
||||
let (go_tx, go_rx) = channel::<()>();
|
||||
let t = spawn(move || {
|
||||
let _ = go_rx.recv();
|
||||
// Short-lived target whose death fires the monitor below.
|
||||
// spawn_monitor: registered before publish, so the Down is
|
||||
// the finalize-sent Exit this test is about, never NoProc
|
||||
// (spawn-then-monitor raced ~8% at 4 threads).
|
||||
let (t, m) = smarm::spawn_monitor(move || {
|
||||
tx.send(i).unwrap();
|
||||
});
|
||||
let m = smarm::monitor(t.pid());
|
||||
go_tx.send(()).unwrap();
|
||||
t.join().unwrap();
|
||||
// Down delivery exercises send-from-finalize.
|
||||
let d = m.rx.recv().unwrap();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user