[v1.x] fix(server): close StdioServerTransport when stdin ends or closes - #2911
Open
maxymlyskov wants to merge 1 commit into
Open
maxymlyskov wants to merge 1 commit into
maxymlyskov wants to merge 1 commit into
Conversation
StdioServerTransport listened only for 'data' and 'error' on stdin, so a server whose client hung up its end of the pipe never closed: onclose never fired, and a server holding a keep-alive handle kept running with no parent (modelcontextprotocol#2002). Close the transport on stdin 'end' and 'close', also when the stream had already ended or been destroyed before start(), and fire onclose once however many of those arrive. This ports the stdin part of modelcontextprotocol#2494 (6a05402) and its tests; stdout error handling stays with modelcontextprotocol#2579.
🦋 Changeset detectedLatest commit: 5fec54b The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backport of #2494 for #2002 on v1.x.
On v1.x,
StdioServerTransport.start()listens for stdindataanderrorand nothing else (src/server/stdio.ts#L53-L63). So when the client hangs up the pipe, the transport never closes andonclosenever fires. A server that frees a timer or pool inonclosekeeps running with no parent. #2002 has 6 of them piling up in ~17 hours (all PPID=1) and another report of 7 in 5 days on 1.29.0, and its triage comment reproduced the same code on v1.x.Driver: a real child server holding a
setIntervalit clears inonclose. It completes initialize, thenchild.stdin.end(), no signal:The fix is the stdin half of #2494:
endandcloselisteners that close the transport, removed again inclose(), a_closedguard (today a secondclose()firesonclosetwice), and asetImmediateclose when stdin already ended or was destroyed beforestart().One change a v1 user can notice: requests still in flight at stdin EOF are aborted and their responses aren't written. Same as main, and the changeset says so.
Left out: stdout
errorhandling,send()rejecting after close and the swallow-listener sweep. On v1.x that's open #2579, which edits this file and adds its own_closedguard, so whichever lands second gets a small rebase.Tests
test/server/stdio.test.ts: fix(server): close StdioServerTransport when stdin ends or closes #2494's seven stdin tests, unchanged, plus one checkingclose()removes theend/closelisteners. stdin defaults to the sharedprocess.stdinand the_closedguard would hide a leftover listener, so nothing else catches it.test/integration-tests/processCleanup.test.ts+src/__fixtures__/serverWithKeepAlive.tsfrom fix(server): close StdioServerTransport when stdin ends or closes #2494: the child exits on its own when the client closes stdin. It takes its own 15 s timeout, since the describe uses 5 s and the zombie bound is 8 s.On untouched v1.x, 8 of the 9 fail and the listener test passes on both:
npm test: 1818 passed (Windows 11 / Node 22.12)npm run test:e2e: 1114 passed, 1 failed.transport:stdio:shutdown-escalationfails the same way on untouched v1.x (Windows delivers no catchable SIGTERM). On WSL2 Linux / Node 22.22: 1115 passed.npm run typecheck,npm run lint: cleanChangeset included.