fix: detect stdin EOF on parent death for stdio transport#2258
fix: detect stdin EOF on parent death for stdio transport#2258Bortlesboat wants to merge 11 commits intomodelcontextprotocol:mainfrom
Conversation
|
Pushed a follow-up to address CI failure: pyright flagged g as possibly unbound in ests/server/test_stdio.py. Commit: e4ae4d2 This removes the unbound reference and should unblock the pre-commit/type check lane. |
Add a background monitor that uses select.poll() to detect POLLHUP on stdin's file descriptor. When the parent process dies and the pipe's write end closes, the monitor cancels the task group, triggering a clean shutdown. The anyio.wrap_file async iterator may not propagate EOF promptly because it runs readline() in a worker thread. The poll-based monitor detects the hang-up at the OS level independent of the worker thread. Only enabled on non-Windows platforms where select.poll() is available.
Move select import and TaskGroup import to module level, add explicit return type annotation, and add tests covering the win32 early-return, fileno failure path, POLLHUP detection, and POLLIN-only event handling.
The write_fd cleanup in finally blocks is defensive code for error cases that don't occur in the happy path. Mark with pragma: no cover to satisfy 100% coverage requirement.
Use await anyio.sleep() instead of while loops to wait for monitor cancellation. The while loop's False-condition branch was never taken because the scope always exits via cancellation, not loop termination.
e4ae4d2 to
b0f866f
Compare
The while loop at line 144 always executes at least once — POLLHUP fires after the write end is closed, not before. The zero-iteration branch (condition False on first check) is structurally unreachable in this test but triggers a coverage miss under branch coverage. Mark with pragma: no branch, consistent with the pattern used elsewhere in the test suite (e.g. test_notification_response.py).
|
Pushed coverage fix (commit 33a96b8): added |
…itor Use pragma: lax no cover for select.poll()-based code paths that are only exercised on non-Windows platforms. Remove incorrect pragma: no cover from test finally blocks that are always executed. Github-Issue:modelcontextprotocol#2231
|
@Bortlesboat closing this PR as the original issue hasn't been marked as good first issue or ready for work |
Fixes #2231
Summary
When an MCP server using
transport="stdio"has its parent process die, the server process is orphaned and continues running indefinitely. Theanyio.wrap_fileasync iterator may not propagate stdin EOF promptly because it runsreadline()in a worker thread.This adds a background monitor that uses
select.poll()to detectPOLLHUPon stdin's file descriptor. When the parent process dies and the pipe's write end closes, the monitor cancels the task group, triggering a clean shutdown.select.poll()is availableTest plan
test_stdio_serverpasses (uses custom stdin, monitor correctly skipped)