INTEGRITY Документация

Выполнение процессов

В превью версии 1.0 воспринимайте песочницу как компьютер, которым вы управляете с помощью явных программ.

Каждый exec() запускает новый управляемый процесс от argv. Вызов разрешается при успешном запуске (вы получаете дескриптор процесса с id и pid свойства), а не при завершении процесса. Каждый запуск независим: передавайте cwd и env когда они нужны процессу, либо поместите многошаговый синтаксис shell в один явный shell argv. Для интерактивного PTY используйте терминал API.

Долгая работа часто охватывает множество коротких запросов Worker. ID процесса достаточен только пока тот же контейнер по-прежнему содержит этот процесс. При остановке из-за простоя, сбое или замене сохраняйте полные данные запуска (команду, параметры и контрольную точку приложения), чтобы иметь возможность запустить его заново. См. Продолжение работы между запросами.

ID песочницы, контейнер и процесс

Большинство приложений используют одна песочница на пользователя или задачу:

const sandbox = getSandbox(env.Sandbox, "user-123");
const sandbox = getSandbox(env.Sandbox, "user-123");

Здесь задействованы три разные вещи:

Термин Значение
ID песочницы Стабильная строка, по которой ваше приложение находит эту песочницу снова (например, "user-123").
Контейнер Containers экземпляр, который сейчас выполняет работу для этой песочницы. Песочницы работают на контейнерах. ID песочницы постоянен. Экземпляр контейнера за ним не всегда один и тот же.
Процесс Программа, которую вы запускаете с помощью exec() внутри текущего контейнера. Дескриптор и process.id означают «эту программу в этом контейнере», а не «этот ID песочницы навсегда».

Одинаковый ID песочницы не означает тот же контейнер. Процессы существуют только в контейнере, который их запустил. После того как новый контейнер начинает обслуживать этот ID, нужно запускать новые процессы: старые возобновить нельзя. Полная модель песочницы: Жизненный цикл песочницы.

Модель команд

В превью 1.0 модель команд отличается от текущего стабильного пакета следующим образом:

Текущий стабильный пакет 1.0 preview
exec(string) разрешается по завершении команды exec(argv) разрешается при запуске процесса
Сессия по умолчанию может сохранять cd / export Каждый запуск независим
startProcess / execStream для других форм Один дескриптор процесса охватывает как краткосрочные, так и долгосрочные задачи

Используйте argv для одного исполняемого файла:

const process = await sandbox.exec(["node", "--version"]);
const output = await process.output({ encoding: "utf8" });
const process = await sandbox.exec(["node", "--version"]);
const output = await process.output({ encoding: "utf8" });

Используйте явный shell, когда нужен синтаксис shell:

const process = await sandbox.exec([
	"/bin/bash",
	"-lc",
	"cd /workspace/app && npm test",
]);
const process = await sandbox.exec([
	"/bin/bash",
	"-lc",
	"cd /workspace/app && npm test",
]);

Либо передайте cwd и env при запуске, вместо того чтобы полагаться на результат предыдущей команды:

const process = await sandbox.exec(["npm", "test"], {
	cwd: "/workspace/app",
	env: { NODE_ENV: "test" },
});
const process = await sandbox.exec(["npm", "test"], {
	cwd: "/workspace/app",
	env: { NODE_ENV: "test" },
});

Значения на уровне песочницы используют setEnvVars. См. Переменные окружения.

Дескрипторы процессов

await sandbox.exec(argv) возвращает дескриптор процесса:

Возможность Участники
Идентификация id, pid
Наблюдение status(), logs(), output(), waitForExit(), waitForLog(), waitForPort(), exitCode
Управление kill(signal?) с числовым сигналом (по умолчанию 15)

Тайм-ауты наблюдения и AbortSignal значения отменяют только те, что ожидают ответа или передают поток данных. Они не останавливают процесс. Вызовите kill() когда вы собираетесь остановить его.

exec(argv, { timeout }) задаёт время жизни в удалённой среде крайний срок. Когда супервизор останавливает процесс по истечении этого срока, завершение может сообщать timedOut: true.

Для коротких команд output() достаточно. Для большого объема данных или длительного вывода используйте logs({ since, replay, follow }) и оставьте только последний курсор чтобы более поздний запрос мог возобновить поток, пока процесс еще находится в текущем контейнере. Подробности API: API процессов.

Сколько живёт процесс

Процесс существует только пока продолжает выполняться в текущем контейнере для этого сандбокса. После остановки контейнера старые ID процессов недействительны в новом контейнере, обслуживающем тот же ID сандбокса.

Когда контейнер останавливается

Контейнер песочницы не предназначен для постоянной работы. После периода бездействия Cloudflare может остановить его. Контейнер также может остановиться после сбоев или когда платформа заменяет его в ходе штатных операций (например, после некоторых развёртываний).

Когда это происходит:

Остановка и замена контейнера не являются чем-то новым в версии 1.0. Предварительная версия обеспечивает то, что дескрипторы процессов завершаются с ошибкой после исчезновения контейнера, которому они принадлежали: SDK не перенаправляет старый ID процесса на новый контейнер в рамках того же ID песочницы.

Что вы видите в API

Что вы пробуете Что происходит
Процесс всё ещё выполняется в текущем контейнере getProcess(id) возвращает его; вы можете читать логи и ждать как обычно
Для этой песочницы пока не запущен контейнер getProcess и listProcesses возвращать null / []. Они выполняют не запускать контейнер только ради ответа на этот запрос
У вас всё ещё есть дескриптор, полученный до остановки контейнера Вызовы для этого дескриптора завершаются ошибкой с StaleProcessHandleError
Требуется тот же задача после остановки Запустите новую exec() из запуска и контрольной точки, сохранённой вашим приложением

Процедуры восстановления: Ошибки и восстановление.

Поддерживайте работу контейнера

Пока процесс или терминал активен, контейнер может продолжать работать, чтобы задача не прерывалась между запросами. Когда ничего не активно, контейнер может снова остановиться после периода простоя. В долго выполняющихся сценариях продукта следует либо поддерживать активность значимой работы, либо полагаться на контрольные точки и повторный запуск.

Продолжение работы между запросами

Запросы Worker короткие. Процессы Sandbox часто таковыми не являются. Спроектируйте задачу так, чтобы последующий запрос мог либо возобновить тот же процесс или запустить задачу заново.

Что хранить

Всегда полезно При потоковой передаче логов
ID песочницы Последняя запись журнала курсор из доставленных событий
Полный exec argv
cwd и env если они нужны для запуска
Контрольная точка приложения (путь к репозиторию, шаг, состояние агента)

Идентификатор процесса служит ключом для возобновления текущий только контейнер. Недостаточно просто перезапустить задачу после того, как контейнер мог остановиться.

Случай 1: контейнер всё ещё выполняет процесс

Используйте этот путь, если работа ещё продолжается и контейнер не заменён: например, через несколько секунд поступает ещё один запрос, пока идёт сборка или уже работает сервер.

const process = await sandbox.getProcess(storedProcessId);
if (process) {
	const stream = await process.logs({
		since: storedCursor,
		replay: true,
		follow: true,
	});
	// consume events; keep the latest cursor from each event
	return;
}
const process = await sandbox.getProcess(storedProcessId);
if (process) {
	const stream = await process.logs({
		since: storedCursor,
		replay: true,
		follow: true,
	});
	// consume events; keep the latest cursor from each event
	return;
}

Вы также можете вызвать status(), waitForPort(), waitForExit(), или kill() на этом хендле. Курсоры логов действительны только пока этот процесс ещё существует в этом контейнере.

Случай 2: процесс завершён, начните с сохранённого задания

Используйте этот путь, если getProcess возвращает null, вызов выбрасывает StaleProcessHandleError, либо прошло достаточно времени, и контейнер мог быть остановлен или заменён.

const process = await sandbox.exec(storedCommand, {
	cwd: storedCwd,
	env: storedEnv,
});
// persist process.id (and clear any old cursor)
await process.waitForPort(3000, { timeout: 60_000 });
const process = await sandbox.exec(storedCommand, {
	cwd: storedCwd,
	env: storedEnv,
});
// persist process.id (and clear any old cursor)
await process.waitForPort(3000, { timeout: 60_000 });

Если задача также зависит от файлов, которые существовали только в предыдущем контейнере, резервное копирование и восстановление эти каталоги или подключите постоянное хранилище, прежде чем снова полагаться на это дерево файлов. Резервное копирование и восстановление заменяют состояние файловой системы. Они не восстанавливают старые идентификаторы процессов и буферы логов.

Если контейнер ещё не готов, вы можете получить ContainerUnavailableError : подождите, а затем повторите то же задание ещё раз. См. Ошибки и восстановление.

Выберите путь

async function continueJob(sandbox, job) {
	if (job.processId) {
		const existing = await sandbox.getProcess(job.processId);
		if (existing) {
			return existing; // Case 1 — same container, same process
		}
		// null: no container, or this ID is not in the current container
	}

	// Case 2 — relaunch from stored command and checkpoint
	const process = await sandbox.exec(job.command, {
		cwd: job.cwd,
		env: job.env,
	});
	job.processId = process.id;
	job.cursor = undefined;
	return process;
}
async function continueJob(sandbox: Sandbox, job: StoredJob) {
	if (job.processId) {
		const existing = await sandbox.getProcess(job.processId);
		if (existing) {
			return existing; // Case 1 — same container, same process
		}
		// null: no container, or this ID is not in the current container
	}

	// Case 2 — relaunch from stored command and checkpoint
	const process = await sandbox.exec(job.command, {
		cwd: job.cwd,
		env: job.env,
	});
	job.processId = process.id;
	job.cursor = undefined;
	return process;
}

Если у вас всё ещё есть объект-дескриптор, полученный до остановки контейнера, вызовы через этот дескриптор выбрасывают исключение StaleProcessHandleError. Предпочитайте getProcess(id) при каждом новом запросе вместо повторного использования старого хендла между запросами.

Процессы и терминалы

Процесс (exec) Терминал
Роль Управляемый процесс argv Интерактивный PTY
Ввод argv при запуске Ввод PTY (write / браузер connect)
Остановка kill(signal?) interrupt() / terminate()
Поиск getProcess / listProcesses getTerminal / listTerminals

Оба используют один и тот же правила времени жизни контейнера. Документация по терминалу: Терминалы. API: Terminals API.

Логи и большой объем выводимых данных

output() буферизует stdout и stderr и может задавать truncated: true. Предпочитайте logs() когда вывод может быть большим или процесс работает дольше одного запроса Worker.

const stream = await process.logs({ follow: true, replay: true });
// each data/terminal event includes a cursor — store the latest
const stream = await process.logs({ follow: true, replay: true });
// each data/terminal event includes a cursor — store the latest

При последующем запросе к тот же ещё работающий контейнер, вызовите getProcess(id) и возобновите работу с logs({ since: cursor, replay: true, follow: true }). После запуска нового контейнера для песочницы запустите новый процесс: старый курсор больше не действителен.

Структуры событий, параметры ожидания и проверки готовности: API процессов.