修复 AGC Ctrl+C 残留上个工作树后端导致切换工作树复用旧后端 #315

Merged
lhk229 merged 1 commits from fix/agc-ctrl-c-stale-backend into master 2026-09-09 20:08:04 +08:00
Member

closes #314

现象

npm run agc 按 Ctrl+C 后有概率残留上个工作树的 api-server.exe / SpacetimeDB,切换 worktree 再启动时 AGC 复用旧后端,改过数据库 / schema 的工作树会串库。

根因

  1. Windows 下长驻服务都经 Node shell: truecmd.exe /d /s /c 包装层启动,Ctrl+C 先杀包装层(0xC000013A);dev.mjsstopProcess 见到直接子进程已退出就 return,taskkill /PID <已退出 PID> /T /F 也只会失败,深处的 cargo → api-server.exe 无人清理。
  2. 按根 PID 遍历依赖快照里的父子链,中间层先消失时链断,只能拿到根 PID。
  3. 复用判据只看 .app/dev-stack.json status 与 /healthz/readyz/v1/ping,不校验端口上的进程属于哪个工作树,残留后端照样被判为健康并复用。

改动

  • 新增 scripts/dev-windows-process.mjs:按根 PID 遍历 + 按身份匹配(server-rs/target/debug/api-server.exe 绝对路径、SpacetimeDB --data-dir)两条独立清理路径,带 1s 快照缓存避免清理被拖慢。
  • scripts/dev.mjs:直接子进程已退出时仍按记录 PID 清理后代;退出时按身份兜底清扫本工作树后端(复用他人 standalone 时跳过);启动前清理旧 api-server 保留 Wait-Process 语义,避免 failed to remove file
  • apps/ai-game-creator-shell/scripts/start-dev-stack.mjs:复用前校验端口监听进程归属,无法证明归属就不复用、改为启动本工作树后端并允许端口漂移;信号与 finally 各兜底清扫一次;taskkill 失败时降级按 PID 遍历;等待就绪时输出归属校验失败原因,避免静默超时。探测不可用时退化为旧行为,不阻断本地启动。
  • 测试与文档:新增 scripts/dev-windows-process.test.ts、扩充 AGC 复用门禁用例;同步 docs/project-memory/shared-memory/pitfalls.md 与本地开发运维文档。

验证

  • 伪造 api-server.exe 进程:按身份精确命中并杀掉(matched=[17284] stopped=[17284])。
  • 3 个真实监听进程下归属判定:owned / api-server-owner-mismatch / spacetime-owner-mismatch 均正确。
  • npx vitest run scripts/dev.test.ts scripts/dev-windows-process.test.ts scripts/dev-stack-port-utils.test.ts apps/ai-game-creator-shell/tests/...:119 passed(唯一失败为 Windows 文件权限用例,已确认在合并基线 origin/master 上同样失败)。
  • node --checkeslint --max-warnings 0prettier --checknpm run check:encodinggit diff --check 全部通过。

备注

Rust 侧 api-serverwith_graceful_shutdown 没有超时上限,是「有概率」的来源之一;本次只在 Node 侧收口,是否给优雅退出加 deadline 可另行评估。

closes #314 ## 现象 `npm run agc` 按 Ctrl+C 后有概率残留上个工作树的 `api-server.exe` / SpacetimeDB,切换 worktree 再启动时 AGC 复用旧后端,改过数据库 / schema 的工作树会串库。 ## 根因 1. Windows 下长驻服务都经 Node `shell: true` 的 `cmd.exe /d /s /c` 包装层启动,Ctrl+C 先杀包装层(`0xC000013A`);`dev.mjs` 的 `stopProcess` 见到直接子进程已退出就 return,`taskkill /PID <已退出 PID> /T /F` 也只会失败,深处的 `cargo → api-server.exe` 无人清理。 2. 按根 PID 遍历依赖快照里的父子链,中间层先消失时链断,只能拿到根 PID。 3. 复用判据只看 `.app/dev-stack.json` status 与 `/healthz`、`/readyz`、`/v1/ping`,不校验端口上的进程属于哪个工作树,残留后端照样被判为健康并复用。 ## 改动 - 新增 `scripts/dev-windows-process.mjs`:按根 PID 遍历 + 按身份匹配(`server-rs/target/debug/api-server.exe` 绝对路径、SpacetimeDB `--data-dir`)两条独立清理路径,带 1s 快照缓存避免清理被拖慢。 - `scripts/dev.mjs`:直接子进程已退出时仍按记录 PID 清理后代;退出时按身份兜底清扫本工作树后端(复用他人 standalone 时跳过);启动前清理旧 api-server 保留 `Wait-Process` 语义,避免 `failed to remove file`。 - `apps/ai-game-creator-shell/scripts/start-dev-stack.mjs`:复用前校验端口监听进程归属,无法证明归属就不复用、改为启动本工作树后端并允许端口漂移;信号与 `finally` 各兜底清扫一次;`taskkill` 失败时降级按 PID 遍历;等待就绪时输出归属校验失败原因,避免静默超时。探测不可用时退化为旧行为,不阻断本地启动。 - 测试与文档:新增 `scripts/dev-windows-process.test.ts`、扩充 AGC 复用门禁用例;同步 `docs/project-memory/shared-memory/pitfalls.md` 与本地开发运维文档。 ## 验证 - 伪造 `api-server.exe` 进程:按身份精确命中并杀掉(`matched=[17284] stopped=[17284]`)。 - 3 个真实监听进程下归属判定:`owned` / `api-server-owner-mismatch` / `spacetime-owner-mismatch` 均正确。 - `npx vitest run scripts/dev.test.ts scripts/dev-windows-process.test.ts scripts/dev-stack-port-utils.test.ts apps/ai-game-creator-shell/tests/...`:119 passed(唯一失败为 Windows 文件权限用例,已确认在合并基线 `origin/master` 上同样失败)。 - `node --check`、`eslint --max-warnings 0`、`prettier --check`、`npm run check:encoding`、`git diff --check` 全部通过。 ## 备注 Rust 侧 `api-server` 的 `with_graceful_shutdown` 没有超时上限,是「有概率」的来源之一;本次只在 Node 侧收口,是否给优雅退出加 deadline 可另行评估。
suzmii added the
Priority
High
2
Kind/Bug
labels 2026-09-09 19:40:53 +08:00
suzmii added 1 commit 2026-09-09 19:40:53 +08:00
修复 AGC Ctrl+C 残留上个工作树后端导致切换工作树复用旧后端
Project CI / Repository checks (pull_request) Successful in 2m56s
Project CI / Frontend tests (pull_request) Successful in 3m45s
Project CI / Backend tests (pull_request) Successful in 7m8s
Project CI / Native shell tests (pull_request) Successful in 18m40s
c5219f7ad5
closes #314

新增 scripts/dev-windows-process.mjs:提供按根 PID 遍历与按身份匹配(api-server.exe 绝对路径、SpacetimeDB --data-dir)两条独立清理路径
scripts/dev.mjs:直接子进程(cmd.exe 包装层)已退出时仍按记录 PID 清理后代,不再提前 return
scripts/dev.mjs:退出时按身份兜底清扫本工作树 api-server 与自建 SpacetimeDB,复用他人 standalone 时跳过
scripts/dev.mjs:启动前清理旧 api-server 保留 Wait-Process 等待语义,避免 cargo 报 failed to remove file
apps/ai-game-creator-shell/scripts/start-dev-stack.mjs:复用配套后端前校验端口监听进程归属,无法证明归属则改为启动本工作树后端并允许端口漂移
apps/ai-game-creator-shell/scripts/start-dev-stack.mjs:收到信号与 finally 各兜底清扫一次本工作树 api-server.exe,taskkill 失败时降级为按 PID 遍历
apps/ai-game-creator-shell/scripts/start-dev-stack.mjs:等待后端就绪时输出归属校验未通过的具体原因,避免只表现为 600 秒超时
新增 scripts/dev-windows-process.test.ts 并扩充 AGC 复用门禁用例:覆盖断链遍历、身份匹配、归属判定与探测不可用退化
同步 docs/project-memory/shared-memory/pitfalls.md 与本地开发运维文档的进程清理与复用归属口径
suzmii requested review from lhk229 2026-09-09 20:00:13 +08:00
lhk229 merged commit a1b9b24891 into master 2026-09-09 20:08:04 +08:00
lhk229 deleted branch fix/agc-ctrl-c-stale-backend 2026-09-09 20:08:04 +08:00
lhk229 declined to review 2026-09-09 20:10:02 +08:00
Sign in to join this conversation.