fix(Task#1601): gui.py CUDA子进程安全探针,防止access violation崩溃

- 新增_probe_cuda_safety()静态方法:子进程做CUDA显存分配(torch.zeros(1,device='cuda'))
- 子进程崩溃(returncode=0xC0000005 ACCESS_VIOLATION)只杀子进程,主进程存活
- _check_memory_for_gpu()内存检查通过后调用探针,探针失败→降级CPU模式
- 验证:本机GTX1650+torch2.5.1+cu121探针returncode=3221225477,探针正确拦截
- monitor.py已有子进程隔离(_ensure_easyocr_subprocess),无需修改
This commit is contained in:
youpaishidifu
2026-08-25 10:12:41 +08:00
parent bdb4e5c168
commit eef3e08f8f
+51 -2
View File
@@ -123,8 +123,44 @@ class OCREngineManager:
self._timer_thread = None
self._timer_stop = threading.Event()
@staticmethod
def _probe_cuda_safety(timeout: int = 30) -> bool:
"""子进程 CUDA 安全探针:在子进程里做 CUDA 显存分配。
返回 True = CUDA 可用且安全,False = 不可用或崩溃。
崩溃只杀子进程,不影响主进程。"""
import subprocess, sys
_probe_code = (
"import torch; "
"assert torch.cuda.is_available(), 'CUDA not available'; "
"t = torch.zeros(1, device='cuda'); "
"del t; "
"torch.cuda.empty_cache(); "
"print('CUDA_OK')"
)
try:
_py = sys.executable
_r = subprocess.run(
[_py, "-c", _probe_code],
capture_output=True, text=True, timeout=timeout,
creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0)
)
ok = _r.returncode == 0 and "CUDA_OK" in _r.stdout
if not ok:
# 子进程崩溃或超时
_stderr_snip = (_r.stderr or "")[:200]
logging.getLogger(__name__).warning(
f"[gui] CUDA 子进程探针失败(rc={_r.returncode}): {_stderr_snip}"
)
return ok
except subprocess.TimeoutExpired:
logging.getLogger(__name__).warning("[gui] CUDA 子进程探针超时")
return False
except Exception as e:
logging.getLogger(__name__).warning(f"[gui] CUDA 子进程探针异常: {e}")
return False
def _check_memory_for_gpu(self) -> bool:
"""检查系统内存,决定是否可用GPU模式。与monitor.py同逻辑。"""
"""检查系统内存 + CUDA 安全性,决定是否可用GPU模式。"""
try:
import torch
if not torch.cuda.is_available():
@@ -167,6 +203,16 @@ class OCREngineManager:
self._log(f" ⚠ 系统提交量过高({commit_pct:.0f}% > 95%),降级CPU模式")
return False
# ⭐ Task #1601: CUDA 子进程安全探针
# torch.cuda.is_available() 通过不代表 CUDA 安全——
# 显存分配/access violation 在 native 层崩溃,Python try/except 捕获不到。
# 子进程探针做真实 CUDA tensor 分配,崩溃只杀子进程。
self._log(" 🔍 运行 CUDA 安全探针...")
if not self._probe_cuda_safety(timeout=30):
self._log(" ⚠ CUDA 探针失败(驱动/显存异常),降级CPU模式")
return False
self._log(" ✅ CUDA 探针通过")
self._log(f" ✅ 内存充足,启用GPU模式")
return True
@@ -186,6 +232,9 @@ class OCREngineManager:
self._log("🔧 初始化OCR引擎...")
# 初始化EasyOCR(根据内存条件选择GPU/CPU)
# ⭐ Task #1601: GPU 探针已拦截 CUDA 异常,此处用 try/except 兜底
# access violation (native crash) 无法被 Python 捕获,
# 但探针通过后此概率极低;CPU 模式不受 native crash 影响。
use_gpu = False
try:
import easyocr
@@ -196,7 +245,7 @@ class OCREngineManager:
self._easyocr_reader = easyocr.Reader(['ch_sim', 'en'], gpu=use_gpu, verbose=False)
self._log(f" ✅ EasyOCR就绪({mode_str}模式)")
except Exception as e:
# GPU失败 → 降级CPU重试
# GPU失败 → 降级CPU重试(注意:access violation 不可被 except 捕获)
if use_gpu:
self._log(f" ⚠ EasyOCR GPU初始化失败: {e},降级CPU重试...")
try: