From df8a36ced4ef4e765b7d619344da4b6ff072b82a Mon Sep 17 00:00:00 2001 From: Patrick Buckley Date: Sun, 15 Mar 2026 21:42:05 -0700 Subject: [PATCH] fix: add timeout to MCP server disconnect to prevent hung removals stack.aclose() on a stuck streamable-http transport hangs indefinitely, causing 50% CPU on all nodes when removing a broken remote server via reconcile_sync. Wrap with asyncio.wait_for(timeout=10s) so cleanup proceeds even if the transport refuses to close cleanly. --- turnstone/core/mcp_client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/turnstone/core/mcp_client.py b/turnstone/core/mcp_client.py index 48dd3e41..9f598152 100644 --- a/turnstone/core/mcp_client.py +++ b/turnstone/core/mcp_client.py @@ -977,8 +977,10 @@ class MCPClientManager: self._sessions.pop(name, None) stack = self._per_server_stacks.pop(name, None) if stack is not None: - with contextlib.suppress(Exception): - await stack.aclose() + try: + await asyncio.wait_for(stack.aclose(), timeout=10) + except (TimeoutError, Exception): + log.warning("Timed out closing MCP server '%s', forcing cleanup", name) # Clean up per-server state (on the event loop thread) self._per_server_tools.pop(name, None) self._per_server_resources.pop(name, None)