fix(wecom): raise on silent ws close to prevent 100% CPU reconnect spin - #54574
leonxia1010 wants to merge 2 commits into
Conversation
_read_events exited cleanly when self._ws became None or closed between iterations (no CLOSE-typed message). _listen_loop treated this as a successful read, reset backoff_idx to 0, and immediately re-entered _read_events — which returned cleanly again. Result: a tight loop that burned ~100% CPU indefinitely with no asyncio.sleep ever firing, observed in production as a single profile accumulating 90+ CPU-hours over 11 days while gateway.log went silent. Raise explicitly when the loop exits while still _running so the caller takes the except path and applies the existing [2, 5, 10, 30, 60] RECONNECT_BACKOFF.
Required by .github/workflows/contributor-check.yml so the attribution check on this PR's fix commit passes.
Related: this fixes issue #49918 (the |
|
Thanks for the focused regression fix. The premise remains present on current main: The approach also matches the established closed-websocket guard in This is an automated hermes-sweeper review. |
What does this PR do?
_read_events()in the WeCom adapter exited cleanly whenself._wsbecameNoneorclosedbetween iterations without any CLOSE-typed message arriving._listen_looptreated this as a successful read, resetbackoff_idxto 0, andimmediately re-entered
_read_events— which returned cleanly again.Result: a tight loop that burned ~100% CPU indefinitely with no
asyncio.sleepever firing.
Observed in production as a single profile accumulating 90+ CPU-hours over
11 days while
gateway.logwent silent.This complements #28311 (
fix(wecom): handle WSMsgType.CLOSING to prevent CPU spin), which covered graceful server-side shutdowns via a new terminalmessage type. This PR covers the orthogonal case where the websocket
transitions to closed/None between iterations without emitting any terminal
message type at all.
Related Issue
No prior issue — surfaced in a local production deployment after an extended
silent-close interval. Reproducer included in the new test class.
Fixes # (none)
Type of Change
Changes Made
plugins/platforms/wecom/adapter.py—_read_events()now raisesRuntimeError("WeCom websocket closed (no message)")when the loop exitswhile
self._runningis True._listen_loopalready catches this andapplies the existing
[2, 5, 10, 30, 60]RECONNECT_BACKOFF.tests/gateway/test_wecom.py— new regression classTestReadEventsRaisesOnSilentClosewith three cases (ws=None, ws closedwithout close-typed message, clean shutdown when not running).
scripts/release.py— addhuachi1990@hotmail.com→leonxia1010mapping to
AUTHOR_MAPsocontributor-check.ymlpasses for this PR'sfix commit.
How to Test
scripts/run_tests.sh(orvenv/bin/python -m pytest tests/gateway/test_wecom.py -v)pytest tests/gateway/test_wecom.py::TestReadEventsRaisesOnSilentClose -vshould report 3 passed.
hermesconnected to WeCom, force a silent ws close (e.g.break the tunnel without sending a CLOSE frame). With the fix, reconnect
backoff kicks in (
[2,5,10,30,60]s); without it, CPU pegs at 100% andgateway.loggoes silent.Checklist
Code
scripts/release.pyAUTHOR_MAP entry is required by.github/workflows/contributor-check.ymlto gate this PR's attribution)scripts/run_tests.shand all tests passDocumentation & Housekeeping
docs/, docstrings) — N/A (pure asyncio control flow, no public API or doc surface)cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A