fix: keep DingTalk stream reconnecting

This commit is contained in:
Soulter
2026-06-06 00:19:52 +08:00
parent 7e22a07e0d
commit a308ce9496
2 changed files with 59 additions and 17 deletions

View File

@@ -34,6 +34,18 @@ from astrbot.core.utils.media_utils import (
from ...register import register_platform_adapter
from .dingtalk_event import DingtalkMessageEvent
DINGTALK_RECONNECT_INITIAL_DELAY = 10
DINGTALK_RECONNECT_MAX_DELAY = 300
DINGTALK_RECONNECT_STABLE_SECONDS = 300
def _dingtalk_reconnect_delay(retry_count: int) -> int:
safe_retry_count = max(retry_count, 1)
return min(
DINGTALK_RECONNECT_INITIAL_DELAY * 2 ** (safe_retry_count - 1),
DINGTALK_RECONNECT_MAX_DELAY,
)
class MyEventHandler(dingtalk_stream.EventHandler):
async def process(self, event: dingtalk_stream.EventMessage):
@@ -750,26 +762,26 @@ class DingtalkPlatformAdapter(Platform):
# 钉钉的 SDK 并没有实现真正的异步start() 里面有堵塞方法。
# SDK 内部已有 while True 重连循环,但需要监控 task 状态,
# 如果 task 意外退出则重新启动。
MAX_RETRIES = 5
RETRY_INTERVAL = 10
def start_client(loop: asyncio.AbstractEventLoop) -> None:
retry_count = 0
def handle_retry(error_msg: str) -> bool:
"""处理重试逻辑,返回 True 表示需要继续重试False 表示放弃。"""
def handle_retry(error_msg: str, run_seconds: float) -> None:
nonlocal retry_count
logger.error(error_msg)
if run_seconds >= DINGTALK_RECONNECT_STABLE_SECONDS:
retry_count = 0
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
delay = _dingtalk_reconnect_delay(retry_count)
logger.info(
f"钉钉适配器将在 {delay} 秒后重连 (第 {retry_count} 次)...",
)
time.sleep(delay)
while retry_count < MAX_RETRIES:
while True:
task = None
should_cancel_task = False
start_time = time.monotonic()
try:
self._shutdown_event = threading.Event()
task = loop.create_task(self.client_.start())
@@ -786,20 +798,27 @@ class DingtalkPlatformAdapter(Platform):
if "Graceful shutdown" in str(exc):
logger.info("钉钉适配器已被关闭")
return
if handle_retry(f"钉钉 SDK task 异常退出: {exc}"):
continue
return
should_cancel_task = True
handle_retry(
f"钉钉 SDK task 异常退出: {exc}",
time.monotonic() - start_time,
)
continue
# task 仍在运行shutdown_event 被设置(正常关闭)
return
except Exception as e:
if "Graceful shutdown" in str(e):
logger.info("钉钉适配器已被关闭")
return
if not handle_retry(f"钉钉机器人启动失败: {e}"):
return
should_cancel_task = True
handle_retry(
f"钉钉机器人启动失败: {e}",
time.monotonic() - start_time,
)
continue
finally:
# 仅在重试/失败路径取消 task正常关闭不取消
if task is not None and not task.done() and retry_count > 0:
if task is not None and not task.done() and should_cancel_task:
task.cancel()
loop = asyncio.get_running_loop()

View File

@@ -0,0 +1,23 @@
from astrbot.core.platform.sources.dingtalk.dingtalk_adapter import (
DINGTALK_RECONNECT_INITIAL_DELAY,
DINGTALK_RECONNECT_MAX_DELAY,
_dingtalk_reconnect_delay,
)
def test_dingtalk_reconnect_delay_uses_exponential_backoff():
assert [_dingtalk_reconnect_delay(i) for i in range(1, 5)] == [
10,
20,
40,
80,
]
def test_dingtalk_reconnect_delay_has_minimum_delay():
assert _dingtalk_reconnect_delay(0) == DINGTALK_RECONNECT_INITIAL_DELAY
assert _dingtalk_reconnect_delay(-1) == DINGTALK_RECONNECT_INITIAL_DELAY
def test_dingtalk_reconnect_delay_is_capped():
assert _dingtalk_reconnect_delay(20) == DINGTALK_RECONNECT_MAX_DELAY