Graceful SSE drain on session manager shutdown#2239
Closed
wiggzz wants to merge 1 commit intomodelcontextprotocol:mainfrom
Closed
Graceful SSE drain on session manager shutdown#2239wiggzz wants to merge 1 commit intomodelcontextprotocol:mainfrom
wiggzz wants to merge 1 commit intomodelcontextprotocol:mainfrom
Conversation
wiggzz
added a commit
to dbt-labs/mcp-python-sdk
that referenced
this pull request
Mar 6, 2026
Terminate all active transports before cancelling the task group during shutdown. This closes in-memory anyio streams cleanly, allowing EventSourceResponse to send a final `more_body=False` chunk — a clean HTTP close instead of a connection reset that triggers "upstream prematurely closed connection" errors at reverse proxies. Changes: - Track in-flight stateless transports in _stateless_transports set - In run() finally block, call terminate() on all transports (both stateful and stateless) before tg.cancel_scope.cancel() - Add E2E tests for graceful shutdown in both stateless and stateful modes using httpx.ASGITransport Upstream PR: modelcontextprotocol#2239
b9909d8 to
05a1639
Compare
wiggzz
added a commit
to dbt-labs/mcp-python-sdk
that referenced
this pull request
Mar 9, 2026
Terminate all active transports before cancelling the task group during shutdown, allowing EventSourceResponse to send a final more_body=False chunk for clean HTTP close instead of a connection reset. Upstream PR: modelcontextprotocol#2239
wiggzz
added a commit
to dbt-labs/mcp-python-sdk
that referenced
this pull request
Mar 9, 2026
Terminate all active transports before cancelling the task group during shutdown, allowing EventSourceResponse to send a final more_body=False chunk for clean HTTP close instead of a connection reset. Upstream PR: modelcontextprotocol#2239
05a1639 to
5112436
Compare
wiggzz
added a commit
to dbt-labs/mcp-python-sdk
that referenced
this pull request
Mar 9, 2026
Terminate all active transports before cancelling the task group during shutdown, allowing EventSourceResponse to send a final more_body=False chunk for clean HTTP close instead of a connection reset. Upstream PR: modelcontextprotocol#2239
Terminate all active transports before cancelling the task group during StreamableHTTPSessionManager shutdown. This closes their in-memory streams, allowing EventSourceResponse to send a final `more_body=False` chunk — a clean HTTP close instead of a connection reset. Without this, reverse proxies like nginx see "upstream prematurely closed connection" and return 502 to clients during rolling deploys. Changes: - Track in-flight stateless transports in `_stateless_transports` set - In `run()` finally block, call `terminate()` on all stateful and stateless transports before `tg.cancel_scope.cancel()` - Add E2E tests for both stateless and stateful modes that verify the SSE stream closes cleanly when the manager shuts down while a tool call is in-flight
5112436 to
1a82b51
Compare
Author
|
I'm going to move this into #2145 since it is related (deals with cancellation logic during shutdown) and without this, there will be a regression in the connection closure logic in the other PR. |
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.
Summary
Fixes #1272
Fixes #1739
When
StreamableHTTPSessionManager.run()exits (e.g., during server shutdown), active SSE streams are abruptly cancelled by the task group cancellation. This meansEventSourceResponsenever sends its finalmore_body=Falsechunk, causing the TCP connection to reset. Reverse proxies like nginx interpret this as "upstream prematurely closed connection" and return 502 to clients.This PR fixes the issue by terminating all active transports before cancelling the task group during shutdown.
transport.terminate()closes the in-memory anyio streams, which causessse_writerto exit cleanly →sse_stream_writercloses →EventSourceResponse._stream_responseiterator ends →more_body=Falseis sent → clean HTTP close.Changes
streamable_http_manager.py: Track in-flight stateless transports in_stateless_transportsset. Inrun()'s finally block, callterminate()on all stateful and stateless transports beforetg.cancel_scope.cancel().test_streamable_http_manager.py: Add E2E tests for both stateless and stateful modes that verify the SSE stream closes cleanly when the manager shuts down while a tool call is in-flight.streamable_http.py: Remove unnecessarypragma: no coverannotations on lines already covered by the test suite.Motivation
In production, every rolling deploy triggers connection errors at our gateway since the streams are not terminated gracefully.
Breaking changes
None. This is an internal implementation change that only affects shutdown behavior.