在 Sandbox SDK 0.12 中,startProcess() 返回 Process 对象,容器会保留进程表供后续请求查询。在 1.0 中,你的 Durable Object 在容器中为每个进程保留一个目录。每个 Process 方法都成为接受进程 ID 的 Durable Object 方法。
你的 1.0 类已替换 0.12 Sandbox 类,并具有 替换 Sandbox 类 中的 container getter、ensureRunning() 方法,以及 ENV 和 INACTIVITY_TIMEOUT_MS 常量。
本页替换 startProcess()、Process 方法、listProcesses()、getProcess()、getProcessLogs()、streamProcessLogs()、killProcess()、killAllProcesses()、cleanupCompletedProcesses() 以及进程回调。它不涵盖请求打开期间完成的命令。那些请参阅 更改命令调用。
-
将运行后台进程中的脚本和方法添加到你的类中,包括 alarm 处理程序,以及跟踪后台进程中的
follow()和waitForLog()。一个 Durable Object 只有一个 alarm。如果你的类已有
alarm()handler(例如 通过一次部署迁移所有 sandbox 中的那个),请将进程检查合并进去,而不是添加第二个。 -
更改
startProcess(),用ensureRunning()启动容器,并用 0.12 提供给它的工作目录和环境运行每个进程:src/index.tsts export class MySandbox extends DurableObject<Env> { // ... async startProcess(id: string, argv: string[]): Promise<boolean> { await this.ensureRunning(); // mkdir fails when another process already uses the ID. const dir = `${ROOT}/${id}`; await run(this.container, ["mkdir", "-p", ROOT]); const created = await run(this.container, ["mkdir", dir]); if (created.exitCode !== 0) { return false; } // Ignoring the output lets the process outlive this request. const command = ["sh", "-c", RUN, "sh", dir, ...argv]; await this.container.exec(command, { cwd: "/workspace", env: ENV, stdout: "ignore", stderr: "ignore", }); // exec() returns before the process records its ID. Wait for it, so // a request that follows the output right away finds it running. for (let i = 0; i < 50; i++) { if ((await this.status(id))?.state !== "starting") break; await scheduler.wait(100); } await this.ctx.storage.put(`process:${id}`, argv); await this.ctx.storage.setAlarm(Date.now() + 60_000); return true; } }0.12 在
/workspace中运行每个进程,并带有来自envVars的变量。exec()从/启动,即使镜像设置了WORKDIR。它不会继承start()设置的变量,PATH除外。 -
从第 1 步添加的脚本中删除
INACTIVITY_TIMEOUT_MS。你的类文件已定义此常量,因此构造函数和 alarm 处理程序会继续使用类的不活动超时设置。
在本页中,sandbox 是 getByName() 返回的 stub。
0.12 在 bash 中运行每条命令字符串。要保留字符串,请用 ["bash", "-c", command] 运行它:
const process = await sandbox.startProcess("npm run dev", {
processId: "dev",
});const started = await sandbox.startProcess("dev", [
"bash",
"-c",
"npm run dev",
]);每个进程都需要 ID。0.12 生成 ID 的地方(例如 proc_1790379992394_6snsuw)请使用 crypto.randomUUID()。ID 会成为目录名,因此只使用路径中安全的字符。
当已有进程使用该 ID 时,startProcess() 返回 false。0.12 会替换记录,而第一个进程会在没有记录的情况下继续运行。已结束的进程会保留其目录,因此 startProcess() 也会对该 ID 返回 false。要再次启动该 ID,请先删除目录,如 停止进程 所述。
python3 -m http.server(0.12 用作预览服务器)在 1.0 容器中启动时会退出。有关原因和替换项,请参阅 主机名长于 DNS 标签。
删除 timeout 选项;它在 0.12 中无法停止后台进程。要限制进程的运行时间,请使用 timeout 命令启动进程,例如 ["timeout", "300", "npm", "test"]。时间到时,命令会以代码 124 退出;如果进程忽略 SIGTERM,使用 --kill-after 时则会以 137 退出。
有关 sessionId 选项,请参阅 替换会话。
将 getProcess() 和 Process.getStatus() 替换为 status(),它对没有任何进程使用的 ID 返回 undefined。每个 0.12 状态映射到 1.0 状态:
| 0.12 状态 | 1.0 状态 |
|---|---|
starting |
starting |
running |
running,带有 pid |
completed |
exited,exitCode 为 0 |
failed |
exited,带有另一个 exitCode |
killed |
exited,SIGTERM 后 exitCode 为 143,SIGKILL 后为 137 |
error |
startProcess() 抛出错误 |
| — | lost,当进程结束却没有记录退出码时 |
要替换 listProcesses(),添加列出进程目录的方法:
export class MySandbox extends DurableObject<Env> {
// ...
async listProcesses(): Promise<{ id: string; status: Status }[]> {
if (!this.container.running) {
return [];
}
const { stdout } = await run(this.container, ["ls", ROOT]);
const processes = [];
for (const id of stdout.split("\n").filter((line) => line !== "")) {
const status = await this.status(id);
if (status) {
processes.push({ id, status });
}
}
return processes;
}
}目录会保留到你删除它们,就像已结束的进程留在 0.12 表中一样。alarm 会在一分钟内删除已结束进程的 process: 键,因此请列出目录以找到已结束的进程。
将 getProcessLogs() 和 Process.getLogs() 替换为对每个流调用 logs():
const { stdout, stderr } = await sandbox.getProcessLogs("dev");const stdout = await sandbox.logs("dev", "stdout");
const stderr = await sandbox.logs("dev", "stderr");将 streamProcessLogs() 替换为 follow()。它返回一个 Response,跟随一个流直到进程退出,然后发送带有退出码的 exit 事件。
0.12 发送没有名称的事件,每个事件的 data 字段中有一个 LogEvent 对象。follow() 将每个事件命名为 stdout、stderr 或 exit。输出事件的 data 字段保存 JSON 编码的输出块,exit 事件的 data 字段保存 { "exitCode": <CODE> }。将 parseSSEStream() 替换为每个事件名称的监听器,或读取每个事件的 event 行并用 JSON.parse() 解析其 data 字段。
将 Process.waitForLog() 替换为 waitForLog()。它返回状态,而 0.12 会抛出错误:
const { line } = await process.waitForLog("ready.", 30_000);const result = await sandbox.waitForLog(
"dev",
literal("ready."),
30_000,
);
if (result?.state !== "matched") {
throw new Error(`The server did not start: ${result?.state}`);
}状态为 timed-out 时对应 0.12 抛出 ProcessReadyTimeoutError,为 exited 时对应抛出 ProcessExitedBeforeReadyError。
该模式是 grep -E 扩展正则表达式。0.12 按原样匹配字符串模式,因此请将每个字符串模式通过转义函数传入:
// Escapes a 0.12 string pattern, so that it matches as written.
function literal(text: string): string {
return text.replace(/[\\^$.|?*+()[\]{}]/g, "\\$&");
}没有它时,模式 [ok] 会匹配包含 o 或 k 的任何行。将 0.12 RegExp 作为其 source 传入,并将 \d 替换为 grep 需要的 [0-9]。
要替换 Process.waitForExit(),添加一个检查状态直到进程结束的方法:
export class MySandbox extends DurableObject<Env> {
// ...
async waitForExit(id: string, timeoutMs: number) {
const deadline = Date.now() + timeoutMs;
let status = await this.status(id);
while (
(status?.state === "running" || status?.state === "starting") &&
Date.now() < deadline
) {
await scheduler.wait(500);
status = await this.status(id);
}
return status;
}
}当 timeoutMs 先到期时,该方法返回状态 running 或 starting。
将 Process.waitForPort() 替换为对端口的请求,如 预览 Web 应用 所示。
将 Process.kill() 和 killProcess() 替换为 stop():
await sandbox.killProcess("dev");await sandbox.stop("dev");stop() 向进程组发送 SIGTERM,它会到达该命令启动的进程。0.12 会在 5 秒后向仍在运行的任何进程发送 SIGKILL,并忽略 signal 参数。要停止忽略 SIGTERM 的进程,请在 stop() 中将 TERM 改为 KILL。
将 killAllProcesses() 替换为对 listProcesses() 返回的每个正在运行的进程调用 stop()。移除 cleanupCompletedProcesses() 和 autoCleanup 选项,它们在 0.12 中不做任何事。要删除已结束进程的目录,请对其运行 rm -rf。
运行后台进程中的 alarm 每分钟检查每个进程,并在任何进程运行时保持容器运行。每个 0.12 回调都会移到你自己的代码:
| 0.12 | 1.0 |
|---|---|
onStart |
startProcess() 返回 true 之后的代码。 |
onOutput |
follow(),或用于目前输出的 logs()。 |
onExit |
alarm 的 process.ended 分支,在退出后一分钟内。要立即得到退出,请调用 waitForExit()。 |
onError |
抛出或返回 false 的 startProcess() 调用。 |
1.0 看不到 0.12 启动的进程。status() 对它们的 ID 返回 undefined,listProcesses() 会省略它们。0.12 进程表会随其容器结束。切换后请启动应用需要的每个进程,例如在第一次调用 ensureRunning() 的方法中。
部署切换后,用 0.12 进程的 ID(例如 dev)启动进程。startProcess() 返回 true,status("dev") 会报告它:
{ "state": "running", "pid": 142 }logs("dev", "stdout") 返回来自 /workspace 的输出,并带有 ENV 中的变量。stop("dev") 之后,listProcesses() 返回退出码:
[{ "id": "dev", "status": { "state": "exited", "exitCode": 143 } }]