修复EasyOCR超时和内存泄漏问题
This commit is contained in:
+59
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user