Repository navigation
Replies: 3 comments
更正:问题不止 ptc preset原帖把范围写成"ptc preset 专属",这个表述不准确。我对本机 1914 个会话全量跑了官方
那 2 个 ptc 失败项报的是"事件数与期望不符"——因为我在统计事件数之后、验证器读取之前,文件仍被运行中的应用继续追加。属于并发读写,不是损坏。重新用准确计数复核,这 2 个会话实际是 0 越界。 第二类:step 编号在同一个 turn 内从头重数9 个 standard 会话的失败签名完全一致,全是小会话(21–35 事件): 读取器规则( case "step/start":
if (this.turn === null || data.turn !== this.turn || this.step !== null
|| data.step !== this.nextStep) throw new SessionFormatError("step/start does not match the open turn and next step");
this.step = this.nextStep;
case "step/end":
this.requireStep(event, data);
this.step = null;
this.nextStep += 1; // ← 这里之后 nextStep 应为 2
官方验证器对其中 3 个会话的实测确认: 两类问题的关系
相同点:都是事件坐标(turn / step)与实际状态不一致;写入侧不做校验,读取侧严格校验并整份拒绝加载。 不同点:A 类是"事件写在 turn 之外";B 类是"step 序号在 turn 内被重置"。两者应分开定位和修复——B 类需要检查 turn 中途被打断(本例为 附带说明此前修好的 3 个 ptc 会话经复查仍为 0 越界,文件在被应用继续正常追加,说明磁盘侧修复没有被打回。 |
|
Cross-linking the first failure class from this thread ( Confirmed on
Details, repro, and the fix design are in my comment on #11971: #11971 (comment) On your second class (step numbers restarting inside one turn, 9 small |
|
Follow-up on your second class (step numbers restarting inside one turn). I could not find a live writer path in current master that can produce that signature — here is the evidence, and what I need to go further. Current writer analysis (
So on current master, What would settle it: the One thing worth flagging regardless: the reader still has no repair/quarantine path for already-corrupted generations — those 9 files stay unloadable forever. If the maintainers want a proposal for a read-time quarantine (mark the generation corrupt, keep the bytes, allow listing) I can draft it separately from the writer fix. |
Uh oh!
There was an error while loading. Please reload this page.
现象
某个会话打开时报错,整份历史不可读,无降级:
环境
ptc触发条件
不是必现,但已复现 3 个独立会话损坏,共同点很稳定:
ptcpreset(只有run_code桥接才会产生 PTC 派发事件)aborted(user) 或error(上游 401 / 502 TLS handshake eof) 结束tool/ptc-dispatch-start+tool/ptc-dispatch成对出现,位置固定在turn/end之后、下一个turn/start之前三个会话的孤立事件数量分别是 2 / 2 / 16(16 条 = 8 对,连续多轮中止时累积)。
实际落盘序列(实例)
根因定位
写端在
packages/core/tools(@deepseek-ai/dsh-tools)的run_codePTC 桥接里。以下行号取自发行版 bundlelib/index.js,源码对应run_code的start()/settle分支:两条 append 路径没有任何 turn 存活判断:
但同一个对象另外两个入口是有守卫的:
也就是说:同一个
runOver()不变量,两个入口加了检查,两条真正写事件的路径漏了。另外
abandon()只做reject(...),没有取消已经排进pendingQueue的start(),也没有在 append 前复查runOver(),形成一个明确的竞态窗口。时序
为什么写入时没被拦住(关键)
同一个不变量,写端不守、读端死守:
dsh-sessionappend()validateSessionEventData(事件自身数据格式),不校验 turn 归属dsh-session-persistence-jsonlrequireTurn()SessionFormatError,整份日志拒绝加载结果:非法事件能静默落盘,只有当用户下次打开会话时才炸,而且没有 partial read / 降级恢复,一条坏事件让整段历史(本例 6000+ 事件)全部不可访问。
建议修复
dsh-tools(主责):start()与 settle 的 append 前加if (runOver()) return;;abandon()时把已排队的 pending 项标记为丢弃,避免start()在 abort 之后仍然执行dsh-session(次责):append()对 turn 作用域事件做归属校验,写入时即抛错,把问题暴露在产生现场而不是下次加载已做的临时处置(供参考)
手工剔除越界事件并重映射其后所有 seq 引用,重映射范围需覆盖四处(少一处都会被验证器打回):
event.seq(必须 dense 连续)event.sourceEventSeqs(顶层字段,数字数组或[start,end]区间对,要求每个值 < 自身 seq 且唯一)event.surfaceOp.startSeq/endSeqdata.*Seq/data.*Seqs以及data.*Range.{start,end}(compaction/prune的shadowedRange键名是start/end,不按Seq后缀命名,最容易漏)修好后用官方
verifyJsonlCurrentGeneration校验通过。tool/ptc-dispatch*在opaque判定里不参与 surface,所以剔除它们不改变 surface 语义。All reactions