Skip to content

fix: use ThreadPoolExecutor for MCP stdio on all platforms#615

Open
HouMinXi wants to merge 2 commits into
tirth8205:mainfrom
HouMinXi:fix/mcp-stdio-zombie-fd-inheritance
Open

fix: use ThreadPoolExecutor for MCP stdio on all platforms#615
HouMinXi wants to merge 2 commits into
tirth8205:mainfrom
HouMinXi:fix/mcp-stdio-zombie-fd-inheritance

Conversation

@HouMinXi

@HouMinXi HouMinXi commented Jul 9, 2026

Copy link
Copy Markdown

fix: use ThreadPoolExecutor for MCP stdio on all platforms

Problem

When code-review-graph runs as an MCP stdio server,
ProcessPoolExecutor creates a fork server (fork+exec) and workers
(fork) that both inherit stdin as a Unix socket to the host. When
the host disconnects, Linux delivers EOF only when all fd references
are closed. The inherited fds keep the socket alive indefinitely --
the server's stdin read loop never terminates, producing zombie
processes (20+ after 8h, ~765MB RSS).

Existing Workaround

_select_executor_kind() already switches to ThreadPoolExecutor on
Windows when stdin is not a TTY (#46, #136). The docstring
explains the rationale: "Tree-sitter parsing in the worker releases
the GIL during native parsing, so the speedup loss for falling back
to threads is small (typically <30% on the full-build path) and the
trade is worth it."

The same logic applies to all Unix platforms. MCP stdio transport
means stdin is a socket, not a TTY, on every platform.

Fix

Remove the sys.platform == "win32" guard from the thread-fallback
condition. When stdin is not a TTY (MCP stdio mode), use
ThreadPoolExecutor on all platforms.

ThreadPoolExecutor avoids fork entirely:

  • No fork server process
  • No fd inheritance
  • No zombie accumulation
  • tree-sitter still runs in parallel (GIL released during native
    parsing)

CRG_PARSE_EXECUTOR=process/thread still overrides.

Testing

Verified end-to-end on Linux (Python 3.14, Fedora 44):

  • MCP server spawned via stdio transport
  • build_or_update_graph_tool called with full_rebuild=True
  • stdin closed to simulate host disconnect
  • Server exited within 1s, zero zombie processes

HouMinXi added 2 commits July 8, 2026 23:34
ProcessPoolExecutor fork server and workers inherit stdin as a Unix
socket. When the host disconnects, inherited fds block EOF, producing
zombie processes. The existing Windows-only thread fallback (tirth8205#46,
tirth8205#136) already solves this. Remove the platform guard so MCP stdio
mode uses ThreadPoolExecutor on all platforms.

tree-sitter releases GIL during native parsing, so thread parallelism
is preserved. CRG_PARSE_EXECUTOR still overrides.

Signed-off-by: Minxi Hou <houminxi@gmail.com>
ProcessPoolExecutor fork server and workers inherit stdin as a Unix
socket. When the host disconnects, inherited fds block EOF, producing
zombie processes. The existing Windows-only thread fallback (tirth8205#46,
tirth8205#136) already solves this. Remove the platform guard so MCP stdio
mode uses ThreadPoolExecutor on all platforms.

tree-sitter releases GIL during native parsing, so thread parallelism
is preserved. CRG_PARSE_EXECUTOR still overrides.

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant