Repository navigation
fix(runtime): reap children after shutdown group observation - #7460
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)
动机 / Motivation
运行研究验证或隔离测试的维护者,在停止自有临时 runtime 后需要安全清理目录。旧实现若在首次回收与进程组观测之间退出,会报告停止成功却留下未回收的直属子进程;当前实现返回成功前再回收一次,随后 PID 检查可确认退出。已验证这段真实子进程竞态得到修复,认证停止、失败观测和普通服务交接边界保持。本 PR 不认证正式安装、原生 Windows/Linux、真实模型采用或长期资源收益。
本次审阅完整 head 20fc9706b599f7a7c4c5ce7cf4cf85ae3952c4fa,对照不可变基线 71057d44f0d50a3636fcd8796f7c98717ce41742。未发现阻塞问题。
改动思路 / Approach
在既有 OS transport 中复用非阻塞回收即可闭合竞态;另建等待器、状态字段或信号路径会扩大同一生命周期的所有权。本 PR 限定为成功观测后的直属子进程回收及其真实 OS 回归,不修改 TS admission、drain、授权或普通 restart 的成功定义。
原路径先发一次认证 runtime.shutdown,随后在明确要求实际退出的调用中检查 PID 和进程组。组内只剩 zombie(已退出、尚待父进程回收的进程)可以证明没有活跃 writer,却不能证明父进程已经消费退出状态。新增调用补上这两次观测之间的空隙;WNOHANG 不等待活跃进程,非直属进程继续由现有异常处理接纳。
普通 doctor --restart-runtime 仍采用 serving locator/锁退休边界;本次没有把它强行改成等待整个进程组,也没有发第二次 shutdown。
具体改动 / Changes
完整 diff 为两个文件 +50/-0,其中生产文件 +4 实际只有一条新调用,其余为说明;测试 +46。规范依据是 docs/development/testing-and-quality.md,固定 revision 71057d44f0d50a3636fcd8796f7c98717ce41742,按修改前文本判断:
- Isolate the managed Effect process as well as the data:保留临时 namespace 与归属验证,真实停止后再做独立 PID 回读;未修改 live runtime 或活动 Goal。
- Smoke cleanup owns every valid runtime locator:认证停止后确认实际退出,保留观测失败和普通 serving handoff 的区别。新增竞态回归在旧基线失败、当前头通过。
关键代码讲解
_stop_effect_runtime:wait_for_process_exit=True的 POSIX 成功观测分支,在break前调用既有 reaper;未知或失败观测仍返回shutdown_pending。_reap_exited_runtime_child:原定义未改,验证 PID 并执行waitpid(..., WNOHANG);不增加信号、阻塞等待或持久状态。test_namespace_shutdown_reaps_child_exiting_during_group_observation:真实子进程先保持存活,再于观测阶段退出;WNOWAIT保留退出状态,真实ps验证组内没有活跃 writer。最终分别断言成功返回、PID 不存在,以及无剩余 wait status。
对主干的风险 / Risks
最强反例是“停止已经报告成功,但直属子进程仍未被回收”。同一冻结测试在基线稳定失败于 PID 断言,当前头通过;并未以 sleep、返回值桩或放宽断言替代 OS 结果。该确定性用例的 shutdown transport 是合成边界,另以真实 Node runtime.ping → shutdown → replacement → shutdown 验证认证入口和后续运行,两个 PID 均消失。
本次独立执行相关 Python 三模块:89 PASS、1 SKIP,跳过的是未变化的原生 Windows junction 用例。冻结的原 26 条 restart 控制用例在基线通过,当前完整 restart 测试也保留这些路径。配置的全量 Ruff、mypy(17 源文件)、TS typecheck 和 diff hygiene 均 PASS;standard premerge 6 项直接检查、10 项选中检查全部 PASS,无人工留置。未查询或等待 CI。
现有 PID/locator 归属、安全 token、TS drain 和失败观测未改;本改动没有新 capability、actor 权限或 Agent 指令。保留 Python 作为 OS transport 的现有放置即可,不需要新增 TS 规则或兼容模式。CQR 资格以本次准确作用域回执单独记录,评审与合并授权仍分开。
我的整体评价 / Verdict
APPROVE。这是闭合可复现退出竞态的完整有用修复,改动与问题成比例,复用了既有非阻塞回收函数。long_horizon 在隔离清理与后续替换连续性上改善,user_experience 减少了误导性 PID 清理失败;没有据此宣称长期净收益。effect_runtime.py 与进程测试没有改变渲染界面、导航、文案或页面布局,无需视觉截图。未验证范围是原生 Windows/Linux、正式安装和自然竞态发生率。
当前无须额外抽象、迁移或新跟进任务。批准只绑定 20fc9706b599f7a7c4c5ce7cf4cf85ae3952c4fa,不代表其他研究 PR、发行包或模型采用通过,也不授予合并权限。
English verdict: APPROVE 20fc970; the real-child race fails on base and passes at head, with native shutdown/replacement and focused validation. Native Windows/Linux and installed operation remain unverified.
POSIX runtime 在首次非阻塞回收后、进程组观测期间退出时,停止操作会返回成功,却留下尚未回收的直属子进程;研究 HTTP fixture 随后的 PID 检查因此可能报失败。现在在确认进程组已退出后复用原有非阻塞回收函数,清理返回前收走直属子进程的退出状态。
沿用原认证 shutdown、drain、截止时间及非直属 zombie 处理;普通服务交接模式保持原行为,没有增加信号、等待循环或执行权限。确定性回归用真实子进程和 OS 进程组观测固定这段退出顺序,旧 main 在未回收 PID 断言失败,修复后通过。
验证(20fc9706b):
此修复来自 #7451 的验证。它不倒补该 PR 的旧质量回执;包含本修复后的组合仍需以准确 head 重新资格。当前仅作者提案,合并与正式安装留给维护者。