Skip to content

Expose container exit — box state, exit code, and resource cleanup #933

Description

@NavidMitchell

What we're building

We orchestrate boxlite micro VMs as managed workloads (long-running services and
run-to-completion jobs such as database migrations). For batch jobs the host needs to
know that the workload finished, whether it succeeded, and to release the VM's
resources when it does.

Current behavior (0.9.7, macOS arm64, Node SDK)

When a box's entrypoint exits, nothing observable happens at the box level:

  • The VM stays up indefinitely and runtime.getInfo(id) keeps reporting
    state.running: true / status: "running" — we polled for 60s past a 3-second
    entrypoint's exit with no transition.

  • The exit code is not exposed through the API (we can see an exit.previous file is
    written inside the box directory, so it appears to be recorded internally).

  • The only external signal that the workload ended is a subsequent exec failing:

    spawn_failed: Container init process exited — cannot exec.
    Original error: ... failed operation due to incompatible container status: `Stopped`
    

So a completed job is indistinguishable from a running one, its exit status is
unreachable, and the VM continues to hold its memory until the host calls stop().
Our current workaround is to poll with exec and treat that specific failure as
"completed" — which is fragile, loses the exit code, and keeps the VM alive in the
meantime.

What we'd like

When the container's init process exits:

  1. Transition the box state so getInfo/listInfo reflect it — e.g.
    status: "exited", running: false.
  2. Expose the exit code on the info object (e.g. state.exitCode) — it appears to
    already be recorded on disk.
  3. Release the VM's resources at that point (or offer an option to), so a finished
    job doesn't hold guest memory until the host notices.

A wait(idOrName)-style API resolving with the exit code would be a welcome
complement, but the state transition and exit code alone would unblock us.

Reproduction

import { SimpleBox, getJsBoxlite } from "@boxlite-ai/boxlite";

const runtime = getJsBoxlite().withDefaultConfig();
const box = new SimpleBox({
  image: "alpine:latest",
  name: "batch-demo",
  autoRemove: false,
  runtime,
  entrypoint: ["sh", "-c", "echo working && sleep 3"],
});
await box.getId();
const handle = await runtime.get("batch-demo");
await handle.start();

// entrypoint exits after ~3s; poll for a minute:
for (let i = 0; i < 60; i++) {
  await new Promise((r) => setTimeout(r, 1000));
  const info = await runtime.getInfo("batch-demo");
  console.log(info.state.status, info.state.running); // stays "running" true forever
}
await box.exec("true"); // -> spawn_failed ... container status: `Stopped`

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions