Skip to content

fix(engine): up --wait 超时清理按运行身份停止 - #276

Merged
SheathedSharp merged 4 commits into
mainfrom
fix/engine-timeout-cleanup-identity
Nov 11, 2026
Merged

SheathedSharp merged 4 commits into
mainfrom
fix/engine-timeout-cleanup-identity

Conversation

@SheathedSharp

Copy link
Copy Markdown
Owner

问题

up --wait 就绪超时后的清理只发送 groups.kill{only: [服务名]},不带预期运行身份。A 判定旧运行超时、B 已重启出同名新运行时,A 随后的清理会把 B 的运行一起停掉。本次修复前该风险未动态复现;现在有确定性回归覆盖。

改动

  • daemon:groups.kill 新增 expect_runs: [{service, run_id}](与 only 互斥)。端口选择与 no-port 运行选择都只认指名的 run id;快照无法归属到运行、或 run 名与声明矛盾的行不选。字段不完整、服务未声明、同一服务重复出现均为调用方错误。新能力 groups.kill.expect_runs 随 daemon.hello 宣告。
  • CLI:等待结果记录每个超时服务的 run id,清理改发 expect_runs。daemon 未宣告该能力时不发只带服务名的清理(会失败并说明超时运行仍在运行);拿不到 run id 时同样拒绝。仅 skipped 且无 run id 的旧 daemon 才从状态行回填。
  • 协议 schema 重新生成;docs/cli.md、docs/cli.zh.md、docs/agent/architecture.md 同步。

回归(先红后绿)

engine/internal/daemon/groups_kill_expect_test.go 用构造快照 + dry_run: true,不发送任何真实信号。修复前 TestTimeoutCleanupStopsOnlyTheRunItStarted/the_replacement_run_is_not_selected 失败:

cleanup selected the replacement run's listener: [{Host:localhost Port:19001 ... PID:424242 Name:api Method:sigterm OK:true}]

修复后通过;同一测试还覆盖“本次启动的 run 仍会被选中”。engine/internal/cmd/timeout_cleanup_test.go 覆盖请求携带 expect_runs、不带 only,以及旧 daemon / 无 run id 时拒绝清理。

验证(实际执行)

  • go build ./...、go vet ./...、go test -count=1 -timeout=8m ./... 全绿
  • go test -race 跑完整 scripts/race-packages.txt 清单,全绿
  • python3 scripts/check_docs.py:6 对文档一致;python3 -m unittest discover -s scripts -p 'check_*_test.py':52 项通过
  • 隔离 HOME/BERTH_HOME 的真实 daemon 冒烟:
    • 正向:up --wait --wait-timeout 2s(10s 预算、服务不监听)→ 退出 1、failed/ready_timeout、last_exit exit 143,进程已回收
    • 替换场景:A 的 up --wait 等待中,B 用 oberth start -d --group smoke-demo --name api 起同名新运行;A 超时(run 75ee154a)后 B 的进程仍在,status 显示 running
    • 沙箱 daemon 已 stop,进程与临时目录已清理

未跑

  • go test -tags integration ./internal/scenario/... 真实服务场景套件
  • Windows 原生构建 / 服务场景
  • Nightly / 发布门槛(完整 race + 真实服务场景)

🤖 Generated with Claude Code

SheathedSharp and others added 4 commits November 11, 2026 16:33
`expect_runs` 让调用方为每个服务指名本次启动的 run,端口选择和 no-port
运行选择都只认这个 run id:另一个控制者在此期间重启出的新运行服务名相同、
run 不同,因此不在停止范围内;快照无法归属到运行的行同样不选。

字段与 `only` 互斥(`only` 按服务名选,会把范围放大回被替换的运行);
给定时要求 service 与 run_id 都完整、服务在清单中声明、每个服务只出现一次。
带 `expect_runs` 的停止属于 scoped 停止,保留 `port: auto` 预占。
daemon 通过 `groups.kill.expect_runs` 能力宣告该字段,旧 daemon 会静默丢弃它。

Co-Authored-By: Claude Code <noreply@anthropic.com>
等待记录每个超时服务的 run id,清理时用 `expect_runs` 发送,而不是只带服务名:
A 判断旧运行就绪超时、B 已重启出同一服务的新运行时,清理不会再选中 B 的运行。
本次启动的 run 只认启动 chunk;只有 skipped 且无 run id 的旧 daemon 才从
状态行回填,避免把当下正在跑的替换运行当成自己的运行。

daemon 未宣告 `groups.kill.expect_runs` 时不发送只带服务名的清理,改为失败并
说明超时的运行仍在运行;拿不到 run id 时同样拒绝清理。

Co-Authored-By: Claude Code <noreply@anthropic.com>
cli.md / cli.zh.md 两侧同步写明:清理按 run_id 只停本次启动的运行,
未宣告 `groups.kill.expect_runs` 的 daemon 不会收到只带服务名的清理;
architecture.md 把这条归入「可靠控制身份缺失时拒绝操作」。

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
@SheathedSharp
SheathedSharp merged commit c8b4104 into main Nov 11, 2026
14 of 15 checks passed
@SheathedSharp
SheathedSharp deleted the fix/engine-timeout-cleanup-identity branch November 11, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant