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:
youpaishidifu
2026-08-02 21:35:33 +08:00
parent a858bbb9f6
commit c853b3e504
2 changed files with 89 additions and 2 deletions
+57 -1
View File
@@ -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
View File
@@ -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)