fix(msgbot): 修复内存未释放——延迟easyocr/torch导入+修正SetProcessWorkingSetSize参数+添加_heapmin(Task #1625)
This commit is contained in:
+49
-23
@@ -18,19 +18,12 @@ logger = logging.getLogger("msgbot.monitor")
|
||||
|
||||
# ⚠ 顺序重要: torch/easyocr 必须在 COM 模块 (uiautomation/winrt) 之前导入,
|
||||
# 否则 c10.dll 会因 COM 初始化冲突抛出 WinError 1114。
|
||||
_HAS_EASYOCR = False
|
||||
# ⭐ Task #1625: 延迟导入 easyocr/numpy 到 _init_easyocr() 惰性加载,
|
||||
# 避免主进程启动时加载 torch DLL (~300MB) + numpy (~100MB)。
|
||||
# 模块级乐观设 True,首次 _init_easyocr() 尝试导入;失败则改 False。
|
||||
_HAS_EASYOCR = True # 乐观标记,延迟确认
|
||||
_easyocr = None
|
||||
_np = None
|
||||
try:
|
||||
import easyocr as _easyocr
|
||||
import numpy as _np
|
||||
_HAS_EASYOCR = True
|
||||
except (ImportError, OSError) as _e:
|
||||
import traceback as _tb
|
||||
_easyocr = None
|
||||
_np = None
|
||||
logger.debug(f"[ocr] EasyOCR 导入失败: {type(_e).__name__}: {_e}")
|
||||
logger.debug(f"[ocr] Traceback: {_tb.format_exc()}")
|
||||
|
||||
import uiautomation as auto
|
||||
import win32gui
|
||||
@@ -1061,13 +1054,25 @@ class MessageMonitor:
|
||||
gc.collect()
|
||||
|
||||
# 2) Windows 特定:把工作集物理内存归还 OS
|
||||
# ⭐ Task #1625: 修正 SetProcessWorkingSetSize 参数和调用顺序
|
||||
try:
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
psapi = ctypes.windll.psapi
|
||||
h_process = kernel32.GetCurrentProcess()
|
||||
|
||||
# EmptyWorkingSet:强制把进程工作集所有页面换出到页面文件/丢弃,
|
||||
# 是真正把物理 RAM 交还 OS 的调用(SetProcessWorkingSetSize 仅设上下限)。
|
||||
# 先 SetProcessWorkingSetSize(1, -1):设置最小=1字节,最大=无限制
|
||||
# 允许系统回收所有可回收页面,为 EmptyWorkingSet 创造条件
|
||||
try:
|
||||
kernel32.SetProcessWorkingSetSize(
|
||||
h_process,
|
||||
1, # 最小工作集:1字节,允许系统回收所有可回收页面
|
||||
-1 # 最大工作集:无限制
|
||||
)
|
||||
logger.debug("[memory] 已调用 SetProcessWorkingSetSize(1, -1) 设置工作集下限")
|
||||
except Exception as e:
|
||||
logger.debug(f"[memory] SetProcessWorkingSetSize 失败: {e}")
|
||||
|
||||
# 再 EmptyWorkingSet:强制把进程工作集所有页面换出到页面文件/丢弃
|
||||
try:
|
||||
if psapi.EmptyWorkingSet(h_process):
|
||||
logger.debug("[memory] 已调用 EmptyWorkingSet 归还物理内存")
|
||||
@@ -1075,16 +1080,8 @@ class MessageMonitor:
|
||||
logger.debug("[memory] EmptyWorkingSet 返回 0(无需归还)")
|
||||
except Exception as e:
|
||||
logger.debug(f"[memory] EmptyWorkingSet 失败: {e}")
|
||||
|
||||
# SetProcessWorkingSetSize(-1,-1):允许系统后续按需在最小/最大间调度
|
||||
kernel32.SetProcessWorkingSetSize(
|
||||
h_process,
|
||||
-1, # 最小工作集:允许系统回收所有可回收页面
|
||||
-1 # 最大工作集:允许系统回收所有可回收页面
|
||||
)
|
||||
logger.debug("[memory] 已调用 SetProcessWorkingSetSize 释放工作集内存")
|
||||
except Exception as e:
|
||||
logger.debug(f"[memory] SetProcessWorkingSetSize 失败: {e}")
|
||||
logger.debug(f"[memory] 内存释放调用失败: {e}")
|
||||
|
||||
# 3) 归还 GPU 显存(若 torch/CUDA 可用)
|
||||
try:
|
||||
@@ -1100,6 +1097,14 @@ class MessageMonitor:
|
||||
# 再次垃圾回收,确保上述释放后的垃圾被最终回收
|
||||
gc.collect()
|
||||
|
||||
# ⭐ Task #1625: 强制 C 运行时释放空闲堆内存回 OS
|
||||
try:
|
||||
import msvcrt
|
||||
msvcrt._heapmin()
|
||||
logger.debug("[memory] 已调用 msvcrt._heapmin() 释放 C 运行时堆内存")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.info("[memory] 内存释放完成,已交还系统")
|
||||
|
||||
def _kill_easyocr_proc(self, reason: str = ""):
|
||||
@@ -2175,10 +2180,24 @@ class MessageMonitor:
|
||||
|
||||
def _init_easyocr(self):
|
||||
"""惰性初始化 EasyOCR(首次约 3-10 秒)。"""
|
||||
if not _HAS_EASYOCR or self._easyocr_reader is not None:
|
||||
global _easyocr, _np, _HAS_EASYOCR
|
||||
if self._easyocr_reader is not None:
|
||||
return
|
||||
try:
|
||||
logger.info("[ocr] EasyOCR 初始化中(首次约 3-10 秒)...")
|
||||
# ⭐ Task #1625: 延迟导入 easyocr 和 numpy,避免主进程加载 torch DLL
|
||||
try:
|
||||
import easyocr as _easyocr
|
||||
import numpy as _np
|
||||
_HAS_EASYOCR = True
|
||||
except (ImportError, OSError) as _e:
|
||||
import traceback as _tb
|
||||
_easyocr = None
|
||||
_np = None
|
||||
_HAS_EASYOCR = False
|
||||
logger.debug(f"[ocr] EasyOCR 导入失败: {type(_e).__name__}: {_e}")
|
||||
logger.debug(f"[ocr] Traceback: {_tb.format_exc()}")
|
||||
return
|
||||
# 限制 torch 线程数,避免 MKL/OpenMP 在多线程环境段错误
|
||||
import torch
|
||||
torch.set_num_threads(1)
|
||||
@@ -2939,6 +2958,13 @@ class MessageMonitor:
|
||||
if not _HAS_EASYOCR or self._easyocr_broken:
|
||||
return
|
||||
# EasyOCR 用原图(1x):2x 图送 GPU 显存压力大 + 加速泄漏
|
||||
# ⭐ Task #1625: numpy 懒加载(主进程不需要 torch,仅需 numpy 做 PIL→array 转换)
|
||||
global _np
|
||||
if _np is None:
|
||||
try:
|
||||
import numpy as _np
|
||||
except ImportError:
|
||||
return
|
||||
img_np = _np.array(im_original)
|
||||
# 使用动态超时:初始45秒,每次超时增加15秒,成功后重置
|
||||
_inner_timeout = self._easyocr_current_timeout
|
||||
|
||||
Reference in New Issue
Block a user