Repository navigation
fix(chat): recover lost execution workers without replay - #7970
Conversation
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 (Codex); OpenAI; self_reported
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
结论:APPROVE,精确 head f4fc03bc2fb6d97e77a57e34828b2492eca370eb 未发现阻塞问题。按已知模型家族自述;同一 Turn 的宿主身份读取不可用,这不是独立人工评审或 GitHub 正式自批准。
动机 / Motivation
同一服务 PID 仍存活,原执行线程因进度及失败收尾落盘同时失败而退出;旧版保留 running,新版核实已释放的执行锁后记录失败,原队列继续下一条已保存输入。 恢复原 FIFO 的前进条件,保留原消息、部分进度和未知执行效果;不重新发送失败输入。
Existing App and Lark Chat users with saved follow-up input and ordinary service activation. 本变更不代办业务、不重放未知效果输入、不强制重启忙碌服务、不增加 listener,也不从激活入口执行模型。 实际安装、旧服务自动接续、provider 送达和历史业务最终答复尚需独立验收;这份评审不认证生产恢复完成。
已有进程被杀恢复测试无法覆盖这个区别:进程还活着不代表其每个线程仍有执行权。文件写入失败可能同时打断进度 checkpoint 与失败处理;活动位留在磁盘上,线程的 finally 却已经释放执行锁。继续等待 PID 死亡会让后续消息永久停住,也令普通升级持续报告 busy。这里交付 justified_increment:修复原 owner 的缺失观察和队列异常存活,而不把业务补办或生产回传声称为已完成。
改动思路 / Approach
恢复只处理本机、确切执行锁身份和未更换记录下的受管 starting/running/interrupting;排队、attached、外来/未知记录和仍被持有的锁不允许推断。
没有新增 watchdog、第二消息消费者、状态文件或重试预算。恢复仍走既有 managed_turn_recovery_allowed 与 _fail_turn;物理执行锁的生命周期对应执行线程,PID 对应服务。新增规则要求本机、正整数 PID、确切 lock_id、agent/operation/policy,并在取得原非阻塞内核锁后再次确认持有记录没有更换。只有这组证据成立才能向既有 typed owner 说明执行 owner 已退出,服务 PID 本身仍可存活。失败使用现有 runtime_error,明确 execution may have occurred,不能把该失败当作可安全重发。
我比较了继续等进程退出、直接终止服务、重发输入和另建监控线程。前者保留死锁,终止服务可能中断其它原请求,重发会重复未知效果;第二消费者会产生新的同步及授权问题。当前方案保留原生命周期决定、原 FIFO 和普通激活入口。队列枚举与终态补写放回已有 ingress store,避免在大型运行/存储模块继续增加责任;这段整理没有提高模块限制。
具体改动 / Changes
依据是固定主干的 原会话/FIFO 与回执恢复契约。original-fifo-owner、unknown-effects-no-replay 与 remaining-work-guard 三项落实到代码和反例;provider-final-return 明确 out_of_scope。该文档没有赋予未知执行重放权;本次从缺失的执行所有权事实修复既有原队列前进条件。
五个既有文件共增加 374、删除 98 行,其中测试新增 236 行;生产 diff 包含两个相邻数据操作的责任归位,没有新增模块、公共命令、状态 schema、权限或执行器配置。
关键代码讲解
recover_exited_turn在原执行锁上核验持有者身份,再在锁内读回 Session、Turn 和未更换的 holder。死 PID 延续 server_restarted;同一活 PID 下已经失去物理执行权的线程报告现有 runtime_error。锁仍被持有、证据未知或被替换时,不写回原请求。_drain_session_queue捕获执行收尾逸出的 OSError,使同一个队列线程保留接续机会。存储不可用时只重试非执行性的状态恢复;恢复可用后,原失败输入不重发,既有 FIFO 领取下一条已保存消息。该路径没有启动替代 controller 或 consumer。complete_recovered_terminalization完成已经持久化的失败结果:分别补齐终态事件、错误消息和活动位释放,并逐项去重。runtime_error 的完整诊断 payload 匹配只用来确认该 owner 先前记录的恢复结果,不用来从自然语言推断业务状态或权限。队列枚举保留同一 store API,经 AST 对照,除局部依赖导入外与旧实现相同。managed_chat_activation_guard在现有激活事务内执行这段数据恢复,再进行严格 nonterminal 检查。作用域 store 显式关闭与当前恢复无关的启动压缩及完成处理;默认 store 初始化仍启用二者。剩余排队或运行工作照旧阻止重启,helper 不开 adapter、不领取队列。原队列磁盘故障回归使用真实临时文件存储、原执行锁及原队列线程,只有模型 provider 被合成。进度与错误 flush 同时抛 ENOSPC 后,只恢复存储,不重新提交、唤醒或启动第二消费者;断言 provider 原输入仅一次,下一条原排队消息完成。
对主干的风险 / Risks
最强反例是误判仍在执行或已被替换的 worker。新逻辑不能只看 released_at、存活 PID 或活动位;实际内核锁和锁内一致性重读一起约束恢复。最终 190 项测试覆盖真实跨进程仍持锁、替换 holder、无/外来/坏证据、queued/attached 排除,以及终态事件、错误消息、claim release 分段写入失败后的幂等恢复。普通激活后新建的 managed 会话仍被鲜读覆盖,同一 runtime 内 attached 会话保持原状;移除活动指针或关闭 Session 摘要也不能隐藏仍为 running 的原 Turn,剩余工作继续阻止重启。
| 同一最终夹具,真实 detached Git 基线与候选 | 基线 | 候选 |
|---|---|---|
| 活 PID、退出线程,release metadata 成功/失败两类 | 两例仍 running,失败 | 两例原失败收敛,不重发 |
| 原队列进度及失败 flush 同时 ENOSPC | 工作线程退出,下一条不执行 | 同线程在存储恢复后完成下一条 |
| 实际执行锁仍被持有 | 原数据不变,激活 deferred | 同样不变且 deferred |
基线是实际 Git worktree 中的固定来源,并复制同一最终测试文件,得到 3 个故障反例和 1 个正常反例通过;没有使用缺模块、导入失败或重新生成 golden 作为有效反例。最终 head 的 190 项回归 PASS,Ruff、队列枚举 AST 等价、标准 premerge 的 6 直接及 2 选择检查也通过,没有 blocking failure/manual hold。较早 885 项共享回归通过于整理前的检查点,未冒充此精确 head 的全量证据。
最初实现触发 runtime/store 两个模块增长失败,已通过上述责任归位修复,未提升 ceiling。保留原有 render_canonical_todo_sections 告警:真实 Git 基线与最终候选的同一命令都退出 1,完整 stdout 相同,源码不在本 diff。五个变更文件与公开 PR 草稿的内容边界扫描通过;宿主汇总 check 的两个既有外部 Goal authority 错误仍保留。未发布运行日志、私有原始材料或本机路径。
语义、默认行为与 CI
这是默认恢复修复,没有 default-off 或新增 opt-in 的声明。普通运行和启动维护保留原行为;只有已证明失联的执行、存储收尾异常和普通升级的数据恢复路径改变。没有增加用户必须确认的步骤,也没有把 readiness、安装成功或 provider 可用性当成派发许可。现有 Python runtime/storage provider 保留,typed Core 生命周期继续决定可恢复状态,没有复制一套控制面状态机。
当前 Goal 的 wait_for_ci=false,未获取、查询、轮询或等待 CI,不推断 CI 成功。实际安装、旧服务自动接续、provider 送达和历史业务最终答复尚需独立验收;这份评审不认证生产恢复完成。 持久存储持续不可用、未进入 starting/running 的请求,以及无确切执行证据的旧记录仍须保留其真实状态;本增量不承诺解决所有资源故障或批准通用重放。
我的整体评价 / Verdict
long_horizon 与 user_experience 的改进由同一个原队列实际接续得到支持,而非只由状态字段、安装或测试数量推断。修复复用执行 fence、typed lifecycle 与既有 FIFO;责任归位保持可审阅、可回滚,范围 proportionate,兼容性 retain。允许这个有界增量合入,同时保留实际服务采用和原受众最终答复验收,不能把父任务关闭。
English verdict: APPROVE at f4fc03b. Exact execution-fence recovery preserves unknown effects and original FIFO without replay or a second consumer; 190 final-head regressions and standard premerge pass. Retain the inherited advisory and unverified production adoption/final returns.
当执行线程因进度写入和失败收尾同时遇到存储错误而退出时,Chat 服务进程仍然可能存活。旧恢复逻辑只认进程死亡,原会话会一直保留 running,后续已保存的消息及普通升级被堵住。
本改动在既有生命周期 owner 内核验同一执行锁、锁持有记录及受管 Turn 状态,只有取得已释放的内核执行锁且记录没有更换时才收敛失联执行。原请求和部分结果保留,执行效果明确未知,不重发原输入。原队列线程在存储恢复后继续领取下一条已保存输入;普通激活可执行这段非执行性恢复,仍然对剩余排队或执行中的工作保持忙碌保护,不创建第二消费者。队列枚举和终态补写回到现有 ingress store,保持现有 API,不提高模块限制。
验证:同一最终测试夹具在真实 detached Git 基线上暴露 3 个旧故障,活跃执行锁反例通过。Ruff、队列枚举 AST 等价及标准 premerge 通过;保留未修改的 render_canonical_todo_sections 维护性告警,基线/候选的完整输出相同。五个变更文件的公开边界扫描通过;宿主汇总 check 的两个既有外部 Goal authority 错误未修饰为成功。最终 head 的激活、跨进程恢复、传输、队列、活动位、事件缓冲及等待回归共 190 项通过,另覆盖激活后新增会话、同 runtime 的 attached 会话隔离及可变摘要字段不能隐藏运行工作。
不宣称已经安装或处理完历史消息。生产采用、原 Bot 自行领取队列及原受众最终回复分别验收;效果未知的历史失败不会自动重放。当前 Goal 禁止等待 CI,未获取或轮询 CI。