Skip to content

Commit 43e5a9d

Browse files
committed
fix: terminate active StreamableHTTP sessions during shutdown (#2150)
This commit addresses issue #2150 where StreamableHTTP sessions were not properly terminating active HTTP sessions during shutdown. Root causes fixed: 1. StreamableHTTPSessionManager.run() was directly canceling the task group without first calling terminate() on active transport instances 2. StreamableHTTPServerTransport.terminate() was only closing _request_streams, leaving _sse_stream_writers unclosed Changes made: 1. In streamable_http_manager.py: Added explicit terminate() calls for all active server instances before canceling the task group in the finally block of run() 2. In streamable_http.py: Enhanced terminate() to close all SSE stream writers by iterating through _sse_stream_writers and calling close() on each writer before clearing the dictionary This ensures all active HTTP connections and streams are properly closed during shutdown, preventing resource leaks and ensuring clean termination. Github-Issue: #2150
1 parent f8d98b6 commit 43e5a9d

File tree

2 files changed

+13
-0
lines changed

2 files changed

+13
-0
lines changed

src/mcp/server/streamable_http.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,16 @@ async def terminate(self) -> None:
784784
self._terminated = True
785785
logger.info(f"Terminating session: {self.mcp_session_id}")
786786

787+
# Close all SSE stream writers
788+
sse_stream_writer_keys = list(self._sse_stream_writers.keys())
789+
for key in sse_stream_writer_keys: # pragma: no cover
790+
writer = self._sse_stream_writers.pop(key, None)
791+
if writer:
792+
writer.close()
793+
794+
# Clear the SSE stream writers dictionary
795+
self._sse_stream_writers.clear()
796+
787797
# We need a copy of the keys to avoid modification during iteration
788798
request_stream_keys = list(self._request_streams.keys())
789799

src/mcp/server/streamable_http_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]:
130130
yield # Let the application run
131131
finally:
132132
logger.info("StreamableHTTP session manager shutting down")
133+
# Terminate all active server instances before cancelling tasks
134+
for transport in list(self._server_instances.values()):
135+
await transport.terminate()
133136
# Cancel task group to stop all spawned tasks
134137
tg.cancel_scope.cancel()
135138
self._task_group = None

0 commit comments

Comments
 (0)