fix(runtime): a root actor panic escapes run()
The trampoline caught the root's panic, recorded it as Outcome::Panic on the slot, and run() dropped the initial handle without reading it — every assert inside run(), the standard test-suite pattern, was silently vacuous (found live: a failing-first test passed). run() now reads the root outcome before the handle drop and resume_unwinds the payload after full teardown, so a caller's catch_unwind leaves the Runtime reusable; Exit and Stopped return normally. The payload message is printed before re-raising (the throw-site hook output was suppressed in-actor). Correct the two tests this unmasked, both born failing and never run: the select loser-arm test kept a closed arm in the set (the documented closed-arm rule: a closed arm reports ready forever — observe the disconnect and drop it); the send_after-to-dead test expected Ok(None) from a closed+empty channel (documented: Err(RecvError), which proves nothing-delivered even more strongly).
This commit is contained in:
@@ -485,3 +485,35 @@ fn multi_thread_timer_only_no_pipe_contention() {
|
||||
SLEEP_MS * 2,
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Root panic propagation
|
||||
|
||||
/// A panic in the root actor escapes `run()` to the caller. Anything else
|
||||
/// makes every assert inside `run` silently vacuous — found live when a
|
||||
/// failing-first test passed: the tripped assert was caught by the
|
||||
/// trampoline, recorded as `Outcome::Panic` on the root slot, and dropped
|
||||
/// unread with the initial handle.
|
||||
#[test]
|
||||
#[should_panic(expected = "root actor panic escapes")]
|
||||
fn root_panic_escapes_run() {
|
||||
rt1().run(|| {
|
||||
panic!("root actor panic escapes");
|
||||
});
|
||||
}
|
||||
|
||||
/// Teardown completes before the root panic propagates: a caller that
|
||||
/// catches it can immediately `run()` again on the same `Runtime` (the
|
||||
/// documented sequential-reuse contract).
|
||||
#[test]
|
||||
fn runtime_reusable_after_root_panic() {
|
||||
let r = rt1();
|
||||
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
r.run(|| panic!("boom"));
|
||||
}));
|
||||
assert!(caught.is_err(), "root panic must escape run()");
|
||||
let ran = Arc::new(AtomicBool::new(false));
|
||||
let ran_t = ran.clone();
|
||||
r.run(move || ran_t.store(true, Ordering::Relaxed));
|
||||
assert!(ran.load(Ordering::Relaxed), "runtime unusable after root panic");
|
||||
}
|
||||
|
||||
+7
-1
@@ -108,8 +108,14 @@ fn loser_arm_wake_after_parked_select_stays_precise() {
|
||||
t0.elapsed() >= Duration::from_millis(40),
|
||||
"one-shot park returned early: a stale loser-arm wake landed"
|
||||
);
|
||||
// Arm 0 is now closed (the sender actor exited after its sends) and
|
||||
// a closed arm reports ready forever under priority order — observe
|
||||
// the disconnect and drop it from the set, per the documented
|
||||
// closed-arm rule.
|
||||
assert_eq!(select(&[&rxa, &rxb]), 0);
|
||||
assert!(rxa.try_recv().is_err(), "arm 0 must report disconnect");
|
||||
// The loser's message was never lost.
|
||||
assert_eq!(select(&[&rxa, &rxb]), 1);
|
||||
assert_eq!(select(&[&rxb]), 0);
|
||||
assert_eq!(rxb.try_recv().unwrap(), Some(2));
|
||||
h.join().unwrap();
|
||||
});
|
||||
|
||||
+4
-2
@@ -401,8 +401,10 @@ fn send_after_to_dead_typed_pid_is_silent() {
|
||||
assert_eq!(report_rx.recv().unwrap(), 1); // sink has now exited
|
||||
let _id = send_after(Duration::from_millis(15), sink, 2);
|
||||
sleep(Duration::from_millis(45)); // let it fire against the dead pid
|
||||
// No panic, and nothing further delivered.
|
||||
assert_eq!(report_rx.try_recv(), Ok(None));
|
||||
// No panic; the sink is gone, so its report sender dropped with it —
|
||||
// closed+empty is Err (documented), which also proves nothing
|
||||
// further was delivered.
|
||||
assert!(report_rx.try_recv().is_err(), "nothing further delivered");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user