From 859ab28d4305d5a7c392318b194fd0dd6562fa92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=83=E5=B2=9A=E4=B9=8B=E5=A4=8F?= <108566281+Blueteemo@users.noreply.github.com> Date: Sun, 3 May 2026 15:09:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=92=89=E9=92=89=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E5=90=8E=E8=87=AA=E5=8A=A8=E9=87=8D=E8=BF=9E?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=20(#7924)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: improve DingTalk adapter error handling in run() method * fix: add retry logic for DingTalk SDK task unexpected exit * fix: use task.add_done_callback to wake thread on task completion, handle UnboundLocalError * refactor: extract retry logic into handle_retry helper function --------- Co-authored-by: Blueteemo --- .../sources/dingtalk/dingtalk_adapter.py | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py index 37c3b09ab..b1a8156a4 100644 --- a/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +++ b/astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py @@ -1,6 +1,7 @@ import asyncio import json import threading +import time import uuid from pathlib import Path from typing import Literal, NoReturn, cast @@ -747,18 +748,59 @@ class DingtalkPlatformAdapter(Platform): async def run(self) -> None: # await self.client_.start() # 钉钉的 SDK 并没有实现真正的异步,start() 里面有堵塞方法。 + # SDK 内部已有 while True 重连循环,但需要监控 task 状态, + # 如果 task 意外退出则重新启动。 + MAX_RETRIES = 5 + RETRY_INTERVAL = 10 + def start_client(loop: asyncio.AbstractEventLoop) -> None: - try: - self._shutdown_event = threading.Event() - task = loop.create_task(self.client_.start()) - self._shutdown_event.wait() - if task.done(): - task.result() - except Exception as e: - if "Graceful shutdown" in str(e): - logger.info("钉钉适配器已被关闭") + retry_count = 0 + + def handle_retry(error_msg: str) -> bool: + """处理重试逻辑,返回 True 表示需要继续重试,False 表示放弃。""" + nonlocal retry_count + logger.error(error_msg) + retry_count += 1 + if retry_count < MAX_RETRIES: + logger.info(f"钉钉适配器尝试重连 ({retry_count}/{MAX_RETRIES})...") + time.sleep(RETRY_INTERVAL) + return True + logger.error("钉钉适配器重连失败,已达最大重试次数") + return False + + while retry_count < MAX_RETRIES: + task = None + try: + self._shutdown_event = threading.Event() + task = loop.create_task(self.client_.start()) + # 当 task 完成时唤醒线程(无论是正常退出还是异常退出) + task.add_done_callback(lambda _: self._shutdown_event.set()) + self._shutdown_event.wait() + if task.done(): + try: + exc = task.exception() + except asyncio.CancelledError: + logger.info("钉钉适配器 task 已取消") + return + if exc: + if "Graceful shutdown" in str(exc): + logger.info("钉钉适配器已被关闭") + return + if handle_retry(f"钉钉 SDK task 异常退出: {exc}"): + continue + return + # task 仍在运行,shutdown_event 被设置(正常关闭) return - logger.error(f"钉钉机器人启动失败: {e}") + except Exception as e: + if "Graceful shutdown" in str(e): + logger.info("钉钉适配器已被关闭") + return + if not handle_retry(f"钉钉机器人启动失败: {e}"): + return + finally: + # 仅在重试/失败路径取消 task,正常关闭不取消 + if task is not None and not task.done() and retry_count > 0: + task.cancel() loop = asyncio.get_running_loop() await loop.run_in_executor(None, start_client, loop)