mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
refactor(discord): slim down the race-polish fix (#12644)
PR #12558 was heavy for what the fix actually is — essay-length comments, a dedicated helper method where a setdefault would do, and a source-inspection test with no real behavior coverage. The genuine code change is ~5 lines of new logic (1 field, 2 async with, an on_ready wait block). Trimmed: - Replaced the 12-line _voice_lock_for helper with a setdefault one-liner at each call site (join_voice_channel, leave_voice_channel). - Collapsed the 12-line comment on on_message's _ready_event wait to 3 lines. Dropped the warning log on timeout — pass-on-timeout is fine; if on_ready hangs that long, the bot is already broken and the log wouldn't help. - Dropped the source-inspection test (greps the module source for expected substrings). It was low-value scaffolding; the voice-serialization test covers actual behavior. Net: -73 lines vs PR #12558. Same two guarantees preserved, same test passes (verified by stashing the fix and confirming failure).
This commit is contained in:
@@ -637,29 +637,14 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||
|
||||
@self._client.event
|
||||
async def on_message(message: DiscordMessage):
|
||||
# Wait for on_ready to finish resolving username-based
|
||||
# allowlist entries. Without this block, messages
|
||||
# arriving between Discord's READY event and the end
|
||||
# of _resolve_allowed_usernames compare author IDs
|
||||
# (numeric) against a set that may still contain raw
|
||||
# usernames (strings) from DISCORD_ALLOWED_USERS —
|
||||
# legitimate users get silently rejected for the first
|
||||
# few seconds after every reconnect. The wait is a
|
||||
# near-instant no-op in steady state (_ready_event is
|
||||
# already set); only the startup / reconnect window
|
||||
# ever blocks.
|
||||
# Block until _resolve_allowed_usernames has swapped
|
||||
# any raw usernames in DISCORD_ALLOWED_USERS for numeric
|
||||
# IDs (otherwise on_message's author.id lookup can miss).
|
||||
if not adapter_self._ready_event.is_set():
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
adapter_self._ready_event.wait(),
|
||||
timeout=30.0,
|
||||
)
|
||||
await asyncio.wait_for(adapter_self._ready_event.wait(), timeout=30.0)
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning(
|
||||
"[%s] on_message timed out waiting for _ready_event; "
|
||||
"allowlist check may use pre-resolved entries",
|
||||
adapter_self.name,
|
||||
)
|
||||
pass
|
||||
|
||||
# Dedup: Discord RESUME replays events after reconnects (#4777)
|
||||
if adapter_self._dedup.is_duplicate(str(message.id)):
|
||||
@@ -1256,28 +1241,13 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||
# Voice channel methods (join / leave / play)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _voice_lock_for(self, guild_id: int) -> "asyncio.Lock":
|
||||
"""Return the per-guild lock, creating it on first use.
|
||||
|
||||
Voice join/leave/move must be serialized per guild — without
|
||||
this, two concurrent /voice channel invocations both see
|
||||
_voice_clients.get(guild_id) return None, both call
|
||||
channel.connect(), and discord.py raises ClientException
|
||||
('Already connected') on the loser.
|
||||
"""
|
||||
lock = self._voice_locks.get(guild_id)
|
||||
if lock is None:
|
||||
lock = asyncio.Lock()
|
||||
self._voice_locks[guild_id] = lock
|
||||
return lock
|
||||
|
||||
async def join_voice_channel(self, channel) -> bool:
|
||||
"""Join a Discord voice channel. Returns True on success."""
|
||||
if not self._client or not DISCORD_AVAILABLE:
|
||||
return False
|
||||
guild_id = channel.guild.id
|
||||
|
||||
async with self._voice_lock_for(guild_id):
|
||||
async with self._voice_locks.setdefault(guild_id, asyncio.Lock()):
|
||||
# Already connected in this guild?
|
||||
existing = self._voice_clients.get(guild_id)
|
||||
if existing and existing.is_connected():
|
||||
@@ -1307,7 +1277,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||
|
||||
async def leave_voice_channel(self, guild_id: int) -> None:
|
||||
"""Disconnect from the voice channel in a guild."""
|
||||
async with self._voice_lock_for(guild_id):
|
||||
async with self._voice_locks.setdefault(guild_id, asyncio.Lock()):
|
||||
# Stop voice receiver first
|
||||
receiver = self._voice_receivers.pop(guild_id, None)
|
||||
if receiver:
|
||||
|
||||
Reference in New Issue
Block a user