#!/usr/bin/env bash # ============================================================================= # 守护重启: main.py 被外部 kill / 异常退出后, 自动拉起(共享机上挡不住别人kill, 就让它自己回来)。 # 【带明确开关, 想关随时关, 不会和你抢】: # 关闭原理 = 放一个"暂停标记文件" .guard_paused, 守护看到它就什么都不做。 # # 用法: # bash scripts/守护重启.sh install # 安装(每分钟检测一次, 没在跑就启动) # bash scripts/守护重启.sh off # ★想停就用这个★ 暂停自动重启 + 停掉当前进程 # bash scripts/守护重启.sh on # 重新开启自动重启(下一分钟内会被拉起) # bash scripts/守护重启.sh status # 看状态(装没装/开没开/在没在跑) # bash scripts/守护重启.sh uninstall # 彻底卸载(删掉定时任务, 进程不动) # bash scripts/守护重启.sh tick # (cron 内部调用, 别手动跑) # # 启动参数想改, 改下面 START= 这一行即可。 # ============================================================================= set -u ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" PAUSE="$ROOT/.guard_paused" # 这个文件存在 = 暂停自动重启 LOCK="$ROOT/.guard.lock" GLOG="$ROOT/guard.log" CRON_TAG="# ocr-guard" # crontab 里的标识, 用于增删去重 # ★ 启动命令(按之前调优建议; 想改并发/参数改这里) ★ # MODE=worker(默认): 常驻 worker 从 COS 认领任务; MODE=local: 旧的本地 text2 模式。 # nice -n 15: 让 co-located 的 OCR 服务优先抢 CPU; max-tasks-per-child 20: 本机内存偏紧 # (44G可用/184G总量), 定期回收worker释放常驻内存; 3.11回收有缩水风险(见CLAUDE.md), 但20这个 # 量级配合 --mem-guard-gb 观察下来风险可控, 内存不紧的机器可以改回0(不回收)。 if [ -z "${MODE:-}" ]; then _src="$(python -c "from config.config_utils import config; print((config.get_dispatch_config() or {}).get('source','cos'))" 2>/dev/null || echo cos)" [ "$_src" = "local" ] && MODE="local" || MODE="worker" fi MODE="${MODE:-worker}" if [ "$MODE" = "worker" ]; then START='nice -n 15 python run_worker.py --max-workers 30 --max-tasks-per-child 20 --mem-guard-gb 30 --executor process' PROC_PAT='python .*run_worker\.py' else START='nice -n 15 python main.py --source-list ./text2 --max-workers 30 --max-tasks-per-child 20 --mem-guard-gb 30 --executor process' PROC_PAT='python .*main\.py' fi # 判断父进程是否在跑(forkserver worker 命令行不含入口文件名, 不会误判) running() { pgrep -af "$PROC_PAT" 2>/dev/null | grep -v 'multiprocessing' | grep -v 'grep' >/dev/null 2>&1 } # app.log 轮转: 进程可能正在往它里面 >> 追加写, 不能直接 mv(会导致进程还在写旧inode, 新文件 # 看起来永远是空的) —— 用 copytruncate: 先 cp 一份出去, 再原地截断; O_APPEND 的fd是按"当前 # 文件大小"找写入位置的, 原地截断后进程会自动从offset 0继续写这个(同一个)文件, 不受影响。 _rotate_applog() { local f="$ROOT/app.log" maxmb=200 [ -f "$f" ] || return 0 local sz_mb=$(( $(stat -c%s "$f" 2>/dev/null || echo 0) / 1024 / 1024 )) if [ "$sz_mb" -ge "$maxmb" ]; then local ts; ts="$(date +%Y%m%d_%H%M%S)" cp "$f" "${f}.${ts}" 2>/dev/null && : > "$f" \ && echo "$(date '+%F %T') [日志轮转] app.log(${sz_mb}M) → app.log.${ts}, 原地清空续写" >> "$GLOG" ls -t "${f}".[0-9]* 2>/dev/null | tail -n +6 | xargs -r rm -f # 只留最近5份 fi } case "${1:-status}" in install) ( crontab -l 2>/dev/null | grep -vF "$CRON_TAG" echo "* * * * * cd $ROOT && bash scripts/守护重启.sh tick $CRON_TAG" ) | crontab - rm -f "$PAUSE" echo "✓ 守护已安装(每分钟检测), 自动重启: 开启" echo " 想停: bash scripts/守护重启.sh off" ;; uninstall) crontab -l 2>/dev/null | grep -vF "$CRON_TAG" | crontab - echo "✓ 已卸载守护定时任务(不再自动重启)。当前进程未动。" echo " 如要同时停进程: bash scripts/stop.sh -y" ;; off) touch "$PAUSE" echo "✓ 已暂停自动重启(标记 $PAUSE)。正在停止当前进程..." bash "$ROOT/scripts/stop.sh" -y echo "✓ 已停止, 且守护不会再把它拉起。恢复运行: bash scripts/守护重启.sh on" ;; on) rm -f "$PAUSE" echo "✓ 自动重启已开启。若当前没在跑, 一分钟内会被拉起。" ;; tick) # 暂停中 → 什么都不做(这就是 off 之所以能稳关掉的原因) [ -f "$PAUSE" ] && exit 0 # 防并发: 同一时刻只允许一个 tick 启动 exec 9>"$LOCK" flock -n 9 || exit 0 _rotate_applog # 每分钟顺带检查一次, 跟要不要重启无关(常驻不重启时全靠这个兜底) running && exit 0 echo "$(date '+%F %T') 检测到未运行 → 启动" >> "$GLOG" setsid nohup $START >> "$ROOT/app.log" 2>&1 & ;; status) echo "项目目录: $ROOT" if crontab -l 2>/dev/null | grep -qF "$CRON_TAG"; then echo "守护定时任务: ✓ 已安装(每分钟检测)" else echo "守护定时任务: ✗ 未安装" fi if [ -f "$PAUSE" ]; then echo "自动重启: ⏸ 已暂停 (.guard_paused 存在 → 不会拉起)" else echo "自动重启: ▶ 开启" fi if running; then echo "main.py: 运行中"; else echo "main.py: 未运行"; fi ;; *) echo "用法: install | on | off | uninstall | status | tick" ;; esac