mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-19 16:12:30 +00:00
fix: use sequential MCP server connections to prevent CPU spin
asyncio.create_task in connect_mcp_servers creates child tasks for each MCP server, but close_mcp calls stack.aclose() from the main task. anyio CancelScope requires enter/exit in the same task, so the cross-task exit raises RuntimeError which gets silently caught. The orphaned cancel scope keeps retrying via call_soon on every event loop tick, consuming 100% CPU. Fix: remove create_task/gather and connect servers sequentially in the caller task. MCP servers are typically 1-2, so parallel connection provides negligible benefit while introducing the cancel scope hazard. Closes #3638
This commit is contained in:
parent
99209a806d
commit
4fad19dc17
@ -436,8 +436,8 @@ async def connect_mcp_servers(
|
|||||||
"""Connect to configured MCP servers and register their tools, resources, prompts.
|
"""Connect to configured MCP servers and register their tools, resources, prompts.
|
||||||
|
|
||||||
Returns a dict mapping server name -> its dedicated AsyncExitStack.
|
Returns a dict mapping server name -> its dedicated AsyncExitStack.
|
||||||
Each server gets its own stack and runs in its own task to prevent
|
Each server gets its own stack to prevent cancel scope conflicts
|
||||||
cancel scope conflicts when multiple MCP servers are configured.
|
when multiple MCP servers are configured.
|
||||||
"""
|
"""
|
||||||
from mcp import ClientSession, StdioServerParameters
|
from mcp import ClientSession, StdioServerParameters
|
||||||
from mcp.client.sse import sse_client
|
from mcp.client.sse import sse_client
|
||||||
@ -612,19 +612,13 @@ async def connect_mcp_servers(
|
|||||||
|
|
||||||
server_stacks: dict[str, AsyncExitStack] = {}
|
server_stacks: dict[str, AsyncExitStack] = {}
|
||||||
|
|
||||||
tasks: list[asyncio.Task] = []
|
|
||||||
for name, cfg in mcp_servers.items():
|
for name, cfg in mcp_servers.items():
|
||||||
task = asyncio.create_task(connect_single_server(name, cfg))
|
try:
|
||||||
tasks.append(task)
|
result = await connect_single_server(name, cfg)
|
||||||
|
except Exception as e:
|
||||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
logger.error("MCP server '{}' connection failed: {}", name, e)
|
||||||
|
continue
|
||||||
for i, result in enumerate(results):
|
if result is not None and result[1] is not None:
|
||||||
name = list(mcp_servers.keys())[i]
|
|
||||||
if isinstance(result, BaseException):
|
|
||||||
if not isinstance(result, asyncio.CancelledError):
|
|
||||||
logger.error("MCP server '{}' connection task failed: {}", name, result)
|
|
||||||
elif result is not None and result[1] is not None:
|
|
||||||
server_stacks[result[0]] = result[1]
|
server_stacks[result[0]] = result[1]
|
||||||
|
|
||||||
return server_stacks
|
return server_stacks
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user