diff --git a/easyocr_worker.py b/easyocr_worker.py index 950caec..2604612 100644 --- a/easyocr_worker.py +++ b/easyocr_worker.py @@ -15,14 +15,24 @@ import sys import base64 import io import os +import traceback -# ── 线程/进程限制:避免 MKL/OpenMP 在子进程多线程冲突 ── -os.environ.setdefault("OMP_NUM_THREADS", "1") -os.environ.setdefault("MKL_NUM_THREADS", "1") +# ── 顶层异常捕获:确保崩溃原因写入 stderr(否则父进程看到空 stderr) ── +def _flush_exit(msg, code=1): + sys.stderr.write(f"[easyocr_worker] FATAL: {msg}\n") + sys.stderr.flush() + sys.exit(code) -from PIL import Image -import numpy as np -import easyocr +try: + # ── 线程/进程限制:避免 MKL/OpenMP 在子进程多线程冲突 ── + os.environ.setdefault("OMP_NUM_THREADS", "1") + os.environ.setdefault("MKL_NUM_THREADS", "1") + + from PIL import Image + import numpy as np + import easyocr +except Exception as e: + _flush_exit(f"import 失败: {e}\n{traceback.format_exc()}") def _init_reader(): @@ -51,6 +61,8 @@ def _init_reader(): def _process_image(img_b64: str, reader) -> str: """解码 base64 图片 → EasyOCR 识别 → 返回空格拼接文本。""" + if reader is None: + return "" raw = base64.b64decode(img_b64) img = Image.open(io.BytesIO(raw)) # 保持与 monitor.py 一致:RGBA → numpy @@ -62,9 +74,13 @@ def _process_image(img_b64: str, reader) -> str: def _main(): - reader = _init_reader() - # ⭐ 就绪握手:初始化完成后先输出 {"ready": true},让父进程知道可以开始请求 - # 否则父进程的 10s 请求超时会包含 3-10s 的初始化时间 → 永远超时 + reader = None + try: + reader = _init_reader() + except Exception as e: + sys.stderr.write(f"[easyocr_worker] Reader 初始化失败: {e}\n") + sys.stderr.flush() + # ⭐ 就绪握手:即使 Reader 失败也要发 ready,否则父进程永远等握手 sys.stdout.write(json.dumps({"ready": True}, ensure_ascii=False) + "\n") sys.stdout.flush() sys.stderr.write("[easyocr_worker] 就绪,等待 stdin 输入\n") @@ -80,7 +96,11 @@ def _main(): img_b64 = payload.get("img", "") if not img_b64: raise ValueError("缺少 img 字段") + sys.stderr.write(f"[easyocr_worker] 收到请求,处理中... (img长度={len(img_b64)})\n") + sys.stderr.flush() text = _process_image(img_b64, reader) + sys.stderr.write(f"[easyocr_worker] 处理完成,结果长度={len(text)}\n") + sys.stderr.flush() out = json.dumps({"ok": True, "text": text}, ensure_ascii=False) sys.stdout.write(out + "\n") sys.stdout.flush() @@ -93,4 +113,7 @@ def _main(): if __name__ == "__main__": - _main() + try: + _main() + except Exception as e: + _flush_exit(f"_main 崩溃: {e}\n{traceback.format_exc()}") diff --git a/monitor.py b/monitor.py index 66c31a2..80a77b6 100644 --- a/monitor.py +++ b/monitor.py @@ -1159,6 +1159,9 @@ class MessageMonitor: img_b64 = base64.b64encode(buf.getvalue()).decode() with self._easyocr_lock: + # ⚠ 竞态保护:另一个线程可能已杀死进程并设 None + if not self._easyocr_proc or self._easyocr_proc.poll() is not None: + return None self._easyocr_proc.stdin.write(json.dumps({"img": img_b64}) + "\n") self._easyocr_proc.stdin.flush() @@ -1168,7 +1171,9 @@ class MessageMonitor: _q = Queue() def _read_worker(): try: - _q.put(self._easyocr_proc.stdout.readline()) + proc = self._easyocr_proc + if proc and proc.poll() is None: + _q.put(proc.stdout.readline()) except Exception: pass _t = threading.Thread(target=_read_worker, daemon=True) @@ -1334,7 +1339,8 @@ class MessageMonitor: return # EasyOCR 用原图(1x):2x 图送 GPU 显存压力大 + 加速泄漏 img_np = _np.array(im_original) - text = self._call_easyocr_safe(img_np, timeout=10) + # ⚠ 超时不能太短:首次加载模型 3-10s + 图片处理,10s 必超时 + text = self._call_easyocr_safe(img_np, timeout=30) if text: easy_text[0] = text