mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
_read_events() returned normally when self._ws was closed-but-non-None (the while-condition is false on entry). _listen_loop treats a normal return as a clean read, resets backoff to 0, and immediately retries — a tight busy-loop pinning CPU. Raising on entry routes it through the reconnect/backoff path instead. Co-authored-by: xushibo <xushibo@users.noreply.github.com> Co-authored-by: cnfi <cnfi@users.noreply.github.com>
This commit is contained in:
parent
5b55f4fe8e
commit
3eeca4613d
2 changed files with 30 additions and 0 deletions
|
|
@ -681,6 +681,12 @@ class QQAdapter(BasePlatformAdapter):
|
|||
"""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()
|
||||
|
|
|
|||
|
|
@ -2196,3 +2196,27 @@ class TestCloseCodeClassification:
|
|||
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())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue