diff --git a/.gitea/workflows/vps-deploy.yml b/.gitea/workflows/vps-deploy.yml index 1784a24..bfdb734 100644 --- a/.gitea/workflows/vps-deploy.yml +++ b/.gitea/workflows/vps-deploy.yml @@ -66,12 +66,21 @@ jobs: set -e ssh 49.232.102.198 'C:\Python310\python.exe -X utf8 C:\sanguo_vnpy_v2\scripts\nas_sync\vps_update_dist.py' | tee /tmp/dist_swap.log grep -q DIST_UPDATE_DONE /tmp/dist_swap.log - # 停 API → 等端口彻底释放(旧实例被杀后端口有滞留, 立即重启会 10048 静默崩) + # 停 API → 僵尸端口守卫 → 等端口彻底释放(否则 fail) → 重启 + # 2026-08-16 实录: /end 杀不掉 uvicorn reload 孤儿子进程, 旧进程跨两轮 + # 部署占 8000, 新实例静默抢不到端口, /docs 200 验的还是旧代码 → 500 旧 bug 重现 ssh 49.232.102.198 'schtasks /end /tn sanguo-api' || true + ZPID=$(ssh 49.232.102.198 'netstat -ano -p tcp' | tr -d '\r' | grep ':8000' | grep -i LISTENING | awk '{print $NF}' | sort -u | head -1) + if [ -n "$ZPID" ]; then + echo "僵尸 API 进程 PID=$ZPID 仍占 8000, 强杀" + ssh 49.232.102.198 "taskkill /F /T /PID $ZPID" || true + fi + PORT_FREED=0 for i in $(seq 1 30); do - ssh 49.232.102.198 'netstat -ano -p tcp' | tr -d '\r' | grep -q ':8000.*LISTEN' || { echo "port freed after $((i*2))s"; break; } + ssh 49.232.102.198 'netstat -ano -p tcp' | tr -d '\r' | grep -q ':8000.*LISTEN' || { echo "port freed after $((i*2))s"; PORT_FREED=1; break; } sleep 2 done + if [ "$PORT_FREED" != "1" ]; then echo "❌ 端口 8000 未释放, 强杀后重启会 10048, 终止"; exit 1; fi ssh 49.232.102.198 'schtasks /run /tn sanguo-api' # 等新实例监听 + /docs 可用(VPS 启动需数分钟, 上限 10 分钟) for i in $(seq 1 60); do diff --git a/frontend/src/views/backtest/PortfolioBacktest.vue b/frontend/src/views/backtest/PortfolioBacktest.vue index 4c5d6c2..afa5d8a 100644 --- a/frontend/src/views/backtest/PortfolioBacktest.vue +++ b/frontend/src/views/backtest/PortfolioBacktest.vue @@ -436,7 +436,8 @@ async function prefillFromInstance(instId: number): Promise { const p = inst.params || {} if (typeof p.max_pool === 'number') form.max_pool = p.max_pool if (typeof p.benchmark === 'string') form.benchmark = p.benchmark - if (inst.symbol_or_pool && inst.symbol_or_pool !== '') form.pool = inst.symbol_or_pool + // #89 标的池不预填:#86a 定稿「实例不选标的,发起时定」——老实例存的 + // symbol_or_pool(all 等)不该悄悄改用户的池选择 } catch { /* 预填失败走默认 */ }