cmux-imessage: a send never waits on a descendant holding stderr (#33301)

Merged
TE
3d ago
+92-18

Fixes #33301, a follow-up from the Codex review of #33219: a send could hang past its timeout when a descendant of osascript inherited stderr and kept it open.

What changes

  • stderr is drained on its own thread into a shared buffer, and nothing joins that thread.
  • On timeout: the send returns as soon as the child is killed and reaped.
  • On a normal exit: it keeps what stderr has delivered within a 2-second grace, then returns.
  • TimedRunner { timeout } is the runner. SystemRunner keeps its name and API and delegates to it with SEND_TIMEOUT.
  • Stop condition: the drain thread ends at end of file, when the last holder of the pipe closes it. It never blocks a send.

Tests

Two commits: the failing tests first (both red on Linux CI, each waiting the full 20 s on the descendant), then the fix. Green: 9 send tests pass in about 2 s, including a 1-second timeout with sleep 20 & wait and an exit that leaves sleep 20 & holding stderr. The 13 reader tests still pass. clippy -D warnings, the panic ratchet and rustfmt are clean.

Agent session: agent session

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes #33301: an iMessage send no longer hangs past its timeout when a descendant of the child keeps stderr open.

  • stderr is drained on its own thread into a shared buffer and never joined, so a descendant holding the pipe open can't block the send; on timeout the send returns as soon as the child is killed and reaped, and on a normal exit it takes what stderr delivered within a 2-second grace.
  • Adds TimedRunner { timeout }; SystemRunner keeps its API and delegates with SEND_TIMEOUT.
  • Adds two tests: a 1-second timeout with sleep 20 & wait and an exit that leaves sleep 20 & holding stderr; both previously waited the full 20 seconds.