RuntimeError: The current task is not holding this lock)

该报错通常出现在长时间运行的 MCP 客户端中,当 httpx 在请求中断(网络掉线、超时、任务组销毁)时从另一个任务关闭了 OAuthClientProvider.async_auth_flow 生成器,导致 anyio.Lock 的释放发生在非加锁任务上并抛出 RuntimeError: The

快速结论:该报错通常出现在长时间运行的 MCP 客户端中,当 httpx 在请求中断(网络掉线、超时、任务组销毁)时从另一个任务关闭了 OAuthClientProvider.async_auth_flow 生成器,导致 anyio.Lock 的释放发生在非加锁任务上并抛出 RuntimeError: The current task is not holding this lock。锁随后被永久占用,之后所有共用同一个 provider 的请求都会卡在 async with self.context.lock 处且不发出任何 HTTP 请求。

适用环境:已确认环境为 mcp 2.0.0(同样代码存在于 2.1.0 和 main),Python 3.11.15,macOS (darwin),anyio 4.14.2,asyncio 后端。生产报告中提到 CPython 3.11、macOS arm64、anyio 4.x asyncio 后端。使用 OAuth 认证的 streamable-HTTP MCP 服务器。

最快修复方案:暂无确认的一步修复方案。Issue 被官方作为 #2847 的重复项关闭,讨论中提出并验证了将 OAuthContext.lock 从 anyio.Lock 改为 anyio.Semaphore(1) 的方向,但尚未合并到发布版本。

注意事项:Semaphore(1) 方案只在 Issue 讨论的复现实验中被确认可跨任务干净关闭,并非官方已发布修复。该方案会改变释放语义,但由于 OAuthContext.lock 仅被 OAuthClientProvider.async_auth_flow 使用且需要跨 yield 持有,因此讨论认为可以保持在 OAuth context 局部范围内。未经确认的修复可能导致并发流序列化行为变化。

问题场景

用户在运行长时间存活的 MCP 客户端进程(例如托管多个 OAuth streamable-HTTP MCP 服务器的 ACP server,报告中提到 Notion、Composio、Consensus,以及 Nous Research Hermes agent 场景)时触发该问题。典型触发场景是:服务器关闭了 standalone GET 流并触发重连(日志中表现为 GET stream disconnected, reconnecting in 1000ms...),随后会话拆除过程从另一个 asyncio 任务关闭了正在进行的 async_auth_flow 生成器。

报错原文

ERROR asyncio: Task exception was never retrieved
future: <Task finished name='Task-598' coro=<()> exception=RuntimeError('The current task is not holding this lock')>
Traceback (most recent call last):
  File ".../site-packages/mcp/client/auth/oauth2.py", line 754, in async_auth_flow
    yield request
GeneratorExit

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../site-packages/mcp/client/auth/oauth2.py", line 582, in async_auth_flow
    async with self.context.lock:
  File ".../site-packages/anyio/_core/_synchronization.py", line 173, in __aexit__
    self.release()
  File ".../site-packages/anyio/_backends/_asyncio.py", line 1935, in release
    raise RuntimeError("The current task is not holding this lock")
RuntimeError: The current task is not holding this lock

同类报错的另一处捕获(authorization-code 交换阶段)除 GeneratorExit 落在 token_response = yield await self._perform_authorization() 外,其余一致。

原因分析

OAuthClientProvider.async_auth_flow 在 v2.0.0 的 src/mcp/client/auth/oauth2.py:582(2.1.0 和 main 中代码相同)处使用 async with self.context.lock: 覆盖了整个 httpx auth-flow 生成器,包括每一次 yield。而 anyio.Lock.release() 绑定到获取该锁的任务。当 httpx 从与推进生成器不同的任务关闭该生成器时(请求被取消、网络中断、超时、任务组销毁时常见),async with 的 __aexit__ 在关闭任务中执行并抛出 RuntimeError: The current task is not holding this lock。该异常逃逸到 fire-and-forget 的拆除任务中(表现为 “Task exception was never retrieved”),锁则被永久保持在已占用状态。

讨论中复现确认:挂起在 async with anyio.Lock() 内的流在另一个 asyncio 任务调用 aclose() 时抛出该 RuntimeError,且锁仍保持占用;而同样的流改用 anyio.Semaphore(1) 则可以从另一任务干净关闭,后续流能正常获取信号量。OAuthContext.lock 只被 OAuthClientProvider.async_auth_flow 使用,因此该修改被认为可以保持在 OAuth context 局部范围内。

环境排查

  • 确认 mcp 版本是否为 2.0.0,或是否有 2.1.0 / main 中包含相同 async with self.context.lock 模式的代码。
  • 确认 Python 版本(报告为 CPython 3.11.15 / 3.11)。
  • 确认 anyio 版本(报告为 4.14.2)及后端(asyncio)。
  • 确认操作系统与架构(报告为 macOS darwin / macOS arm64)。
  • 确认是否使用了 OAuth 认证的 streamable-HTTP MCP 服务器,以及是否为长时间存活且复用同一个 OAuthClientProvider 的进程。
  • 确认日志中是否出现重连提示(如 GET stream disconnected, reconnecting...)后紧跟 Task exception was never retrieved。

解决步骤

  1. 先通过日志确认出现了 RuntimeError: The current task is not holding this lock,且指向 mcp/client/auth/oauth2.py 中的 async with self.context.lock。这是定位该缺陷的关键线索。
  2. 确认现象:后续对受影响服务器的连接尝试卡在 async with self.context.lock 处,没有任何 HTTP 流量(报告中通过 socket 检查确认无 SYN,仅存在 CLOSE_WAIT),直到达到连接超时(报告中为 300s)。
  3. 确认毒化状态是否局限于单个进程:从全新进程连接同一 OAuth 服务器应能正常握手(报告中小于 1s 完成 initialize + list_tools),说明 token、网络、服务器本身无问题。
  4. 作为已确认有效的临时缓解措施:重启该进程。报告中手动 kill 进程后问题完全解决。注意其他客户端进程的重启不会影响被毒化的进程,因为该状态是每进程内存态。
  5. 若需从代码层面修复,可优先尝试讨论中提出的方向:将 OAuthContext.lock 从 anyio.Lock 改为 anyio.Semaphore(1),使释放路径不再假设释放任务是获取任务。该方向在复现实验中被验证可跨任务干净关闭。此方案未在 Issue 中合并到发布版本,属于可优先尝试的修改。
  6. 配套回归测试应覆盖生成器生命周期而非仅检查字段类型:从一个任务推进 async_auth_flow 至 yield,从第二个任务关闭它,再证明后续流能继续执行;测试使用仓库的 anyio 风格并用 fail_after 限定等待时间,覆盖跨任务拆除路径。

验证方法

修复后应确认:从非加锁任务关闭正在挂起的 async_auth_flow 生成器时不再抛出 RuntimeError: The current task is not holding this lock,并且后续同一 provider 的 async_auth_flow 能正常获取并执行,连接不再卡在 async with self.context.lock 处。可参照讨论中的最小复现(无网络需求)验证 provider.context.lock.locked() 在关闭后不再保持占用。

参考来源

modelcontextprotocol/python-sdk #3382

官方将本 Issue 作为重复项关闭,并指向 #2847。

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 28765

发表回复

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