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:
Claude (sandbox)
2026-07-18 21:52:50 +00:00
parent 527f045e17
commit 7eae56a296
4 changed files with 85 additions and 3 deletions
+7 -1
View File
@@ -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();
});