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:
Teknium
2026-04-19 11:08:10 -07:00
committed by GitHub
parent 5a23f3291a
commit 7e3b356574
2 changed files with 50 additions and 123 deletions

View File

@@ -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: