fix(mcp): fix lifecycle-task leak in stateful clients and refactor shared logic…#4152
Merged
Conversation
|
Hi @qbc2016, this is your 60th Pull Request. 🎁 Milestone Celebration🎖️🏅 Congratulations! This is your 60th contribution. Thank you for your continued support! You're an important member of the QwenPaw community! 🐾 📋 About PR TemplateTo help maintainers review your PR faster, please make sure to include:
Complete PR information helps speed up the review process. You can edit the PR description to add these details. 🙌 Join Developer CommunityThanks so much for your contribution! We'd love to invite you to join the official QwenPaw developer group! You can find the Discord and DingTalk group links under the "Developer Community" section on our docs page: We truly appreciate your enthusiasm—and look forward to your future contributions! 😊 We'll review your PR soon. |
This was
linked to
issues
May 9, 2026
xieyxclack
approved these changes
May 9, 2026
cofly-io
pushed a commit
to cofly-io/xClaw
that referenced
this pull request
May 12, 2026
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
MCP subprocesses were accumulating as children of the
qwenpaw appdaemon across agent hot-reloads (#4105). Root cause:StdIOStatefulClient.close()andHttpStatefulClient.close()returned early whenis_connected=False, even if the background lifecycle task was still running in a reconnect loop. Every hot-reload calledmcp_manager.close_all(), but the reconnecting clients were silently skipped — their tasks eventually spawned fresh subprocesses that were never cleaned up.A secondary issue (#4100):
HttpStatefulClientdid not recover from transport-layer failures (anyio.ClosedResourceError, HTTP timeouts, etc.) because the error never propagated back to the lifecycle loop.Changes
Bug fixes
close()lifecycle-task leak — always stop the lifecycle task when it is running, regardless ofis_connectedstate (is_connected=False+ running task = reconnect loop, not a clean idle state).close()stale_lifecycle_taskreference on cancellation — movedself._lifecycle_task = Noneto afinallyblock so it is cleared even when the calling coroutine is cancelled (CancelledErrorisBaseException, notException).connect()spurious ready on reconnect — cleared_ready_eventbefore creating the lifecycle task so a secondconnect()afterclose()waits for the new connection rather than returning immediately.reload()returning before reconnect completes — cleared_ready_eventbefore waiting so the caller blocks until the new session is actually established.connect()double lifecycle-task on transport error — addedhas_taskguard to prevent creating a second task when one is already running in a reconnect loop.#4100) — introduced_handle_transport_error()and_TRANSPORT_ERRORSto detect broken-pipe, EOF, andanyio/httpxtransport errors incall_tool/list_toolsand trigger a reconnect via_reload_event, fixing the case wherestreamable_http_client's internalpost_writertask silently closes thewrite stream.
_force_cleanup_clientsilent no-op (manager.py) — replaced the deadclient.stackattribute access (which was alwaysNonefor the new clients) with a directclient.close(ignore_errors=True)call.replace_client()holding lock duringclose()(manager.py) — movedold_client.close()outside the lock to matchremove_client()'s pattern and avoid blockingget_clients()/close_all()for the duration of the lifecycle-task shutdown.Refactoring
_MCPClientMixincontaining all shared logic (_run_lifecycle,connect,reload,close,list_tools,call_tool,_handle_transport_error,_validate_connection). Each concrete class now only implements__init__and_setup_transport.reload()was previously only onStdIOStatefulClient; it is now available onHttpStatefulClientas well via the mixin._TRANSPORT_ERRORStuple for clarity.