Repository navigation
test: reap owned runtime children during drain cleanup - #6651
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=max
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
完整 PR 评审,精确 head:d0dbbb9a8c42853e1dcd5434871f2198c5665700。
未发现阻塞本次夹具修复的缺陷。原生平台和持续完整lane的资格仍单独保留。
动机
维护运行器排空测试的开发者,需要让真实写入失败保留原错误,并只清理这次测试自己启动的子进程。
真实写入遇到目录替换时,旧清理会在过期探活与拒绝信号下抛PermissionError,遮住原I/O错误;新清理等待保留的Popen对象,只在超时后终止自己捕获的那个孩子,并保持真正的终止失败可见。
同一个真实运行器故障与同一Python解释器下,旧基线失败于清理PermissionError,新head保留预期I/O错误并通过;邻居进程存活、未知PID拒绝和终止错误不被吞掉也得到实跑验证。
本次完成测试夹具的清理修复,不修改生产运行器、调度、CI并发或超时;没有独立验证原生Windows整lane、完整六分片、覆盖率终门或持续运行资格。
改动思路
在现有观察夹具中保留Popen并复用一个近邻清理函数,取代裸PID探活后发信号的竞态;不新建进程管理框架,也不复制生产生命周期owner。
本PR只修复测试所拥有孩子的清理和故障可见性,真实生产状态机与共享协议保持逐字不变。
生产运行器仍决定接受写入、排空和退役。夹具只在原启动包装处保留其创建的Popen对象,finally按原PID从捕获列表中要求恰好一个对象;先等待退出,只有等待超时才终止,再等待回收。已退出孩子不再因一次过期探活而接收裸PID信号,真正的wait/terminate错误仍会使测试失败。
| 条件 | 旧夹具 | 本 head |
|---|---|---|
| 真实 I/O 失败后探活过期、信号被拒 | 清理 PermissionError 覆盖原错误 | 等待所拥有孩子,保留预期 I/O 错误 |
| 孩子尚在运行 | 裸 PID 信号 | 仅对捕获的 Popen 等待、超时后终止 |
| 邻居/未捕获 PID/真实终止拒绝 | 所有权证据不足 | 邻居保留、未知 PID 拒绝、拒绝错误外露 |
具体改动
关键代码讲解
observe_copied_runtime:observe_copied_runtime扩展已有stderr观察包装,参数省略仍保持旧调用;只有启动参数包含此隔离副本server路径时才把真实Popen加入调用方列表。reap_copied_runtime:reap_copied_runtime要求恰好一个匹配的所拥有孩子,以Popen.wait完成回收,超时后terminate再wait;没有捕获或吞掉真正的终止失败。test_runtime_drains_admitted_write_before_exit:既有drain测试finally移除裸PID探活和os.kill,调用上述近邻helper;接受写入、真实文件锁、排空、日志及原错误断言保留。test_drain_cleanup_keeps_io_error_after_stale_pid_probe:新回归在真实写入EISDIR之后注入过期探活和信号拒绝;另外用真实孩子/邻居验证正常退出、超时清理、邻居存活及拒绝路径。
三个测试文件 +121/-13。loopx、packages、scripts、Desktop及Dashboard的完整Git对象清单与实际base逐字相同;既有fixture内的Python实现有明确归属,不是新增平行控制面决策源。
规范:docs/development/testing-and-quality.md,spec_revision=dfcb03e6d31361ab65d882e3294b9fdf92b39f6a。看diff之前已读这份实际基线的相关条款;原PR没有额外评审评论框架,作者验证不代替验收。逐项映射:
- Refactor Real-Path Gate — implemented:Use immutable baseline/head real entrypoints and a historical-defect-sensitive counterexample; do not substitute mocks. 对应的实际入口、负例及公开边界已经核对。
- Risk-Based Review — implemented:Run focused validation and preserve actual failures, skips and scope limits. 对应的实际入口、负例及公开边界已经核对。
- Evidence Boundary — implemented:Use isolated synthetic state and public-safe reusable tests. 对应的实际入口、负例及公开边界已经核对。
对主干的风险
关键风险是清理仍掩盖第一处真实失败,或者误把同号PID的新进程当成自己的孩子。这里不靠只含mock的单测:运行隔离副本的真实TypeScript server,实际把写目标替换成目录,待I/O失败以后才注入过期探活和拒绝信号。同一个不可变夹具、同一个Python3.12.15解释器,实际base失败于清理PermissionError,本head通过且保留EffectRuntimePermanentIOError。head另外实跑孩子/邻居、未捕获PID和live-child终止拒绝;原错误断言没有删除。
完整原integration模块base为97 passed;head两完整模块101 passed、0 skipped,均为两worker并开启coverage和CI timing observer,包含12个drain/cache组合及两个原诊断失败用例。基线完整模块使用Python3.11.17、head使用3.12.15;不能拿不同环境的时长作性能比较,所以上述决定性的单个反例另外用同一个3.12.15跑两端。Node24.21。
Ruff、diff、公私扫描三路径、先advisory后全树语义通过;原生premerge四项direct通过,catalog实际选择0项,不把“未选中”冒充行为测试。最初猜错的semantic脚本路径未运行,真实检查入口重跑通过并保留原失败记录。源码及真实子进程行为覆盖足够支持这个夹具修复;本机没有原生Windows运行,不独立背书作者引用的Windows job、19个skip身份、六分片覆盖率或持续通过。未查询、等待或触发远端CI。
我的整体评价
本PR只修复测试所拥有孩子的清理和故障可见性,真实生产状态机与共享协议保持逐字不变。
long_horizon:preserved。生产生命周期和所有权契约没有改变;夹具不再用清理异常掩盖原I/O错误,不能据此宣布生产Windows或持续运行已全面合格。
user_experience:improved。这里的直接使用者是维护者,重复故障时能读到原错误,邻居不受清理影响;没有新增人工准备、参数填写或批准轮次。
有界未来重构判断:这次已把启动捕获和近邻清理连在既有helper边界,删除裸PID竞态;进一步抽通用进程框架会增加维护面,没有当前收益。新回归具备真实旧缺陷敏感性,未发现重复造smoke或同形批量产物。原生Windows与持续完整lane不由macOS夹具回归证明;原有跨平台和CI资格仍由现有owner验收。
APPROVE;整个 d0dbbb9a8c42853e1dcd5434871f2198c5665700 的有界夹具修复达到本次目标,未发现阻断项。本次完成测试夹具的清理修复,不修改生产运行器、调度、CI并发或超时;没有独立验证原生Windows整lane、完整六分片、覆盖率终门或持续运行资格。 精确head合并门与权限仍单独判断。
English verdict: APPROVE - d0dbbb9; retained owned subprocess handles prevent stale-PID cleanup from masking the real IO error. Full local modules, same-interpreter base/head fault sensitivity and real ownership/error cases pass; native Windows and sustained complete-lane qualification remain separate.
The Windows job for #6645 returned the expected permanent I/O error in the drain fault test, then failed in fixture cleanup with
WinError 5from a raw PID signal. Retain the subprocess launched by the existing copied-runtime observer and reap that child throughPopen.wait/terminate. This removes the stale PID check/signal race and keeps real termination errors visible.A regression injects the same stale liveness observation and signal refusal after a real runtime write failure: it fails on the base with
PermissionErrorand passes with this change while retaining the expected I/O error. Additional real-process cases verify an already exited child, forced cleanup, neighbor survival, uncaptured PID rejection and unsuppressed termination failure. The related refactor stays inside the existing test fixture helper.Validation:
Independent PR review is pending. Full six-shard/JUnit/coverage and sustained qualification remain open; this proposes the fixture cleanup repair. Production runtime behavior, CI worker counts and job deadlines are unchanged.