From c853b3e5047ab4f5292aae975163f958cdc7a098 Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Sun, 2 Aug 2026 21:35:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(ocr):=20=E9=A1=B5=E9=9D=A2=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=8D=E8=B6=B3=E9=98=B2=E6=8A=A4=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=8F=8C=E9=87=8D=E9=A2=84=E6=A3=80+=E9=95=BF=E6=9C=9F?= =?UTF-8?q?=E5=86=B7=E5=8D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: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页面文件' --- easyocr_worker.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++- monitor.py | 33 ++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/easyocr_worker.py b/easyocr_worker.py index b4a428a..53466d7 100644 --- a/easyocr_worker.py +++ b/easyocr_worker.py @@ -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(): diff --git a/monitor.py b/monitor.py index d809543..2780036 100644 --- a/monitor.py +++ b/monitor.py @@ -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)