快照与恢复
快照会冻结沙箱文件系统并返回一个ID。恢复会从该ID创建一个新沙箱。文件相同,状态相同,虚拟机不同。
机制很简单。出错的地方却没有。这三者都接近幂等性,即调用某事两次的结果与调用一次相同的性质。沙箱充满了你真的不想在不考虑的情况下重复调用的操作。
学习成果
你可以描述snapshot保护什么、restore创建了什么,并识别生产生命周期代码中出现的三种幂等性危害。
API
const { snapshotId } = await sandbox.snapshot!();
const restored = await createCloudSandbox({ snapshotId });那只是表面。所有有趣的事情都围绕着它发生。
快照保存了什么
快照捕捉文件系统状态。/workspace的内容(或者你沙箱根的地方)、任何已安装包的状态、Agent创建的任何文件。它们不会捕捉正在运行的进程、在运行中的网络连接或内存状态。一个运行已经过编译步骤一半的构建,在还原后不会从中断处继续。 文件系统快照;正在进行的工作则没有。
这对Agent的心理模型很重要。恢复后,Agent会保留之前所有的文件,但如果快照触发时正在运行测试,测试必须重新运行。
三大幂等性险
1. 快照正在进行中
用户(或生命周期工作流)在快照正在运行时触发快照。没有守卫,你会看到两个快照争夺同一个虚拟机,或者出现提供者错误,或者一个看起来有效但实际上不存在的部分快照。
let inFlight: Promise<{ snapshotId: string }> | null = null;
snapshot: async () => {
if (inFlight) return inFlight;
inFlight = vm.snapshot();
try {
return await inFlight;
} finally {
inFlight = null;
}
},缓存承诺,第二调用天还回,工作完成后清除。
2. 沙箱已经在还原中运行
用户重新连接到已经活跃的会话,Harness会在会话上触发恢复。现在你有两个虚拟机。新车是在浪费钱。旧的那台还在为交通服务。
async function attachOrRestore(sessionId: string, snapshotId: string) {
const existing = await findActiveSandbox(sessionId);
if (existing) return existing;
return createCloudSandbox({ snapshotId });
}创作前先看清楚。恢复路径应先检查是否有活跃沙箱。
3. 双音
stop由非活动计时器调用一次,用户调用一次。或者一次在休眠,一次在硬到期。第二个调用遇到的沙箱已经没了,根据供应商不同,要么大声故障,要么悄无声息地损坏状态。
let stopped = false;
stop: async () => {
if (stopped) return;
stopped = true;
await vm.close();
},一个布尔值就足够了。关键是第二个stop没有做任何事,而不是直接攻击死机。
恢复解决不了的问题
快照是时间中的一个瞬间。从昨天的快照恢复,仍然会得到昨天的代码、昨天的依赖和昨天的环境。如果项目已经转移(新提交、新包、新环境变量),恢复后的沙箱就是化石。
生产生命周期通过在项目变更时失效快照,或在恢复后重运行安装钩子(模块4afterStart)来句柄这一点。这两者都不是自动的。两者都必须刻意接线。
**注意:当地沙箱可以伪造这个**
本地后端没有真正的快照机制,但你可以用git stash或 tarball 来绘制一个快照工作目录。它不等同于云快照(没有虚拟机状态,没有安装缓存),但它教会了接缝。如果你想玩玩形状,那是个不错的起点。
动手试试
这是一个概念课。自我检视:
- 不要回头,说出三个幂等性危险
- 用你自己的话写下
snapshot保存和不保存的内容 - 用序列图勾勒
attachOrRestore流程。活跃沙箱检查相对于快照查找在哪里进行?
如果你想本地玩形状,可以在本地沙箱添加一个snapshot,运行git stash push并返回仓库引用作为快照ID。语义上有问题(真实快照会冻结所有内容,不仅仅是追踪文件),但接缝是真实存在的。
提交
这节课里没有代码,除非你是在绘制本地快照。如果需要,建议把它提交到另一个分支,这样就不会和工作Harness纠缠。
完成标准
- [ ] 你可以描述快照保留哪些内容,哪些不保留
- [ ] 你可以说出三个幂等性危险和每个的防守模式
- [ ] 你可以解释为什么恢复后的快照仍可能需要
afterStart重新运行