fix(easyocr): 顶层崩溃全程留痕,消除"一声不吭"结束进程
- easyocr_worker.py: 新增 worker 状态文件 easyocr_worker_state.json (pid/mode/status/last_img_len/ts/error),就绪/忙碌/空闲实时落盘; 强化 sys.excepthook / _flush_exit / _signal_handler 崩溃写状态+crash.log - monitor.py: _kill_easyocr_proc 杀前诊断三路 (状态文件 + 残留stderr + crash.log尾),以 warning 汇总进主日志; _read_easyocr_crash_tail 新增 ignore_age 跳过60s时间窗用于诊断路径 - unicode_radicals.md: 丿(U+4E3F)行改 - 不参与偏旁过滤,修复OCR碎片误杀儿下载等
This commit is contained in:
+48
-1
@@ -58,6 +58,44 @@ def _get_crash_log_path() -> str:
|
||||
|
||||
CRASH_LOG = _get_crash_log_path()
|
||||
|
||||
# ── 状态文件:记录 worker 当前在处理什么/卡多久,父进程 kill 前读取,避免"一声不吭" ──
|
||||
def _get_state_path() -> str:
|
||||
try:
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
except Exception:
|
||||
base = "."
|
||||
return os.path.join(base, "easyocr_worker_state.json")
|
||||
|
||||
|
||||
STATE_PATH = _get_state_path()
|
||||
|
||||
# worker 当前运行模式(GPU/CPU),由 _init_reader 设置
|
||||
_WORKER_MODE = "未知"
|
||||
|
||||
|
||||
def _write_state(status: str, last_img_len: int = 0, ts: float = None, error: str = ""):
|
||||
"""把当前 worker 状态落盘为 JSON,便于父进程 kill 前诊断死前状态。
|
||||
|
||||
status: idle / busy / crashed / terminated
|
||||
"""
|
||||
try:
|
||||
_ts = ts if ts is not None else datetime.now().timestamp()
|
||||
_rec = {
|
||||
"pid": os.getpid(),
|
||||
"mode": _WORKER_MODE,
|
||||
"status": status,
|
||||
"last_img_len": last_img_len,
|
||||
"ts": _ts,
|
||||
"iso": datetime.fromtimestamp(_ts).isoformat() if _ts else "",
|
||||
"error": error,
|
||||
}
|
||||
with open(STATE_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(_rec, f, ensure_ascii=False)
|
||||
f.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _write_crash_log(msg: str):
|
||||
try:
|
||||
with open(CRASH_LOG, "a", encoding="utf-8") as f:
|
||||
@@ -72,6 +110,7 @@ def _flush_exit(msg, code=1):
|
||||
sys.stderr.write(full + "\n")
|
||||
sys.stderr.flush()
|
||||
_write_crash_log(f"{datetime.now()} {full}")
|
||||
_write_state("crashed", error=full[:500])
|
||||
sys.exit(code)
|
||||
|
||||
# ── 全局未捕获异常钩子:任何漏网的 Python 异常都落盘 ──
|
||||
@@ -80,6 +119,7 @@ def _global_excepthook(exc_type, exc_val, exc_tb):
|
||||
sys.stderr.write(f"[easyocr_worker] 未捕获异常:\n{msg}")
|
||||
sys.stderr.flush()
|
||||
_write_crash_log(f"{datetime.now()} [easyocr_worker] 未捕获异常:\n{msg}")
|
||||
_write_state("crashed", error=msg[:500])
|
||||
|
||||
sys.excepthook = _global_excepthook
|
||||
|
||||
@@ -172,6 +212,7 @@ except Exception as e:
|
||||
|
||||
def _init_reader():
|
||||
"""初始化 EasyOCR reader(GPU 优先,父进程可强制 CPU)。"""
|
||||
global _WORKER_MODE
|
||||
# 先检查环境变量是否强制CPU
|
||||
_force_cpu = os.environ.get("EASYOCR_FORCE_CPU") == "1"
|
||||
|
||||
@@ -215,6 +256,7 @@ def _init_reader():
|
||||
try:
|
||||
reader = easyocr.Reader(["ch_sim", "en"], gpu=use_gpu)
|
||||
mode = "GPU" if use_gpu else "CPU"
|
||||
_WORKER_MODE = mode
|
||||
sys.stderr.write(f"[easyocr_worker] Reader 就绪({mode}模式)\n")
|
||||
sys.stderr.flush()
|
||||
return reader
|
||||
@@ -223,6 +265,7 @@ def _init_reader():
|
||||
sys.stderr.write(f"[easyocr_worker] GPU 初始化失败({e}),尝试 CPU\n")
|
||||
sys.stderr.flush()
|
||||
reader = easyocr.Reader(["ch_sim", "en"], gpu=False)
|
||||
_WORKER_MODE = "CPU"
|
||||
sys.stderr.write("[easyocr_worker] Reader 就绪(CPU 模式)\n")
|
||||
sys.stderr.flush()
|
||||
return reader
|
||||
@@ -256,6 +299,7 @@ def _main():
|
||||
sys.stdout.flush()
|
||||
sys.stderr.write("[easyocr_worker] 就绪,等待 stdin 输入\n")
|
||||
sys.stderr.flush()
|
||||
_write_state("idle")
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
@@ -267,6 +311,7 @@ def _main():
|
||||
img_b64 = payload.get("img", "")
|
||||
if not img_b64:
|
||||
raise ValueError("缺少 img 字段")
|
||||
_write_state("busy", last_img_len=len(img_b64))
|
||||
sys.stderr.write(f"[easyocr_worker] 收到请求,处理中... (img长度={len(img_b64)})\n")
|
||||
sys.stderr.flush()
|
||||
text = _process_image(img_b64, reader)
|
||||
@@ -275,6 +320,7 @@ def _main():
|
||||
out = json.dumps({"ok": True, "text": text}, ensure_ascii=False)
|
||||
sys.stdout.write(out + "\n")
|
||||
sys.stdout.flush()
|
||||
_write_state("idle")
|
||||
except Exception as e:
|
||||
estr = str(e)
|
||||
# ⭐ CUDA OOM / kernel error:GPU context 已损坏,继续跑只会反复卡死占显存
|
||||
@@ -306,7 +352,8 @@ def _cleanup_gpu():
|
||||
|
||||
|
||||
def _signal_handler(signum, frame):
|
||||
"""捕获 SIGTERM/SIGINT,先清理 GPU 再退出(proc.terminate() 触发此处理)。"""
|
||||
"""捕获 SIGTERM/SIGINT,先记录被终止状态再清理 GPU 退出(proc.terminate() 触发此处理)。"""
|
||||
_write_state("terminated", error=f"收到信号 {signum},父进程请求终止,正在清理退出")
|
||||
_cleanup_gpu()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user