From f20f4f11e110e071d6b49270649681122a10849c Mon Sep 17 00:00:00 2001 From: Beatrice-betty Date: Wed, 19 Aug 2026 23:45:37 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(config,watchdog):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E7=9C=8B=E9=97=A8=E7=8B=97=E9=80=BB=E8=BE=91=EF=BC=8C=E6=8B=86?= =?UTF-8?q?=E5=88=86=E9=85=8D=E7=BD=AE=E9=A1=B9=E5=B9=B6=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E5=88=B6=E5=AE=9A=E6=97=B6=E9=87=8D=E5=90=AF=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- alas.py | 165 ++++++++++++++++----------- config/template.json | 4 +- module/config/argument/args.json | 20 ++-- module/config/argument/argument.yaml | 6 +- module/config/config_generated.py | 4 +- module/config/i18n/en-US.json | 16 ++- module/config/i18n/ja-JP.json | 16 ++- module/config/i18n/zh-CN.json | 16 ++- module/config/i18n/zh-MIAO.json | 16 ++- module/config/i18n/zh-TW.json | 16 ++- 10 files changed, 182 insertions(+), 97 deletions(-) diff --git a/alas.py b/alas.py index 9424a8994..7d2f70391 100644 --- a/alas.py +++ b/alas.py @@ -1,5 +1,4 @@ import json -import logging import os import re import shutil @@ -30,15 +29,11 @@ # 看门狗配置 -# 守护线程每 N 秒检查一次最近一条日志的时间戳;任务执行期间若超过 -# WATCHDOG_THRESHOLD 秒无任何日志输出,则判定主线程卡死,强制杀死模拟器 -# 进程以解除阻塞(主线程的下次 I/O 调用会失败并走异常恢复流程)。 +# 守护线程每 N 秒检查一次任务运行状态;任务执行期间若超过配置的 +# 超时时间,则判定任务逻辑死循环,强制杀死模拟器进程以中断任务。 # 看门狗仅在任务执行阶段(self.run() 期间)激活,空闲等待(wait_until、 # 服务器维护检查)期间自动暂停,避免误触发。 WATCHDOG_CHECK_INTERVAL = 30 -# 日志心跳超时(秒),仅作为配置读取失败的兜底默认值 -# 实际值从配置 Error.WatchdogLogTimeout 读取,可在 WebUI「调试设置」中修改 -WATCHDOG_LOG_TIMEOUT_DEFAULT = 300 # 单个任务最长运行时间(分钟),仅作为配置读取失败的兜底默认值 # 实际值从配置 Error.WatchdogTaskTimeout 读取,0 表示禁用 WATCHDOG_TASK_TIMEOUT_DEFAULT = 120 @@ -46,20 +41,6 @@ RESTART_EMULATOR_OP_TIMEOUT = 120 -class _LogHeartbeatHandler(logging.Handler): - """记录最近一条日志时间戳的日志处理器,供看门狗线程检测主线程卡死。 - - 任何 logger.info/warning/error 等调用都会更新 last_log_time, - 包括看门狗自身的日志——这恰好防止看门狗在触发恢复后立即再次触发。 - """ - - def __init__(self): - super().__init__(level=logging.DEBUG) - self.last_log_time = time.monotonic() - - def emit(self, record): - self.last_log_time = time.monotonic() - # 缓存 i18n 任务名查找 _i18n_task_names = None def _get_task_display_name(task_command): @@ -115,7 +96,6 @@ def __init__(self, config_name=DEFAULT_CONFIG_NAME): self._watchdog_stop = threading.Event() self._watchdog_active = False # 仅在任务执行期间激活 self._watchdog_thread = None - self._log_heartbeat = _LogHeartbeatHandler() self._watchdog_task_start = 0.0 # 当前任务开始时间(monotonic) self._watchdog_task_name = '' # 当前任务名 @@ -233,20 +213,44 @@ def worker(): return result[0] def _start_watchdog(self): - """启动看门狗守护线程,并注册日志心跳处理器。""" + """启动看门狗守护线程。 + + 以下任一条件满足时启动: + 1. Error.WatchdogEnable 为 True(任务超时检测) + 2. EmulatorManagement.ScheduledEmulatorRestart 和 ForceScheduledRestart + 都为 True(强制定时重启) + + 启动后各检测由对应子开关单独控制。 + """ + # 检查是否需要启动看门狗 + try: + master_enable = bool(self.config.Error_WatchdogEnable) + except Exception: + master_enable = False + try: + force_restart = ( + bool(self.config.EmulatorManagement_ScheduledEmulatorRestart) + and bool(self.config.EmulatorManagement_ForceScheduledRestart) + ) + except Exception: + force_restart = False + + if not master_enable and not force_restart: + logger.info('[Alas][看门狗] 无需启动看门狗(总开关和强制定时重启均未开启)') + return + if self._watchdog_thread is not None and self._watchdog_thread.is_alive(): logger.warning('[Alas][看门狗] 看门狗已在运行,跳过启动') return - # 注册日志心跳处理器(若未注册) - if self._log_heartbeat not in logger.handlers: - logger.addHandler(self._log_heartbeat) self._watchdog_stop.clear() - self._log_heartbeat.last_log_time = time.monotonic() self._watchdog_thread = threading.Thread( target=self._watchdog_loop, daemon=True, name='alas-watchdog' ) self._watchdog_thread.start() - logger.info('[Alas][看门狗] 看门狗已启动') + logger.info( + f'[Alas][看门狗] 看门狗已启动' + f'(任务超时: {master_enable}, 强制定时重启: {force_restart})' + ) def _stop_watchdog(self): """停止看门狗守护线程。""" @@ -257,38 +261,74 @@ def _stop_watchdog(self): logger.info('[Alas][看门狗] 看门狗已停止') def _watchdog_loop(self): - """看门狗主循环:检测两类异常并强制恢复。 + """看门狗主循环:检测任务运行时间超时和强制定时重启。 + + 看门狗在 _start_watchdog 判断是否启动(任一检测开启即启动)。 + 各检测由对应开关单独控制: - 1. 日志心跳超时:任务执行期间 WATCHDOG_THRESHOLD 秒无任何日志输出 - → 主线程卡死在 I/O 调用中(u2 HTTP / ADB shell) - 2. 任务运行时间超时:单个任务运行超过配置的 WatchdogTaskTimeout 分钟 + 1. 任务运行时间超时:Error.WatchdogEnable + Error.WatchdogTaskEnable + 单个任务运行超过配置的 WatchdogTaskTimeout 分钟 → 任务逻辑死循环(如 story_skip 不断点击但剧情无法跳过) 此时日志仍在更新,但任务无法自然退出 + 2. 强制定时重启:EmulatorManagement.ScheduledEmulatorRestart + + EmulatorManagement.ForceScheduledRestart + 到达重启间隔且当前为非敏感任务,强制重启模拟器 - 两类异常的恢复方式相同:强制杀死模拟器进程,使主线程的下次 I/O - 调用失败并抛出异常,触发正常的异常恢复流程。 + 恢复方式:强制杀死模拟器进程,使主线程的下次 I/O 调用失败并抛出 + 异常,触发正常的异常恢复流程。 """ while not self._watchdog_stop.wait(WATCHDOG_CHECK_INTERVAL): if not self._watchdog_active: continue - # 检查 1:日志心跳超时(主线程卡死) - # 从配置读取超时阈值(秒),0 表示禁用 + # 检查 1:强制定时重启(非敏感任务时强制中断) + # 需要 ScheduledEmulatorRestart 和 ForceScheduledRestart 都为 True try: - log_timeout = int(self.config.Error_WatchdogLogTimeout) + scheduled = bool(self.config.EmulatorManagement_ScheduledEmulatorRestart) + force = bool(self.config.EmulatorManagement_ForceScheduledRestart) except Exception: - log_timeout = WATCHDOG_LOG_TIMEOUT_DEFAULT - if log_timeout > 0: - elapsed_log = time.monotonic() - self._log_heartbeat.last_log_time - if elapsed_log > log_timeout: - self._watchdog_recover(elapsed_log, reason='log_timeout') - continue + scheduled = False + force = False + if scheduled and force and self._watchdog_task_name: + # 检查当前任务是否为敏感任务 + task_name_camelize = inflection.camelize(self._watchdog_task_name) + try: + sensitive = self.config.cross_get( + keys=f'{task_name_camelize}.Scheduler.Sensitive', default=False + ) + except Exception: + sensitive = False + if not sensitive: + # 检查是否到了重启间隔 + try: + interval = int(self.config.EmulatorManagement_RestartIntervalHours) + except Exception: + interval = 4 + elapsed_hours = (time.monotonic() - self.last_emulator_restart_time) / 3600 + if elapsed_hours >= interval: + logger.critical( + f'[Alas][看门狗] 模拟器已运行 {elapsed_hours:.1f} 小时' + f'(超过 {interval} 小时),开启强制定时重启,' + f'当前任务 `{self._watchdog_task_name}` 为非敏感任务,' + f'强制杀死模拟器进程以中断任务' + ) + self._watchdog_recover( + elapsed_hours * 3600, + reason='force_scheduled_restart', + task_name=self._watchdog_task_name, + ) + continue # 检查 2:任务运行时间超时(逻辑死循环) + # 需要 WatchdogEnable 和 WatchdogTaskEnable 都为 True # 即使日志在更新,如果任务运行时间过长,说明陷入了无法 # 自然退出的循环(如 GameTooManyClickError 被 click_record_clear # 绕过、地图寻路死循环等),需强制中断 - if self._watchdog_task_start > 0: + try: + task_enable = bool(self.config.Error_WatchdogTaskEnable) + except Exception: + task_enable = False + if task_enable and self._watchdog_task_start > 0: # 从配置读取超时阈值(分钟),0 表示禁用 try: timeout_min = int(self.config.Error_WatchdogTaskTimeout) @@ -303,28 +343,23 @@ def _watchdog_loop(self): task_name=self._watchdog_task_name, ) - def _watchdog_recover(self, elapsed, reason='log_timeout', task_name=''): - """看门狗恢复动作:强制杀死模拟器进程以解除主线程阻塞。 + def _watchdog_recover(self, elapsed, reason='task_timeout', task_name=''): + """看门狗恢复动作:强制杀死模拟器进程以中断任务。 - 主线程可能卡在 u2 HTTP 调用、ADB shell、截图等 I/O 操作中, - 或陷入逻辑死循环(如 story_skip 不断点击但剧情无法跳过)。 - 杀死模拟器进程会同时杀死 atx-agent,使主线程的下次 I/O 调用 - 因连接断开而失败并抛出异常,触发正常的异常恢复流程 + 任务陷入逻辑死循环(如 story_skip 不断点击但剧情无法跳过), + 日志仍在更新但任务无法自然退出。杀死模拟器进程会同时杀死 + atx-agent,使主线程的下次 I/O 调用因连接断开而失败并抛出异常, + 触发正常的异常恢复流程 (EmulatorNotRunningError → _try_restart_emulator + task_call('Restart'))。 - 本方法的日志会更新 last_log_time,防止看门狗在恢复期间重复触发; - 若主线程仍未恢复,下一个阈值周期后看门狗会再次触发。 - emulator_stop() 本身也可能卡住(如 psutil 遍历缓慢或 subprocess 不返回),因此用 _emulator_op_with_timeout 包装,超时后放弃本轮 恢复,等待下一个阈值周期重试。 Args: elapsed (float): 已经过的秒数。 - reason (str): 触发原因: - 'log_timeout' — 日志心跳超时(主线程 I/O 卡死) - 'task_timeout' — 任务运行时间超时(逻辑死循环) - task_name (str): 当前任务名(仅 task_timeout 时使用)。 + reason (str): 触发原因,当前仅支持 'task_timeout'。 + task_name (str): 当前任务名。 """ if reason == 'task_timeout': try: @@ -336,15 +371,17 @@ def _watchdog_recover(self, elapsed, reason='log_timeout', task_name=''): f'(超过 {timeout_min} 分钟),判定逻辑死循环,' f'强制杀死模拟器进程以中断任务' ) + elif reason == 'force_scheduled_restart': + logger.critical( + f'[Alas][看门狗] 任务 `{task_name}` 执行期间触发强制定时重启,' + f'强制杀死模拟器进程以中断任务' + ) + # 更新重启时间戳,避免恢复后立即重复触发 + self.last_emulator_restart_time = time.monotonic() else: - try: - log_timeout = int(self.config.Error_WatchdogLogTimeout) - except Exception: - log_timeout = WATCHDOG_LOG_TIMEOUT_DEFAULT logger.critical( - f'[Alas][看门狗] 任务执行中已 {int(elapsed)} 秒无任何日志输出' - f'(超过 {log_timeout} 秒),判定主线程卡死,' - f'强制杀死模拟器进程以解除阻塞' + f'[Alas][看门狗] 检测到异常(reason={reason}),' + f'强制杀死模拟器进程以中断任务' ) try: diff --git a/config/template.json b/config/template.json index 333fa7c6f..54db80bb7 100644 --- a/config/template.json +++ b/config/template.json @@ -101,8 +101,9 @@ "GameStuckThreshold": 3, "AdbOfflineRestart": false, "AdbOfflineThreshold": 3, + "WatchdogEnable": false, + "WatchdogTaskEnable": false, "WatchdogTaskTimeout": 120, - "WatchdogLogTimeout": 300, "RestartOperationTimeout": 120, "LlmAnalysis": true, "LlmApiKey": null, @@ -138,6 +139,7 @@ }, "EmulatorManagement": { "ScheduledEmulatorRestart": false, + "ForceScheduledRestart": false, "RestartIntervalHours": 4 }, "Storage": { diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 431e65a9a..1050bc092 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -492,6 +492,14 @@ 10 ] }, + "WatchdogEnable": { + "type": "checkbox", + "value": false + }, + "WatchdogTaskEnable": { + "type": "checkbox", + "value": false + }, "WatchdogTaskTimeout": { "type": "input", "value": 120, @@ -500,14 +508,6 @@ 99999 ] }, - "WatchdogLogTimeout": { - "type": "input", - "value": 300, - "validate": [ - 60, - 1800 - ] - }, "RestartOperationTimeout": { "type": "input", "value": 120, @@ -725,6 +725,10 @@ "type": "checkbox", "value": false }, + "ForceScheduledRestart": { + "type": "checkbox", + "value": false + }, "RestartIntervalHours": { "type": "input", "value": 4, diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index 6be07fb61..d2d057b9c 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -192,12 +192,11 @@ Error: AdbOfflineThreshold: value: 3 validate: [1, 10] + WatchdogEnable: false + WatchdogTaskEnable: false WatchdogTaskTimeout: value: 120 validate: [0, 99999] - WatchdogLogTimeout: - value: 300 - validate: [60, 1800] RestartOperationTimeout: value: 120 validate: [10, 600] @@ -1951,6 +1950,7 @@ GameManager: AutoRestart: true EmulatorManagement: ScheduledEmulatorRestart: false + ForceScheduledRestart: false RestartIntervalHours: value: 4 validate: [1, 24] diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 4ffc5a166..2670e2310 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -122,8 +122,9 @@ class GeneratedConfig: Error_GameStuckThreshold = 3 Error_AdbOfflineRestart = False Error_AdbOfflineThreshold = 3 + Error_WatchdogEnable = False + Error_WatchdogTaskEnable = False Error_WatchdogTaskTimeout = 120 - Error_WatchdogLogTimeout = 300 Error_RestartOperationTimeout = 120 Error_LlmAnalysis = True Error_LlmApiKey = None @@ -1009,6 +1010,7 @@ class GeneratedConfig: # 配置组 `EmulatorManagement` EmulatorManagement_ScheduledEmulatorRestart = False + EmulatorManagement_ForceScheduledRestart = False EmulatorManagement_RestartIntervalHours = 4 # 配置组 `EmulatorManager` diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index a8557bfe2..c13b9b17a 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -745,14 +745,18 @@ "name": "Restart When Device Unreachable for X Seconds", "help": "The number of times the device cannot be detected in a row triggers a restart." }, + "WatchdogEnable": { + "name": "Enable Watchdog Master Switch", + "help": "Master switch for the watchdog. When off, all watchdog checks are disabled. When on, you still need to enable the individual sub-switches below. Default off." + }, + "WatchdogTaskEnable": { + "name": "Enable Task Timeout Watchdog", + "help": "Detects if a task is stuck in a logic loop (e.g. unskippable story, pathfinding loop), where logs are still being updated but the task cannot exit naturally. If it runs longer than the 'Task Stuck Watchdog Timeout', the emulator is force-restarted. Requires the master switch to be on. Default off." + }, "WatchdogTaskTimeout": { "name": "Task Stuck Watchdog Timeout (min)", "help": "If a single task runs longer than this, the watchdog treats it as a logic loop (e.g. unskippable story, pathfinding loop) and force-restarts the emulator.\n0 = disabled; 99999 = virtually unlimited.\nDefault 120 min (2h)." }, - "WatchdogLogTimeout": { - "name": "Log Heartbeat Timeout (sec)", - "help": "During task execution, if no log is output for this duration, the main thread is considered stuck in an I/O call (e.g. u2 HTTP, ADB shell) and the emulator is force-killed.\nDefault 300 sec (5 min)." - }, "RestartOperationTimeout": { "name": "Restart Operation Timeout (sec)", "help": "Hard timeout for a single app_stop/app_start operation during game restart. If exceeded, the emulator or atx-agent is considered stuck and an emulator restart is triggered.\nDefault 120 sec." @@ -5966,6 +5970,10 @@ "name": "Restart the emulator regularly", "help": "Once enabled, the emulator will automatically restart at specified intervals to avoid memory leaks or lagging caused by long-term running. The restart will wait until the current task is completed before executing it." }, + "ForceScheduledRestart": { + "name": "Force scheduled restart", + "help": "When enabled, upon reaching the restart interval, if a non-sensitive task is currently running, the task will be forcibly interrupted and the emulator will be restarted immediately, without waiting for the task to complete. Requires 'Restart the emulator regularly' to be enabled. Default off." + }, "RestartIntervalHours": { "name": "Restart interval (hours)", "help": "Automatically restart the emulator every few hours." diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index e1244d00a..b5dc10046 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -745,14 +745,18 @@ "name": "デバイス未検出からX秒後に再起動", "help": "デバイスが連続して検出できなかった回数に応じて、再起動がトリガーされます。" }, + "WatchdogEnable": { + "name": "watchdog マスタースイッチを有効化", + "help": "watchdog のマスタースイッチ。オフの場合、すべての watchdog 検出が無効になります。オンにしても、下位の個別スイッチを別途有効にする必要があります。デフォルトはオフ。" + }, + "WatchdogTaskEnable": { + "name": "タスクタイムアウト watchdog を有効化", + "help": "タスクが論理ループ(スキップ不可のストーリーや経路探索ループなど)に陥っているかを検出します。ログは更新されているがタスクが自然に終了できない場合、「タスクスタック watchdog タイムアウト」を超えるとエミュレータを強制再起動します。マスタースイッチがオンである必要があります。デフォルトはオフ。" + }, "WatchdogTaskTimeout": { "name": "タスクスタック watchdog タイムアウト(分)", "help": "単一タスクが指定時間以上実行された場合、watchdog は論理ループ(スキップ不可のストーリーや経路探索ループなど)と判定し、エミュレータを強制再起動します。\n0 = 無効;99999 = ほぼ無制限。\nデフォルト 120 分(2 時間)。" }, - "WatchdogLogTimeout": { - "name": "ログハートビートタイムアウト(秒)", - "help": "タスク実行中、指定時間以上ログ出力がない場合、メインスレッドが I/O 呼び出し(u2 HTTP、ADB shell など)でスタックしたと判定し、エミュレータを強制終了します。\nデフォルト 300 秒(5 分)。" - }, "RestartOperationTimeout": { "name": "再起動操作ハードタイムアウト(秒)", "help": "ゲーム再起動時の単一 app_stop/app_start 操作のハードタイムアウト秒数。超過した場合、エミュレータまたは atx-agent がスタックしたと判定し、エミュレータ再起動をトリガーします。\nデフォルト 120 秒。" @@ -5966,6 +5970,10 @@ "name": "シミュレータを定期的に再起動する", "help": "有効にすると、長時間の実行によるメモリ リークや遅延を避けるために、エミュレータは指定された間隔で自動的に再起動されます。再起動は、現在のタスクが完了するまで待ってから実行します。" }, + "ForceScheduledRestart": { + "name": "強制スケジュール再起動", + "help": "有効にすると、再起動間隔に達した際に、非敏感タスクが実行中の場合、タスクを強制中断してエミュレータを直ちに再起動します。タスクの完了を待ちません。「シミュレータを定期的に再起動する」が有効である必要があります。デフォルトはオフ。" + }, "RestartIntervalHours": { "name": "再起動間隔 (時間)", "help": "数時間ごとにシミュレーターを自動的に再起動します。" diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index c0a7f0d69..8cda48971 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -745,14 +745,18 @@ "name": "检测不到设备超过 X 秒后重启", "help": "连续检测不到设备多少次后触发重启。" }, + "WatchdogEnable": { + "name": "启用看门狗总开关", + "help": "看门狗总开关。关闭后,所有看门狗检测均不生效。开启后,仍需单独开启下面的子开关。默认关闭。" + }, + "WatchdogTaskEnable": { + "name": "启用任务超时看门狗", + "help": "检测任务是否陷入逻辑死循环(如剧情无法跳过、寻路死循环等),日志仍在更新但任务无法自然退出。超过「任务卡死保护超时」时,强制重启模拟器以中断任务。需要先开启「看门狗总开关」。默认关闭。" + }, "WatchdogTaskTimeout": { "name": "任务卡死保护超时(分钟)", "help": "单个任务运行超过指定时间后没切换其他任务(一般委托科研切换后会重新计算)判定任务逻辑死循环(如剧情无法跳过、寻路死循环等),强制重启模拟器以中断任务。\n0 表示禁用;99999 表示几乎无限制。\n默认 120 分钟(2 小时)。" }, - "WatchdogLogTimeout": { - "name": "日志心跳超时(秒)", - "help": "任务执行期间,如果超过指定时间无任何日志输出,判定主线程卡死在 I/O 调用中(如 u2 HTTP 请求、ADB shell),强制杀死模拟器以解除阻塞。\n默认 300 秒(5 分钟)。" - }, "RestartOperationTimeout": { "name": "重启操作硬超时(秒)", "help": "重启游戏时的操作硬超时秒数。超时判定模拟器卡死,触发模拟器重启。\n默认 120 秒。" @@ -5966,6 +5970,10 @@ "name": "定时重启模拟器", "help": "启用后,每隔指定时间自动重启模拟器,避免长时间运行导致的内存泄漏或卡顿问题。重启会等当前任务完成后再执行。" }, + "ForceScheduledRestart": { + "name": "强制定时重启", + "help": "启用后,到达重启间隔时,如果当前正在运行非敏感任务,将强制中断任务并立即重启模拟器,而不等待任务完成。需要先开启「定时重启模拟器」。默认关闭。" + }, "RestartIntervalHours": { "name": "重启间隔(小时)", "help": "每隔多少小时自动重启一次模拟器。" diff --git a/module/config/i18n/zh-MIAO.json b/module/config/i18n/zh-MIAO.json index bf629dfbe..4e95b0714 100644 --- a/module/config/i18n/zh-MIAO.json +++ b/module/config/i18n/zh-MIAO.json @@ -745,14 +745,18 @@ "name": "检测不到设备重启阈值", "help": "连续多少次触发重启喵。" }, + "WatchdogEnable": { + "name": "Error.WatchdogEnable.name", + "help": "Error.WatchdogEnable.help" + }, + "WatchdogTaskEnable": { + "name": "Error.WatchdogTaskEnable.name", + "help": "Error.WatchdogTaskEnable.help" + }, "WatchdogTaskTimeout": { "name": "Error.WatchdogTaskTimeout.name", "help": "Error.WatchdogTaskTimeout.help" }, - "WatchdogLogTimeout": { - "name": "Error.WatchdogLogTimeout.name", - "help": "Error.WatchdogLogTimeout.help" - }, "RestartOperationTimeout": { "name": "Error.RestartOperationTimeout.name", "help": "Error.RestartOperationTimeout.help" @@ -5966,6 +5970,10 @@ "name": "定时重启模拟器", "help": "每隔指定时间自动重启模拟器,防内存泄漏卡顿,等当前任务完成再重启喵。 (´・ω・`)" }, + "ForceScheduledRestart": { + "name": "EmulatorManagement.ForceScheduledRestart.name", + "help": "EmulatorManagement.ForceScheduledRestart.help" + }, "RestartIntervalHours": { "name": "重启间隔", "help": "" diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 4de02fc75..2a741a260 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -745,14 +745,18 @@ "name": "偵測不到裝置超過 X 秒後重啟", "help": "連續檢測不到裝置多少次後觸發重啟。" }, + "WatchdogEnable": { + "name": "啟用看門狗總開關", + "help": "看門狗總開關。關閉後,所有看門狗偵測均不生效。開啟後,仍需單獨開啟下面的子開關。預設關閉。" + }, + "WatchdogTaskEnable": { + "name": "啟用任務逾時看門狗", + "help": "偵測任務是否陷入邏輯死循環(如劇情無法跳過、尋路死循環等),日誌仍在更新但任務無法自然結束。超過「任務卡死保護逾時」時,強制重啟模擬器以中斷任務。需要先開啟「看門狗總開關」。預設關閉。" + }, "WatchdogTaskTimeout": { "name": "任務卡死保護逾時(分鐘)", "help": "單一任務執行超過指定時間後,看門狗判定任務邏輯死循環(如劇情無法跳過、尋路死循環等),強制重啟模擬器以中斷任務。\n0 = 停用;99999 = 幾乎無限制。\n預設 120 分鐘(2 小時)。" }, - "WatchdogLogTimeout": { - "name": "日誌心跳逾時(秒)", - "help": "任務執行期間,如果超過指定時間無任何日誌輸出,判定主線程卡死在 I/O 呼叫中(如 u2 HTTP 請求、ADB shell),強制殺死模擬器以解除阻塞。\n預設 300 秒(5 分鐘)。" - }, "RestartOperationTimeout": { "name": "重啟操作硬逾時(秒)", "help": "重啟遊戲時,單一 app_stop/app_start 操作的硬逾時秒數。逾時判定模擬器或 atx-agent 卡死,觸發模擬器重啟。\n預設 120 秒。" @@ -5966,6 +5970,10 @@ "name": "定時重啟模擬器", "help": "啟用後,每隔指定時間自動重啟模擬器,避免長時間執行導致的記憶體洩漏或卡頓問題。重啟會等當前任務完成後再執行。" }, + "ForceScheduledRestart": { + "name": "強制定時重啟", + "help": "啟用後,到達重啟間隔時,如果目前正在執行非敏感任務,將強制中斷任務並立即重啟模擬器,而不等待任務完成。需要先開啟「定時重啟模擬器」。預設關閉。" + }, "RestartIntervalHours": { "name": "重啟間隔(小時)", "help": "每隔多少小時自動重啟一次模擬器。" From 4c69000aaeec135885fc5f3a3586eaf42e65b0c0 Mon Sep 17 00:00:00 2001 From: Beatrice-betty Date: Wed, 19 Aug 2026 23:55:09 +0800 Subject: [PATCH 2/3] feat(error): add restart operation timeout protection switch --- config/template.json | 1 + module/config/argument/args.json | 4 ++ module/config/argument/argument.yaml | 1 + module/config/config_generated.py | 1 + module/config/i18n/en-US.json | 30 ++++++++------ module/config/i18n/ja-JP.json | 30 ++++++++------ module/config/i18n/zh-CN.json | 14 +++---- module/config/i18n/zh-MIAO.json | 4 ++ module/config/i18n/zh-TW.json | 30 ++++++++------ module/handler/login.py | 59 ++++++++++++++++++++-------- 10 files changed, 112 insertions(+), 62 deletions(-) diff --git a/config/template.json b/config/template.json index 54db80bb7..fd047cf0d 100644 --- a/config/template.json +++ b/config/template.json @@ -104,6 +104,7 @@ "WatchdogEnable": false, "WatchdogTaskEnable": false, "WatchdogTaskTimeout": 120, + "RestartOperationTimeoutEnable": false, "RestartOperationTimeout": 120, "LlmAnalysis": true, "LlmApiKey": null, diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 1050bc092..b909d54f5 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -508,6 +508,10 @@ 99999 ] }, + "RestartOperationTimeoutEnable": { + "type": "checkbox", + "value": false + }, "RestartOperationTimeout": { "type": "input", "value": 120, diff --git a/module/config/argument/argument.yaml b/module/config/argument/argument.yaml index d2d057b9c..3e74bd61f 100644 --- a/module/config/argument/argument.yaml +++ b/module/config/argument/argument.yaml @@ -197,6 +197,7 @@ Error: WatchdogTaskTimeout: value: 120 validate: [0, 99999] + RestartOperationTimeoutEnable: false RestartOperationTimeout: value: 120 validate: [10, 600] diff --git a/module/config/config_generated.py b/module/config/config_generated.py index 2670e2310..b14d84257 100644 --- a/module/config/config_generated.py +++ b/module/config/config_generated.py @@ -125,6 +125,7 @@ class GeneratedConfig: Error_WatchdogEnable = False Error_WatchdogTaskEnable = False Error_WatchdogTaskTimeout = 120 + Error_RestartOperationTimeoutEnable = False Error_RestartOperationTimeout = 120 Error_LlmAnalysis = True Error_LlmApiKey = None diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index c13b9b17a..839b675ec 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -746,20 +746,24 @@ "help": "The number of times the device cannot be detected in a row triggers a restart." }, "WatchdogEnable": { - "name": "Enable Watchdog Master Switch", - "help": "Master switch for the watchdog. When off, all watchdog checks are disabled. When on, you still need to enable the individual sub-switches below. Default off." + "name": "Watchdog Master Switch", + "help": "What's a watchdog? It's a bodyguard running in the background. Turn it on to enable the checks below; turn it off and nothing below matters. Default off." }, "WatchdogTaskEnable": { - "name": "Enable Task Timeout Watchdog", - "help": "Detects if a task is stuck in a logic loop (e.g. unskippable story, pathfinding loop), where logs are still being updated but the task cannot exit naturally. If it runs longer than the 'Task Stuck Watchdog Timeout', the emulator is force-restarted. Requires the master switch to be on. Default off." + "name": "Auto-restart if task runs too long", + "help": "When enabled, if a task runs too long (e.g. stuck on an unskippable story, pathfinding loop), the emulator is force-restarted to break out of it.\nRequires the master switch above to be on. Default off." }, "WatchdogTaskTimeout": { - "name": "Task Stuck Watchdog Timeout (min)", - "help": "If a single task runs longer than this, the watchdog treats it as a logic loop (e.g. unskippable story, pathfinding loop) and force-restarts the emulator.\n0 = disabled; 99999 = virtually unlimited.\nDefault 120 min (2h)." + "name": "How long before a task is considered stuck (min)", + "help": "How many minutes a task can run before it's considered stuck. If exceeded, the emulator is force-restarted.\n0 = never trigger; 99999 = basically never.\nDefault 120 min (2 hours)." + }, + "RestartOperationTimeoutEnable": { + "name": "Game restart timeout protection", + "help": "When enabled, if a step during game restart (stopping/starting the game) gets stuck for longer than the timeout below, the emulator is considered dead and gets restarted.\nWhen disabled, this check is skipped.\nDefault off." }, "RestartOperationTimeout": { - "name": "Restart Operation Timeout (sec)", - "help": "Hard timeout for a single app_stop/app_start operation during game restart. If exceeded, the emulator or atx-agent is considered stuck and an emulator restart is triggered.\nDefault 120 sec." + "name": "How long before game restart is considered stuck (sec)", + "help": "During game restart, if stopping or starting the game is stuck for longer than this many seconds, the emulator is considered dead and gets restarted.\nRequires 'Game restart timeout protection' above to be on.\nDefault 120 sec." }, "LlmAnalysis": { "name": "Enable LLM Error Analysis", @@ -5968,15 +5972,15 @@ }, "ScheduledEmulatorRestart": { "name": "Restart the emulator regularly", - "help": "Once enabled, the emulator will automatically restart at specified intervals to avoid memory leaks or lagging caused by long-term running. The restart will wait until the current task is completed before executing it." + "help": "Restart the emulator every so often to prevent it from getting slow or leaky after running for a long time.\nNote: By default, it waits for the current task to finish before restarting." }, "ForceScheduledRestart": { - "name": "Force scheduled restart", - "help": "When enabled, upon reaching the restart interval, if a non-sensitive task is currently running, the task will be forcibly interrupted and the emulator will be restarted immediately, without waiting for the task to complete. Requires 'Restart the emulator regularly' to be enabled. Default off." + "name": "Restart on schedule (don't wait for task)", + "help": "When enabled, restarts the emulator right on schedule, without waiting for the current task to finish.\nBut if a sensitive task is currently running, it will still wait for it to finish.\nRequires 'Restart the emulator regularly' above to be on. Default off." }, "RestartIntervalHours": { - "name": "Restart interval (hours)", - "help": "Automatically restart the emulator every few hours." + "name": "How often to restart (hours)", + "help": "How many hours between emulator restarts." } }, "EmulatorManager": { diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index b5dc10046..5113b850b 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -746,20 +746,24 @@ "help": "デバイスが連続して検出できなかった回数に応じて、再起動がトリガーされます。" }, "WatchdogEnable": { - "name": "watchdog マスタースイッチを有効化", - "help": "watchdog のマスタースイッチ。オフの場合、すべての watchdog 検出が無効になります。オンにしても、下位の個別スイッチを別途有効にする必要があります。デフォルトはオフ。" + "name": "watchdog マスタースイッチ", + "help": "watchdog って何?バックグラウンドでこっそり見張ってるボディガードです。オンにすると以下のチェックが有効になり、オフにすると何もしなくなります。デフォルトはオフ。" }, "WatchdogTaskEnable": { - "name": "タスクタイムアウト watchdog を有効化", - "help": "タスクが論理ループ(スキップ不可のストーリーや経路探索ループなど)に陥っているかを検出します。ログは更新されているがタスクが自然に終了できない場合、「タスクスタック watchdog タイムアウト」を超えるとエミュレータを強制再起動します。マスタースイッチがオンである必要があります。デフォルトはオフ。" + "name": "タスクが長引いたら自動再起動", + "help": "オンにすると、タスクが長く動きすぎた時(スキップできないストーリーに引っかかった、地図の経路探索ループなど)、エミュレータを強制再起動して中断します。\n上のマスタースイッチをオンにする必要があります。デフォルトはオフ。" }, "WatchdogTaskTimeout": { - "name": "タスクスタック watchdog タイムアウト(分)", - "help": "単一タスクが指定時間以上実行された場合、watchdog は論理ループ(スキップ不可のストーリーや経路探索ループなど)と判定し、エミュレータを強制再起動します。\n0 = 無効;99999 = ほぼ無制限。\nデフォルト 120 分(2 時間)。" + "name": "タスクが何分でスタック判定(分)", + "help": "何分。タスクがこの時間を超えて動いたらスタックとみなし、エミュレータを強制再起動します。\n0 = 判定しない;99999 = ほぼ永遠に判定しない。\nデフォルト 120 分(2 時間)。" + }, + "RestartOperationTimeoutEnable": { + "name": "ゲーム再起動タイムアウト保護", + "help": "オンにすると、ゲーム再起動時のあるステップ(ゲーム停止/起動)が下の時間以上に引っかかった場合、エミュレータが死んだとみなしてエミュレータを再起動します。\nオフの場合このチェックをしません。\nデフォルトはオフ。" }, "RestartOperationTimeout": { - "name": "再起動操作ハードタイムアウト(秒)", - "help": "ゲーム再起動時の単一 app_stop/app_start 操作のハードタイムアウト秒数。超過した場合、エミュレータまたは atx-agent がスタックしたと判定し、エミュレータ再起動をトリガーします。\nデフォルト 120 秒。" + "name": "ゲーム再起動が何秒でスタック判定(秒)", + "help": "ゲーム再起動時、ゲーム停止や起動がこの秒数以上引っかかったら、エミュレータが死んだとみなしてエミュレータを再起動します。\n上の「ゲーム再起動タイムアウト保護」をオンにする必要があります。\nデフォルト 120 秒。" }, "LlmAnalysis": { "name": "LLMエラー分析を有効にする", @@ -5968,15 +5972,15 @@ }, "ScheduledEmulatorRestart": { "name": "シミュレータを定期的に再起動する", - "help": "有効にすると、長時間の実行によるメモリ リークや遅延を避けるために、エミュレータは指定された間隔で自動的に再起動されます。再起動は、現在のタスクが完了するまで待ってから実行します。" + "help": "長時間動いて重くなったりメモリ漏れしたりするのを防ぐため、一定間隔で自動的にエミュレータを再起動します。\n注意:デフォルトでは現在のタスクが終わるまで待ってから再起動します。" }, "ForceScheduledRestart": { - "name": "強制スケジュール再起動", - "help": "有効にすると、再起動間隔に達した際に、非敏感タスクが実行中の場合、タスクを強制中断してエミュレータを直ちに再起動します。タスクの完了を待ちません。「シミュレータを定期的に再起動する」が有効である必要があります。デフォルトはオフ。" + "name": "時間になったら即再起動(タスク待ちしない)", + "help": "オンにすると、時間になったらタスクが終わるのを待たずにエミュレータを再起動します。\nただし、現在敏感タスクを実行中の場合は、終わるまで待ちます。\n上の「シミュレータを定期的に再起動する」をオンにする必要があります。デフォルトはオフ。" }, "RestartIntervalHours": { - "name": "再起動間隔 (時間)", - "help": "数時間ごとにシミュレーターを自動的に再起動します。" + "name": "どれくらいの頻度で再起動(時間)", + "help": "何時間おきにエミュレータを再起動するか。" } }, "EmulatorManager": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 8cda48971..89d46d452 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -750,16 +750,16 @@ "help": "看门狗总开关。关闭后,所有看门狗检测均不生效。开启后,仍需单独开启下面的子开关。默认关闭。" }, "WatchdogTaskEnable": { - "name": "启用任务超时看门狗", - "help": "检测任务是否陷入逻辑死循环(如剧情无法跳过、寻路死循环等),日志仍在更新但任务无法自然退出。超过「任务卡死保护超时」时,强制重启模拟器以中断任务。需要先开启「看门狗总开关」。默认关闭。" + "name": "任务跑太久自动重启", + "help": "开了之后,如果某个任务跑得太久(比如一直卡在某个剧情跳不过去、地图走不动了),就会自动重启模拟器把它打断。\n得先开上面的「看门狗总开关」才管用。默认关。" }, "WatchdogTaskTimeout": { "name": "任务卡死保护超时(分钟)", "help": "单个任务运行超过指定时间后没切换其他任务(一般委托科研切换后会重新计算)判定任务逻辑死循环(如剧情无法跳过、寻路死循环等),强制重启模拟器以中断任务。\n0 表示禁用;99999 表示几乎无限制。\n默认 120 分钟(2 小时)。" }, "RestartOperationTimeout": { - "name": "重启操作硬超时(秒)", - "help": "重启游戏时的操作硬超时秒数。超时判定模拟器卡死,触发模拟器重启。\n默认 120 秒。" + "name": "重启游戏卡多久算死(秒)", + "help": "重启游戏时,关游戏或开游戏这一步卡住超过这个秒数,就当模拟器卡死了,直接去重启模拟器。\n得先开上面的「重启游戏超时保护」才管用。\n默认 120 秒。" }, "LlmAnalysis": { "name": "启用 LLM 错误分析", @@ -5968,11 +5968,11 @@ }, "ScheduledEmulatorRestart": { "name": "定时重启模拟器", - "help": "启用后,每隔指定时间自动重启模拟器,避免长时间运行导致的内存泄漏或卡顿问题。重启会等当前任务完成后再执行。" + "help": "每隔一段时间自动重启模拟器,免得跑久了变卡或漏内存。\n注意:默认是等当前任务跑完才重启。" }, "ForceScheduledRestart": { - "name": "强制定时重启", - "help": "启用后,到达重启间隔时,如果当前正在运行非敏感任务,将强制中断任务并立即重启模拟器,而不等待任务完成。需要先开启「定时重启模拟器」。默认关闭。" + "name": "强制定时重启(不等任务)", + "help": "开了后,到点就直接重启模拟器,不等当前任务跑完。\n不过如果当前正在跑重要任务(敏感任务),还是会等它跑完再重启。\n得先开上面的「定时重启模拟器」才管用。默认关。" }, "RestartIntervalHours": { "name": "重启间隔(小时)", diff --git a/module/config/i18n/zh-MIAO.json b/module/config/i18n/zh-MIAO.json index 4e95b0714..89fd97da9 100644 --- a/module/config/i18n/zh-MIAO.json +++ b/module/config/i18n/zh-MIAO.json @@ -757,6 +757,10 @@ "name": "Error.WatchdogTaskTimeout.name", "help": "Error.WatchdogTaskTimeout.help" }, + "RestartOperationTimeoutEnable": { + "name": "Error.RestartOperationTimeoutEnable.name", + "help": "Error.RestartOperationTimeoutEnable.help" + }, "RestartOperationTimeout": { "name": "Error.RestartOperationTimeout.name", "help": "Error.RestartOperationTimeout.help" diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index 2a741a260..eeffc0eb9 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -746,20 +746,24 @@ "help": "連續檢測不到裝置多少次後觸發重啟。" }, "WatchdogEnable": { - "name": "啟用看門狗總開關", - "help": "看門狗總開關。關閉後,所有看門狗偵測均不生效。開啟後,仍需單獨開啟下面的子開關。預設關閉。" + "name": "看門狗總開關", + "help": "看門狗是什麼?就是後台偷偷盯著的保鏢。開著它才會去管下面那些事,關了就什麼都不管。預設關。" }, "WatchdogTaskEnable": { - "name": "啟用任務逾時看門狗", - "help": "偵測任務是否陷入邏輯死循環(如劇情無法跳過、尋路死循環等),日誌仍在更新但任務無法自然結束。超過「任務卡死保護逾時」時,強制重啟模擬器以中斷任務。需要先開啟「看門狗總開關」。預設關閉。" + "name": "任務跑太久自動重啟", + "help": "開了之後,如果某個任務跑得太久(比如一直卡在某個劇情跳不過去、地圖走不動了),就會自動重啟模擬器把它打斷。\n得先開上面的「看門狗總開關」才管用。預設關。" }, "WatchdogTaskTimeout": { - "name": "任務卡死保護逾時(分鐘)", - "help": "單一任務執行超過指定時間後,看門狗判定任務邏輯死循環(如劇情無法跳過、尋路死循環等),強制重啟模擬器以中斷任務。\n0 = 停用;99999 = 幾乎無限制。\n預設 120 分鐘(2 小時)。" + "name": "任務跑多久算卡死(分鐘)", + "help": "填多久。任務跑超過這個時間就當成卡死了,直接重啟模擬器。\n0 = 不管;99999 = 基本永遠不管。\n預設 120 分鐘(2 小時)。" + }, + "RestartOperationTimeoutEnable": { + "name": "重啟遊戲逾時保護", + "help": "開著後,重啟遊戲時如果某一步(關遊戲/開遊戲)卡住超過下面那個時間,就直接判模擬器卡死,跳去重啟模擬器。\n關著的話就不做這個檢查。\n預設關。" }, "RestartOperationTimeout": { - "name": "重啟操作硬逾時(秒)", - "help": "重啟遊戲時,單一 app_stop/app_start 操作的硬逾時秒數。逾時判定模擬器或 atx-agent 卡死,觸發模擬器重啟。\n預設 120 秒。" + "name": "重啟遊戲卡多久算死(秒)", + "help": "重啟遊戲時,關遊戲或開遊戲這一步卡住超過這個秒數,就當模擬器卡死了,直接去重啟模擬器。\n得先開上面的「重啟遊戲逾時保護」才管用。\n預設 120 秒。" }, "LlmAnalysis": { "name": "啟用 LLM 錯誤分析", @@ -5968,15 +5972,15 @@ }, "ScheduledEmulatorRestart": { "name": "定時重啟模擬器", - "help": "啟用後,每隔指定時間自動重啟模擬器,避免長時間執行導致的記憶體洩漏或卡頓問題。重啟會等當前任務完成後再執行。" + "help": "每隔一段時間自動重啟模擬器,免得跑久了變卡或漏記憶體。\n注意:預設是等當前任務跑完才重啟。" }, "ForceScheduledRestart": { - "name": "強制定時重啟", - "help": "啟用後,到達重啟間隔時,如果目前正在執行非敏感任務,將強制中斷任務並立即重啟模擬器,而不等待任務完成。需要先開啟「定時重啟模擬器」。預設關閉。" + "name": "到點就重啟(不等任務)", + "help": "開了後,到點就直接重啟模擬器,不等當前任務跑完。\n不過如果目前正在跑重要任務(敏感任務),還是會等它跑完再重啟。\n得先開上面的「定時重啟模擬器」才管用。預設關。" }, "RestartIntervalHours": { - "name": "重啟間隔(小時)", - "help": "每隔多少小時自動重啟一次模擬器。" + "name": "多久重啟一次(小時)", + "help": "隔幾小時重啟一次模擬器。" } }, "EmulatorManager": { diff --git a/module/handler/login.py b/module/handler/login.py index b6fd3982e..fd6e0137a 100644 --- a/module/handler/login.py +++ b/module/handler/login.py @@ -214,6 +214,22 @@ def _login_wait_timeout(self): return 3600.0 return timeout + def _restart_operation_timeout_enabled(self): + """ + 检查是否启用了重启操作硬超时保护。 + + 对应配置项 Alas.Error.RestartOperationTimeoutEnable,默认关闭。 + 关闭时 app_stop/app_start 不做硬超时检查,回退原有行为。 + + Returns: + bool: True 表示启用硬超时保护。 + """ + value = deep_get( + self.config.data, 'Alas.Error.RestartOperationTimeoutEnable', + default=False, + ) + return bool(value) + def _restart_operation_timeout(self): """ 获取 app_stop/app_start 操作的硬超时秒数。 @@ -346,29 +362,40 @@ def app_restart(self): logger.hr('应用重启') is_restart_success = False - # 从配置读取硬超时(秒),配置非法时回退默认 120 秒 - op_timeout = self._restart_operation_timeout() - logger.info(f'[重启] app_stop/app_start 硬超时 {op_timeout} 秒') + # 检查是否启用了重启操作硬超时保护 + op_timeout_enabled = self._restart_operation_timeout_enabled() + if op_timeout_enabled: + op_timeout = self._restart_operation_timeout() + logger.info(f'[重启] app_stop/app_start 硬超时保护已启用,超时 {op_timeout} 秒') + else: + op_timeout = None + logger.info('[重启] app_stop/app_start 硬超时保护未启用,回退原有行为') clear_cache = getattr(self.config, 'Restart_ClearCache', False) for i in range(RESTART_TRIES): logger.info(f"[重启] 应用重启尝试 {i + 1}/{RESTART_TRIES}...") - # 用硬超时包装 app_stop/app_start,防止 atx-agent 异常时 - # u2 HTTP 调用无限挂起导致 LoginWaitTimeout/GameStuckRestart - # 等保护机制(依赖 screenshot() 中的 stuck_record_check)失效 - self._call_with_restart_deadline( - self.device.app_stop, - timeout=op_timeout, - operation_name='应用停止', - ) + # 启用硬超时时,用 _call_with_restart_deadline 包装 app_stop/app_start, + # 防止 atx-agent 异常时 u2 HTTP 调用无限挂起导致 + # LoginWaitTimeout/GameStuckRestart 等保护机制失效 + if op_timeout_enabled: + self._call_with_restart_deadline( + self.device.app_stop, + timeout=op_timeout, + operation_name='应用停止', + ) + else: + self.device.app_stop() if clear_cache: self.device.app_clear() self.device.sleep(3) - self._call_with_restart_deadline( - self.device.app_start, - timeout=op_timeout, - operation_name='应用启动', - ) + if op_timeout_enabled: + self._call_with_restart_deadline( + self.device.app_start, + timeout=op_timeout, + operation_name='应用启动', + ) + else: + self.device.app_start() wait_seconds = RESTART_FIRST_TRY_WAIT_SECONDS if i == 0 else RESTART_SUBSEQUENT_TRY_WAIT_SECONDS logger.info(f"[重启] 等待 {wait_seconds} 秒让应用启动和稳定...") self.device.sleep(wait_seconds) From 9a2dcc54ac7f3daaac1067b82976df660ebbe02e Mon Sep 17 00:00:00 2001 From: Beatrice-betty Date: Thu, 20 Aug 2026 00:04:37 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- module/config/i18n/zh-CN.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index 89d46d452..ee860e776 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -757,6 +757,10 @@ "name": "任务卡死保护超时(分钟)", "help": "单个任务运行超过指定时间后没切换其他任务(一般委托科研切换后会重新计算)判定任务逻辑死循环(如剧情无法跳过、寻路死循环等),强制重启模拟器以中断任务。\n0 表示禁用;99999 表示几乎无限制。\n默认 120 分钟(2 小时)。" }, + "RestartOperationTimeoutEnable": { + "name": "重启游戏保护开关", + "help": "" + }, "RestartOperationTimeout": { "name": "重启游戏卡多久算死(秒)", "help": "重启游戏时,关游戏或开游戏这一步卡住超过这个秒数,就当模拟器卡死了,直接去重启模拟器。\n得先开上面的「重启游戏超时保护」才管用。\n默认 120 秒。"