fix(gateway): honor Discord connect timeout for ready wait

This commit is contained in:
konsisumer 2026-06-12 00:39:59 +02:00 committed by Teknium
parent e675a60846
commit 46ab06c238
2 changed files with 57 additions and 3 deletions

View file

@ -125,7 +125,7 @@ from tools.url_safety import is_safe_url
async def _wait_for_ready_or_bot_exit(
ready_event: asyncio.Event,
bot_task: asyncio.Task,
timeout: float,
timeout: Optional[float],
) -> None:
"""Wait until Discord is ready, or surface early bot startup failure.
@ -328,6 +328,20 @@ def _build_allowed_mentions():
)
def _discord_ready_timeout_seconds() -> float:
"""Return the Discord ready wait timeout during gateway startup."""
raw = os.getenv("HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT", "").strip()
if raw:
try:
return max(0.0, float(raw))
except ValueError:
logger.warning(
"Ignoring invalid HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT=%r",
raw,
)
return 30.0
class VoiceReceiver:
"""Captures and decodes voice audio from a Discord voice channel.
@ -1152,9 +1166,14 @@ class DiscordAdapter(BasePlatformAdapter):
self._bot_task = asyncio.create_task(self._client.start(self.config.token))
self._bot_task.add_done_callback(self._handle_bot_task_done)
ready_timeout = _discord_ready_timeout_seconds()
# Wait for ready, but fail fast if discord.py's background startup
# task dies first (for example on SOCKS/proxy connect errors).
await _wait_for_ready_or_bot_exit(self._ready_event, self._bot_task, timeout=30)
await _wait_for_ready_or_bot_exit(
self._ready_event,
self._bot_task,
timeout=None if ready_timeout <= 0 else ready_timeout,
)
self._running = True
self._start_liveness_probe()

View file

@ -382,7 +382,6 @@ async def test_connect_timeout_cancels_bot_task(monkeypatch):
"leaving it alive creates a zombie Discord client that produces duplicate threads"
)
@pytest.mark.asyncio
async def test_disconnect_cancels_running_bot_task(monkeypatch):
"""Regression: disconnect() must cancel _bot_task even when connect() timed out.
@ -415,6 +414,42 @@ async def test_disconnect_cancels_running_bot_task(monkeypatch):
assert zombie_task.cancelled(), "disconnect() must cancel the zombie bot task"
@pytest.mark.asyncio
async def test_connect_ready_wait_uses_gateway_platform_connect_timeout(monkeypatch):
adapter = DiscordAdapter(PlatformConfig(enabled=True, token="test-token"))
monkeypatch.setenv("HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT", "90")
monkeypatch.setattr("gateway.status.acquire_scoped_lock", lambda scope, identity, metadata=None: (True, None))
monkeypatch.setattr("gateway.status.release_scoped_lock", lambda scope, identity: None)
intents = SimpleNamespace(message_content=False, dm_messages=False, guild_messages=False, members=False, voice_states=False)
monkeypatch.setattr(discord_platform.Intents, "default", lambda: intents)
monkeypatch.setattr(
discord_platform.commands,
"Bot",
lambda **kwargs: FakeBot(
intents=kwargs["intents"],
proxy=kwargs.get("proxy"),
allowed_mentions=kwargs.get("allowed_mentions"),
),
)
seen_timeouts = []
async def fake_wait_for_ready(ready_event, bot_task, timeout):
seen_timeouts.append(timeout)
raise asyncio.TimeoutError()
monkeypatch.setattr(
discord_platform, "_wait_for_ready_or_bot_exit", fake_wait_for_ready
)
ok = await adapter.connect()
assert ok is False
assert seen_timeouts == [90.0]
@pytest.mark.asyncio
async def test_connect_does_not_wait_for_slash_sync(monkeypatch):
adapter = DiscordAdapter(PlatformConfig(enabled=True, token="test-token"))