This commit is contained in:
Timothy Jaeryang Baek committed 2026-06-29 11:56:00 -05:00
1 parent 10558173fb
commit ff5cec43bd
2 files changed
+9 -7

No files matched your search

+2 -1
View File
@@ -1553,7 +1553,8 @@ async def chat_completion(
# task's current cancel scope", which propagates as a # task's current cancel scope", which propagates as a
# BaseException through the finally block, discards the response # BaseException through the finally block, discards the response
# return value, and surfaces as a 500 "No response returned." # return value, and surfaces as a 500 "No response returned."
# MCPClient.disconnect() already catches BaseException internally. # MCPClient.disconnect() suppresses known transport teardown errors
# while still propagating real task cancellation.
try: try:
if mcp_clients := metadata.get('mcp_clients'): if mcp_clients := metadata.get('mcp_clients'):
for client in reversed(list(mcp_clients.values())): for client in reversed(list(mcp_clients.values())):
+7 -6
View File
@@ -82,7 +82,7 @@ class MCPClient:
await self.session.initialize() await self.session.initialize()
self.exit_stack = exit_stack.pop_all() self.exit_stack = exit_stack.pop_all()
except Exception as e: except Exception as e:
await asyncio.shield(self.disconnect()) await self.disconnect()
raise e raise e
async def list_tool_specs(self) -> Optional[dict]: async def list_tool_specs(self) -> Optional[dict]:
@@ -150,9 +150,7 @@ class MCPClient:
"""Clean up and close the session. """Clean up and close the session.
This method is idempotent — calling it multiple times or on a This method is idempotent — calling it multiple times or on a
client that was never connected is safe. It shields the close client that was never connected is safe.
operation from CancelledError and adds a timeout so a hung MCP
server cannot block the event loop indefinitely.
""" """
exit_stack = self.exit_stack exit_stack = self.exit_stack
if exit_stack is None: if exit_stack is None:
@@ -172,8 +170,11 @@ class MCPClient:
# We simply call aclose() directly. If the task is cancelled, the # We simply call aclose() directly. If the task is cancelled, the
# sockets will eventually be cleaned up by garbage collection. # sockets will eventually be cleaned up by garbage collection.
await exit_stack.aclose() await exit_stack.aclose()
except TimeoutError: except asyncio.CancelledError as exc:
log.warning('MCPClient.disconnect() timed out after 5 s') task = asyncio.current_task()
if task is not None and task.cancelling():
raise
log.debug('MCPClient.disconnect() suppressed internal cancellation: %s', exc)
except RuntimeError as exc: except RuntimeError as exc:
log.debug('MCPClient.disconnect() suppressed RuntimeError: %s', exc) log.debug('MCPClient.disconnect() suppressed RuntimeError: %s', exc)
except Exception as exc: except Exception as exc: