fix(ocr): 页面文件不足防护——双重预检+长期冷却
问题:EasyOCR worker启动报 WinError 1455(页面文件太小), 无法加载 cufft64_11.dll,物理内存充足但虚拟内存(已提交32.5/35.7GB)不足。 修复: 1. worker端:import前预检虚拟内存——剩余<2GB则直接exit(2),不进入torch/easyocr加载 2. worker端:import捕获WinError 1455时写入虚拟内存诊断信息(物理/页面/提交率) 3. 父进程端:启动worker前预检虚拟内存——<2GB直接冷却1小时,不启动worker 4. 父进程端:识别exit(2)/页面文件错误——冷却1小时而非30秒(系统级问题不反复重试) 诊断增强: - _get_virtual_memory_info():通过Win32 API获取物理/虚拟内存/页面文件状态 - 崩溃日志包含完整虚拟内存快照,便于排查 - 日志明确提示'增大Windows页面文件'
This commit is contained in:
+57
-1
@@ -19,6 +19,36 @@ import traceback
|
||||
from datetime import datetime
|
||||
|
||||
# ── 崩溃日志文件:写入源码目录根(easyocr_crash.log),统一管理、便于父进程读取 ──
|
||||
def _get_virtual_memory_info() -> dict:
|
||||
"""获取虚拟内存(页面文件)状态,用于诊断 WinError 1455。"""
|
||||
try:
|
||||
import ctypes
|
||||
class MEMORYSTATUSEX(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dwLength", ctypes.c_ulong),
|
||||
("dwMemoryLoad", ctypes.c_ulong),
|
||||
("ullTotalPhys", ctypes.c_ulonglong),
|
||||
("ullAvailPhys", ctypes.c_ulonglong),
|
||||
("ullTotalPageFile", ctypes.c_ulonglong),
|
||||
("ullAvailPageFile", ctypes.c_ulonglong),
|
||||
("ullTotalVirtual", ctypes.c_ulonglong),
|
||||
("ullAvailVirtual", ctypes.c_ulonglong),
|
||||
("ullAvailExtendedVirtual", ctypes.c_ulonglong),
|
||||
]
|
||||
mem = MEMORYSTATUSEX()
|
||||
mem.dwLength = ctypes.sizeof(MEMORYSTATUSEX)
|
||||
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(mem))
|
||||
return {
|
||||
"total_phys_gb": round(mem.ullTotalPhys / (1024**3), 1),
|
||||
"avail_phys_gb": round(mem.ullAvailPhys / (1024**3), 1),
|
||||
"total_pagefile_gb": round(mem.ullTotalPageFile / (1024**3), 1),
|
||||
"avail_pagefile_gb": round(mem.ullAvailPageFile / (1024**3), 1),
|
||||
"commit_pct": round((1 - mem.ullAvailPageFile / max(mem.ullTotalPageFile, 1)) * 100, 1),
|
||||
}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _get_crash_log_path() -> str:
|
||||
try:
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -67,11 +97,37 @@ try:
|
||||
os.environ.setdefault("OMP_NUM_THREADS", "1")
|
||||
os.environ.setdefault("MKL_NUM_THREADS", "1")
|
||||
|
||||
# ── 预检:虚拟内存是否足够加载 CUDA 库(cufft64_11.dll ~800MB) ──
|
||||
_vm = _get_virtual_memory_info()
|
||||
if _vm and _vm.get("avail_pagefile_gb", 99) < 2.0:
|
||||
_msg = (
|
||||
f"页面文件不足: 剩余{_vm.get('avail_pagefile_gb', '?')}GB"
|
||||
f"(提交{_vm.get('commit_pct', '?')}%)"
|
||||
f",CUDA库需要≥2GB连续虚拟地址空间"
|
||||
)
|
||||
_write_crash_log(f"{datetime.now()} [easyocr_worker] 页面文件预检失败: {_msg}")
|
||||
_flush_exit(f"页面文件不足: {_msg}", code=2)
|
||||
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import easyocr
|
||||
except Exception as e:
|
||||
_flush_exit(f"import 失败: {e}\n{traceback.format_exc()}")
|
||||
_vm = _get_virtual_memory_info()
|
||||
_vm_hint = ""
|
||||
if _vm:
|
||||
_vm_hint = (
|
||||
f"\n[virtual memory] 物理内存: {_vm.get('avail_phys_gb',0)}/{_vm.get('total_phys_gb',0)} GB"
|
||||
f" | 页面文件: {_vm.get('avail_pagefile_gb',0)}/{_vm.get('total_pagefile_gb',0)} GB"
|
||||
f" | 提交使用率: {_vm.get('commit_pct',0)}%"
|
||||
)
|
||||
# WinError 1455 = 页面文件太小 → 标记为永久失败,不重试
|
||||
_is_pagefile_error = "1455" in str(e) or "页面文件" in str(e)
|
||||
_exit_code = 2 if _is_pagefile_error else 1 # exit(2) = 虚拟内存不足
|
||||
_write_crash_log(
|
||||
f"{datetime.now()} [easyocr_worker] import {_exit_code}: "
|
||||
f"{'页面文件不足' if _is_pagefile_error else 'import失败'}: {e}{_vm_hint}"
|
||||
)
|
||||
_flush_exit(f"import 失败{'(页面文件不足)' if _is_pagefile_error else ''}: {e}{_vm_hint}", code=_exit_code)
|
||||
|
||||
|
||||
def _init_reader():
|
||||
|
||||
+32
-1
@@ -1229,6 +1229,30 @@ class MessageMonitor:
|
||||
if time.time() < self._easyocr_retry_until:
|
||||
return False
|
||||
|
||||
# ⭐ 页面文件预检:如果虚拟内存不足,直接跳过 EasyOCR(避免反复崩溃循环)
|
||||
try:
|
||||
import ctypes
|
||||
class _MEMORYSTATUSEX(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dwLength", ctypes.c_ulong), ("dwMemoryLoad", ctypes.c_ulong),
|
||||
("ullTotalPhys", ctypes.c_ulonglong), ("ullAvailPhys", ctypes.c_ulonglong),
|
||||
("ullTotalPageFile", ctypes.c_ulonglong), ("ullAvailPageFile", ctypes.c_ulonglong),
|
||||
("ullTotalVirtual", ctypes.c_ulonglong), ("ullAvailVirtual", ctypes.c_ulonglong),
|
||||
("ullAvailExtendedVirtual", ctypes.c_ulonglong),
|
||||
]
|
||||
_mem = _MEMORYSTATUSEX()
|
||||
_mem.dwLength = ctypes.sizeof(_MEMORYSTATUSEX)
|
||||
ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(_mem))
|
||||
_avail_page_gb = _mem.ullAvailPageFile / (1024**3)
|
||||
_commit_pct = round((1 - _mem.ullAvailPageFile / max(_mem.ullTotalPageFile, 1)) * 100, 1)
|
||||
if _avail_page_gb < 2.0:
|
||||
logger.error(f"[ocr] ⚠️ 页面文件不足: 剩余{_avail_page_gb:.1f}GB(提交{_commit_pct}%),"
|
||||
f"EasyOCR 冷却1小时。建议:增大 Windows 页面文件")
|
||||
self._easyocr_retry_until = time.time() + 3600
|
||||
return False
|
||||
except Exception:
|
||||
pass # 获取失败则不阻断
|
||||
|
||||
worker_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
'easyocr_worker.py'
|
||||
@@ -1298,8 +1322,15 @@ class MessageMonitor:
|
||||
if crash and ("access violation" in crash.lower() or "torch" in crash.lower()):
|
||||
self._easyocr_cuda_oom_count += 1
|
||||
logger.warning(f"[ocr] 检测到 CUDA 崩溃,累计{self._easyocr_cuda_oom_count}次,下次可能强制 CPU")
|
||||
# ⭐ 检测页面文件不足(WinError 1455)→ 长期冷却,不反复重试
|
||||
_is_pagefile_error = "页面文件" in str(detail) or "1455" in str(detail)
|
||||
if _is_pagefile_error:
|
||||
self._easyocr_retry_until = time.time() + 3600 # 1小时冷却(系统级问题)
|
||||
logger.error("[ocr] ⚠️ 页面文件不足(WinError 1455),EasyOCR 冷却1小时。"
|
||||
"建议:增大 Windows 页面文件大小(系统属性→高级→性能→虚拟内存)")
|
||||
else:
|
||||
self._easyocr_retry_until = time.time() + 30
|
||||
self._easyocr_proc = None
|
||||
self._easyocr_retry_until = time.time() + 30
|
||||
return False
|
||||
try:
|
||||
payload = json.loads(line)
|
||||
|
||||
Reference in New Issue
Block a user