Skip to content

修复 state.json 持久化竞态、负载膨胀与幂等重放 (PR-1) - #29

Merged
Fei2411 merged 4 commits into
fix/mcp-lifecycle-acp-diagnosticsfrom
devin/fix-pr1-persistence
Oct 6, 2026
Merged

Fei2411 merged 4 commits into
fix/mcp-lifecycle-acp-diagnosticsfrom
devin/fix-pr1-persistence

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

深度测试(soak 三次崩溃复现)发现的持久化层缺陷修复:state.json 既当「索引」又当「数据」,且 flush_state 的快照语义错误。

  • S1 竞态:flush_state 原实现要求 await 后 _pending_state 全局为空,并发 save() 落在 await 之后会虚假抛 RuntimeError 拖垮整个 Registry。改为三代际计数(_save_gen/_pending_gen/_flushed_gen),只等调用时刻已发起的 save 落盘。
  • 降级语义:flush_state 永不 raise——最多 3 次尝试后置 _state_error + warning,快照累积性保证下次 save() 自动重试(H-08)。
  • S2 负载:_state_task_row 瘦身(message≤2000、result_text≤6000,全文仍在 result/transcript 工件);RESIDENT_SESSION_KEEP=50 按 last_active 强卸最老空闲常驻会话——soak 实测 idle_unload_sec=0 下 ~970 会话 → ~20GB 子进程 RSS 是 OOM 主因。
  • H-01:request_id 幂等绑定持久化到 state.json requests 键(sha256 指纹、上限 1000、owner_pid 戳使 _merge_owned 可删自身行),重启后重放返回 reused: true。
  • B7:wait_task 终态返回前 flush_state();返回体新增 persisted: bool 告知落盘是否真实完成。

测试:+8 用例,全套 630 passed / 1 skipped;repro_flush_race(bursty save 下原代码 3s 必中)现 0/76000。

Link to Devin session: https://app.devin.ai/sessions/b270264aa3634bc5934cad9bcec9f9ae
Open in Devin Desktop: https://app.devin.ai/desktop/session/b270264aa3634bc5934cad9bcec9f9ae?variant=devin
Requested by: @FeiZhuLulu


Devin Review

- flush_state 改代际语义并降级不 raise,消除并发 save 触发的虚假 RuntimeError(S1)
- 落盘失败最多重试 3 次后降级为 warning + _state_error(H-08)
- state task 行瘦身 message<=2000/result_text<=6000,全文留 result/transcript(S2)
- RESIDENT_SESSION_KEEP=50 限制常驻 worker 会话(S2,soak 实测 ~970 worker/20GB)
- request_id 幂等键持久化到 state.json 的 requests 键,sha256 指纹,上限 1000(H-01)
- wait_task 终态返回前 flush,新增 persisted 字段(B7)
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

- flush_state 不再 await 共享 flusher 全程:持续 save 流下按代际轮询,目标代际落盘即返回(wait_task 不再被拖死)
- 抽出 _cap_resident_sessions,_run_task 完成即复查常驻上限——burst 完成不再全体滞留(此前只在 dispatch/start 时修剪)
- unload 成功后弹出 _adapters 映射(over-cap 与 idle 路径一致),否则 unloaded 会话永不进入淘汰候选、状态索引持续增长
devin-ai-integration[bot]

This comment was marked as resolved.

…e worker 已由 idle_unload_sec 覆盖,上限只会误伤不可复活的 cursor/dsh);flush_state 返回落盘结果、缩短轮询至 50ms,start/stop 恢复失败即抛错,wait_task 按代际上报 persisted

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

3 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread src/agent_bridge/registry.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

代际 flush_state 与 request_id 持久化整体方向正确;发现 1 个高置信问题:wait_task 的 persisted 会在终态 save() 之前误报 True。

Open in Web View Automation 

Sent by Cursor Automation: GitHub PR 自动审核

Comment on lines +1312 to +1316
persisted = False
if task.status in TERMINAL_STATUSES:
# A terminal answer must also be on disk, not merely queued
# behind the background flusher.
persisted = await self.flush_state()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

严重程度:高

问题: persisted 只保证「当前 _save_gen 已落盘」,并不保证「这份终态快照」已落盘。wait_task 在 task.status 已是终态时会跳过 _done.wait(),立刻 flush_state()。而 _run_task 是先改 status,再 await(finally 里的 await watch、merge_files_changed;stall 路径里更是先标 failed 再 await adapter.cancel),最后才 _done.set() + save()。窗口内上一档 running 快照往往已经 flushed,于是 persisted=True 配上内存里的终态。

触发条件: 协调端按文档短轮询 wait_task(例如 Cursor timeout_sec≈30)。上一轮 event.wait() 超时返回时,任务已在 stall/cancel/收尾中变成终态但尚未 save()。下一轮看到终态、跳过 wait、把旧代际当成已持久化。stall 取消可维持到 STALL_CANCEL_GRACE_SEC(15s),窗口足够跨过一次短轮询。

影响: 调用方以为终态已在 state.json。若此时进程退出,重启会把磁盘上的 running 收成 failed/bridge_restarted,stop_reason、error 与刚返回的 stalled/cancelled 不一致;request_id 重放也会绑到这条被改写的记录,而不是调用方以为已落盘的终态。

建议改法: 以 _done 为准,而不是内存 status:event 未 set 就继续 wait(超时仍返回 timed_out)。_run_task 里 save() 与 _done.set() 之间没有 await,等到 event 后再 flush_state() 才对应终态代际。更稳的是把终态 save() 挪到任何收尾 await 之前,并让 flush_state 等到那一档 _save_gen。

@Fei2411
Fei2411 merged commit b515ff3 into fix/mcp-lifecycle-acp-diagnostics Oct 6, 2026
6 checks passed
Fei2411 pushed a commit that referenced this pull request Oct 6, 2026
- 新增 diagnostics.redact_diagnostic,task.error/warnings、Codex/Antigravity 错误事件落盘前脱敏
- [env] deny 支持用户配置的 glob 名单,显式 set/agent env 优先;无内置禁止名单;dsh 回填同样遵守
- [agents.<name>] permission_policy:allow_once(默认)/ allow_always / deny;allow_once 下不选择 allow_always
- 同步 ORCHESTRATION、SETUP、skill 与 agents.toml.example 中 #29–#32 的契约变化
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant