现象(本地复现 + CI 实锤)
httpapi exerciser(bun run test:httpapi)在 summary 打印(pass=229 fail=0)之后进程不退出,挂起直至被 step timeout(15 分钟)杀掉。CI 实锤:PR #315 同 commit 两个 run 一红一绿(红 run 31994077328:summary 04:33:58 → step timeout 04:48:57,期间零输出)。本地抓网第一轮即复现(summary 后 120s 进程存活)。
挂起签名
挂起期间进程每 ~1.2 秒刷一条(dagID 各不相同、时间戳递减):
WARN DagSummaryPublisher: failed to publish summaries { dagID: dag_fe5ff1e2…, cause: … }
已定位的机制面
summary-publisher.ts:151-169:publisher 订阅 dag 触发事件,每个事件 forkIn(scope) 一个发布 fiber;发布体失败 → catchCause 打警告 → fiber 死亡
- 警告流不止 = 拆伙期间仍有 dag 事件持续涌入;store 已 dispose → 发布必败
- exerciser 尾段(index.ts:2154/2198):summary → Effect.scoped finalizer
disposeApps() → process.exit(0);挂点在 disposeApps 的 Promise.all(backend.ts:60 并行 dispose 全部 app)不返回
- Memory 实例状态本身是纯 Map(memory.ts:91)、无后台 fiber——嫌疑在于装配图变化改变了 teardown 时序/事件流
归因线索
#313(Memory.node 进 server.ts app 图)合入前 15+ 个 ci-test run 全绿;合入后首个 dev run 绿,第二批次出现一红一绿。强相关,机制未钉死——需要:谁在 dispose 期间持续发 dag 事件、哪个 fiber/scope 未了结。
风险
不止测试路径:真实 server 关停同样走 dispose——若同款事件风暴存在,生产实例关停也会 hang。需确认。
复现
/tmp/opencode-repro/hang-catcher.sh(10 轮循环 + summary 后 120s 存活判定 + SIGQUIT 抓栈),第一轮命中。日志:/tmp/opencode-repro/loop-1.log。
验收
- exerciser teardown 有界退出(summary 后 <60s 必 process.exit)
- 挂起根因证明(事件源 + 未了结 fiber 定位)
- 真实 server 关停路径确认不受同款风暴影响(或一并修复)
- flaky 消除验证:抓网脚本 10 轮无挂起
现象(本地复现 + CI 实锤)
httpapi exerciser(
bun run test:httpapi)在 summary 打印(pass=229 fail=0)之后进程不退出,挂起直至被 step timeout(15 分钟)杀掉。CI 实锤:PR #315 同 commit 两个 run 一红一绿(红 run 31994077328:summary 04:33:58 → step timeout 04:48:57,期间零输出)。本地抓网第一轮即复现(summary 后 120s 进程存活)。挂起签名
挂起期间进程每 ~1.2 秒刷一条(dagID 各不相同、时间戳递减):
已定位的机制面
summary-publisher.ts:151-169:publisher 订阅 dag 触发事件,每个事件forkIn(scope)一个发布 fiber;发布体失败 → catchCause 打警告 → fiber 死亡disposeApps()→process.exit(0);挂点在 disposeApps 的 Promise.all(backend.ts:60 并行 dispose 全部 app)不返回归因线索
#313(Memory.node 进 server.ts app 图)合入前 15+ 个 ci-test run 全绿;合入后首个 dev run 绿,第二批次出现一红一绿。强相关,机制未钉死——需要:谁在 dispose 期间持续发 dag 事件、哪个 fiber/scope 未了结。
风险
不止测试路径:真实 server 关停同样走 dispose——若同款事件风暴存在,生产实例关停也会 hang。需确认。
复现
/tmp/opencode-repro/hang-catcher.sh(10 轮循环 + summary 后 120s 存活判定 + SIGQUIT 抓栈),第一轮命中。日志:/tmp/opencode-repro/loop-1.log。验收