Skip to content

feat(p2): deferred-main-spawn — full box semantics for pooled sandboxes#22

Merged
ZhiXiao-Lin merged 4 commits into
mainfrom
feat/p2-deferred-main
Jun 11, 2026
Merged

feat(p2): deferred-main-spawn — full box semantics for pooled sandboxes#22
ZhiXiao-Lin merged 4 commits into
mainfrom
feat/p2-deferred-main

Conversation

@ZhiXiao-Lin

Copy link
Copy Markdown
Contributor

P2 of the CoW plan (design #21, now implemented). Gives a pre-warmed pool sandbox full box semantics — the command runs as the box's real container main (real exit code via /.a3s_exit_code + stdout/stderr in the json-file console logs) — instead of the keepalive+exec MVP's exec-stream output, with no cold boot.

Usage

a3s-box pool start --deferred --image alpine:latest --size 4 --socket /tmp/p.sock
a3s-box pool run --socket /tmp/p.sock -- sh -c 'echo hi; exit 7'   # exit 7; output in json-file logs

How

  • IDLE boot (BoxConfig.deferred_main / BOX_DEFERRED_MAIN=1): skip the boot spawn; PID 1 keeps waiting (the ECHILD-with-no-container case no longer exits early — that was the key prototype finding).
  • spawn-main control frame: bare (uses the boot-stashed command — run path) or carrying a command (the pool, which pre-warms before the command is known).
  • Safe + secure spawn: the deferred main is spawned via the exec server's build_command (identical seccomp / user / no-new-privs to a boot main — Command::spawn, not spawn_isolated's raw fork, so it's safe from the multi-threaded PID 1) with stdio overridden to inherit (→ console → json-file logs). The pid is CAS-published while MANAGED, then the supervision loop reaps it for the real exit code.
  • Pool: pool start --deferred boots IDLE VMs; VmManager::run_deferred_main sends spawn-main, waits for the main to exit, and reads the box's container.json split by stream. The boot auto-trigger fires only for the env-run path — pool VMs are driven explicitly per request.
  • Resource limits: free — they're VM-level (libkrun set_vm_config), so a deferred main shares the boot main's limits (no guest cgroup needed; ContainerCgroup is exec-path-only).

Verified (KVM)

Exit codes 7/3/0, stdout+stderr from the json-file logs, seccomp applied (Seccomp: 2), --user 1000→uid 1000 — all from a pre-warmed pool. Single + detached + 8-concurrent-exec (no fork deadlock). Tests: deferred_spec_json unit + test_real_pool_deferred_main host e2e (passes on KVM).

Roy Lin added 4 commits June 11, 2026 17:27
Deferred-main-spawn for IDLE-booted (pooled) sandboxes: full box semantics
(exit code + json-file console logs) without a cold boot.

- Guest: BOX_DEFERRED_MAIN=1 boots IDLE (skip boot spawn, stash BOX_EXEC_* cmd,
  CONTAINER_PID=-1; ECHILD-with-no-container keeps waiting instead of exiting).
  A spawn-main control frame runs the stashed cmd as the MAIN via the exec
  server's build_command (SAME seccomp/user/no-new-privs as a boot main, async-
  signal-safe pre_exec) with stdio overridden to inherit (→ console → json-file
  logs), under reaper::spawn_managed; pid CAS-published while MANAGED, then the
  supervision loop (reads the atomic each tick) reaps it for the real exit code.
- Host: spec.rs passes BOX_DEFERRED_MAIN to the guest; ExecClient::spawn_main
  sends the trigger; boot sends it post-readiness.

KVM-verified: (a) safe multi-threaded fork no deadlock (incl. 8 concurrent execs),
(b) stdout/stderr stream-tagged in container.json, (c) exit code via /.a3s_exit_code,
and security parity (seccomp + uid identical to a boot main; --user 1000 → uid 1000).

Remaining for production: cgroup join (resource limits), config field instead of
the env trigger, pool Request::SpawnMain, unit/e2e tests, docs.
`pool start --deferred` boots pooled VMs IDLE; `pool run` then spawns the per-
request command as the box's real MAIN via the spawn-main control frame (hybrid:
frame carries the command since the VM is pre-warmed before the command is known),
giving full box semantics — real exit code (/.a3s_exit_code) + json-file console
logs — vs the keepalive+exec MVP's piped exec-stream output.

- core: BoxConfig.deferred_main; spec.rs/mod.rs honor it (env override kept for
  tests). Guest spawn-main now accepts an optional command in the frame.
- runtime: VmManager::run_deferred_main (send spawn-main + wait for the main to
  exit + read the box's container.json split by stream); ExecClient::spawn_main
  takes an optional spec.
- cli: pool --deferred flag; handle_conn routes to run_deferred_main.
Unit: deferred_spec_json builds the spawn-main JSON (executable+args+PATH, shell
fallback). Host-backed e2e test_real_pool_deferred_main: pool start --deferred +
pool run asserts stdout/stderr come back from the box's json-file logs and the
real exit code propagates.
Update the P2 design doc to IMPLEMENTED with usage (pool start --deferred / pool
run) and what landed vs the design (incl. cgroup parity is free via VM-level
limits). Add a warm-pool + --deferred bullet to the README verified-behavior list.
@ZhiXiao-Lin ZhiXiao-Lin merged commit 3ad0e29 into main Jun 11, 2026
7 checks passed
@ZhiXiao-Lin ZhiXiao-Lin deleted the feat/p2-deferred-main branch June 11, 2026 10:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant