chrome 泄漏根因:cleanup() 只 kill $CHROME_PID(父)→ renderer/gpu 子进程 reparent 给 init 成孤儿、 跨款累积(实测 U5 Round1 撞 wall_timeout、杀批组仍残留)。修=cleanup 加 pkill -f "$USER_DATA_DIR" 杀本实例整棵 chrome 树(user-data-dir mktemp 唯一、不误伤别的 chrome/live worker)。 plan 执行发现:① env 闸缺口(fresh worktree 无 node_modules/esbuild → build 失败 → M3 假性不收敛, 已补 U1 S0 + Risks);② M3 已证能收敛(smoke 面包+水果 clean accept、九门全过); ③ U5 Round1 被 confound(chrome 泄漏 + live worker:9501/@8200 争 4 核)、需隔离干净重跑。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
5.7 KiB
Bash
Executable File
102 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# serve-and-play-phaser.sh —— tier2 富游戏线真玩取证一键编排(fork 自 wg1 serve-and-play.sh,Phaser 化)
|
||
# owner:tier2 引擎线(M2 harness)+ 集成线(run.run_gates 调它)| cwd 必须 = 仓根(games-tier2-engine/)
|
||
#
|
||
# 【与 wg1 版的硬差异】
|
||
# ① serve【仓根】而非 game-runtime:宿主页 index.html 经绝对路径 /tier2/engine/boot-phaser-host.js 引 host,
|
||
# 而 tier2/ 是 game-runtime/ 的兄弟目录、在它外面 → 必须 serve 仓根才同时够到 game-runtime/games/ 与 tier2/engine/。
|
||
# ② 端口段 4330/9322(避开 wg1 的 4320/9222,可并行)。
|
||
# ③ gameId 形参 = 仓内相对路径段 _tier2-gen/<id>;URL = http://localhost:<port>/game-runtime/games/_tier2-gen/<id>/index.html。
|
||
# ④ 真玩驱动 = play-phaser.cdp.cjs(Phaser 探针重写版,非 wg1 的 play.cdp.cjs)。
|
||
#
|
||
# 用法:bash tier2/harness/serve-and-play-phaser.sh <gameRel> [port] [cdpPort]
|
||
# <gameRel> 例:_tier2-gen/feie-001
|
||
# 流程:净场 → 起 static-serve(仓根) → 起 headless Chrome → 等就绪 → play-phaser.cdp.cjs → trap 清理,返 play 退出码。
|
||
# 真跑必须在 mini-desktop(6c6g 禁 chrome)。本机只 node --check 静态校验本脚本无法做(bash),用 bash -n。
|
||
set -u
|
||
|
||
GAME_REL="${1:?用法: serve-and-play-phaser.sh <gameRel 如 _tier2-gen/feie-001> [port] [cdpPort]}"
|
||
PORT="${2:-4330}"
|
||
CDP_PORT="${3:-9322}"
|
||
|
||
# 仓根 = 本脚本所在目录上溯两级(tier2/harness/ → tier2/ → 仓根)。
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
|
||
# 生成游戏目录(绝对)+ 证据目录(显式给 play harness 的 --out,避免它内部 path.resolve('games',...) 依赖 cwd)。
|
||
GAME_DIR="$REPO_ROOT/game-runtime/games/$GAME_REL"
|
||
EV_DIR="$GAME_DIR/evidence"
|
||
[ -d "$GAME_DIR" ] || { echo "❌ 游戏目录不存在: $GAME_DIR"; exit 6; }
|
||
[ -f "$GAME_DIR/index.html" ] || { echo "❌ 宿主页缺失: $GAME_DIR/index.html(run.scaffold 应已拷模板)"; exit 6; }
|
||
[ -f "$GAME_DIR/bundle.iife.js" ] || { echo "❌ 构建产物缺失: $GAME_DIR/bundle.iife.js(先跑 build-phaser.mjs)"; exit 7; }
|
||
mkdir -p "$EV_DIR"
|
||
|
||
# Chrome 路径探测(跨平台:CHROME_BIN 显式覆盖 > Linux(mini-desktop x86 e2e门) > macOS)。
|
||
CHROME=""
|
||
if [ -n "${CHROME_BIN:-}" ] && [ -x "${CHROME_BIN}" ]; then
|
||
CHROME="${CHROME_BIN}"
|
||
else
|
||
for p in "/usr/bin/google-chrome" "/usr/bin/google-chrome-stable" \
|
||
"/usr/bin/chromium" "/usr/bin/chromium-browser" "/opt/google/chrome/chrome" \
|
||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
|
||
"/Applications/Chromium.app/Contents/MacOS/Chromium"; do
|
||
[ -x "$p" ] && CHROME="$p" && break
|
||
done
|
||
[ -z "$CHROME" ] && CHROME="$(command -v google-chrome chromium chromium-browser 2>/dev/null | head -1)"
|
||
fi
|
||
[ -z "$CHROME" ] && { echo "❌ 未找到 Chrome(探测 /usr/bin、/Applications、PATH、CHROME_BIN 均无)"; exit 3; }
|
||
|
||
USER_DATA_DIR="$(mktemp -d -t tier2-chrome-XXXX)"
|
||
SERVE_PID=""; CHROME_PID=""
|
||
cleanup() {
|
||
# 杀整棵 chrome 进程树:只 kill $CHROME_PID(父)会把 renderer/gpu 子进程 reparent 给 init 成孤儿、泄漏
|
||
# (实测串行批跑跨款累积、撞 wall_timeout)。用唯一的 $USER_DATA_DIR 精确匹配本实例【全部】chrome 进程,
|
||
# 不误伤别的 chrome 实例 / live worker 的 chrome(它们 user-data-dir 不同)。
|
||
[ -n "$USER_DATA_DIR" ] && pkill -f "$USER_DATA_DIR" 2>/dev/null
|
||
[ -n "$CHROME_PID" ] && kill "$CHROME_PID" 2>/dev/null
|
||
[ -n "$SERVE_PID" ] && kill "$SERVE_PID" 2>/dev/null
|
||
rm -rf "$USER_DATA_DIR" 2>/dev/null
|
||
}
|
||
trap cleanup EXIT INT TERM
|
||
|
||
# 净场:杀掉占用端口的旧进程(保守,仅杀本脚本端口相关)。
|
||
lsof -ti tcp:"$PORT" 2>/dev/null | xargs kill 2>/dev/null || true
|
||
lsof -ti tcp:"$CDP_PORT" 2>/dev/null | xargs kill 2>/dev/null || true
|
||
sleep 0.3
|
||
|
||
# 1) 静态服务器:serve【仓根】(复用 game-runtime 的零依赖 static-serve.cjs;它 serve 任意 root 子树,拒路径穿越)。
|
||
node "$REPO_ROOT/game-runtime/test/harness/static-serve.cjs" "$REPO_ROOT" "$PORT" \
|
||
>/tmp/tier2-serve-$PORT.log 2>&1 &
|
||
SERVE_PID=$!
|
||
|
||
# 2) Chrome headless(CDP 必备旗标:--remote-allow-origins=* + --autoplay-policy=...)。
|
||
"$CHROME" --headless=new --no-sandbox --no-zygote --disable-gpu \
|
||
--remote-debugging-port="$CDP_PORT" --remote-allow-origins='*' \
|
||
--autoplay-policy=no-user-gesture-required \
|
||
--user-data-dir="$USER_DATA_DIR" about:blank >/tmp/tier2-chrome-$PORT.log 2>&1 &
|
||
CHROME_PID=$!
|
||
|
||
# 3) 就绪等待(serve 仓根 → 宿主页 URL 含 /game-runtime/ 前缀)。
|
||
GAME_URL_PATH="/game-runtime/games/$GAME_REL/index.html"
|
||
ok_serve=0; ok_cdp=0
|
||
for i in $(seq 1 20); do
|
||
curl -sf "http://localhost:$PORT$GAME_URL_PATH" -o /dev/null 2>/dev/null && ok_serve=1
|
||
curl -sf "http://localhost:$CDP_PORT/json/version" -o /dev/null 2>/dev/null && ok_cdp=1
|
||
[ "$ok_serve" = 1 ] && [ "$ok_cdp" = 1 ] && break
|
||
sleep 0.5
|
||
done
|
||
[ "$ok_serve" != 1 ] && { echo "❌ static-serve 未就绪(见 /tmp/tier2-serve-$PORT.log)"; cat /tmp/tier2-serve-$PORT.log; exit 4; }
|
||
[ "$ok_cdp" != 1 ] && { echo "❌ Chrome CDP 未就绪(见 /tmp/tier2-chrome-$PORT.log)"; tail -5 /tmp/tier2-chrome-$PORT.log; exit 5; }
|
||
|
||
# 4) 真玩驱动(play-phaser.cdp.cjs):
|
||
# --base = serve 根 + /game-runtime(harness 内部 URL = ${base}/games/${gameId}/index.html,故 gameId=_tier2-gen/<id>);
|
||
# --out = 显式证据目录(绝对,避免依赖 cwd);
|
||
# --spec = 生成游戏目录下的 play-spec.json(run.run_gates 已写)。
|
||
node "$SCRIPT_DIR/play-phaser.cdp.cjs" "$GAME_REL" \
|
||
--base="http://localhost:$PORT/game-runtime" \
|
||
--cdp="http://localhost:$CDP_PORT" \
|
||
--spec="$GAME_DIR/play-spec.json" \
|
||
--out="$EV_DIR"
|
||
PLAY_RC=$?
|
||
exit "$PLAY_RC"
|