Files
hermes-agent/tests/gateway/test_ws_dial_happy_eyeballs.py
teknium1 3ffee76761 fix(gateway): race IPv6/IPv4 on every cold-start WebSocket dial
The bootstrap racer covers sync connects only. The gateway's WebSocket dials
(relay connector, Yuanbao, Buzz) go through ``websockets.connect`` →
``loop.create_connection``, whose ``happy_eyeballs_delay`` defaults to ``None``:
a serial walk that burns the full connect timeout on every blackholed AAAA
record before IPv4 answers — the same stall class #114265 reports, one layer up.

``websockets`` forwards unknown kwargs to ``loop.create_connection``, so each
call site passes ``happy_eyeballs_delay=0.25`` (the RFC 8305 delay anyio and
the sync racer already use). One invariant test per call site captures the
kwargs at a mocked ``websockets.connect``.
2026-09-18 09:56:28 -07:00

57 lines
1.9 KiB
Python

"""Cold-start WebSocket dials race IPv6/IPv4 (#114265).
``websockets.connect`` forwards unknown kwargs to ``loop.create_connection``, whose
``happy_eyeballs_delay`` defaults to ``None`` (serial walk over getaddrinfo results). On a
network with an advertised-but-blackholed IPv6 route every AAAA record burns the full
connect timeout before IPv4 answers — the same stall class the bootstrap racer closes for
sync connects. Each Hermes ``websockets.connect`` call site must pass the RFC 8305 delay.
"""
from __future__ import annotations
import asyncio
from unittest.mock import MagicMock, patch
import pytest
pytest.importorskip("websockets")
EXPECTED_DELAY = 0.25
class _Recorder:
def __init__(self):
self.kwargs = None
async def __call__(self, *_args, **kwargs):
self.kwargs = kwargs
raise ConnectionRefusedError("recorded")
@pytest.mark.asyncio
async def test_relay_ws_transport_dial_races_ipv6_ipv4(monkeypatch):
import gateway.relay.ws_transport as mod
rec = _Recorder()
monkeypatch.setattr(mod.websockets, "connect", rec)
t = mod.WebSocketRelayTransport("ws://unused", "discord", "bot", reconnect=False)
with pytest.raises(ConnectionRefusedError):
await t._dial_and_start()
assert rec.kwargs["happy_eyeballs_delay"] == EXPECTED_DELAY
@pytest.mark.asyncio
async def test_yuanbao_dial_races_ipv6_ipv4():
from gateway.platforms.yuanbao import ConnectionManager, YuanbaoAdapter
adapter = MagicMock(spec=YuanbaoAdapter)
adapter.name = "yuanbao"
adapter._bot_id = "b"
adapter._ws_url = "wss://test.example.com/ws"
cm = ConnectionManager(adapter)
rec = _Recorder()
with patch("gateway.platforms.yuanbao.websockets.connect", rec), pytest.raises(ConnectionRefusedError):
await cm._dial({"bot_id": "b", "token": "t"})
assert rec.kwargs["happy_eyeballs_delay"] == EXPECTED_DELAY