From 5b38423a891b6f6d60da2c476b2257a351c30afb Mon Sep 17 00:00:00 2001 From: Ronifue Date: Wed, 24 Dec 2025 11:56:49 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=86=E5=BD=93?= =?UTF-8?q?=20Runner=20=E8=BF=9B=E7=A8=8B=E6=84=8F=E5=A4=96=E9=80=80?= =?UTF-8?q?=E5=87=BA=E6=97=B6=E6=AE=8B=E7=95=99=20Worker=20=E5=AD=A4?= =?UTF-8?q?=E5=84=BF=E8=BF=9B=E7=A8=8B=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/bot.py b/bot.py index 612e0a17..b0d8ee70 100644 --- a/bot.py +++ b/bot.py @@ -56,6 +56,7 @@ def run_runner_process(): # 设置环境变量,标记子进程为 Worker 进程 env = os.environ.copy() env["MAIBOT_WORKER_PROCESS"] = "1" + env["MAIBOT_RUNNER_PID"] = str(os.getpid()) # 传递 Runner PID 供 Worker 监控 while True: logger.info(f"正在启动 {script_file}...") @@ -175,6 +176,52 @@ def easter_egg(): print(rainbow_text) +def _start_parent_monitor(): + """启动父进程存活监控守护线程,检测到 Runner 终止后触发优雅退出""" + import ctypes + import signal + import threading + + try: + runner_pid = int(os.environ.get("MAIBOT_RUNNER_PID", "0")) + except (ValueError, TypeError): + return + if not runner_pid: + return + + def is_alive_windows(pid): + PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 + STILL_ACTIVE = 259 + kernel32 = ctypes.windll.kernel32 + handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) + if not handle: + return False + try: + exit_code = ctypes.c_ulong() + if kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)): + return exit_code.value == STILL_ACTIVE + return False + finally: + kernel32.CloseHandle(handle) + + def is_alive_unix(pid): + return os.getppid() == pid + + is_alive = is_alive_windows if platform.system() == "Windows" else is_alive_unix + + def monitor(): + while is_alive(runner_pid): + time.sleep(2) + # Logger 容错:解释器关闭阶段 Logger 可能已被销毁 + try: + get_logger("main").warning("检测到 Runner 进程已终止,正在触发优雅退出...") + except Exception: + print("[ParentMonitor] 检测到 Runner 进程已终止,正在触发优雅退出...") + signal.raise_signal(signal.SIGINT) # 触发 KeyboardInterrupt,走正常关闭流程 + + threading.Thread(target=monitor, daemon=True, name="ParentMonitor").start() + + async def graceful_shutdown(): # sourcery skip: use-named-expression try: logger.info("正在优雅关闭麦麦...") @@ -322,6 +369,9 @@ def raw_main(): if __name__ == "__main__": exit_code = 0 # 用于记录程序最终的退出状态 try: + # 启动父进程存活监控(Runner 异常退出时自动触发优雅关闭) + _start_parent_monitor() + # 获取MainSystem实例 main_system = raw_main() From a867b44867411186b0d1287ae2f9b22c32a1619a Mon Sep 17 00:00:00 2001 From: Ronifue Date: Wed, 24 Dec 2025 12:06:44 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E9=87=8D=E6=9E=84=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E5=8F=8D=E5=A4=8D=E5=88=9B=E5=BB=BA=E5=92=8C=E9=94=80?= =?UTF-8?q?=E6=AF=81=E5=86=85=E6=A0=B8=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/bot.py b/bot.py index b0d8ee70..9be6c7c8 100644 --- a/bot.py +++ b/bot.py @@ -189,29 +189,10 @@ def _start_parent_monitor(): if not runner_pid: return - def is_alive_windows(pid): - PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 - STILL_ACTIVE = 259 - kernel32 = ctypes.windll.kernel32 - handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) - if not handle: - return False - try: - exit_code = ctypes.c_ulong() - if kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)): - return exit_code.value == STILL_ACTIVE - return False - finally: - kernel32.CloseHandle(handle) - def is_alive_unix(pid): return os.getppid() == pid - is_alive = is_alive_windows if platform.system() == "Windows" else is_alive_unix - - def monitor(): - while is_alive(runner_pid): - time.sleep(2) + def trigger_exit(): # Logger 容错:解释器关闭阶段 Logger 可能已被销毁 try: get_logger("main").warning("检测到 Runner 进程已终止,正在触发优雅退出...") @@ -219,6 +200,33 @@ def _start_parent_monitor(): print("[ParentMonitor] 检测到 Runner 进程已终止,正在触发优雅退出...") signal.raise_signal(signal.SIGINT) # 触发 KeyboardInterrupt,走正常关闭流程 + def monitor(): + if platform.system() == "Windows": + # Windows: 循环外获取句柄,循环内只检查退出码,减少系统调用 + PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 + STILL_ACTIVE = 259 + kernel32 = ctypes.windll.kernel32 + handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, runner_pid) + if not handle: + # 进程已不存在或无权限访问,直接触发退出 + return trigger_exit() + try: + exit_code = ctypes.c_ulong() + while True: + if not kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)): + break # API 调用失败,假定进程已退出 + if exit_code.value != STILL_ACTIVE: + break # 进程已退出 + time.sleep(2) + finally: + kernel32.CloseHandle(handle) + else: + # Unix: 检测 ppid 是否变化 + while is_alive_unix(runner_pid): + time.sleep(2) + + trigger_exit() + threading.Thread(target=monitor, daemon=True, name="ParentMonitor").start()