Skip to content

fix(gateway): prevent 100% CPU spin when QQBot WebSocket is closed - #29010

Closed
win847 wants to merge 2 commits into
NousResearch:mainfrom
win847:fix/gateway-read-events-ws-closed-loop
Closed

fix(gateway): prevent 100% CPU spin when QQBot WebSocket is closed#29010
win847 wants to merge 2 commits into
NousResearch:mainfrom
win847:fix/gateway-read-events-ws-closed-loop

Conversation

@win847

@win847 win847 commented May 20, 2026

Copy link
Copy Markdown

When the QQ Bot WebSocket transitions to a closed state (msg.type == WSMsgType.CLOSED), _read_events() raises RuntimeError once, which is caught by the except Exception block. However, _reconnect() only sets self._ws.closed = True but does not set self._ws = None. On the next loop iteration, _read_events() checks 'if not self._ws' (False, since it is still set) and enters the inner while loop, whose condition 'not self._ws.closed' is immediately False. The function returns normally without raising, causing the outer loop to reset backoff_idx to 0 - resulting in a tight 100% CPU spin loop that never reconnects until the process is killed.

Fix: add a 'self._ws.closed' guard at the top of _read_events() so it raises RuntimeError consistently whenever the transport is unusable, forcing the caller's except Exception block to drive proper reconnection with backoff.

When the QQ Bot WebSocket transitions to a closed state (msg.type ==
WSMsgType.CLOSED), _read_events() raises RuntimeError once, which is
caught by the except Exception block. However, _reconnect() temporarily
closes the old ws object (self._ws.closed = True) but does not set
self._ws = None. On the next loop iteration, _read_events() checks
'if not self._ws' (False, since it's still set) and enters the inner
while loop, whose condition 'not self._ws.closed' is immediately False.
The function returns normally without raising, causing the outer loop
to reset backoff_idx to 0 — resulting in a tight 100% CPU spin loop
that never reconnects until the process is killed.

Fix: add a 'self._ws.closed' guard at the top of _read_events() so it
raises RuntimeError consistently whenever the transport is unusable,
forcing the caller's except Exception block to drive proper reconnection
with backoff.
ethernet8023
ethernet8023 previously approved these changes May 20, 2026
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists platform/qqbot QQ Bot adapter comp/gateway Gateway runner, session dispatch, delivery labels May 20, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Competing fix for #17703 (QQBot CPU spin on closed WebSocket). See also #27821, #20994, and #19414 which address the same root cause via different approaches.

@teknium1

Copy link
Copy Markdown
Contributor

This has been implemented on current main by the merged QQBot reconnect fix.

Evidence from this automated hermes-sweeper review:

  • gateway/platforms/qqbot/adapter.py:684 on current main now checks self._ws.closed at _read_events() entry and raises RuntimeError("WebSocket closed"), preventing the normal-return/backoff-reset busy loop.
  • tests/gateway/test_qqbot.py:2201 adds regression coverage for closed-but-non-None and None WebSocket entry states.
  • The implementing merge is 3eeca4613d618618093db416b564a2b9ef8dbe6a from fix(qqbot): stop 100% CPU spin when WebSocket is closed but not None (#31193, #31771) #40574, which explicitly fixes the same QQBot 100% CPU spin described here.

Thanks for the report and patch. The earlier maintainer note correctly identified this as one of several competing fixes for the same QQBot root cause; main now carries that fix.

@teknium1 teknium1 closed this Jun 15, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/qqbot QQ Bot adapter sweeper:implemented-on-main Sweeper: behavior already present on current main type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants