Files
wehub-resource-sync e0e362d700
SDK Tests / SDK CI (push) Blocked by required conditions
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00
..

opensandbox-supervisor

A lightweight process supervisor that wraps a single worker with restart backoff, lifecycle hooks, a crashloop circuit breaker, and a structured event log. Designed to run as a container ENTRYPOINT or as a child of another process; it does not assume PID 1 and performs no zombie reaping.

Usage

opensandbox-supervisor [flags] -- <worker-cmd> [worker-args...]

Everything after -- is the worker command. The supervisor starts the worker, monitors it, and restarts it on unexpected exits.

Example (egress sidecar)

ENTRYPOINT ["/opt/opensandbox-egress/supervisor", \
            "--pre-start=/opt/opensandbox-egress/cleanup.sh", \
            "--name=egress", \
            "--grace-period=20s", \
            "--", \
            "/opt/opensandbox-egress/egress"]

Flags

Flag Default Description
--pre-start (none) Executable to run before each worker launch (repeatable). No shell expansion; wrap in a script if needed.
--post-exit (none) Executable to run after each worker exit (repeatable). Receives WORKER_* env vars. Failures are logged, not fatal.
--event-log stderr Path to JSONL event log file. Supports rotation via lumberjack.
--backoff-min 1s Minimum restart backoff.
--backoff-max 30s Maximum restart backoff (exponential growth capped here).
--backoff-jitter 0.1 Jitter fraction (±10%). Set to 0 to disable.
--stable-after 60s Worker uptime after which backoff resets to minimum.
--burst-window 5m Sliding window for crashloop detection.
--burst-max 10 Maximum launches allowed within burst-window before the breaker trips.
--on-burst-exit true true: supervisor exits non-zero when burst budget trips (lets kubelet react). false: keep retrying indefinitely.
--grace-period 10s Time between SIGTERM and SIGKILL when shutting the worker down.
--pre-start-timeout 30s Timeout for each pre-start hook execution.
--post-exit-timeout 30s Timeout for each post-exit hook execution.
--name (basename of worker cmd) Worker name shown in logs and events.
--log-level info Supervisor diagnostic log level (debug|info|warn|error).

Restart Behavior

Exponential Backoff

When the worker exits unexpectedly, the supervisor sleeps before restarting:

1s → 2s → 4s → 8s → 16s → 30s → 30s → ...

Each delay is perturbed by ±backoff-jitter (default ±10%) to avoid thundering herds. After the worker has been alive at least stable-after (default 60 s), the backoff resets to backoff-min.

Crashloop Circuit Breaker

A sliding-window counter tracks launches. If more than burst-max (default 10) launches occur within burst-window (default 5 min), the supervisor either:

  • Exits non-zero (--on-burst-exit=true, default) — surfacing the crashloop via Kubernetes pod status instead of silently retrying.
  • Continues retrying (--on-burst-exit=false) — for environments without an outer restart supervisor.

Lifecycle Hooks

Pre-start hooks

Run before each worker launch. A non-zero exit aborts that launch attempt and counts toward the crashloop budget. Use for cleanup tasks like reaping orphaned child processes from a previous crash.

Post-exit hooks

Run after the worker has been reaped. Failures are logged but do not block the restart loop. Post-exit hooks run to completion even during shutdown (bounded by --post-exit-timeout) so cleanup paths are not aborted.

Post-exit hooks receive these environment variables:

Variable Description
WORKER_EXIT_CODE Worker's exit code (-1 if not available)
WORKER_SIGNAL Signal name if worker was signaled (e.g. terminated, killed)
WORKER_DURATION_MS Wall-clock worker runtime in milliseconds
WORKER_PID Worker's PID
WORKER_ATTEMPT Launch attempt number (1-based)

Graceful Shutdown

On context cancellation (typically from SIGTERM or SIGINT):

  1. Supervisor sends SIGTERM to the worker.
  2. Waits up to --grace-period for the worker to exit on its own.
  3. Sends SIGKILL if the worker does not exit in time.

Signal Handling

  • The supervisor does not install signal.Notify itself; the caller (e.g. cmd/supervisor/main.go) translates OS signals into context cancellation.
  • SIGINT and SIGTERM both result in SIGTERM to the worker.
  • Other signals (SIGHUP, SIGUSR1, etc.) are not forwarded. Add forwarding in the caller if the worker needs them.

Process Group Isolation

The worker is started with Setpgid=true on Unix so signals delivered to the supervisor's process group do not reach the worker by side channel. The supervisor signals the worker explicitly via its PID.

Structured Event Log

One JSONL record per lifecycle event, written to stderr by default or to the file specified by --event-log (with automatic rotation).

Event Kinds

Event When Key Fields
start Worker process launched pid, gen, attempt
exit Worker exited pid, gen, attempt, exit_code, signal, duration_ms, reason
prestart Pre-start hook ran hook, exit_code, duration_ms
postexit Post-exit hook ran hook, exit_code, duration_ms
backoff Sleeping before next restart sleep_ms, next_attempt
stable Worker uptime exceeded stable-after; backoff reset pid, gen, duration_ms, reset_backoff
burst_exit Crashloop budget exceeded attempts, window
shutdown Supervisor shutting down reason

Example Events

{"ts":"2026-01-15T10:30:00Z","name":"egress","event":"start","pid":42,"gen":1,"attempt":1}
{"ts":"2026-01-15T10:30:00.15Z","name":"egress","event":"exit","pid":42,"gen":1,"attempt":1,"exit_code":1,"duration_ms":150,"reason":"crashed"}
{"ts":"2026-01-15T10:30:00.15Z","name":"egress","event":"backoff","sleep_ms":1000,"next_attempt":2}
{"ts":"2026-01-15T10:30:01.15Z","name":"egress","event":"prestart","hook":"cleanup.sh","exit_code":0,"duration_ms":50}
{"ts":"2026-01-15T10:30:01.2Z","name":"egress","event":"start","pid":43,"gen":2,"attempt":2}

Exit Reasons

Reason Meaning
exited Worker exited with code 0
crashed Worker exited with non-zero code
signaled Worker killed by signal
shutdown Supervisor-initiated stop (context cancelled)
launch_failed Worker binary could not be started
no_processstate Unexpected: no process state available

Library Usage

The internal/supervisor package can be used programmatically:

import "github.com/alibaba/opensandbox/internal/supervisor"

spec := supervisor.Spec{
    Name:        "my-worker",
    Cmd:         "/usr/local/bin/worker",
    Args:        []string{"--config", "/etc/worker.toml"},
    PreStart:    []supervisor.Hook{{Argv: []string{"/usr/local/bin/cleanup.sh"}}},
    BackoffMin:  time.Second,
    BackoffMax:  30 * time.Second,
    GracePeriod: 15 * time.Second,
}

ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()

err := supervisor.Run(ctx, spec)

Run blocks until context cancellation or ErrBurstExceeded. Zero-valued fields receive sensible defaults (see Flags table above for values).