[Bug] seq gap + 跨 session 内容串写 -> 手动修复后单文件损坏导致 dsh web 启动崩溃 #4767
Replies: 6 comments
|
There are two separate corruptions in this timeline, and preserving that distinction matters. Original artifact: two logical events share Replacement artifact: ordinary For diagnosis I would freeze every writer first, copy and hash the whole Session directory, and record each concurrent DSH PID, profile, resolved Because foreign content may be sensitive, keep the unredacted payload private, publish only minimized/redacted structure, and identify which Sessions/principals could have received it. The safest operational outcome remains quarantining the one Session and continuing in a fresh Session. A format-aware repair is possible only after a tool proves one authoritative history, emits a staged multi-frame artifact, and cold-loads it in an isolated Harness home; same-seq different values provide no exact-duplicate proof. I added this specific rc.2 branch, evidence record, and recompression boundary to the independent recovery runbook: https://github.com/sandbaseai/deepseek-harness-handbook/blob/main/docs/en/troubleshooting/duplicate-committed-session-seq.md |
|
三件事:根因、为什么手动修复失败(附可用的修复步骤,我实测过),以及为什么一个坏文件能拖垮整个启动。 1. 根因:rc.2 的 session 日志没有跨进程写入串行化你同时跑了两个 dsh 实例,这正是没有被保护的场景。
为什么恰好是 seq 重复,而不是别的乱码: seq: this.log.length,也就是说 seq 就是内存中日志数组的下标。两个写者各自持有自己的数组、各自从自己的长度算下一个 seq,于是必然撞号——你看到的
2. 手动修复为什么失败,以及正确做法
/** Assert that the independently decodable first frame contains only the header record. */
function assertZstdHeaderFrame(plaintext: Buffer): void {
if (plaintext.length === 0 || plaintext.indexOf(0x0A) !== plaintext.length - 1) {
throw new Error('corrupt Zstandard session log: first frame is not exactly one header line')
}
}第一个可独立解码的帧必须恰好只有 header 那一行。普通 正确的做法是把 header 单独压成一帧,再把其余内容压成后续帧,然后拼接(zstd 支持帧串联): head -n 1 session.jsonl > header.jsonl
tail -n +2 session.jsonl > body.jsonl
zstd -q -o header.zst header.jsonl
zstd -q -o body.zst body.jsonl
cat header.zst body.zst > session.jsonl.zstd我按读取侧那条规则实测过:单帧写法被拒绝,「header 一帧 + 正文一帧」被接受,且逐帧解码后与原始字节完全一致(帧边界处能看到 zstd 魔数 一个验证时的坑:某些单次调用式的解压 API 只解第一帧,你会以为内容丢了。用 修好之后仍建议先备份,并确认 seq 连续——删掉那条超长的重复行之后,后面所有事件的 seq 相对 header 是否还连续,是这套校验的下一道关。 3. 「一个坏文件让整个 dsh 起不来」是另一个问题这一条独立于并发写,值得单独知道:会话枚举以前遇到任何一个有问题的目录就整体抛错,于是一个坏 session 会让所有依赖枚举的功能一起失效——这正是 #4746 报的形态(那边是同一目录同时存在 这一点我在本地已经改成:坏目录跳过并记一条警告,健康 session 照常可用;但如果所有目录都坏,则明确报错而不是假装“空仓库”。你的经历正是这个改动想避免的:为了一个 session 起不来,代价是整个 dsh 起不来。 关于你的两个诉求
|
|
感谢两位的详细分析! 的确 rc.2 没有跨进程写锁,seq 直接用内存 log.length 分配,两个实例撞号就说得通了。 我这边没有更多可补充的证据了,原始损坏文件已隔离。后续会改从源码跟踪更新。 再次感谢。 |
|
To make the catalog-isolation behavior described above by
Scope is catalog discovery only. A missing/malformed raw header or malformed Zstandard header frame is isolated when at least one unrelated healthy Session remains, with a once-per-path warning. Targeted reads still reject the artifact. An all-corrupt root still rejects rather than appearing empty. Wrong root/cwd/id, physical case aliases, duplicate valid IDs, opposite encoding/layout, cancellation, permission, and I/O faults remain fail-closed. The original same-sequence divergent history is deliberately not repaired or selected. It receives an explicit diagnostic that automatic history selection is unsafe. Writer serialization remains the separate #4662 prevention lane; dual-encoding directory collisions remain the separate #4746 lane. This reference does not prove the incident's root cause or identify an authoritative event history. Exact-head verification: JSONL/Zstandard focused suites 239/239; full package suite 247/247 with 100% coverage of the changed source files; full TypeScript, changed-file lint, pre-push host/contracts build, and all 28 bilingual documentation/catalog gates passed. An independent adversarial review found no blocking isolation or fail-open path. |
|
手动用普通 dsh-session-surgeon 按这个帧布局读写,不会把整份压成一帧: dsh plugin --profile web add "github:xiaoshenming/dsh-session-surgeon#main"
启动连坐:把坏 session 目录移出 |
|
同簇问题(同 #1497:seq 重叠/回退导致 committed 区拒读)。两条不丢历史的解法(官方未打补丁 0.1.1-rc.2 实测:931,455 事件零 seq gap,
已坏在磁盘上就用 2;想让未来崩溃也看不见就上 1。 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
环境
dsh web过程
yay -Syu滚包,但是没有重启。Ctrl+C强制中断了两个 dsh 进程。dsh web打开工作 workspace + session,出现:session.jsonl.zstd后发现 同一个 seq=977737 出现了两次:{"type":"session/end-seed","seq":977737,...}{"type":"agent/inbox/spliced","seq":977737,...},里面塞了一大段完全不属于本 session 的对话内容(也就是另一个实例打开的workspace的一个session的内容)。zstd重新压缩,结果出现了:Error: corrupt Zstandard session log: first frame is not exactly one header line和一大段报错。整个dsh完全无法启动。
尝试的修复
agent/inbox/spliced行zstd单帧)→ 触发 first frame 校验失败相似问题
#4127
希望能增加并发写的保护以及session修复工具。
感谢!
All reactions