ci: stall guard 自证仍会触发,并修复从未真正执行的 SIGKILL 升级 (#4250) - #4960
Merged
Conversation
scripts/run-with-stall-guard.mjs 是 Test Core 停摆变成一条明确红的唯一机制, 现已被五个 workflow 的六个 job 依赖。但它的触发路径只在真实停摆时执行 —— 一个罕见且无法按需复现的事件,所以两次停摆之间没有任何东西断言它还有效。 一次重构可以悄悄解除 CI 唯一的停摆探测器,而所有 run 依旧全绿。 新增 --self-test(pnpm check:stall-guard),用合成停摆驱动 guard 自身: idle 挂起、同步自旋挂起、一行输出都没有的挂起、以及一个截获 SIGTERM 的后代。 断言 exit 75 判词、idle/ON-CPU 分类、SIGUSR2 取栈(含「没有报告 = 事件循环被 阻塞」这一反向推断)、进程组彻底收尾,以及反方向:健康的 run 仍然透传自己的 退出码,持续输出永远不会被误判成停摆。 写这个 harness 的过程中定位到一个真实缺陷,已一并修复:SIGKILL 升级挂在一个 unref 的定时器上,而 guard 在直接子进程的 'exit' 回调里就退出了 —— 直接子进程 (pnpm -> turbo,或 sh)对 SIGTERM 立即死亡,于是定时器永远不会 fire。任何 截获 SIGTERM 的**后代**都会活过 guard,而这正是 ObjectKernelConfig.gracefulShutdown 在每个测试 boot 的 kernel 里安装的形状(#4250 自己的根因排查数到一次 objectql 套件运行 boot 47 个 kernel、截获 48 次 SIGTERM)。现在 guard 会等待进程组真正 清空,再对赖着不走的进程 SIGKILL,并在日志里点名它们。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
xuyushun441-sys
marked this pull request as ready for review
August 3, 2026 18:09
xuyushun441-sys
enabled auto-merge
August 3, 2026 18:09
…t-core-stall # Conflicts: # package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4250
先核实前提:本单描述的症状已被覆盖,但覆盖它的那个机制本身没人验证
先读了仓内四个既有 changeset(
test-core-stall-guard、stall-guard-rollout、temporal-conformance-stall-guard、stall-forensics-and-kernel-test-hygiene)和.github/workflows/ci.yml里Test Core的现状。结论:议题原始症状(日志冻结、job 一直
in_progress、等人工取消)现在不会再发生。Test Core已分片、job timeout 收到 30 分钟,两个测试步骤都走run-with-stall-guard.mjs:静默超过 10 分钟就判定停摆,打印最后一行、做取证、杀进程组、退出 75。议题里「建议的最小动作」(给一个显著短于兜底值的 timeout,把停摆变成一条明确的红)不仅已落地,而且被取证机制超额完成 —— 第四、五次停摆就是靠它在 10 分钟内给出带包名的红,几分钟内定位到driver-mongodb的无上界下载等待(#4322)。所以本 PR 不是再加一个 timeout。 剩下没被覆盖的是另一类东西:
run-with-stall-guard.mjs现在被五个 workflow 的六个 job 依赖,但它只在真实停摆时才执行到有意思的那条路径 —— 一个大约 5-10% 命中率、且本机无法复现的事件。两次真实停摆之间,没有任何东西断言它还有效。一个从没被触发过的 guard,和一个不存在的 guard 无法区分。一次重构可以悄悄解除 CI 唯一的停摆探测器,而所有 run 依旧全绿 —— 下一次真的卡住时,我们会退回到本议题描述的原始状态,外加一个「已经修好了」的错误信念。
交付物一:
--self-test,用合成停摆驱动 guard 自身pnpm check:stall-guard(接入lint.yml,约 50 秒,不构建、不联网)。四类人为构造的卡死:idle、SIGUSR2 取到栈ON-CPU、没有报告这一沉默被当成判词反方向同样断言:健康的 run 退出 0、失败的套件透传自己的退出码(取代
| tee+pipefail的那条不变量,回归了的话每个失败套件都会报绿)、持续输出永远不会被误判成停摆。交付物二:一个真实缺陷 —— SIGKILL 升级从来没有执行过
写 harness 的过程中定位到的,不是猜的。证据链:
原来的形状是
killGroup先发 SIGTERM,再挂一个setTimeout(SIGKILL, 10s).unref(),并在直接子进程的 exit 回调里process.exit()。但直接子进程(pnpm→turbo,或sh)对 SIGTERM 立即死亡,于是 guard 先退出了,那个定时器永远不会 fire。任何截获 SIGTERM 的后代都会活过 guard。复现(合成一个
sh父进程 + 一个截获 SIGTERM 的 node 后代):这不是假想的形状:
ObjectKernelConfig.gracefulShutdown默认 true,在每个测试 boot 的 kernel 里装的正是这样一个 handler —— 本议题自己的根因排查数到一次 objectql 套件运行 boot 47 个 kernel、截获 48 次 SIGTERM。也就是说,最可能泄漏 worker 的停摆,恰好就是这个 guard 存在的理由。修法:guard 现在自己拥有停摆后的收尾和退出时机 —— SIGTERM 之后轮询
/proc等进程组真正清空,对赖着不走的进程 SIGKILL,并在日志里点名它们(一个卡死且拒绝 SIGTERM 的进程,本身就是关于这次卡死的线索)。怎么验证这些断言真的会红:变异测试
一个只会绿的自检,和它要防的问题是同一个毛病。所以对 guard 逐个注入缺陷,确认 self-test 会红:
failing suite propagates its exit code — got 0steady output past the stall window is not a stall — exit 75变异测试当场抓到我自己的两个 bug,都已修:
sleep的 TDZ,绿色路径从没走到过那一行;顺带:M2(探测失效)最初让 self-test 自己挂死了 —— 在验证工具里复现了本议题的病。每个用例因此加了上界超时,超时就是一条带标签的红,并按 marker(本次
mkdtemp路径,只匹配自己起的进程)回收残留;不按进程名杀,以免误伤并行 agent 的套件。验证
pnpm check:stall-guard:21/21 通过--no-inline-config)干净lint.yml/ci.ymlYAML 解析通过--stall-minutes 10加--report-dir加NODE_OPTIONS)包住真实套件@objectstack/lint,966 通过 / 4 skipped,guard 退出 0,无误判;下游check-test-completeness.mjs读该 log 仍OK (970 declared, all 970 accounted for)范围与共享文件
改动三个文件加一个 changeset。共享文件请注意:
.github/workflows/lint.yml(在既有 guard 步骤末尾新增一步)和根package.json(在check:*块末尾新增一行)—— 同批有 agent 在packages/lint、packages/cli以及一个可能触及lint.yml的替身闸门脚本上作业。没有动ci.yml。这是取证与自检的加固,不是对原始三次停摆的根因修复。 那三次的根因仍未知,#4341 的策略(等下一次真实停摆自己交出栈)依旧成立;本 PR 保证的是那套取证在被需要的时候确实还在工作。