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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user