#!/bin/bash # systemd 调度的入口:跑一次全量同步 + 配套结构化 watch # # 设计: # - 后台启动 runall_once.py(输出重定向到时间戳 log) # - 同时后台启动 structured_watch.sh 盯进程(13 checkpoint × 递增间隔) # - wrapper 自身 fast-poll runall PID(每 5s 一次) # - runall 一退出就 SIGTERM watch(不靠 watch 自己的 checkpoint 轮询,避开 30min 长 sleep) # - watch 自然结束后整个脚本退出 # - 退出码透传 runall_once.py 的 exit code,systemd 据此判断成功 / 失败 # # 为什么不只用 structured_watch: # - 看 [[background-watcher-pitfalls]] 记忆:先承诺后验证是三大坑之一 # - structured_watch.sh 的 schedule 在后期是 30min / 60min 间隔 # - runall 死后,watch 最坏要等 60min 才发现进程没了 # - wrapper fast-poll 让响应时间 < 5s # # 为什么不让 systemd TimeoutStartSec= 自动收尾: # - 超时是 SIGTERM → SIGKILL,但 SIGKILL 不给 runall / watch 写日志的机会 # - 自己 fast-poll 能优雅收尾(先 SIGTERM watch 留出写日志时间,再 SIGKILL 兜底) set -u PROJECT_ROOT="/home/gao/Development/quant_home/market_sync" LOG_DIR="$PROJECT_ROOT/logs" mkdir -p "$LOG_DIR" TS=$(date +%Y%m%d_%H%M%S) RUNALL_LOG="$LOG_DIR/runall_${TS}.log" WATCH_LOG="$LOG_DIR/watch_${TS}.log" echo "[market-sync-run] start ts=$TS" echo "[market-sync-run] runall_log=$RUNALL_LOG" echo "[market-sync-run] watch_log=$WATCH_LOG" # 注:不做法定节假日过滤。理由(fail-open 原则): # - 节假日配置(TRADING_HOLIDAYS / config 表)一旦出错(误把今天加进去、 # 漏更新本年度、笔误等)= 直接漏一天数据,且没声音 # - 大不了非交易日跑出来 mairui/baostock 返回空数据,task 报 warning 但不丢历史 # - 周末过滤由 systemd timer `OnCalendar=Mon..Fri` 完成,足够 # ── 1) 后台启动 runall_once.py ── cd "$PROJECT_ROOT" .venv/bin/python bin/runall_once.py > "$RUNALL_LOG" 2>&1 & RUNALL_PID=$! echo "[market-sync-run] runall pid=$RUNALL_PID" # ── 2) 后台启动 structured_watch.sh(独立进程,不阻塞 wrapper) # PATTERN 不含 "python" → 触发 watch 内部 case 第二分支,前缀 [p]ython.* 拼出 "$PROJECT_ROOT/bin/structured_watch.sh" \ "runall_once" \ "$RUNALL_LOG" \ "$WATCH_LOG" \ "market-sync-full" > /dev/null 2>&1 & WATCH_PID=$! echo "[market-sync-run] watch pid=$WATCH_PID" # ── 3) wrapper 自身 fast-poll runall PID(每 5s) # 这是关键修复:runall 死后 < 5s 内就触发 watch 退出 RUNALL_EXIT_CODE=1 while kill -0 "$RUNALL_PID" 2>/dev/null; do sleep 5 done # runall 已退出,取 exit code wait "$RUNALL_PID" 2>/dev/null RUNALL_EXIT_CODE=$? echo "[market-sync-run] runall 已退出 exit_code=$RUNALL_EXIT_CODE" # ── 4) 通知 watch 退出(如果还在跑)── if kill -0 "$WATCH_PID" 2>/dev/null; then echo "[market-sync-run] 通知 watch 退出(pid=$WATCH_PID)" kill -TERM "$WATCH_PID" 2>/dev/null || true # 给 watch 30s 自然退出(不要立即 KILL,留它写最后日志) for _ in 1 2 3 4 5 6; do sleep 5 kill -0 "$WATCH_PID" 2>/dev/null || break done if kill -0 "$WATCH_PID" 2>/dev/null; then echo "[market-sync-run] ⚠️ watch 30s 内未退出,强制 kill -9" kill -KILL "$WATCH_PID" 2>/dev/null || true fi fi # ── 5) 输出 summary ── SUMMARY="$LOG_DIR/runall_summary.json" if [ -f "$SUMMARY" ]; then echo "[market-sync-run] === summary ===" cat "$SUMMARY" echo "[market-sync-run] === /summary ===" fi echo "[market-sync-run] done exit_code=$RUNALL_EXIT_CODE" exit "$RUNALL_EXIT_CODE"