修复EasyOCR超时和内存泄漏问题

This commit is contained in:
youpaishidifu
2026-08-09 01:07:23 +08:00
parent 87c78dd1e0
commit 17f071471f
+59 -3
View File
@@ -429,6 +429,11 @@ class MessageMonitor:
self._easyocr_cuda_oom_count: int = 0
# EasyOCR 正常调度重启标志:正常资源释放后重启子进程时跳过内存检测
self._easyocr_normal_restart: bool = False
# EasyOCR 动态超时:每次超时增加15秒,成功后重置为45秒,最大120秒
self._easyocr_current_timeout: float = 45.0
self._easyocr_min_timeout: float = 45.0
self._easyocr_max_timeout: float = 120.0
self._easyocr_timeout_step: float = 15.0
# 引擎选择计数器
self._ocr_engine_wins: dict = {"windows": 0, "easyocr": 0}
# 上次 OCR 原始文本(用于增量检测)
@@ -599,11 +604,56 @@ class MessageMonitor:
self._empty_reads.clear()
self._poll_count.clear()
logger.info("消息监控已停止")
# 主动释放内存,交还系统重新分配调度
self._release_memory()
@property
def is_running(self) -> bool:
return self._running
def _release_memory(self):
"""主动释放内存,交还系统重新分配调度。只保留GUI需要的基本内存。"""
import gc
import ctypes
import sys
# 强制垃圾回收
gc.collect()
# Windows 特定:使用 EmptyProcessWorkingSet 释放工作集内存
try:
# 获取当前进程句柄
kernel32 = ctypes.windll.kernel32
psapi = ctypes.windll.psapi
# EmptyProcessWorkingSet 函数:将进程工作集最小化
# 参数:进程句柄,最小值(-1表示当前进程),最大值(-1表示当前进程)
# 返回:非零表示成功
kernel32.SetProcessWorkingSetSize(
kernel32.GetCurrentProcess(),
-1, # 最小工作集:允许系统回收所有可回收页面
-1 # 最大工作集:允许系统回收所有可回收页面
)
logger.debug("[memory] 已调用 SetProcessWorkingSetSize 释放工作集内存")
except Exception as e:
logger.debug(f"[memory] SetProcessWorkingSetSize 失败: {e}")
# 清空 EasyOCR 相关缓存
if hasattr(self, '_easyocr_proc') and self._easyocr_proc:
# 子进程已杀,但引用可能残留
self._easyocr_proc = None
# 清空大对象引用
for attr in ['_last_texts', '_seen_messages', '_replied_texts']:
if hasattr(self, attr):
getattr(self, attr).clear()
# 再次垃圾回收
gc.collect()
logger.info("[memory] 内存释放完成,已交还系统")
def _kill_easyocr_proc(self, reason: str = ""):
"""安全杀死 EasyOCR 子进程:terminate→等待→kill→GC,防 CUDA 内存泄漏。
@@ -2113,6 +2163,11 @@ class MessageMonitor:
if self._easyocr_call_count > 0:
logger.debug(f"[ocr] EasyOCR 超时重置call_count: {self._easyocr_call_count}→0(防'正常释放'误触发)")
self._easyocr_call_count = 0 # ⭐ 超时重置call_count,防"正常释放"误触发
# 动态增加超时时间,避免EasyOCR初始化未完成就被判超时
old_timeout = self._easyocr_current_timeout
self._easyocr_current_timeout = min(self._easyocr_current_timeout + self._easyocr_timeout_step, self._easyocr_max_timeout)
if self._easyocr_current_timeout != old_timeout:
logger.debug(f"[ocr] EasyOCR 超时增加: {old_timeout}s → {self._easyocr_current_timeout}s")
if self._easyocr_consecutive_failures >= self._easyocr_max_failures:
logger.debug(f"[ocr] EasyOCR 连续{self._easyocr_consecutive_failures}次超时,杀子进程重来")
self._kill_easyocr_proc("连续超时")
@@ -2136,6 +2191,8 @@ class MessageMonitor:
# 成功 → 重置连续失败计数
self._easyocr_consecutive_failures = 0
self._easyocr_consecutive_startup_failures = 0 # 成功启动,重置启动失败计数
# 成功识别后重置超时到最小值,避免累积
self._easyocr_current_timeout = self._easyocr_min_timeout
# 计数 + 主动重启:EasyOCR GPU 内存泄漏累积 → OOM → 冻结
self._easyocr_call_count += 1
if self._easyocr_call_count >= self._easyocr_max_calls:
@@ -2403,9 +2460,8 @@ class MessageMonitor:
return
# EasyOCR 用原图(1x):2x 图送 GPU 显存压力大 + 加速泄漏
img_np = _np.array(im_original)
# inner timeout 必须 < 外层 _OCR_TIMEOUT (10s),
# 否则线程永远在外层 join 超时前不会结束 → 永远 "线程存活,跳过"。
_inner_timeout = max(_OCR_TIMEOUT - 1, 1)
# 使用动态超时:初始45秒,每次超时增加15秒,成功后重置
_inner_timeout = self._easyocr_current_timeout
text = self._call_easyocr_safe(img_np, timeout=_inner_timeout)
if text:
easy_text[0] = text