From d98d29a3e3651279b64f448260a4e8b945bacdb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=90=E8=8F=93?= Date: Mon, 24 Aug 2026 16:24:31 +0800 Subject: [PATCH 1/3] fix(lifecycle): align SDK validation and hook coverage --- components/execd/tests/init_container.sh | 70 +++++++++- docs/.vitepress/config.mts | 1 + docs/guides/lifecycle-hooks.md | 90 +++++++++++++ .../images/lifecycle-hooks-startup.drawio | 121 ++++++++++++++++++ .../public/images/lifecycle-hooks-startup.png | Bin 0 -> 67847 bytes docs/sdks/csharp.md | 34 +++++ docs/sdks/go.md | 27 ++++ docs/sdks/javascript.md | 27 ++++ docs/sdks/kotlin.md | 31 +++++ docs/sdks/python.md | 33 +++++ .../models/sandboxes/LifecycleModels.kt | 8 -- .../models/SandboxLifecycleModelsTest.kt | 15 +-- .../src/opensandbox/models/sandboxes.py | 10 +- .../python/tests/test_models_stability.py | 20 +-- 14 files changed, 453 insertions(+), 34 deletions(-) create mode 100644 docs/guides/lifecycle-hooks.md create mode 100644 docs/public/images/lifecycle-hooks-startup.drawio create mode 100644 docs/public/images/lifecycle-hooks-startup.png diff --git a/components/execd/tests/init_container.sh b/components/execd/tests/init_container.sh index 626fd2d54..53722b1e1 100755 --- a/components/execd/tests/init_container.sh +++ b/components/execd/tests/init_container.sh @@ -24,6 +24,8 @@ # (JUPYTER_TOKEN) while execd's credential (EXECD_ACCESS_TOKEN) is # stripped by the launcher # - on a non-PID-1 topology execd degrades to subreaper and says so +# - while preStart is blocked, /ping stays ready and both the entrypoint +# and periodic hooks wait; lifecycle transport is stripped from user code # # Exit-code propagation, runtime SIGTERM forwarding and the rest of the # hardening floor are covered by the Python e2e suites @@ -87,7 +89,7 @@ wait_file() { dump_container_logs() { local c="$1" echo ">> Container logs for ${c}:" - docker logs "$c" 2>&1 | grep -E "launcher|landlock|FAIL|init:|hardening|exited" | tail -30 || true + docker logs "$c" 2>&1 | grep -E "launcher|landlock|FAIL|init:|hardening|exited|lifecycle|error:" | tail -30 || true } echo "=========================================" @@ -258,6 +260,70 @@ grep -q "subreaper_ok=yes" "${TESTDIR}/subreaper.out" || fail "test 3: subreaper docker rm -f "$C3" >/dev/null echo "PASS: subreaper degradation reported" +# ------------------------------------------------------------------- +# Test 4: init mode runs lifecycle hooks before and alongside workload. +# ------------------------------------------------------------------- +echo "" +echo ">> Test 4: init-mode lifecycle hooks" + +cat > "${TESTDIR}/lifecycle.sh" <<'SCRIPT' +#!/bin/sh +set -eu +out=/mnt/test/lifecycle.out +touch /mnt/test/entrypoint.started +[ "$(cat /proc/1/comm)" = "execd" ] || { echo "FAIL: execd is not PID 1" > "$out"; exit 98; } +[ -f /mnt/test/prestart.done ] || { echo "FAIL: preStart did not finish before entrypoint" > "$out"; exit 99; } +[ -z "${OPENSANDBOX_LIFECYCLE:-}" ] || { echo "FAIL: lifecycle transport leaked" > "$out"; exit 100; } + +i=0 +while [ ! -f /mnt/test/periodic.twice ] && [ "$i" -lt 100 ]; do + sleep 0.2 + i=$((i+1)) +done +[ -f /mnt/test/periodic.twice ] \ + || { echo "FAIL: periodic hook did not run twice" > "$out"; exit 101; } +printf 'lifecycle_hooks_ok=yes\n' > "$out" +exit 0 +SCRIPT +chmod +x "${TESTDIR}/lifecycle.sh" + +C4="${PREFIX}-t4" +RUNNERS+=("$C4") +docker run -d --name "$C4" \ + --entrypoint /bootstrap.sh \ + -e EXECD=/execd \ + -e EXECD_INIT=1 \ + -e 'OPENSANDBOX_LIFECYCLE={"preStart":{"command":["/bin/sh","-c","touch /mnt/test/prestart.started; while [ ! -f /mnt/test/prestart.release ]; do sleep 0.1; done; touch /mnt/test/prestart.done"],"timeoutSeconds":300},"periodic":[{"name":"checkpoint","schedule":"@every 1s","command":["/bin/sh","-c","touch /mnt/test/periodic.started; echo periodic >> /mnt/test/periodic.sequence; test $(grep -c periodic /mnt/test/periodic.sequence) -ge 2 && touch /mnt/test/periodic.twice || true"]}]}' \ + -v "${TESTDIR}:/mnt/test" \ + "${IMAGE}" \ + /mnt/test/lifecycle.sh >/dev/null +if ! wait_file "${TESTDIR}/prestart.started"; then + dump_container_logs "$C4" + fail "test 4: preStart did not reach the startup barrier" +fi +if ! docker exec "$C4" /bin/sh -c \ + 'i=0; while [ "$i" -lt 30 ]; do wget -qO- http://127.0.0.1:44772/ping >/dev/null && exit 0; sleep 0.5; i=$((i+1)); done; exit 1'; then + dump_container_logs "$C4" + fail "test 4: execd /ping was unavailable while preStart was blocked" +fi +# Keep the barrier closed across at least one @every 1s tick. +sleep 2 +[ ! -f "${TESTDIR}/entrypoint.started" ] \ + || fail "test 4: entrypoint started before preStart was released" +[ ! -f "${TESTDIR}/periodic.started" ] \ + || fail "test 4: periodic hook started before preStart was released" +touch "${TESTDIR}/prestart.release" +if ! wait_file "${TESTDIR}/lifecycle.out"; then + dump_container_logs "$C4" + fail "test 4: container did not produce lifecycle.out" +fi +RC=$(docker wait "$C4") +[ "$RC" = "0" ] || fail "test 4: container exited $RC: $(cat "${TESTDIR}/lifecycle.out")" +grep -q "lifecycle_hooks_ok=yes" "${TESTDIR}/lifecycle.out" \ + || fail "test 4: lifecycle assertions failed: $(cat "${TESTDIR}/lifecycle.out")" +docker rm -f "$C4" >/dev/null +echo "PASS: init-mode health availability, preStart ordering, periodic execution, and environment isolation" + # ------------------------------------------------------------------- echo "" echo "=========================================" @@ -265,4 +331,4 @@ echo " Init-mode container regression PASSED" echo "=========================================" echo " image: ${IMAGE}" echo " cases: pid1 handoff / reaping / signal shield /" -echo " env inheritance / subreaper" +echo " env inheritance / subreaper / lifecycle hooks" diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 86095b8c9..6f89e6def 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -96,6 +96,7 @@ export default defineConfig({ { text: "Multi-Tenancy", link: "/guides/multi-tenancy" }, { text: "Isolation Sessions", link: "/guides/isolation-sessions" }, { text: "Pause & Resume", link: "/guides/pause-resume" }, + { text: "Lifecycle Hooks", link: "/guides/lifecycle-hooks" }, { text: "Windows Sandbox", link: "/guides/windows-sandbox" }, { text: "Client Pool", link: "/guides/client-pool" }, { text: "SDK Telemetry", link: "/guides/sdk-telemetry" }, diff --git a/docs/guides/lifecycle-hooks.md b/docs/guides/lifecycle-hooks.md new file mode 100644 index 000000000..c094cd5d0 --- /dev/null +++ b/docs/guides/lifecycle-hooks.md @@ -0,0 +1,90 @@ +--- +title: Lifecycle Hooks +description: Run setup work before a sandbox entrypoint and schedule recurring work while it is running. +--- + +# Lifecycle Hooks + +Lifecycle hooks let a sandbox run declarative commands at defined points in its runtime. OpenSandbox currently supports a blocking `preStart` hook and one or more non-blocking `periodic` hooks. + +## Supported hooks + +| Hook | Cardinality | When it runs | Failure behavior | +|---|---:|---|---| +| `preStart` | Zero or one | After execd starts listening, before the user entrypoint starts | A failure or timeout prevents the user entrypoint from starting | +| `periodic` | Zero or more | The scheduler starts after `preStart` succeeds and runs each hook on its schedule | A failed or timed-out run is logged and later runs normally continue; if a timed-out process cannot be terminated, that hook is disabled | + +Both hooks run inside the sandbox with the sandbox environment. Commands are argument arrays and are executed directly. To use shell syntax such as pipes, redirects, or variable expansion, invoke a shell explicitly, for example `['sh', '-c', 'command > file']`. + +![Lifecycle hook timing in bootstrap and execd-as-init modes](/images/lifecycle-hooks-startup.png) + +The ordering is the same in both startup modes: + +1. execd starts and its HTTP server begins listening. +2. `preStart` runs to completion, if configured. +3. The periodic scheduler starts, if configured. +4. The user entrypoint starts. + +In execd-as-init mode, execd remains PID 1 and starts the entrypoint as its supervised child. In bootstrap mode, the bootstrap process starts the entrypoint after execd reports that lifecycle startup completed. + +## Configuration + +Lifecycle hooks are part of the sandbox creation request: + +```json +{ + "image": { "uri": "ubuntu:24.04" }, + "entrypoint": ["tail", "-f", "/dev/null"], + "resourceLimits": { + "cpu": "1", + "memory": "1Gi" + }, + "lifecycle": { + "preStart": { + "command": ["sh", "-c", "/opt/hooks/restore.sh"], + "timeoutSeconds": 120 + }, + "periodic": [ + { + "name": "checkpoint", + "schedule": "@every 5m", + "command": ["sh", "-c", "/opt/hooks/checkpoint.sh"], + "timeoutSeconds": 120 + } + ] + } +} +``` + +### `preStart` + +| Field | Required | Description | +|---|---:|---| +| `command` | Yes | Non-empty command and argument array | +| `timeoutSeconds` | No | Server-validated timeout from 1 through 300 seconds; defaults to 60 seconds | + +`preStart` runs on each container start. Make the command idempotent so retrying or restarting a sandbox does not corrupt its state. + +### `periodic` + +| Field | Required | Description | +|---|---:|---| +| `name` | Yes | Non-blank name, unique within the sandbox | +| `schedule` | Yes | Standard five-field cron expression or descriptor such as `@hourly` or `@every 30s` | +| `command` | Yes | Non-empty command and argument array | +| `timeoutSeconds` | No | Server-validated timeout from 1 through 300 seconds; defaults to 60 seconds | + +An `@every` interval must be a whole number of seconds and at least one second. Runs of the same named hook never overlap: if the previous run is still active, the next scheduled run is skipped. + +## Current availability + +Lifecycle hooks currently require the Kubernetes provider. They are rejected by the Docker provider and Fleets backend. A request cannot combine `lifecycle` with `poolRef`. + +The SDKs expose lifecycle fields on their sandbox creation APIs but do not enforce the timeout range. The Server is the authority for request validation. See the language-specific examples in the [SDK documentation](/sdks/). + +## Operational guidance + +- Keep `preStart` bounded and deterministic because the entrypoint waits for it. +- Make periodic work idempotent and safe to retry. +- Store credentials through the platform's supported secret mechanism rather than embedding them in commands. +- Write data that must survive node replacement to durable storage. Lifecycle hooks schedule recovery and synchronization work; they do not make the sandbox filesystem durable by themselves. diff --git a/docs/public/images/lifecycle-hooks-startup.drawio b/docs/public/images/lifecycle-hooks-startup.drawio new file mode 100644 index 000000000..4cd6c6f15 --- /dev/null +++ b/docs/public/images/lifecycle-hooks-startup.drawio @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/lifecycle-hooks-startup.png b/docs/public/images/lifecycle-hooks-startup.png new file mode 100644 index 0000000000000000000000000000000000000000..bc07b4e84841af9d5fb077d9935a8bedd485f576 GIT binary patch literal 67847 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, @@ -43,9 +41,6 @@ class LifecycleHook private constructor( fun command(vararg command: String): Builder = command(command.toList()) fun timeoutSeconds(timeoutSeconds: Int): Builder { - require(timeoutSeconds in 1..MAX_LIFECYCLE_HOOK_TIMEOUT_SECONDS) { - "Lifecycle hook timeoutSeconds must be between 1 and $MAX_LIFECYCLE_HOOK_TIMEOUT_SECONDS" - } this.timeoutSeconds = timeoutSeconds return this } @@ -98,9 +93,6 @@ class PeriodicLifecycleHook private constructor( fun command(vararg command: String): Builder = command(command.toList()) fun timeoutSeconds(timeoutSeconds: Int): Builder { - require(timeoutSeconds in 1..MAX_LIFECYCLE_HOOK_TIMEOUT_SECONDS) { - "Periodic lifecycle hook timeoutSeconds must be between 1 and $MAX_LIFECYCLE_HOOK_TIMEOUT_SECONDS" - } this.timeoutSeconds = timeoutSeconds return this } diff --git a/sdks/sandbox/kotlin/sandbox/src/test/kotlin/com/alibaba/opensandbox/sandbox/domain/models/SandboxLifecycleModelsTest.kt b/sdks/sandbox/kotlin/sandbox/src/test/kotlin/com/alibaba/opensandbox/sandbox/domain/models/SandboxLifecycleModelsTest.kt index 7d8a0e72b..037185f81 100644 --- a/sdks/sandbox/kotlin/sandbox/src/test/kotlin/com/alibaba/opensandbox/sandbox/domain/models/SandboxLifecycleModelsTest.kt +++ b/sdks/sandbox/kotlin/sandbox/src/test/kotlin/com/alibaba/opensandbox/sandbox/domain/models/SandboxLifecycleModelsTest.kt @@ -33,19 +33,18 @@ import com.alibaba.opensandbox.sandbox.domain.models.sandboxes.SandboxLifecycle class SandboxLifecycleModelsTest { @Test - fun `stable lifecycle builders reject timeout above maximum`() { - assertEquals(300, DomainLifecycleHook.builder().command("true").timeoutSeconds(300).build().timeoutSeconds) - - assertThrows(IllegalArgumentException::class.java) { - DomainLifecycleHook.builder().command("true").timeoutSeconds(301) - } - assertThrows(IllegalArgumentException::class.java) { + fun `stable lifecycle builders preserve timeout for server validation`() { + assertEquals(0, DomainLifecycleHook.builder().command("true").timeoutSeconds(0).build().timeoutSeconds) + assertEquals( + 301, DomainPeriodicLifecycleHook.builder() .name("sync") .schedule("@hourly") .command("true") .timeoutSeconds(301) - } + .build() + .timeoutSeconds, + ) } @Test diff --git a/sdks/sandbox/python/src/opensandbox/models/sandboxes.py b/sdks/sandbox/python/src/opensandbox/models/sandboxes.py index 801d1bf5c..975d457c6 100644 --- a/sdks/sandbox/python/src/opensandbox/models/sandboxes.py +++ b/sdks/sandbox/python/src/opensandbox/models/sandboxes.py @@ -25,8 +25,6 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator -_MAX_LIFECYCLE_HOOK_TIMEOUT_SECONDS = 300 - class SandboxImageAuth(BaseModel): """ @@ -168,9 +166,7 @@ class LifecycleHook(BaseModel): timeout_seconds: int | None = Field( default=None, alias="timeoutSeconds", - ge=1, - le=_MAX_LIFECYCLE_HOOK_TIMEOUT_SECONDS, - description="Maximum execution time in seconds, up to 300. The server defaults to 60.", + description="Maximum execution time in seconds. The server validates the value and defaults to 60.", ) @field_validator("command") @@ -192,9 +188,7 @@ class PeriodicLifecycleHook(BaseModel): timeout_seconds: int | None = Field( default=None, alias="timeoutSeconds", - ge=1, - le=_MAX_LIFECYCLE_HOOK_TIMEOUT_SECONDS, - description="Maximum execution time in seconds, up to 300. The server defaults to 60.", + description="Maximum execution time in seconds. The server validates the value and defaults to 60.", ) @field_validator("name", "schedule") diff --git a/sdks/sandbox/python/tests/test_models_stability.py b/sdks/sandbox/python/tests/test_models_stability.py index 73c0d2b78..f9ba2114c 100644 --- a/sdks/sandbox/python/tests/test_models_stability.py +++ b/sdks/sandbox/python/tests/test_models_stability.py @@ -69,17 +69,21 @@ ) -@pytest.mark.parametrize("hook_type", [DomainLifecycleHook, DomainPeriodicLifecycleHook]) -def test_lifecycle_hooks_reject_timeout_above_maximum(hook_type: type) -> None: - kwargs: dict[str, object] = {"command": ["true"], "timeoutSeconds": 300} +@pytest.mark.parametrize( + "hook_type", [DomainLifecycleHook, DomainPeriodicLifecycleHook] +) +@pytest.mark.parametrize("timeout_seconds", [0, 301]) +def test_lifecycle_hooks_preserve_timeout_for_server_validation( + hook_type: type, timeout_seconds: int +) -> None: + kwargs: dict[str, object] = { + "command": ["true"], + "timeoutSeconds": timeout_seconds, + } if hook_type is DomainPeriodicLifecycleHook: kwargs.update(name="sync", schedule="@hourly") - assert hook_type.model_validate(kwargs).timeout_seconds == 300 - - kwargs["timeoutSeconds"] = 301 - with pytest.raises(ValueError, match="less than or equal to 300"): - hook_type.model_validate(kwargs) + assert hook_type.model_validate(kwargs).timeout_seconds == timeout_seconds def test_lifecycle_hooks_reject_blank_commands_and_normalize_periodic_text() -> None: From 7b9b4c0c9f4ea2258507f0ea85e4314ecede2fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=90=E8=8F=93?= Date: Mon, 24 Aug 2026 16:46:57 +0800 Subject: [PATCH 2/3] docs(lifecycle): make hook examples runnable --- docs/guides/lifecycle-hooks.md | 8 ++++---- docs/sdks/csharp.md | 4 ++-- docs/sdks/go.md | 4 ++-- docs/sdks/javascript.md | 4 ++-- docs/sdks/kotlin.md | 4 ++-- docs/sdks/python.md | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/guides/lifecycle-hooks.md b/docs/guides/lifecycle-hooks.md index c094cd5d0..f5614f0db 100644 --- a/docs/guides/lifecycle-hooks.md +++ b/docs/guides/lifecycle-hooks.md @@ -41,14 +41,14 @@ Lifecycle hooks are part of the sandbox creation request: }, "lifecycle": { "preStart": { - "command": ["sh", "-c", "/opt/hooks/restore.sh"], + "command": ["sh", "-c", "echo ready > /tmp/prestart.done"], "timeoutSeconds": 120 }, "periodic": [ { "name": "checkpoint", "schedule": "@every 5m", - "command": ["sh", "-c", "/opt/hooks/checkpoint.sh"], + "command": ["sh", "-c", "date -u >> /tmp/checkpoints.log"], "timeoutSeconds": 120 } ] @@ -61,7 +61,7 @@ Lifecycle hooks are part of the sandbox creation request: | Field | Required | Description | |---|---:|---| | `command` | Yes | Non-empty command and argument array | -| `timeoutSeconds` | No | Server-validated timeout from 1 through 300 seconds; defaults to 60 seconds | +| `timeoutSeconds` | No | When omitted, defaults to 60 seconds; explicit values must be from 1 through 300 seconds | `preStart` runs on each container start. Make the command idempotent so retrying or restarting a sandbox does not corrupt its state. @@ -72,7 +72,7 @@ Lifecycle hooks are part of the sandbox creation request: | `name` | Yes | Non-blank name, unique within the sandbox | | `schedule` | Yes | Standard five-field cron expression or descriptor such as `@hourly` or `@every 30s` | | `command` | Yes | Non-empty command and argument array | -| `timeoutSeconds` | No | Server-validated timeout from 1 through 300 seconds; defaults to 60 seconds | +| `timeoutSeconds` | No | When omitted, defaults to 60 seconds; explicit values must be from 1 through 300 seconds | An `@every` interval must be a whole number of seconds and at least one second. Runs of the same named hook never overlap: if the previous run is still active, the next scheduled run is skipped. diff --git a/docs/sdks/csharp.md b/docs/sdks/csharp.md index f6db9ae04..e54c75e60 100644 --- a/docs/sdks/csharp.md +++ b/docs/sdks/csharp.md @@ -79,7 +79,7 @@ await using var sandbox = await Sandbox.CreateAsync(new SandboxCreateOptions { PreStart = new LifecycleHook { - Command = new[] { "sh", "-c", "/opt/hooks/restore.sh" }, + Command = new[] { "sh", "-c", "echo ready > /tmp/prestart.done" }, TimeoutSeconds = 120, }, Periodic = new[] @@ -88,7 +88,7 @@ await using var sandbox = await Sandbox.CreateAsync(new SandboxCreateOptions { Name = "checkpoint", Schedule = "@every 5m", - Command = new[] { "sh", "-c", "/opt/hooks/checkpoint.sh" }, + Command = new[] { "sh", "-c", "date -u >> /tmp/checkpoints.log" }, TimeoutSeconds = 120, }, }, diff --git a/docs/sdks/go.md b/docs/sdks/go.md index a00997d6d..bcf91cca3 100644 --- a/docs/sdks/go.md +++ b/docs/sdks/go.md @@ -313,14 +313,14 @@ sandbox, err := opensandbox.CreateSandbox(ctx, config, opensandbox.SandboxCreate Image: "ubuntu:24.04", Lifecycle: &opensandbox.SandboxLifecycle{ PreStart: &opensandbox.LifecycleHook{ - Command: []string{"sh", "-c", "/opt/hooks/restore.sh"}, + Command: []string{"sh", "-c", "echo ready > /tmp/prestart.done"}, TimeoutSeconds: &hookTimeout, }, Periodic: []opensandbox.PeriodicLifecycleHook{ { Name: "checkpoint", Schedule: "@every 5m", - Command: []string{"sh", "-c", "/opt/hooks/checkpoint.sh"}, + Command: []string{"sh", "-c", "date -u >> /tmp/checkpoints.log"}, TimeoutSeconds: &hookTimeout, }, }, diff --git a/docs/sdks/javascript.md b/docs/sdks/javascript.md index bbd7de630..bdcdd824e 100644 --- a/docs/sdks/javascript.md +++ b/docs/sdks/javascript.md @@ -80,14 +80,14 @@ const sandbox = await Sandbox.create({ image: "ubuntu:24.04", lifecycle: { preStart: { - command: ["sh", "-c", "/opt/hooks/restore.sh"], + command: ["sh", "-c", "echo ready > /tmp/prestart.done"], timeoutSeconds: 120, }, periodic: [ { name: "checkpoint", schedule: "@every 5m", - command: ["sh", "-c", "/opt/hooks/checkpoint.sh"], + command: ["sh", "-c", "date -u >> /tmp/checkpoints.log"], timeoutSeconds: 120, }, ], diff --git a/docs/sdks/kotlin.md b/docs/sdks/kotlin.md index 4b01a3701..1faea4a8e 100644 --- a/docs/sdks/kotlin.md +++ b/docs/sdks/kotlin.md @@ -88,13 +88,13 @@ import com.alibaba.opensandbox.sandbox.domain.models.sandboxes.SandboxLifecycle; SandboxLifecycle lifecycle = SandboxLifecycle.builder() .preStart(LifecycleHook.builder() - .command("sh", "-c", "/opt/hooks/restore.sh") + .command("sh", "-c", "echo ready > /tmp/prestart.done") .timeoutSeconds(120) .build()) .periodic(PeriodicLifecycleHook.builder() .name("checkpoint") .schedule("@every 5m") - .command("sh", "-c", "/opt/hooks/checkpoint.sh") + .command("sh", "-c", "date -u >> /tmp/checkpoints.log") .timeoutSeconds(120) .build()) .build(); diff --git a/docs/sdks/python.md b/docs/sdks/python.md index 703b914b0..64d328e33 100644 --- a/docs/sdks/python.md +++ b/docs/sdks/python.md @@ -273,14 +273,14 @@ sandbox = await Sandbox.create( connection_config=config, lifecycle=SandboxLifecycle( pre_start=LifecycleHook( - command=["sh", "-c", "/opt/hooks/restore.sh"], + command=["sh", "-c", "echo ready > /tmp/prestart.done"], timeout_seconds=120, ), periodic=[ PeriodicLifecycleHook( name="checkpoint", schedule="@every 5m", - command=["sh", "-c", "/opt/hooks/checkpoint.sh"], + command=["sh", "-c", "date -u >> /tmp/checkpoints.log"], timeout_seconds=120, ) ], From 0bb3fec4eb730c5bc7c69ace63612ae3b18321f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=90=E8=8F=93?= Date: Mon, 24 Aug 2026 17:04:40 +0800 Subject: [PATCH 3/3] test(execd): clarify lifecycle barrier comment --- components/execd/tests/init_container.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/execd/tests/init_container.sh b/components/execd/tests/init_container.sh index 53722b1e1..3b83405d3 100755 --- a/components/execd/tests/init_container.sh +++ b/components/execd/tests/init_container.sh @@ -306,7 +306,7 @@ if ! docker exec "$C4" /bin/sh -c \ dump_container_logs "$C4" fail "test 4: execd /ping was unavailable while preStart was blocked" fi -# Keep the barrier closed across at least one @every 1s tick. +# Keep the barrier closed long enough to observe an incorrectly started entrypoint or periodic hook. sleep 2 [ ! -f "${TESTDIR}/entrypoint.started" ] \ || fail "test 4: entrypoint started before preStart was released"