diff --git a/app.py b/app.py index cf08fba..46150b3 100755 --- a/app.py +++ b/app.py @@ -14,6 +14,24 @@ try: except AttributeError: pass # Windows系统不支持tzset() +# 设置SIGCHLD信号处理器,自动回收僵尸子进程(Docker PID 1 问题) +import signal +def _sigchld_handler(signum, frame): + """SIGCHLD信号处理器 - 自动回收僵尸子进程""" + while True: + try: + pid, status = os.waitpid(-1, os.WNOHANG) + if pid == 0: + break + except ChildProcessError: + break + except Exception: + break + +# 只在Linux上设置(Docker环境) +if os.name != 'nt': + signal.signal(signal.SIGCHLD, _sigchld_handler) + import pytz from datetime import datetime from flask import Flask, render_template, request, jsonify, send_from_directory, redirect, url_for, session @@ -255,6 +273,46 @@ def cleanup_expired_data(): if completed_tasks: logger.debug(f"已清理 {len(completed_tasks)} 个已完成任务状态") + # 5. 清理僵尸进程(防止Chrome子进程变成僵尸) + try: + import os + import signal + # 回收所有已终止的子进程 + while True: + try: + pid, status = os.waitpid(-1, os.WNOHANG) + if pid == 0: + break + logger.debug(f"已回收僵尸进程: PID={pid}") + except ChildProcessError: + break # 没有子进程了 + except Exception as e: + pass # 忽略清理错误 + + # 6. 清理超时的批量截图任务(超过30分钟未完成的) + with batch_task_lock: + expired_batches = [] + for batch_id, batch_data in list(batch_task_screenshots.items()): + created_time = batch_data.get('created_time', 0) + if created_time and (current_time - created_time) > 1800: # 30分钟 + expired_batches.append(batch_id) + for batch_id in expired_batches: + del batch_task_screenshots[batch_id] + if expired_batches: + logger.debug(f"已清理 {len(expired_batches)} 个超时批量截图任务") + + # 7. 清理过期的随机延迟任务(超过2小时未执行的) + with pending_random_lock: + expired_schedules = [] + for schedule_id, schedule_data in list(pending_random_schedules.items()): + created_time = schedule_data.get('created_time', 0) + if created_time and (current_time - created_time) > 7200: # 2小时 + expired_schedules.append(schedule_id) + for schedule_id in expired_schedules: + del pending_random_schedules[schedule_id] + if expired_schedules: + logger.debug(f"已清理 {len(expired_schedules)} 个过期随机延迟任务") + def start_cleanup_scheduler(): """启动定期清理调度器""" diff --git a/browser_installer.py b/browser_installer.py index b940397..778e715 100755 --- a/browser_installer.py +++ b/browser_installer.py @@ -6,6 +6,8 @@ """ import os + import time + time.sleep(delay) import sys import shutil import subprocess @@ -72,6 +74,7 @@ class BrowserInstaller: browser = p.chromium.launch(headless=True, timeout=5000) browser.close() self.log("✓ Chromium浏览器已安装且可用") + self._cleanup_zombie_processes() return True except Exception as e: error_msg = str(e) @@ -81,11 +84,30 @@ class BrowserInstaller: if "Executable doesn't exist" in error_msg: self.log("检测到浏览器文件缺失,需要重新安装") + self._cleanup_zombie_processes() return False except Exception as e: + self._cleanup_zombie_processes() self.log(f"✗ 检查浏览器时出错: {str(e)}") return False + + def _cleanup_zombie_processes(self, delay=1): + """清理僵尸子进程""" + try: + import os + import time + time.sleep(delay) + while True: + try: + pid, status = os.waitpid(-1, os.WNOHANG) + if pid == 0: + break + except ChildProcessError: + break + except Exception: + pass + def install_chromium(self): """安装Chromium浏览器""" try: diff --git a/playwright_automation.py b/playwright_automation.py index 9b4c850..4b2606a 100755 --- a/playwright_automation.py +++ b/playwright_automation.py @@ -1438,7 +1438,7 @@ class PlaywrightAutomation: try: import os import signal - os.kill(browser_pid, signal.SIGKILL) + os.kill(browser_pid, signal.SIGTERM); import time; time.sleep(0.5); os.waitpid(browser_pid, os.WNOHANG) except (ProcessLookupError, PermissionError, OSError): pass # 进程可能已经退出