Repository navigation
Session log can be corrupted when 0.1.0-rc.8 crash recovery races a still-live writer #10429
Replies: 5 comments
|
这个形状(恢复写者先写闭包、原写者用旧游标在同一段 seq 上接着写)我们这边已经能离线修好,并且保住的是真结果、丢掉的是合成闭包 —— 不是反过来。 判据很简单,而且全部在盘上可检:
三条都成立时,丢掉闭包、保留 live 写者,seq 一个都不用改 —— 因为两段写的是同一批 seq,本来就对齐;之后尾部若还开着回合并由一个 closer 补上。任何一条不成立就 refuse,不猜。 实测(按你贴的序列构造:
两个边界值得说清:
工具: |
|
Update / correction after checking the released packages: the write-side race itself is already fixed upstream, and the fix predates the current releases.
So on >= 0.1.3-alpha.2 a crash-recovery writer and a live writer can no longer hold the same session at once; the seq-overlap shape in this report belongs to <= 0.1.2. What is not fixed is files those older versions already corrupted: the lease prevents new overlap but cannot repair a torn log, so those still need the offline stitch. Suggested resolution for this thread: retitle/downgrade to "corrupted by pre-0.1.3 versions — offline repair needed", with the actionable ask being the upgrade path (0.1.7-rc.2 or 0.2.1-rc.3) plus repair of existing files. |
|
Closing with the verified outcome. Root cause fixed upstream. Scope boundary. Versions <= 0.1.2 (0.1.0-rc.8, 0.1.1-rc.2, 0.1.2-rc.1) have no lease and can still produce this seq-overlap shape. The lease prevents new corruption but cannot repair files already damaged, so pre-0.1.3 files still need the offline stitch described above. Local check. I scanned both DSH session roots on this machine with Reopening would be warranted if the same seq-overlap shape is observed on >= 0.1.3-alpha.2, which would mean the lease is not covering some write path. |
|
Reopen condition met on 0.2.1-rc.3 (Windows 11 build 26300, Electron 44.7.0, app.asar built 2026-10-29 22:02, installed 2026-10-31 01:45:38 +08). Three sessions in one workspace, corrupted by this build between 11:41 and 12:25 on 2026-10-31 — complete frames, valid JSON, seq dense 0..N-1 except one re-used block: session-0382f57c: ...2552 approval/asked | [resume 11:41:16] 2553 interrupted tool/result, 2554 step/end, 2555 turn/end{interrupted}, 2556 session/end-seed | [stale writer 11:48:00] 2553 approval/decided, 2554 job/registered, 2555 job/settled, 2556 real tool/result | 2557 step/end ... The third one has no interrupted turn and no synthetic closers: the constructor's session/end-seed at seq=log.length collided with the live writer's next event. So the collision only needs an open while a previous writer is still live. Reader rejects with: "released v2 row 2557 has seq gap (expected 2557, got 2553)" etc. Three suspects in this build: (1) the lease is acquired lazily at first durable write (persistContiguous :226, flush :135), not at write-open as the module doc :637 states, so AgentLoop.resume (dsh-agent-loop/lib/index.js:1986-2003) reads/appends/seeds in a lock-free window; (2) a released lease is never cleared from the handle (ensureLease :250 is Offline stitch (drop the resuming writer's closure/seed records, keep the live writer's, seq untouched) restores all three losslessly; they load via Session.fromRestore and open in the GUI. |
|
补一条 rc.2 上的反例,可能推翻"rc.2 已由 我这边(Windows,0.2.1-rc.2 常驻进程,08:05 才升级到 rc.5)有 3 个坏档,全部是"中断 → 恢复",但没有第二个写者:续跑写的是同一 turn/step 的全新一段运行( 盘上指纹( 即续跑会话的 若 完整现场与修复判据见 #11707。 |
Uh oh!
There was an error while loading. Please reload this page.
Summary
0.1.0-rc.8can corrupt a Session log when crash recovery writes synthetic interrupted-turn records while the original writer is still alive. Both writers believe the same seq is next, producing duplicate seq values in the committed region and making the Session permanently unreadable through normal load.This is a stale-writer / lost write-ownership race, not Zstandard corruption.
Observed production sequence:
The next load fails with:
The message says seq gap, but the actual corruption is seq reuse.
Reproduction
tool/callin a Session with notool/result.session/end-seed.tool/resultusing its old cursor.The tool does not need to run twice. A slow live tool call is sufficient.
Root cause in 0.1.0-rc.8
The affected release has separate in-memory cursors with no durable single-writer exclusion:
appendCore()in session-persistence checks continuity againststate.cursor, but that cursor belongs only to one coordinator instance.commitRepair()in session-persistence-jsonl appends synthetic closers directly to the JSONL artifact.Current status on master
I do not see a new code change to propose here: the current
0.2.1-rc.2architecture already contains the structural fix.flockonsession.lockfor the lifetime of the write handle.Relevant tests:
Proposed fix
Upgrade the affected runtime to
0.2.1-rc.2rather than adding a local lock only aroundcommitRepair(). A short repair-only lock still leaves a TOCTOU window between repair completion and resume/write-handle ownership.The required ownership boundary is:
If
0.1.xremains supported, this should be treated as a data-corruption fix requiring a backport of the complete write-handle/lease model, not an isolated lock around repair.Additional context
I repaired the affected local log from a byte-for-byte backup by removing four synthetic interrupted records, preserving the real tool result, and rebuilding the Zstandard frames. The backend then loaded 1,132,924 events from seq 0 through 1132923.
Given the corruption is committed, normal crash-tail truncation cannot repair it. A released-version upgrade notice may be warranted because affected users cannot infer this race from the generic history-unavailable error.
All reactions