测试执行器负责交出证据,不负责不惜代价凑出一个结论。

1. 每一层都很热心,机器就遭殃了⌗

设想一条 Agent 自发布流水线:模型改代码,测试执行器把验证送到测试机,远端再把测试拆成若干分片。

现在,一个分片不再输出日志。

分片管理器想:重跑一次吧。整套执行器想:基础设施不稳定,再试一下吧。远端连接失败,本地入口又想:那我替它跑吧。

这些决定单独看都挺体贴。组合起来,却可能把一次验证请求变成多轮执行,还把压力搬到承载 Agent 自身的机器上。

这次改动反过来做:远端不可用或停滞,就报告没有结论;执行层不自动重跑,也不把远端工作转到本机。内存保护则直接终止符合条件的测试,不再暂停、等待、恢复。

重点不是“重试不好”,而是:谁有资格决定下一次尝试?如果每一层都能回答“我”,系统其实没有一个完整的恢复策略,只有好几份互不知情的热心。

2. 工作量为什么会被放大⌗

提交前,落地套件对没有结论的远端运行,配置了两段重试等待:30 秒、120 秒。也就是一次初始尝试之后,还可以再尝试两次。远端分片另有自己的策略:无输出时终止,再跑一次。

这两个循环处理的条件并不完全相同,不能简单相乘,声称每次请求必然执行六遍。但它们确实把重试权放在了不同层:有的重建整次运行,有的重建局部分片。

此外,普通测试入口还存在远端失败后的本机回退。

原来的恢复权限分散在多处

测试入口       ── 远端不可用?改成本机执行
套件执行器     ── 整次没有结论?等待后重试
远端分片管理器 ── 分片停滞?终止后再跑
内存保护器     ── 暂停、等待恢复,必要时再终止

问题不只是多花一些 CPU。每层增加的工作,会改变其他层正在观察的故障环境。

资源紧张时重跑,可能继续挤占资源;换机器执行,则改变了依赖、平台和数据环境。新产生的失败不一定还在回答原问题:“这份候选代码是否正确?”

所以,回退不能只用“总比什么都不做强”来论证。它至少需要证明两件事:替代环境能够提供等价证据,新增负载不会扩大原来的故障。此次改动没有继续替这些前提打补丁,而是撤销了执行层的自动回退权。

3. 第三个结果:没有结论⌗

传统命令行很喜欢二分法:退出码零是成功,非零是失败。但自动发布系统需要更细的判断。

观察能支持的结论不应自动推导的结论
测试正常完成,断言通过所选测试通过所有环境、所有测试都通过
测试正常完成,断言失败所选测试存在失败证据一定由本次改动引起
测试机不可用、执行停滞此次验证没有给出结论候选代码失败,或候选代码通过
测试被资源保护终止此次验证不完整模型应该换一种代码写法

test-exec.ts 中的 remoteOnce 只调用一次远端执行。不可用或停滞时,返回 code: null,同时携带 infra 原因;日志明确说明不自动重跑、不转本机。类型检查与迁移 schema 快照也经过这条路径。

lane-suite.ts 对整个远端运行和分片停滞作同样处理。即使一部分分片已经通过,另一部分停滞,整体也不会被包装成通过。

这里保留了两个层次的事实:

分片明细:某片 exit 0,另一片因停滞记为 124
整体结果:code = null,infra = 分片停滞

底层退出码仍可用于诊断,但不再直接冒充候选代码的判决。

这也是恢复权收拢的前提:上层只有拿到“没有证据”这个状态,才可能决定是等待基础设施、调查日志,还是明确发起新一轮验证。执行器偷偷跑到绿,上层反而看不到最需要处理的那次故障。

4. 停滞之后,留下日志,不再启动第二轮⌗

远端分片仍然有无输出检测,并没有因为取消重试就允许它永久挂住。

lane-suite.ts 中,默认分片静默阈值是 300 秒。远端 runner.sh 发现分片超过阈值后,会终止其进程组,输出该分片已有日志,并报告最后识别到的测试文件。

变化在终止之后:以前进入第二轮,现在直接记录停滞,结束该分片。

保留的机制删除的机制
分片并行执行分片停滞后自动重跑一次
基于历史耗时分配分片整次无结论后的等待重试循环
无输出检测与终止远端故障后把测试搬回本机
分片退出码、文件定位与日志用后一次执行掩盖前一次停滞

所以这不是把执行器简化成一个不管死活的 spawn。它仍负责监测、终止、收集与报告,只是不再自己追加一轮验证。

“发现卡住”和“值得再试一次”是两个不同判断。前者可依据执行状态作出,后者还需要资源、故障类型和流程目标等上下文。

5. 内存保护不是暂停键⌗

另一个重要变化在本地资源保护。

暂停进程能降低它继续争抢 CPU 的程度,却不等于释放它持有的内存。在内存压力场景下,保留整棵测试进程树,再等待恢复,还需要维护暂停顺序、恢复条件以及后续终止策略。

新的 test-shed.ts 不再维护这套暂停恢复状态机。

检测到临界压力
    │
    ├── 标记保护状态,新非落地重型运行等待
    │
    ├── 找到已登记、命令匹配的非落地测试运行
    │      写入终止标记 → 对进程树发送 SIGTERM
    │
    ├── 15 秒宽限后仍有已记录进程存活
    │      后续检查发送 SIGKILL
    │
    └── 压力持续平稳达到配置时长
           重新开放准入,不恢复已经结束的测试

这里的“终止测试”不是扫描进程名后大开杀戒。实现先用槽位登记与命令行核对确认目标,再取得其后代进程;只有进程组满足归属检查时才发组信号,否则逐个处理匹配的进程。落地类别也被排除在这项本地终止策略之外。

压力判定同样没有简单等同于 swap 占用率高。代码明确要求内核压力信号参与判断,避免 swap 数值迟迟不回落时一直误认为机器仍然临界。

收到有效终止标记的测试进程,会输出“无结论、日志保留、不自动重跑”,以退出码 75 结束;本地执行封装把这个结果映射为 infra。标记还要属于本次进程启动之后,不能只看到一个旧文件就认领为当前事件。

恢复时恢复的是允许新工作进入的资格,不是刚刚被终止的那份工作。这两件事终于不用共用一个“恢复”动词了。

6. 不回退,不等于不做本地测试⌗

一个容易过度解读的地方是:“既然不能退回本机,是不是本机测试都被禁止了?”

不是。

split-run.ts 仍把明确的平台专属测试安排在本机,把其他测试交给测试机;显式选择本地执行的入口也保留。删除的是远端失败后临时改变执行地点,不是原计划就属于本机的部分。

测试专门覆盖了这个边界:一个混合请求中,即使测试机不可用,本机专属测试仍能完成,但远端部分不会被搬过来,总体仍然没有结论。

此次改动还删除了原先每日补跑本机专属测试的路径,同时让受影响测试的选择包含依赖改动所影响的本机专属文件,不再要求一定直接修改测试文件本身。

这项取舍必须说全:按改动选择验证,可以减少无关工作,但失去了那条每日广泛补跑路径提供的周期性观察。减少工作量不是覆盖能力凭空不变的证明。 平台专属部分也不在远端落地套件中执行,不能把远端套件通过描述成所有平台验证完成。

这里值得保留的原则是:测试在哪儿跑,由选择与平台契约决定,不由故障临时决定。

7. 最有价值的测试:第二次本来能成功,也不跑⌗

这组测试里,最有辨识度的不是“返回了一个错误”,而是刻意准备一个诱惑:第二次会成功。

lane-suite.test.ts 给远端执行器准备两份答案:第一次不可达,第二次成功。断言第一次返回后,成功答案还留在队列里——执行器没有消费它。

远端分片的集成测试也安排了类似情形:一个静默分片如果重跑可能通过,但规格要求仍然结束并报告没有结论。

测试文件它钉住的契约
test-exec.test.ts不可用时,每次测试或类型检查请求只调用一次远端;不本机回退
lane-suite.test.ts第二次可能成功,也不自动重试;单片停滞导致整体无结论
remote-runner.test.ts真实执行路径中的静默分片被终止,不启动第二轮
test-shed.test.ts临界压力终止登记目标;不暂停、不恢复;不处理无关进程
test-shed.test.ts 的真实进程用例退出码为 75,保留终止标记与说明,旁边的无关进程仍存活

这些是提交中的测试规格与断言,不是本文重新运行的性能测量。它们证明系统打算怎样限制执行权限,不能推出吞吐提升百分比或故障率下降幅度。

提交统计是 26 个文件、增加 453 行、删除 793 行。净减代码值得高兴,但真正重要的是少了哪些状态:重试次数、第二轮分片、暂停列表、恢复顺序,以及远端故障后另一种执行地点。

少写几行不是成果本身;少维护几套互相干扰的政策,才是。

8. 单一归属,不是永不恢复⌗

这次改动没有证明系统里从此只存在一个重试,也没有取消上层为了诊断测试失败而明确发起的复查。它收拢的是一个具体边界:一次执行请求,不因基础设施没有给出结果,就在执行层自动繁殖出更多尝试。

代价很明确:短暂故障不会再被这些执行层重试顺手消化,验证可能更早停在“没有结论”;内存保护也会放弃已经投入的测试计算。

但在自发布系统中,测试不是孤立批处理。它和 Agent 的运行、日志、任务状态共享一条生存链。为了得到一盏绿灯,把支撑整条链的资源拖垮,属于目标完成得很彻底,系统也死得很彻底。

相较于逐层增加重试与降级,这个设计更有价值的地方,是把权限分开:

  • 执行器决定怎样运行、何时终止,并完整报告本次观察。
  • 资源保护器决定当前是否还能承受工作,不负责补做被终止的测试。
  • 掌握流程上下文的上层决定下一步,不从一个模糊的非零退出码猜故事。

可靠性不只是失败之后还能再跑。也是资源不够时,系统能够停止制造工作,并准确说出:这次还不知道。


源码依据:落地提交 d8baaa1e 中的 src/testing/test-exec.ts、lane-suite.ts、split-run.ts、offload.ts、test-shed.ts、test-slots.ts、remote/runner.sh,以及对应测试;每日补跑路径的移除见 scripts/chaos/run.ts 与被删除的 src/testing/mac-only-daily.ts。