检查点写入:原子替换失败时旧状态必须仍可读
自动关联目录:检查点写入:原子替换失败时旧状态必须仍可读
核心判断:检查点的首要职责不是“尽快写出最新 JSON”,而是无论共享冲突、磁盘满还是进程崩溃,都至少保留一份可解析的旧状态。
一、业务冲突
Cook 与发布可能持续很久,桌面 UI 又会频繁读取进度。在 Windows 上,读者的共享模式可能短暂阻止文件替换;磁盘不足则可能让临时文件只写出一部分。直接覆盖正式文件,会把一次可重试的写失败放大成整个任务无法恢复。
二、实际实现
实际检查点先写同目录临时文件,Flush 并 fsync 后使用 os.replace 原子替换。共享冲突按有限次数重试,持续冲突返回可重试 I/O 错误;磁盘满不重试。真实 Windows 文件共享回归测试证明替换失败时旧文件内容仍在,而不是只 Mock 正常路径。
三、值得保留
它把“写入最新状态失败”与“破坏已有状态”分开。调用者能保留上一个 committed state,并把失败映射为稳定错误类别。用真实读句柄测试也抓住了 POSIX 环境无法复现的平台差异。
四、结构性缺陷
原子替换只保证单文件可见性,不自动解决两个写者竞争、目录项持久化或旧进程覆盖新 generation。读者若只按路径重试,也无法判断读到的是哪个 Revision。大 Journal 每次全量重写还会扩大失败窗口。
五、目标设计
void CommitCheckpoint(State next, Revision expected) {
Header current = ReadHeader(activePointer);
Require(current.revision == expected);
Bytes bytes = EncodeWithChecksum(next, expected + 1);
Path immutable = WriteAndSync(GenerationPath(expected + 1), bytes);
ReadBackAndVerify(immutable);
AtomicReplacePointer(activePointer, immutable, expected + 1);
}小型任务可继续使用临时文件替换,但头部必须带 Schema、Revision 和摘要;多写者场景增加进程锁或 CAS。大型 Journal 可拆成追加日志与小型活动指针,减少反复重写。
六、取舍与验证
不可变 generation 会增加清理和磁盘占用,需要在没有活动读者后回收。验证矩阵应覆盖共享冲突、磁盘满、断电点、校验失败、双写者和旧进程迟到提交,并断言活动指针永远只指向完整状态。
许可协议:CC BY
作者:Davids
本文链接:https://hustjjd.github.io/aa51eaf3.html
更新于:2026年10月14日