代际与 fail-closed
定义
代际(generation)是 Box 与 A3S OCI Runtime 共用的生命周期单位:同一次产品对象(一个 box、一个 sandbox)的每一次创建或重启,对应恰好一代运行时状态。一代拥有不可变的创建身份、附件摘要、端点与路由。崩溃恢复、日志投影、exec 句柄与清理都绑定到这一代,而不是绑定到「名字叫 web 的那个东西」。
fail-closed(失败即关闭)是决策规则:当实现无法证明「继续是安全且符合已记录意图的」时,必须返回错误并停止副作用。禁止的行为包括:改走另一后端、合成退出码、把 tmpfs 密钥降级到磁盘、在摘要漂移后复用套接字。
问题背景
本地运行时要处理的典型故障是:进程被 SIGKILL、主机重启、套接字残留、镜像标签被移动、半写入的目录。若恢复逻辑采用「尽量猜」,则可能启动第二个实例、删除别人的根目录,或把已停止的一代标成仍在运行。代际把「这是哪一次启动」变成显式数据;fail-closed 把「猜」从实现策略中排除。
机制说明
代际约束了哪些动作
- 路由:创建时写入
ManagedRuntimeRoute,恢复时不得按新的A3S_BOX_OCI_MIGRATION改道。 - 所有者复用:PID、端点、路径、SHA-256 必须同时匹配,否则启动新的所有者,并把旧代际标为已停止。
- 操作身份:文件写入、资源更新、快照 thaw 使用可重放的操作 ID。已完成的操作在崩溃后不得再执行一次。
- 日志与 attach:投影 worker 在 OCI init 之前就绪,按有序游标写入;删除运行时代际前必须排空。attach 不回退到旧的 runtime socket。
- 退出状态:无法取得真实退出码时,拒绝伪造。对账可以将一代标为已停止,但不能发明一个 0 或 137。
Native Live 观察门证明:在保留 Box manager 的前提下,宿主所有者被
SIGKILL 后,流式 exec
句柄与文件系统连续性仍可维持。这与「仅停止对账」是两种都成立、但不可混淆的性质。主机报告保持
b2_process_session_recovery_closed=false,表示该门尚未被宣称为关闭。
fail-closed 的具体例子
| 情境 | 关闭行为 | 被禁止的开放行为 |
|---|---|---|
| Sandbox 所有者未就绪且策略为显式 sandbox | 预检失败 | 改走 MicroVM |
OCI 路由上的 cp / attach 失败 |
向调用方报错 | 重试 Box 拥有的 socket |
| 密钥 tmpfs 不可用 | 启动前失败 | 把密钥写到磁盘 |
| 客户机文件名无法在 macOS 目录传输中无损表示 | 失败 | 改名后继续 |
| 记录缺少托管元数据 | Internal 错误 | 按当前默认隔离猜测 |
开放(fail-open)在界面上更「好用」,但会破坏取证与多租户假设。Box 的文档反复出现「从不回退」,指的就是这一规则,而不是指程序不得重试同一操作身份。
在 Box 中的位置
- 记录与路由:
src/runtime/src/box_record.rs、local_execution/router.rs、recovery.rs。 - 操作身份与资源更新:
local_execution/resources.rs,以及 core 中的OperationId。 - 日志投影:
local_execution/oci_log_projection.rs、shim/src/managed_oci_log_worker.rs。 - 证据语言:README 的 Sandbox GA
小节、
docs/sandbox-ga-evidence.md。
验证命令
无单一命令能演示全部规则。较接近的观察是:启动一个 Sandbox
box,记录其运行时身份,然后仅杀死 OCI 所有者进程,再执行
inspect
或下一次普通操作。预期是新的所有者与新的代际,而不是把旧套接字上的残留当作同一代继续使用。
相关与易混
- 相关:ExecutionManager、Backend 与 Router、Sandbox(共享内核)
- 易混:代际不是镜像标签,也不是 Kubernetes 的 ReplicaSet generation。它标识一次本地执行。fail-closed 也不是「禁止重试」:对同一操作 ID 的显式重试是允许的,前提是实现能证明该操作至多生效一次。