Skip to content

fix(terminal): close pty master before reaping to avoid event-loop deadlock#10031

Merged
kirangadhave merged 2 commits into
marimo-team:mainfrom
ktaletsk:fix/terminal-waitpid-deadlock
Jul 1, 2026
Merged

fix(terminal): close pty master before reaping to avoid event-loop deadlock#10031
kirangadhave merged 2 commits into
marimo-team:mainfrom
ktaletsk:fix/terminal-waitpid-deadlock

Conversation

@ktaletsk

@ktaletsk ktaletsk commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

Fixes the event-loop deadlock described in #10030. Disconnecting the terminal while a foreground process holds the pty (for example a coding agent like claude) could freeze the entire marimo server: "Failed to connect", Ctrl-C ignored, recoverable only with kill -9.

Why

_create_process_cleanup_handler in marimo/_server/api/endpoints/terminal.py runs a blocking os.waitpid(child, 0) on the asyncio event loop after SIGKILLing the shell. The shell is the pty's session leader. It cannot be reaped while the server still holds the pty master fd open and a foreground child still holds the slave, so the kernel never finishes revoking its controlling terminal, the shell never becomes a reapable zombie, and waitpid blocks forever. That freezes the whole event loop. See #10030 for the full root cause and a deterministic reproduction.

Change

Close the pty master fd before reaping. With the master closed, the controlling-terminal teardown can complete and the child is reaped within a few milliseconds, so waitpid returns instead of blocking.

Test

Adds test_cleanup_closes_master_before_blocking_waitpid, which asserts os.close(fd) runs before the blocking os.waitpid(pid, 0). It fails on the previous ordering and passes with this change. The existing terminal.py test suite still passes.

Scope

This PR addresses only the freeze. The separate behavior of killing the terminal shell on any browser disconnect (#7829) is out of scope.

Review in cubic

…adlock

When the terminal websocket disconnects while a foreground process holds the
pty slave (e.g. a coding agent or REPL), cleanup() SIGKILLed the session-leader
shell and then called a blocking os.waitpid(child, 0) on the asyncio event
loop. The shell cannot finish exiting while the server still holds the pty
master open, so it never becomes reapable and waitpid blocks forever, freezing
the entire server (websockets dead, Ctrl-C ignored), recoverable only via
kill -9.

Close the pty master fd before reaping so the controlling-terminal teardown can
complete; the child then reaps promptly.

Adds a regression test asserting os.close(fd) runs before the blocking
os.waitpid(pid, 0).
@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jun 30, 2026 7:31pm

Request Review

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@ktaletsk

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@kirangadhave kirangadhave added the bug Something isn't working label Jun 30, 2026
@kirangadhave kirangadhave merged commit aee81a8 into marimo-team:main Jul 1, 2026
57 of 59 checks passed
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.12-dev27

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants