fix: suppress RuntimeError in MCP task shutdown when event loop is closed - #80955
Open
shaiful-hisham wants to merge 1 commit into
Open
fix: suppress RuntimeError in MCP task shutdown when event loop is closed#80955shaiful-hisham wants to merge 1 commit into
shaiful-hisham wants to merge 1 commit into
Conversation
…osed
When exiting Hermes, MCP server tasks that are still pending can
encounter an already-closed event loop during cancellation. Calling
t.cancel() after the loop is closed raises RuntimeError('Event loop
is closed'), which surfaces as 'Exception ignored' during garbage
collection.
Four call sites patched:
- _wait_for_lifecycle_event finally block (3 sites): wrap t.cancel()
in try/except RuntimeError so cancelled tasks clean up silently
when the loop is already gone
- _stop_mcp_loop drain path: always attempt drain regardless of
safe_schedule_threadsafe success, so tasks are cancelled before
loop.close() is called
Collaborator
Author
|
Thanks for the triage note. I've updated the PR description with a Consolidation section — this PR is intentionally the consolidated fix for the closed-loop cancellation family:
Happy to coordinate with the #60032 author so we land one shared shutdown strategy rather than overlapping fixes — this PR is ready to serve as the consolidation point. |
zons-zhaozhy
pushed a commit
to zons-zhaozhy/hermes-agent
that referenced
this pull request
Aug 10, 2026
…+ #68270精华 mcp_tool.py (3 call sites): - NousResearch#80955 三段式: t.cancel() in try → except RuntimeError skip await → else await - 消除 TOCTOU 竞态 (is_closed() check + await gap) - 统一所有 3 个 _wait_*_reconnect call sites cli.py: - NousResearch#68270 提取 _suppress_closed_loop_errors 到模块级 (line ~1330) - interactive + single-query 两条路径共享同一 handler - 消除 interactive 路径的闭包重复定义
23 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When exiting Hermes, MCP server tasks that are still pending can encounter an already-closed event loop during cancellation. Calling
t.cancel()after the loop is closed raisesRuntimeError('Event loop is closed'), which surfaces asException ignored in: <coroutine object MCPServerTask.run>during garbage collection.Symptom
Fix (4 call sites in
tools/mcp_tool.py)_wait_for_lifecycle_eventfinally — wrapt.cancel()intry/except RuntimeErrorso cancelled tasks clean up silently when the loop is already gone_wait_for_reconnect_or_shutdownfinally — same pattern_wait_for_lazy_reconnectfinally — same pattern_stop_mcp_loopdrain path — changeelif not loop.is_closed()toif not stop_owned_by_loop and not loop.is_closed()so drain is always attempted regardless of schedule successTested
Verified on macOS — the "Exception ignored" traceback no longer appears on
hermesexit.Consolidation
This PR is the consolidated fix for the closed-loop cancellation family, superseding #60032, #60104, and #60380. Coverage comparison:
_wait_for_lifecycle_eventfinally_wait_for_reconnect_or_shutdownfinally_wait_for_lazy_reconnectfinally_stop_mcp_loopdrain pathThis PR fixes the whole bug class — every
finallyblock that callst.cancel()on a possibly-closed loop, plus the drain path in_stop_mcp_loopso tasks are always cancelled beforeloop.close(). Happy to coordinate with #60032's author (open) so we land one shared shutdown strategy rather than overlapping fixes.