Repository navigation
fix(host): release blocked bridge readers after cleanup failures - #7946
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
|
This pull request has merge conflicts with Choose the remote for the base repository, not an out-of-date fork. git fetch upstream
git rebase upstream/main
# Resolve each conflict, git add the resolved files, then git rebase --continue.
git push --force-with-lease origin HEADFor a same-repository clone whose Keep the DCO |
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
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
通过现有受保护 Host 运行命令、且桥接输出损坏后需要取消和清理的调用者会遇到这个问题。
子进程已经退出,父端先拒绝损坏帧,取消时又遇到一个损坏帧;旧版唯一消费者离开后,读取线程卡在满的单槽队列,线程和管道仍占着资源。新版能释放这两个资源,原来的清理失败仍如实返回。
独立真实子进程和生产 Python 调用链对照均复现旧版泄漏,新版回收读取线程并关闭输出管道;正常输出、准入拒绝和完整异常链保持一致。
本 PR 不证明未观察到的 Host 或整个进程组已经退出,不改变权限、期限、默认值或任务结算,也不声称 Windows 原生行为或长期收益已经验证。
改动思路 / Approach
现有调用链由 run_host_process 接收真实命令和准入条件,TypeScript 监督 Host 进程及其进程组;Python 的桥接读取器仅负责在输出管道与单槽队列间传递观察。问题发生在消费者已经离开、生产者还等着队列空位时,单纯等待线程退出无法解除这个互相等待。
在现有 Python IO helper 内停止发布并释放等待者,TypeScript 继续判断 Host 期限与退出,职责没有增加。
本 PR 只修复清理失败后的读取线程与管道回收;不把它当作 Host 退出证明,也不改变权限、期限、默认或结算。
停止标记表达“调用者不再消费”的本地意图,不能由子进程退出推导:子进程退出后仍可能有有效的缓冲输出需要处理。这里先记录停止,再取走一个待处理项,为唯一生产者腾出位置;生产者醒来看到停止后不再发布错误或结束标记。原有清理期限、单槽背压和条件关闭保持原样。把队列改为无界会引入内存增长,把进程终止迁入 Python 则会复制现有 TypeScript 职责,二者都不适合这个已复现的边界。
下面是该 head 的实际成对观察,用表格呈现,不依赖未验证的图形渲染:
| 相同输入 | 基线 | 当前 head |
|---|---|---|
| 普通及带准入的真实命令 | UTF8 输出、诊断及 exited 结果 | 完整值一致 |
| 准入拒绝 | 拒绝且没有启动 Host | 完整值一致 |
| 已退出子进程;两次损坏帧 | 清理未确认;线程存活、管道未关 | 相同完整异常链和失败状态;线程退出、管道关闭 |
具体改动 / Changes
审核 head 69c8cfe040aab3bdc1ff19b5a7a0ccc2a0c8c74e,对照基线 f736bd2b06b6f321ee16d0a3d924fc9294a7dd7e;三文件 +61/-3,其中生产代码 +18/-3、测试 +42、诊断表新增一行。完整 diff 和未改动的生产调用方均已检查,未发现待修复的阻塞问题。
关键代码讲解
GuardedHostBridge._publish把原来的三处队列发布集中到一个入口。在阻塞前、解除阻塞后分别检查停止意图,因此关闭后的 EOF 或错误不会重新把读取器卡住;运行中的背压容量仍为一。GuardedHostBridge._read保持逐行读取、异常和 EOF 三条路径。唯一生产者在停止后退出循环,finally也走同一发布入口。没有新增重试、结果分类或观察解析规则。GuardedHostBridge.close_output先设置 Event、最多取走一个项,再按既有期限 join;仅在线程已停止时关闭管道。生产调用方的嵌套finally始终执行它,但原来的cancel、launching 记录和结果判断未改变。
两个参数化测试用真实 Python 子进程覆盖 not-json 和合法 JSON 非对象 [],实际等到子进程退出,再让读取及取消依次失败。测试既检查线程和管道回收、重复关闭,也检查 not_launched=False、result=None,避免把资源回收当成 Host 退出证明。相同当前测试置于基线实际源码上,两项均在读取线程仍存活的断言失败;当前源码完整四模块测试通过。
新诊断行通过现有 self-repair Skill → find_pattern.py → catalog 路径可达。实际查找在基线返回 unknown,在当前 head 返回完整新行;其要求保留原始帧错误、清理结果和实际子进程状态,不增加期限、不伪造成功。它是本地诊断指导,未增加强制工作、权限或结算门槛,也未改写其他条目;模型是否会采纳尚未测试。
规格依据:docs/reference/protocols/loopx-turn-v0.md,不可变版本 f736bd2b06b6f321ee16d0a3d924fc9294a7dd7e,在阅读完整实现前已读相应章节。Managed Host Process Lifetime → implemented:通过当前调用链和真实进程测试保留进程退出、管道关闭、进程组清理各自的含义,未知清理仍失败。Execution deadline defaults → implemented:没有引入默认 Turn 期限,没有改变显式期限、lease 或原有 drain 归属;相应正常及期限测试通过。没有将协议对 Windows 的尽力而为边界提升为 POSIX 等价承诺。
对主干的风险 / Risks
最强反例是停止过早丢掉正常结果,或者关管道时读取器仍在工作,继而错误宣称 Host 已终止。检查真实调用方可见停止发生在原消费者结束后的 finally;关闭仍依赖线程确已停止。独立生产调用链探针以真实管道、真实 Python wrapper 和真实进程比较完整返回及异常链,只有两个故障用例的线程/管道布尔值发生预期变化。故障生产者替换外部可执行文件,并实际等待其退出以选择本修复的前提;没有 mock 读取器、关闭函数或清理结果。TypeScript 原生监督的成功、超时、后代进程、未知/EPERM 等路径另由真实原生套件验证。
语义与 CI 对齐
停止 Event 和 host_bridge_reader_orphan 均保持本地含义,没有新增共享协议、持久化枚举或独立决策 owner。普通无 guard 路径、已有 guarded 成功及拒绝路径的完整成对观察一致;本修复也没有 default-off 能力声明。现有 Skill 的新增指导不会授予自动修复、发消息或合并权限。
验证范围与失败保留如下:
- PASS:四模块 Python 当前
50P/0F/0S、基线48P/0F/0S;当前两项回归在基线按预期失败。模块为test_host_process.py、test_leased_host_process.py、test_cold_source_import_host.py、test_turn_host_context_delivery.py。 - PASS:
host_process.test.ts、host_process_group.test.ts共21P/0F/0S;TypeScript typecheck、配置的 17 个 Python 类型检查根、改动 helper 的单独 import-skip 类型检查及目标 Ruff 均通过。 - PASS:以
f736bd2b06b6f321ee16d0a3d924fc9294a7dd7e为差异基线的 source-checkoutloopx canary premerge --from-git-diff,6 项直接检查及 13 项选择检查完成,其中 1 项既有 maintainability advisory 如实保留;三文件 public-boundary 扫描0 errors/0 warnings。 - FAIL,已独立归因为既有无关问题:
interaction_execution_decision.test.ts在基线和当前均为10P/1F,lifecycle-167的完整 expected/actual/error 内容相同,仅规范化工作区路径和耗时;其决策 owner 和测试因果路径未被本 PR 修改。maintainability ratchet 的完整 393 字节输出在两版本逐字节一致,超限函数也未改变。没有放宽预算或删断言。 - 保留未解历史诊断:首次独立合成故障生产者探针尚在运行时遇到两版本相同的
force_terminate_process/PermissionError EPERM,其根因未确定,未宣称修复。当前探针用真实 wait 明确选择“子进程已退出”,与新增回归和故障任务的条件一致;真实监督取消另外通过原生及 Python 套件。此证据不认证任意活跃合成生产者的取消。 - FAIL / UNVERIFIED:作者提供的当前完整 TypeScript
5922P/1F/77 conditional skips及 change-quality receipt 的失败仍保留,未把完整套件或该 receipt 说成通过;历史旧 head 失败也不改写。当前审查只独立归因了实际重跑的相关失败,并核验本修复的必要不变量。CI 按当前策略未查询、未轮询、未等待,其成功不是批准前提;独立 merge-readiness 或质量 hold 仍须如实保留。
我的整体评价 / Verdict
批准这个当前 head 的有界修复:它通过真实调用方消除已经复现的读取线程/管道泄漏,保留不确定清理的错误和恢复责任,达到该故障任务的可观察目标。长程维度判定为 preserved:没有改变 lease、后续恢复、工作承诺或结算;减少重复失败的资源残留有具体证据,但没有据此声称长期效果或完整成本已测量。用户体验判定为 improved:普通调用者无需新增设置、复制、确认或额外交互,得到原有结果或失败的同时避免本例的资源遗留。
现有 _publish 归并了三处发布,是同域且可逆的简化;代码量和维护面与故障规模相称,保留现有 Python IO/TypeScript 生命周期边界,无需扩展为通用取消框架。视觉审查为 not_applicable:已检查 run_host_process、现有调用方和三文件 diff,没有改变页面、渲染布局或可视化交互。未验证范围明确为 Windows 原生执行、已安装 Host canary、模型采纳及长期净收益。本次结论不清除另一个 review、质量 receipt 或 GitHub 分支保护,不授予合并权限;若 head、调用方或清理语义变化需重新审核。
English verdict: APPROVE — 69c8cfe040aab3bdc1ff19b5a7a0ccc2a0c8c74e fixes the independently reproduced reader/pipe leak while preserving cleanup uncertainty. Current Python/native validation passes; inherited failures and unqualified boundaries remain disclosed.
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
通过现有受保护 Host 运行命令、且桥接输出损坏后需要取消和清理的调用者会遇到这个问题。
子进程已经退出,父端先拒绝损坏帧,取消时又遇到一个损坏帧;旧版唯一消费者离开后,读取线程卡在满的单槽队列,线程和管道仍占着资源。新版能释放这两个资源,原来的清理失败仍如实返回。
独立真实子进程和生产 Python 调用链对照均复现旧版泄漏,新版回收读取线程并关闭输出管道;正常输出、准入拒绝和完整异常链保持一致。
本 PR 不证明未观察到的 Host 或整个进程组已经退出,不改变权限、期限、默认值或任务结算,也不声称 Windows 原生行为或长期收益已经验证。
改动思路 / Approach
现有调用链由 run_host_process 接收真实命令和准入条件,TypeScript 监督 Host 进程及其进程组;Python 的桥接读取器仅负责在输出管道与单槽队列间传递观察。问题发生在消费者已经离开、生产者还等着队列空位时,单纯等待线程退出无法解除这个互相等待。
在现有 Python IO helper 内停止发布并释放等待者,TypeScript 继续判断 Host 期限与退出,职责没有增加。
本 PR 只修复清理失败后的读取线程与管道回收;不把它当作 Host 退出证明,也不改变权限、期限、默认或结算。
停止标记表达“调用者不再消费”的本地意图,不能由子进程退出推导:子进程退出后仍可能有有效的缓冲输出需要处理。这里先记录停止,再取走一个待处理项,为唯一生产者腾出位置;生产者醒来看到停止后不再发布错误或结束标记。原有清理期限、单槽背压和条件关闭保持原样。把队列改为无界会引入内存增长,把进程终止迁入 Python 则会复制现有 TypeScript 职责,二者都不适合这个已复现的边界。
下面是该 head 的实际成对观察,用表格呈现,不依赖未验证的图形渲染:
| 相同输入 | 基线 | 当前 head |
|---|---|---|
| 普通及带准入的真实命令 | UTF8 输出、诊断及 exited 结果 | 完整值一致 |
| 准入拒绝 | 拒绝且没有启动 Host | 完整值一致 |
| 已退出子进程;两次损坏帧 | 清理未确认;线程存活、管道未关 | 相同完整异常链和失败状态;线程退出、管道关闭 |
具体改动 / Changes
审核 head e111d54b7f5e6ffe062ea3a500fca29ace73f8fb,对照基线 ec5c9702c79cbc68ab994b18031e9069b0935b59;三文件 +61/-3,其中生产代码 +18/-3、测试 +42、诊断表新增一行。完整 diff 和未改动的生产调用方均已检查,未发现待修复的阻塞问题。 本轮已重新审核整合主干后的版本;完整 PR diff 与此前版本逐字节相同,helper、测试、生产调用方、TypeScript Host owner、协议、Skill 及依赖定义逐文件相同。整合的主干另有 25 文件变化,因此实际重跑当前基线/当前 head 的运行和静态资格,没有直接沿用旧批准。
关键代码讲解
GuardedHostBridge._publish把原来的三处队列发布集中到一个入口。在阻塞前、解除阻塞后分别检查停止意图,因此关闭后的 EOF 或错误不会重新把读取器卡住;运行中的背压容量仍为一。GuardedHostBridge._read保持逐行读取、异常和 EOF 三条路径。唯一生产者在停止后退出循环,finally也走同一发布入口。没有新增重试、结果分类或观察解析规则。GuardedHostBridge.close_output先设置 Event、最多取走一个项,再按既有期限 join;仅在线程已停止时关闭管道。生产调用方的嵌套finally始终执行它,但原来的cancel、launching 记录和结果判断未改变。
两个参数化测试用真实 Python 子进程覆盖 not-json 和合法 JSON 非对象 [],实际等到子进程退出,再让读取及取消依次失败。测试既检查线程和管道回收、重复关闭,也检查 not_launched=False、result=None,避免把资源回收当成 Host 退出证明。相同当前测试置于基线实际源码上,两项均在读取线程仍存活的断言失败;当前源码完整四模块测试通过。
新诊断行通过现有 self-repair Skill → find_pattern.py → catalog 路径可达。实际查找在基线返回 unknown,在当前 head 返回完整新行;其要求保留原始帧错误、清理结果和实际子进程状态,不增加期限、不伪造成功。它是本地诊断指导,未增加强制工作、权限或结算门槛,也未改写其他条目;模型是否会采纳尚未测试。
规格依据:docs/reference/protocols/loopx-turn-v0.md,不可变版本 ec5c9702c79cbc68ab994b18031e9069b0935b59;其字节与此前完整审查前已阅读的接受规格相同,本轮已核对该未变假设。Managed Host Process Lifetime → implemented:通过当前调用链和真实进程测试保留进程退出、管道关闭、进程组清理各自的含义,未知清理仍失败。Execution deadline defaults → implemented:没有引入默认 Turn 期限,没有改变显式期限、lease 或原有 drain 归属;相应正常及期限测试通过。没有将协议对 Windows 的尽力而为边界提升为 POSIX 等价承诺。
对主干的风险 / Risks
最强反例是停止过早丢掉正常结果,或者关管道时读取器仍在工作,继而错误宣称 Host 已终止。检查真实调用方可见停止发生在原消费者结束后的 finally;关闭仍依赖线程确已停止。独立生产调用链探针以真实管道、真实 Python wrapper 和真实进程比较完整返回及异常链,只有两个故障用例的线程/管道布尔值发生预期变化。故障生产者替换外部可执行文件,并实际等待其退出以选择本修复的前提;没有 mock 读取器、关闭函数或清理结果。TypeScript 原生监督的成功、超时、后代进程、未知/EPERM 等路径另由真实原生套件验证。
语义与 CI 对齐
停止 Event 和 host_bridge_reader_orphan 均保持本地含义,没有新增共享协议、持久化枚举或独立决策 owner。普通无 guard 路径、已有 guarded 成功及拒绝路径的完整成对观察一致;本修复也没有 default-off 能力声明。现有 Skill 的新增指导不会授予自动修复、发消息或合并权限。
本轮独立验证及历史边界如下:
- PASS:相同四个完整 Python 模块当前 head
50P/0F/0S、当前基线48P/0F/0S,含实际 Host、lease、cold source 和 context delivery。当前两项新回归在当前不可变基线上仍按预期失败于读取线程存活断言。新一轮生产调用链成对探针保留完整异常链,五条语义观察除预期的四个线程/管道布尔值外完全一致。 - PASS:当前
host_process.test.ts、host_process_group.test.ts、interaction_execution_decision.test.ts共32P/0F/0S。旧版lifecycle-167的共享夹具修复已随主干整合,当前这十一项 lifecycle/interaction 检查实际通过;旧版10P/1F仍保留为旧版本失败,未改记为通过。 - PASS:当前配置的 17 根 mypy、完整 control-plane typecheck;source-checkout
loopx canary premerge --from-git-diff --git-diff-base ec5c9702c79cbc68ab994b18031e9069b0935b59的 6 项直接检查及 13 项选择检查完成,12 项通过、1 项继承 maintainability advisory,零 blocking failure/manual hold。当前 gate 包含 Python 编译、差异卫生与双语检查,另已对三份公共文件执行独立扫描。原 targeted Ruff/单 helper 类型检查、catalog 查找证据只在对应源码、脚本和配置逐字节相同后复用,原回执仍标原 revision。 - FAIL,继承 advisory:
render_canonical_todo_sections的 maintainability ratchet 仍保留失败。原基线/旧 head 的完整 393 字节失败输出相同;该 checker 和 renderer 在原/新基线及两 head 的四份 blob 均相同,本次 gate 继续报告同一 advisory。没有放宽预算、删断言或缩小扫描。 - 历史未解诊断保留:旧版本最初的合成生产者仍活跃时,取消路径曾出现两边相同的
PermissionError EPERM,根因仍未知;当前证据没有把它宣称修复。本次再次实际等待生产者退出后验证修复所声明的前提,真实 TS/live 取消另外由当前原生和 Python 套件覆盖,不认证任意活跃合成生产者。 - 作者报告,未独立全量复跑:新 head 作者报告完整 TypeScript
5923P/0F/77 conditional skips、当前 change-quality PASS,以及 source/wheel 各八个 native 场景。此次 reviewer 独立覆盖上述当前 50 项 Python、32 项原生、静态和 gate,不将作者全量或 wheel 声明混称为自己的验证。旧69c8cfe的5922P/1F/77S/CQR FAIL,以及8285dee的 mypy、TypeScript/CQR 失败仍是历史失败。 - UNVERIFIED:CI 按当前策略未查询、未轮询、未等待;其成功不是批准前提。Windows 原生、已安装/活跃 Host canary、模型采纳和长期完整成本收益未验证;独立 merge-readiness 和授权不由本审查授予。
我的整体评价 / Verdict
批准这个当前 head 的有界修复:它通过真实调用方消除已经复现的读取线程/管道泄漏,保留不确定清理的错误和恢复责任,达到该故障任务的可观察目标。长程维度判定为 preserved:没有改变 lease、后续恢复、工作承诺或结算;减少重复失败的资源残留有具体证据,但没有据此声称长期效果或完整成本已测量。用户体验判定为 improved:普通调用者无需新增设置、复制、确认或额外交互,得到原有结果或失败的同时避免本例的资源遗留。
现有 _publish 归并了三处发布,是同域且可逆的简化;代码量和维护面与故障规模相称,保留现有 Python IO/TypeScript 生命周期边界,无需扩展为通用取消框架。视觉审查为 not_applicable:已检查 run_host_process、现有调用方和三文件 diff,没有改变页面、渲染布局或可视化交互。未验证范围明确为 Windows 原生执行、已安装 Host canary、模型采纳及长期净收益。本次结论不清除另一个 review、质量 receipt 或 GitHub 分支保护,不授予合并权限;若 head、调用方或清理语义变化需重新审核。
English verdict: APPROVE — e111d54b7f5e6ffe062ea3a500fca29ace73f8fb fixes the independently reproduced reader/pipe leak while preserving cleanup uncertainty. Current-base/head runtime evidence, 32 native tests and configured typing pass; historical failures, the inherited advisory and unqualified boundaries remain disclosed.
父端拒绝桥接观察、取消清理又遇到损坏帧时,
GuardedHostBridge的读取线程可能阻塞在已满的单槽队列。即使子进程已退出,线程和输出管道仍然存活。此修复停止发布、释放等待中的 producer,再按原期限 join 并关闭管道;TypeScript 继续负责 Host 期限、终止和 drain,cleanup unconfirmed保持失败。当前 head:
e111d54b7f5e6ffe062ea3a500fca29ace73f8fb;本轮测试基线:mainec5c9702c79cbc68ab994b18031e9069b0935b59。原依赖 #7949 已实际合入,完成上一轮lifecycle-167的共享夹具修复。本 PR 整合该主干后仍只改原 3 个文件,61 additions / 3 deletions;生产 helper 和测试与原69c8cfe字节一致,没有加入夹具修复或扩展产品范围。验证绑定上述 head 和基线:
cqr_671a570dd3bbc228fdcbPASS,精确 scope 已由标准 premerge 验证为 valid。标准 gate passed:6 项 direct checks 通过;13 项 selected checks 为 12 passed + 1 advisory_inherited_failure,0 blocking failure / 0 manual hold。该 advisory 仍是未修改的machine_section_projection.render_canonical_todo_sectionsmaintainability ratchet,原始返回码为 1;未改该函数、例外或门槛,也不把它写成通过。推送前 main 已推进到
0095109a2f7e3a022887fd31e87c36476febfa05,仅多出 #7894;本 PR 三条路径没有目标端增量,实际 merge base 仍为已测试的ec5c970。这保留了本轮的准确验证边界,合并前仍按届时实际目标分支与精确 head 核验。历史记录继续保留:
69c8cfe的 5922 passed / 1 failed / 77 skips 与cqr_1f255d28e6015f03bd57FAIL,以及原8285dee的 mypy 786 errors / 125 files、TypeScript 5906 passed / 1 failed / 77 skips 和原 CQR FAIL,均不改记为通过。当前资格来自本轮实际重跑。改动留在既有 Python IO adapter:行、读取异常与 EOF 共用局部发布规则,TS 生命周期 owner 保持原职责;无新增协议、配置、权限或超时调整。当前是完成的作者提案,独立 exact-head review、远端 CI/readiness 与 maintainer 合并授权尚未由本轮证明。Windows 原生、实际 Host/canary 采用及长程效果/完整成本保留原验收边界。