-
Notifications
You must be signed in to change notification settings - Fork 2k
Client._disconnect() needs timeout — hangs indefinitely on stale HTTP connections #3947
Copy link
Copy link
Closed
Labels
bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.Something isn't working. Reports of errors, unexpected behavior, or broken functionality.clientRelated to the FastMCP client SDK or client-side functionality.Related to the FastMCP client SDK or client-side functionality.httpRelated to HTTP transport, networking, or web server functionality.Related to HTTP transport, networking, or web server functionality.needs MREBug reports require a copy/pastable MRE that can run as-is.Bug reports require a copy/pastable MRE that can run as-is.too-longExcessively verbose or unedited LLM output. Condense before triage.Excessively verbose or unedited LLM output. Condense before triage.
Metadata
Metadata
Assignees
Labels
bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.Something isn't working. Reports of errors, unexpected behavior, or broken functionality.clientRelated to the FastMCP client SDK or client-side functionality.Related to the FastMCP client SDK or client-side functionality.httpRelated to HTTP transport, networking, or web server functionality.Related to HTTP transport, networking, or web server functionality.needs MREBug reports require a copy/pastable MRE that can run as-is.Bug reports require a copy/pastable MRE that can run as-is.too-longExcessively verbose or unedited LLM output. Condense before triage.Excessively verbose or unedited LLM output. Condense before triage.
Problem
When using
FastMCPToolsetwithStreamableHttpTransportfor long-lived processes (eval suites, test runners),Client._disconnect()can hang indefinitely when the remote MCP server has dropped the HTTP keep-alive connection.Root Cause
_disconnect()awaitsself._session_state.session_taskwhich calls_session_runner. The runner'sAsyncExitStack.__aexit__triggers a POST to the server for session termination. When the server connection is stale (idle timeout after 60-90s), this POST hangs untilhttpx.ReadTimeoutfires — but there's no bounded deadline on theawait session_taskitself.Exception Chain
Impact
In our eval suite (15 scenarios, sequential), after a long-running scenario (80-140s), the next call to
client._disconnect()hangs. This poisons all subsequent scenarios because:_disconnect()hangs waiting forsession_tasklist_tools()/call_tool()fail with "Client is not connected"Workaround
We wrapped the
__aexit__call inasyncio.wait_for(..., timeout=3.0)and force-null the client on timeout, thengc.collect().Suggested Fix
Add a
timeoutparameter toClient._disconnect():Environment