BrokenResourceError race condition in stdio_client cleanup when context exits quickly

用户运行 MCP(Model Context Protocol)Python SDK 的 stdio_client 工具时,当客户端快速退出 context(例如用户断开连接),子进程尚未完成数据输出(如初始化消息),导致 BrokenResourceError 异常。该问题在 modelconte

BrokenResourceError race condition in stdio_client cleanup when context exits quickly

BrokenResourceError race condition in stdio_client cleanup when context exits quickly

快速结论:该报错发生在使用 MCP Python SDK 的 stdio_client 异步上下文管理器时,当调用代码在子进程完成输出前快速退出,导致后台任务(stdout_reader)与清理代码(finally 块之间发生竞态条件。优先排查是否在退出 context 前取消了任务组的 scope。

问题场景

用户运行 MCP(Model Context Protocol)Python SDK 的 stdio_client 工具时,当客户端快速退出 context(例如用户断开连接),子进程尚未完成数据输出(如初始化消息),导致 BrokenResourceError 异常。该问题在 modelcontextprotocol/python-sdk 仓库的 main 分支(commit b33c811)上可稳定复现。

报错原文

ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "mcp/client/stdio/__init__.py", line 162, in stdout_reader
    |     await read_stream_writer.send(session_message)
    |   File "anyio/streams/memory.py", line 242, in send
    |     self.send_nowait(item)
    |   File "anyio/streams/memory.py", line 213, in send_nowait
    |     raise BrokenResourceError
    | anyio.BrokenResourceError
    +------------------------------------

原因分析

竞态条件导致的问题:

  1. stdio_client 生成子进程后,stdout_reader 任务在 task group 中运行,调用 await read_stream_writer.send(session_message) 从子进程 stdout 读取数据并发送。
  2. 调用代码退出 context 后,finally 块执行,先终止子进程,然后关闭 read_stream_writer(send 端)和 read_stream(receive 端)。
  3. 此时 stdout_reader 任务可能仍阻塞在 send() 调用上,而 receive 端刚刚被关闭,anyio 抛出 BrokenResourceError(不同于 ClosedResourceError,后者是调用者自己关闭了 send 端)。
  4. 现有的 except 只捕获 ClosedResourceError,不捕获 BrokenResourceError,导致异常冒泡到 ExceptionGroup

环境排查

  • Python 版本:3.12.1(测试环境)
  • MCP Python SDK 版本:当前 main 分支(b33c811)
  • anyio 版本:从 pip 安装的最新版本
  • 操作系统:Linux
  • 确认是否使用了 uv 或 pip 管理依赖

解决步骤

  1. 第一步:在 finally 块中优先取消 task group scope
    在关闭流之前,添加 tg.cancel_scope.cancel(),确保后台任务收到取消信号后停止阻塞。
    修改位置:src/mcp/client/stdio/__init__.pyfinally 块顶部。
  2. 第二步:在 stdout_readerstdin_writer 中同时捕获 BrokenResourceErrorClosedResourceError
    作为防御性编程,在异常处理中增加 anyio.BrokenResourceError,确保即使存在极窄的时间窗口也能安全退出。
  3. 可优先尝试: 如果无法修改 SDK 代码,可参考 Issue 中的 workaround——使用 asyncio.Lock 序列化 MCP 生命周期操作,避免 enter/exit 操作重叠触发竞态条件。

注意: 以上两步修复必须同时应用。仅取消 scope 可能仍有理论窗口,仅捕获异常则未解决根本的排序问题。

验证方法

使用 Issue 中提供的单文件复现脚本:

python3 -m uv run python repro_1960.py

应用修复后,脚本应无异常退出。若之前能稳定复现 BrokenResourceError,修复后应不再抛出。也可参考 PR #2219 的测试用例。

参考来源

modelcontextprotocol/python-sdk #1960

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

celebrityanime
celebrityanime
文章: 8095

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注