Repository navigation
fix(core): stop MCP servers in parallel on shutdown - #58351
Conversation
Server scopes closed one at a time, and each misbehaving stdio server can take up to 4s to stop. With two or more, shutdown outran the service restart's 5s SIGKILL deadline and the servers not yet stopped were orphaned. Fork server scopes from a parallel child scope so shutdown takes as long as the slowest server. Fixes #50758
There was a problem hiding this comment.
The fix works. With 4 local MCP servers that keep running after stdin closes (one also ignoring SIGTERM), opencode serve on the base was still shutting down when SIGKILL hit at 5s, and 3 MCP processes were left orphaned. With this PR the server exits in about 4.2s and leaves nothing behind. The PR's new test fails on the base and passes here. The core MCP tests pass apart from one failure that also happens on the base, and typecheck:tsc passes.
The public API is unchanged.
The parallel scope only changes how all servers are stopped together at shutdown. Disconnect, reconnect and reload still close a single server's scope, so their behaviour stays the same. Registration scopes now close before the server scopes rather than after, which doesn't matter at shutdown.
One thing worth changing, in the tests (see inline): the new test is large for a three-line fix and can be dropped, and the test file also picks up unrelated formatting changes to two existing tests.
Related: #50363
| }), | ||
| ) | ||
|
|
||
| shutdownIt.live("stops MCP servers concurrently when its layer closes", () => |
There was a problem hiding this comment.
This adds about 90 lines (a hand-built JSON-RPC responder, a fake process handle, and stdin parsing) to check a three-line scope change. It's hard to follow and depends on the stop path's grace timer to fail on the base. I'd drop it to keep the change small; if you want to keep it, please cut it down to the bare minimum. The formatting-only changes to the two elicitation tests above aren't related to this fix and can be reverted too.
There was a problem hiding this comment.
Both earlier points are fixed: the added test is gone and the unrelated formatting changes are reverted, so this is now just the scope change. I ran the shutdown check again on the new head: serve exits in about 4.2s after SIGTERM and leaves no MCP processes behind. On the base, the server is killed at 5s and 3 are left orphaned. The public API is unchanged. Nothing further from me.
Fixes #50758
MCP servers were stopped one at a time on shutdown. Each local server that ignores its stdin closing costs 2 to 4 seconds (stdin close grace, then SIGTERM, then SIGKILL).
opencode service restartforce-kills the server after about 5 seconds, so with a couple of those servers the kill lands mid-shutdown and the servers that haven't been stopped yet are left running under PID 1.Shutdown now stops all servers at once, so it takes as long as the slowest one. Disconnect, reconnect and config reload still stop only the server they target, as before.
With four test servers (a wrapper plus a child each, two ignoring stdin close and one also ignoring SIGTERM), shutdown went from about 8.1s to 4.2s, and a SIGTERM followed by SIGKILL at 5s no longer leaves anything behind.
Added by the opencode bot (AI-generated).
Related: #50363