From 08b1c44a5330cb690a26a5e1983ff7719ec4439c Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:51:42 -0700 Subject: [PATCH] fix(discord): extend bot-task cancellation to connect()'s generic exception branch Follow-up to #44389: the generic 'except Exception' branch in connect() had the same orphaned-task hazard as the timeout branch. Extract the cancel-and-await logic into _cancel_bot_task() and call it from all three sites (timeout branch, exception branch, disconnect()). Also adds deaneeth to AUTHOR_MAP. --- plugins/platforms/discord/adapter.py | 30 +++++++++++++++------------- scripts/release.py | 1 + 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/plugins/platforms/discord/adapter.py b/plugins/platforms/discord/adapter.py index 391c4b99f9b..c205c5b942b 100644 --- a/plugins/platforms/discord/adapter.py +++ b/plugins/platforms/discord/adapter.py @@ -914,20 +914,28 @@ class DiscordAdapter(BasePlatformAdapter): # this adapter is discarded. Without this, the task keeps running and # a later successful reconnect leaves two active Discord clients that # each process every message, producing duplicate threads/responses. - if self._bot_task and not self._bot_task.done(): - self._bot_task.cancel() - try: - await self._bot_task - except (asyncio.CancelledError, Exception): - pass - self._bot_task = None + await self._cancel_bot_task() self._release_platform_lock() return False except Exception as e: # pragma: no cover - defensive logging logger.error("[%s] Failed to connect to Discord: %s", self.name, e, exc_info=True) + # Same zombie-client hazard as the timeout branch: the background + # client.start() task may already be running when a later setup + # step raises. Cancel it so the discarded adapter cannot connect. + await self._cancel_bot_task() self._release_platform_lock() return False + async def _cancel_bot_task(self) -> None: + """Cancel and await the background client.start() task, if running.""" + if self._bot_task and not self._bot_task.done(): + self._bot_task.cancel() + try: + await self._bot_task + except (asyncio.CancelledError, Exception): + pass + self._bot_task = None + async def disconnect(self) -> None: """Disconnect from Discord.""" # Cancel the bot task before closing the client. If connect() timed out @@ -936,13 +944,7 @@ class DiscordAdapter(BasePlatformAdapter): # discord.py's reconnect loop can ignore the closed flag while a # WebSocket handshake is in flight. Explicitly cancelling the task here # ensures the zombie client cannot receive or dispatch any further events. - if self._bot_task and not self._bot_task.done(): - self._bot_task.cancel() - try: - await self._bot_task - except (asyncio.CancelledError, Exception): - pass - self._bot_task = None + await self._cancel_bot_task() # Clean up all active voice connections before closing the client for guild_id in list(self._voice_clients.keys()): diff --git a/scripts/release.py b/scripts/release.py index b7dc877b246..ba9fc16e1a6 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -63,6 +63,7 @@ AUTHOR_MAP = { "thomas.paquette@gmail.com": "RyTsYdUp", "techxacm@gmail.com": "ProgramCaiCai", "266365592+bmoore210@users.noreply.github.com": "bmoore210", + "123150002+deaneeth@users.noreply.github.com": "deaneeth", "157839748+psionic73@users.noreply.github.com": "psionic73", "manishbyatroy@gmail.com": "manishbyatroy", "chilltulpa@gmail.com": "TheGardenGallery",