mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
fix: 钉钉连接超时后自动重连失败 (#7924)
* 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 <Blueteemo@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user