QQBot closed WebSocket causes busy-loop, stalled reconnects
Cluster 17703 · gateway. Data as of
2 open items. Fix merged in #40574 on 7 Jun 2026. Check it covers your case.
The short answer
- Known?
- Yes. 18 related items grouped here, 6 issues already closed.
- Fix event
- A fix was merged to main in PR #40574. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #40574. That is separate from the fix event. - Still open
- 1 issue and 1 PR.
Next step: read #40574, then check whether your report is the one still open.
Read #40574Pick up a Lab task
Members consistently concern the QQBot adapter's _read_events/_listen_loop misbehaving when the WebSocket is closed but not None after a network outage or an unsuccessful reconnect, reported as a 100% CPU busy-loop or reconnection stopping. Many are proposed fixes, such as raising an error or adding guards, and some issues mention a similar WeCom pattern.
Open issues1
- Issue#55492QQBot _read_events has same silent-return pattern as WeCom busy-loop (mitigated but inconsistent)
Open PRs1
- PR#55524fix(qqbot): add post-loop guard in _read_events to prevent backoff reset
Fixes on record
-
#40574 fix(qqbot): stop 100% CPU spin when WebSocket is closed but not None (#31193, #31771)
files:
gateway/platforms/qqbot/adapter.py,tests/gateway/test_qqbot.pyShow the diff
-
gateway/platforms/qqbot/adapter.py@@ -681,6 +681,12 @@ async def _read_events(self) -> None: """Read WebSocket frames until connection closes.""" if not self._ws: raise RuntimeError("WebSocket not connected") + if self._ws.closed: + # A closed-but-non-None ws makes the while-condition false on entry, + # so this would return normally — which _listen_loop treats as a + # clean read and immediately retries with backoff reset to 0, + # producing a 100% CPU spin. Raise so the reconnect/backoff path runs. + raise RuntimeError("WebSocket closed") while self._running and self._ws and not self._ws.closed: msg = await self._ws.receive() -
tests/gateway/test_qqbot.py@@ -2196,3 +2196,27 @@ def test_fatal_codes_include_intent_errors(self): assert 4014 in fatal_codes assert 4001 in fatal_codes assert 4915 in fatal_codes + + +class TestReadEventsClosedWsGuard: + """Regression: a closed-but-non-None ws must raise on entry, not return + normally, so _listen_loop goes through reconnect/backoff instead of + busy-looping at 100% CPU (issues #31193 / #31771).""" + + def _make_adapter(self, **extra): + from gateway.platforms.qqbot import QQAdapter + return QQAdapter(_make_config(app_id="a", client_secret="b", **extra)) + + def test_read_events_raises_when_ws_closed_on_entry(self): + adapter = self._make_adapter() + adapter._running = True + adapter._ws = SimpleNamespace(closed=True) + with pytest.raises(RuntimeError): + asyncio.run(adapter._read_events()) + + def test_read_events_raises_when_ws_none(self): + adapter = self._make_adapter() + adapter._running = True + adapter._ws = None + with pytest.raises(RuntimeError): + asyncio.run(adapter._read_events())
-
9 PRs closed without merging
From the close comments: done elsewhere 6 · duplicate 2 · merged via another change 1
-
#17704 fix(qqbot): keep reconnecting after closed websocket · done elsewhere →
3eeca46,#17704,#31193,#31771,#40574 -
#27821 fix(qqbot): prevent busy-loop when WebSocket closes without exception · done elsewhere →
3eeca46,#31193,#31771,#40574 -
#31151 fix(qqbot): raise RuntimeError in _read_events when WebSocket is closed · duplicate →
#17704,#17703,#25070,#20994 -
#31774 fix(qqbot): prevent listener busy-loop after closed-WebSocket reconnect failure (#31771) · merged via another change →
3eeca46,#31771,#40574 -
#32885 fix(qqbot): prevent reconnect stall after closed websocket · done elsewhere →
3eeca46
Show 4 more
-
#39430 fix(qqbot): prevent CPU-spinning tight loop after WebSocket reconnect failure (#17703) · done elsewhere →
#41773 -
#40198 fix(gateway): keep QQBot reconnect loop alive after stale websocket · done elsewhere →
3eeca46 -
#40497 fix(qqbot): prevent busy-loop when WebSocket is closed but not None · done elsewhere →
8dca287 -
#55601 fix(qqbot): raise on silent loop exit to keep _listen_loop consistent · duplicate →
#55524,#55492,#55486
Closed issues · 6
- Closed#17703QQBot stops reconnecting after failed reconnect leaves websocket closed
- Closed#27810[Bug]: QQBot closed WebSocket 导致无限空循环,单线程 CPU 100%
- Closed#31101QQ Bot adapter: _read_events() silent loop after reconnect failure prevents further retries
- Closed#31193QQ Bot Reconnect Busy Loop Causes 100% CPU Spin
- Closed#31771[Bug]: QQBot adapter busy-loops after WebSocket reconnect failure, causing 100% CPU
- Closed#42271fix(qqbot): listen_loop enters permanent dead state after network outage — _read_events() bypasses reconnect logic
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.14: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- gateway
- Merged PRs
- #40574
- Closed issues
- 6
- Attempts
- 9 PRs closed without merging
- Fix link checked
- 7 Jun 2026