DEEP 19 · 第二辑 · 感知与系统服务 · LEARN NOTES

收进学习手册 · 2026-09-11 · duck.whatled.com

解读 19 · updater(下):reconcile 与回滚

解读对象:updater/src/reconcile.rs(373 行)+ updater/src/orphan.rs(352 行)+ updater/src/faults.rs(120 行)
需要的前置:解读 18。

回滚是最容易悄悄坏掉的功能

faults.rs 开头一句话足以立论(faults.rs:1-6):回滚只在"别的东西已经坏了"时运行,所以最容易悄悄坏——"回滚大概能用"必须变成 CI 断言。手段是故障注入:Faults(faults.rs:14)八个开关,从 corrupt_artifact(必须哈希不匹配、不安装)到 fail_rollback(更新失败且恢复失败,必须大声报告)。三条纪律值得抄走:

回滚的机械动作很小:发布布局是 releases/<版本>/ 加一个 current 符号链接(store.rs:6-8),换代 = rename(2) 原子改链接(store.rs:146),回滚 = 把链接指回去。难的是知道何时该回,以及回完之后世界是否一致。

orphan.rs:把 203/EXEC 拦在换链接之前

一个真实事故(orphan.rs:10-14):板子从 dev 构建解析到 stable 0.2.0,而 0.2.0 早于 configd 诞生;configd.service 还在,ExecStart 指向旧版没有的二进制,systemd 报 203/EXEC,失败的重启又让更新失败、触发回滚。结局正确,诊断却只能靠读 systemd 错误码。

would_orphan(orphan.rs:66)把规则变成检查:扫 /etc/systemd/system/*.service,凡 Exec*= 指进 current 符号链接、而候选版本里没有对应文件的,就是孤儿。细节:

reconcile.rs:重启真的发生了吗

更新的延迟重启经 systemd-run 排程,而排程成功 ≠ 重启发生(reconcile.rs:3-8),这留下一个静默态:机器人跑着它没装过的版本。reconcile 在每次 updaterd 启动时对账:读每个守护进程启动时发布的 identity 文件(running_release,reconcile.rs:173),与组件 current 指向的版本比对,过期者 systemctl restart。

裁决函数 verdict_for(reconcile.rs:81)是纯函数、与一切 syscall 分离——"会判错的部分"必须可测,而真实板凑不出那个状态,它恰恰要靠坏更新才能制造。四条裁决里最重的一条:updaterd 永不在这里重启自己(Verdict::ReportedOnly,reconcile.rs:62-74)。若继任者对"哪个版本是活动"意见相左,它会重启、再相左、再重启——在独占恢复的进程里死循环,那是唯一没有出路的故障。检查放在启动时的理由也直白:updaterd 无法目睹自己的重启落地,只有继任者能查(reconcile.rs:21-27)。另外,停着的 unit 不是过期的 unit(reconcile.rs:87-89):每次启动都替人重启它停掉的服务,是没人要的决定。

你带走的收获

延伸

什么鸭 WHATDUCK · 复刻学习笔记 ← 全部手册 · 首页