[fix] (discord_platform_adapter) 解决了 “Discord 平台无法优雅重载” 的 bug
#### 问题现象(AI总结) - 在通过 Web 面板或配置变更热重载 Discord 平台时,适配器的 terminate() 方法会被调用,但经常出现“卡死”或长时间无响应,导致 Discord 平台无法优雅重载。 - 日志显示停留在“正在清理已注册的斜杠指令...”等步骤,甚至出现超时或异常。 #### 2. 原因分析 - 适配器的 terminate() 方法中,涉及多个异步操作(如取消 polling 任务、清理斜杠指令、关闭客户端)。 - 某些 await 操作(如 await self.client.sync_commands() 或 await self.client.close())在网络异常、事件循环被取消等情况下,可能会阻塞或抛出 CancelledError,导致整个重载流程卡住。 - 之前的实现没有对这些 await 操作加超时保护,也没有分步日志,难以定位具体卡点。 #### 3. 修复措施 - 分步日志:在 terminate() 的每个关键步骤前后都加了详细日志,便于定位卡点。 - 超时保护:对所有关键 await 操作(如 polling 任务取消、指令清理、客户端关闭)都加了 asyncio.wait_for(..., timeout=10),防止无限阻塞。 - 健壮性提升:先 cancel polling 任务,再清理指令,最后关闭客户端。每一步都捕获异常并输出日志,保证即使某一步失败也能继续后续清理。 - 避免重复终止:移除了 run() 方法中的 finally: await self.terminate(),只允许外部统一调度,防止重复调用导致资源冲突或日志重复。 #### 4. 修复效果 - 现在 Discord 平台适配器在热重载或终止时,能优雅地依次完成所有清理步骤,不会因某一步阻塞导致整个流程卡死。
This commit is contained in:
@@ -46,6 +46,8 @@ class DiscordPlatformAdapter(Platform):
|
||||
self.enable_command_register = self.config.get("discord_command_register", True)
|
||||
self.guild_id = self.config.get("discord_guild_id_for_debug", None)
|
||||
self.activity_name = self.config.get("discord_activity_name", None)
|
||||
self.shutdown_event = asyncio.Event()
|
||||
self._polling_task = None
|
||||
|
||||
@override
|
||||
async def send_by_session(
|
||||
@@ -106,7 +108,6 @@ class DiscordPlatformAdapter(Platform):
|
||||
@override
|
||||
async def run(self):
|
||||
"""主要运行逻辑"""
|
||||
|
||||
# 初始化回调函数
|
||||
async def on_received(message_data):
|
||||
logger.debug(f"[Discord] 收到消息: {message_data}")
|
||||
@@ -137,13 +138,15 @@ class DiscordPlatformAdapter(Platform):
|
||||
self.client.on_ready_once_callback = callback
|
||||
|
||||
try:
|
||||
await self.client.start_polling()
|
||||
self._polling_task = asyncio.create_task(self.client.start_polling())
|
||||
await self.shutdown_event.wait()
|
||||
except discord.errors.LoginFailure:
|
||||
logger.error("[Discord] 登录失败。请检查你的 Bot Token 是否正确。")
|
||||
except discord.errors.ConnectionClosed:
|
||||
logger.warning("[Discord] 与 Discord 的连接已关闭。")
|
||||
except Exception as e:
|
||||
logger.error(f"[Discord] 适配器运行时发生意外错误: {e}", exc_info=True)
|
||||
# finally: 不再自动调用 self.terminate(),只允许外部统一调度
|
||||
|
||||
def _get_message_type(
|
||||
self, channel: Messageable, guild_id: int | None = None
|
||||
@@ -166,7 +169,6 @@ class DiscordPlatformAdapter(Platform):
|
||||
|
||||
content = message.content
|
||||
|
||||
|
||||
# 如果机器人被@,移除@部分
|
||||
# 剥离 User Mention (<@id>, <@!id>)
|
||||
if self.client and self.client.user:
|
||||
@@ -262,27 +264,39 @@ class DiscordPlatformAdapter(Platform):
|
||||
|
||||
self.commit_event(message_event)
|
||||
|
||||
|
||||
@override
|
||||
async def terminate(self):
|
||||
"""终止适配器"""
|
||||
logger.info("[Discord] 正在终止适配器...")
|
||||
|
||||
logger.info("[Discord] 正在终止适配器... (step 1: cancel polling task)")
|
||||
self.shutdown_event.set()
|
||||
# 优先 cancel polling_task
|
||||
if self._polling_task:
|
||||
self._polling_task.cancel()
|
||||
try:
|
||||
await asyncio.wait_for(self._polling_task, timeout=10)
|
||||
except asyncio.CancelledError:
|
||||
logger.info("[Discord] polling_task 已取消。")
|
||||
except Exception as e:
|
||||
logger.warning(f"[Discord] polling_task 取消异常: {e}")
|
||||
logger.info("[Discord] 正在清理已注册的斜杠指令... (step 2)")
|
||||
# 清理指令
|
||||
if self.enable_command_register and self.client:
|
||||
logger.info("[Discord] 正在清理已注册的斜杠指令...")
|
||||
try:
|
||||
# 传入空的列表来清除所有全局指令
|
||||
# 如果指定了 guild_id,则只清除该服务器的指令
|
||||
await self.client.sync_commands(
|
||||
commands=[], guild_ids=[self.guild_id] if self.guild_id else None
|
||||
await asyncio.wait_for(
|
||||
self.client.sync_commands(
|
||||
commands=[], guild_ids=[self.guild_id] if self.guild_id else None
|
||||
),
|
||||
timeout=10
|
||||
)
|
||||
logger.info("[Discord] 指令清理完成。")
|
||||
except Exception as e:
|
||||
logger.error(f"[Discord] 清理指令时发生错误: {e}", exc_info=True)
|
||||
|
||||
logger.info("[Discord] 正在关闭 Discord 客户端... (step 3)")
|
||||
if self.client and hasattr(self.client, "close"):
|
||||
await self.client.close()
|
||||
try:
|
||||
await asyncio.wait_for(self.client.close(), timeout=10)
|
||||
except Exception as e:
|
||||
logger.warning(f"[Discord] 客户端关闭异常: {e}")
|
||||
logger.info("[Discord] 适配器已终止。")
|
||||
|
||||
def register_handler(self, handler_info):
|
||||
|
||||
Reference in New Issue
Block a user