沙箱的 status 为 starting、running、sleeping 或 failed。awake_because 说明运行中的沙箱为什么在运行。
何时休眠
在 sleep_after_seconds 秒内(默认 30;null 表示永不休眠)同时满足下面所有条件时,沙箱进入休眠:
- 没有调用在等它(等待命令结果的调用算;打开着的输出流或终端不算);
- 它没有在计算:按客户机的统计,它自己的程序使用的 CPU 平均低于一个核心的 2%;
- 没有数据包经过它的网络链路;
- 它没有持有到外部的 TCP 连接(连到它自己 IPv6 地址的连接不算:它休眠时,它的机器会代它响应这些连接的 keepalive);
- 安静开始时尚未到期的每个定时器都已触发;
- 里面没有程序让它保持运行(见下文),或者保持请求已经持续了
sleep_hold_max_ms。
定时器不会让它长时间保持运行:沙箱照样休眠,平台会在它的下一个定时器到期前 0.4 秒唤醒它,并把它的时钟往前拨过休眠的时长。5 秒内就要到期的定时器,不值得为它休眠。
有客户端连接时保持运行。设置了 awake_ports(最多 16 个端口,在创建时或用 PATCH 设置)的沙箱,只要其中某个端口上有一个经由其链接升级的连接打开着,就一直保持运行:这就是 WebSocket,比如 9222 端口上浏览器的 DevTools 客户端,或 6080 端口上的屏幕查看器。在此期间它按运行计费,它的 awake_connections 说明有多少个连接打开着。这样的沙箱是否空闲只看这些连接、它的请求和它的调用:它自己的 CPU、定时器和出站连接都不会让它保持运行,也没有定时器会唤醒它。最后一个连接结束 sleep_after_seconds 秒后,它进入休眠。平台会在这些连接上开启 TCP keepalive,所以没有关闭连接就离开的客户端,大约一分钟内就会被发现;connection_idle_seconds(1 到 86,400)会关闭在两个方向上这么久都没有任何数据的连接。一次性运行不接受这两项设置。
POST /v1/sandboxes/{id}/sleep (glac sandboxes sleep web) 立即让它休眠。休眠的沙箱保留它的内存、进程、磁盘和网络位置:醒来时,打开的连接继续。
glac sandboxes update web --sleep-after-seconds 300 # five minutes idle
glac sandboxes update web --sleep-after-seconds never # only when asked什么会唤醒它
任何需要它的东西:运行命令、读写文件、发往其端口链接的请求、到它自己 IPv6 地址上开放端口的新连接(网络)、它已有连接上的新数据、在它的终端中按下的键、快照、分叉、定时器。没有专门的唤醒调用。需要唤醒它的 API 调用会在 Server-Timing: wake;dur=… 响应头中以毫秒给出唤醒用了多久。读取沙箱或列出沙箱都不会唤醒任何东西。
休眠沙箱的 sleep_tier 说明它如何休眠:
sleep_tier |
含义 | 付费内容 |
|---|---|---|
paused |
保持在机器内存中,CPU 停止。唤醒最快。 | 它的内存,按运行时计;不付 CPU。 |
saved |
它的内存保存在机器上。 | 它保存的内容,按 GB·月计。 |
disk |
只剩下它的磁盘:下一次调用从磁盘启动它,之前运行的东西都没了。 | 它保存的内容,按 GB·月计。 |
从内部保持运行
沙箱中的程序在响应之后还有工作要做时(要清空的队列、要完成的写入、要发送的日志),可以自己让沙箱保持运行。它连接每个进程都能使用的 Unix 套接字 /run/sbx/awake,发送 + 提出一个保持请求,发送 - 撤销一个。每个字节都会得到一行响应 held <this connection's holds> <all holds>;? 只做查询。连接的保持请求随连接一起结束:程序关闭连接,或者无论以何种方式退出,它的保持请求都会消失,所以崩溃永远不会让沙箱一直运行。多个程序的保持请求,以及同一个连接上的多个保持请求,会累加。
import socket
def hold_awake():
"""Raises a hold; close the socket it returns to lower it."""
s = socket.socket(socket.AF_UNIX)
s.connect("/run/sbx/awake")
s.sendall(b"+")
s.recv(64) # "held 1 1": the hold is in place
return sconst net = require('node:net');
// Resolves once the hold is in place, with the function that lowers it.
const holdAwake = () => new Promise((resolve, reject) => {
const s = net.connect('/run/sbx/awake', () => s.write('+'));
s.once('data', () => resolve(() => s.end()));
s.once('error', reject);
});c, err := net.Dial("unix", "/run/sbx/awake")
if err == nil {
c.Write([]byte("+"))
bufio.NewReader(c).ReadString('\n') // "held 1 1"
defer c.Close() // lowers it
}- 被保持期间,沙箱处于运行状态:它在运行,CPU 和内存按运行计量。
awake_because显示its guest holds it awake (…)。最后一个保持请求撤销后,沙箱从那一刻起算作空闲,sleep_after_seconds秒后进入休眠。 - 最多保持一段时间。
sleep_hold_max_ms(0 到 900,000 毫秒;默认 30,000)是保持请求最多能让一个本来空闲的沙箱保持运行多久。超过之后,无论有没有保持请求,它都会休眠;它醒来时保持请求还在,时间从它下一次安静起重新计算。所以忘了撤销保持请求的程序,每次最多也就多花这么多。0表示忽略保持请求。 - 得到通知。设置了
sleep_notice_ms(1 到 60,000 毫秒;默认不通知)时,沙箱因空闲进入休眠之前这么久,套接字上的每个连接都会收到一行sleep <ms>:把手上的数据写出、关闭连接,或者提出保持请求以保持运行。无论有没有回应,到时都会休眠;没有连接时,不会等任何人。如果是上限结束了保持请求,这一行是sleep <ms> cap,这时提出保持请求也挡不住这次休眠。你主动要求的休眠 (POST …/sleep) 不会预先通知。 - 要在需要之前提出。在沙箱静止之前最后一刻(不到一毫秒)才提出的保持请求,要到它下一次醒来时才生效。如果连接不是你主动结束的(套接字背后的代理程序重启了),请重新连接。
重启与故障
POST /v1/sandboxes/{id}/restart (glac sandboxes restart web) 从它的磁盘重新启动它:之前运行的东西都没了,写入的数据还在;它的 id、名称、链接和云盘都不变。机器自行停止时(reboot、poweroff、内核崩溃)也一样:平台会在四分之一秒内发现,不再把它算作运行中,下一个需要它的调用会从磁盘启动它。正在等待它的某个命令的调用,这时会得到 409 command_lost。启动失败的沙箱会在 failure 和 failure_code 中说明原因;重启它就能解决。
检查点
机器丢失时正在运行的沙箱,会在另一台机器上从磁盘重新启动,之前运行的东西都没了(机器丢失时)。有检查点时,它会在那里从最新的检查点带着进程继续运行。检查点默认关闭,需要时再开启:
glac sandboxes create --name agent --checkpoint 10 # every 10 seconds while it is awake (on: 10)
glac sandboxes update agent --checkpoint off # none from now on- 用 API:创建时或在
PATCH中设置checkpoint: {"every_seconds": 10}(1 到 600;默认 10);"checkpoint": null会去掉检查点。SDK 在创建和更新时接受checkpoint,MCP 服务器有sandbox_checkpoint。 - 保存什么,以及接管会丢失什么,与键控实例相同:它运行期间,内存和磁盘自上一个检查点以来的变化会送到对象存储,它的云盘随之固定在同一时刻;带有非云盘存储卷的沙箱不创建检查点。参见检查点。它的
checkpoint说明检查点的进行情况(last_at、behind_seconds、stored_bytes、error),它带着进程恢复时,sandbox.moved会显示with_processes: true。 - 在哪里。只有既把磁盘复制到对象存储、又把内存分层保存的机器才能创建检查点,带检查点的沙箱会创建在这样的机器上:平台没有这样的机器时,会被拒绝 (409
checkpoints_unavailable);这些机器都离线或已满时,返回 503no_capacity,可重试 (retryable)。 - 费用。沙箱运行期间,存储为它最新检查点保存的内容,按休眠沙箱的状态计费(
state,每 GB·月 $0.02);发送检查点不收费。
休眠太久会被删除
沙箱可以设置休眠多久之后被删除 (delete_after_idle_ms)。项目可以为它的所有沙箱设置这个时长,以天为单位(调用 PATCH /v1/project 并带上 delete_after_idle_days,1 到 3650;glac project --delete-after-idle-days 30,以及 SDK 的 update_project)。两者都没有设置时,沙箱不会因休眠而被删除,expires_at 则会在某个时间删除它。因支出上限或套餐而被休眠的沙箱,从不会因此被删除。