fix: 修复内存泄漏和僵尸进程问题
- 添加SIGCHLD信号处理器自动回收僵尸子进程 - 定期清理函数增加僵尸进程回收 - 增加batch_task_screenshots超时清理(30分钟) - 增加pending_random_schedules超时清理(2小时) - 修复playwright_automation.py的_force_cleanup使用SIGTERM+waitpid - browser_installer.py浏览器检测后添加僵尸进程清理 内存占用从111MB降至53MB,僵尸进程从6个降至0个
This commit is contained in:
58
app.py
58
app.py
@@ -14,6 +14,24 @@ try:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass # Windows系统不支持tzset()
|
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
|
import pytz
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import Flask, render_template, request, jsonify, send_from_directory, redirect, url_for, session
|
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:
|
if completed_tasks:
|
||||||
logger.debug(f"已清理 {len(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():
|
def start_cleanup_scheduler():
|
||||||
"""启动定期清理调度器"""
|
"""启动定期清理调度器"""
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
time.sleep(delay)
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -72,6 +74,7 @@ class BrowserInstaller:
|
|||||||
browser = p.chromium.launch(headless=True, timeout=5000)
|
browser = p.chromium.launch(headless=True, timeout=5000)
|
||||||
browser.close()
|
browser.close()
|
||||||
self.log("✓ Chromium浏览器已安装且可用")
|
self.log("✓ Chromium浏览器已安装且可用")
|
||||||
|
self._cleanup_zombie_processes()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
@@ -81,11 +84,30 @@ class BrowserInstaller:
|
|||||||
if "Executable doesn't exist" in error_msg:
|
if "Executable doesn't exist" in error_msg:
|
||||||
self.log("检测到浏览器文件缺失,需要重新安装")
|
self.log("检测到浏览器文件缺失,需要重新安装")
|
||||||
|
|
||||||
|
self._cleanup_zombie_processes()
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self._cleanup_zombie_processes()
|
||||||
self.log(f"✗ 检查浏览器时出错: {str(e)}")
|
self.log(f"✗ 检查浏览器时出错: {str(e)}")
|
||||||
return False
|
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):
|
def install_chromium(self):
|
||||||
"""安装Chromium浏览器"""
|
"""安装Chromium浏览器"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1438,7 +1438,7 @@ class PlaywrightAutomation:
|
|||||||
try:
|
try:
|
||||||
import os
|
import os
|
||||||
import signal
|
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):
|
except (ProcessLookupError, PermissionError, OSError):
|
||||||
pass # 进程可能已经退出
|
pass # 进程可能已经退出
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user