nemo_gym.sandbox.providers.enroot.provider
nemo_gym.sandbox.providers.enroot.provider
Enroot provider implementation.
Enroot (https://github.com/NVIDIA/enroot) is an unprivileged container runtime.
Unlike Apptainer it has no persistent daemon/instance concept, so this provider
emulates a long-lived sandbox by launching a detached enroot start running a
sleeping init and then re-entering it with enroot exec <pid>. See README.md
for the enroot-specific design notes (non-daemonizing start, PID-based
liveness, #-registry URIs, pinned ENROOT_* paths).
Module Contents
Classes
Functions
Data
ENROOT_MISSING_CONTAINER_MARKERS
API
Settings for creating an Enroot sandbox container.
Bases: SandboxCreateError
Raised when Enroot cannot create a sandbox.
Bases: SandboxCreateVerificationError
Raised when a newly-created sandbox cannot execute a probe command.
Settings for running commands inside an Enroot sandbox.
Post-create probe settings: a test command confirming the sandbox is usable.
Sandbox provider backed by the local Enroot CLI.
Poll enroot list until the container’s init PID appears, or raise.
Best-effort teardown of a sandbox that failed to start or verify.
Return a local squashfs path for image, importing (and caching) if needed.
Best-effort SIGTERM then SIGKILL of the detached start’s process group.
Return (pid, present). pid is the running init PID or None; present is whether the rootfs exists at all (running or not).
Return True if /proc/<pid>/cmdline still carries the container’s unique marker.
This guards against PID reuse: if the init exits and Linux reuses the PID for an unrelated process, the container marker won’t appear in the new process’s cmdline, so exec() returns a sandbox error instead of joining the wrong namespace.
Return (import_uri, sqsh_path). Exactly one is non-None.
A local .sqsh file is used directly (no import). Anything else is
translated to an enroot import URI.
Run an enroot CLI command. Returns (return_code, stdout, stderr).
Enforces timeout via asyncio.wait_for and kills the whole process group on timeout so child processes do not linger. Bounds concurrency with a shared semaphore. Decodes output with errors=“replace”. Every call runs with the pinned ENROOT_* environment.
Launch the long-lived enroot start init without awaiting its exit.
enroot start does not daemonize — it stays in the foreground for the
whole container lifetime. We launch it detached in its own session
(start_new_session=True), capture output to temp files (so an early exit
leaves diagnosable stderr), and return the process handle plus the temp
files. The caller confirms readiness by polling enroot list.
Run the readiness probe until the sandbox responds, or raise.
- probe.command is None -> skip (no verification).
- probe.deadline_s is None -> single attempt; a failure raises immediately.
- probe.deadline_s is set -> poll until the sandbox passes the probe
stable_countconsecutive times, or the deadline elapses.
No provider-wide resources to close.
Kill the container init, remove the rootfs, and clean up the staging dir.
Import/create the rootfs, launch a detached init, and return a ready handle.
Download one sandbox file to the host.
Fast path (source under the bind mount): read directly from the host side of the shared folder. Fallback (arbitrary path): cp inside the container into the shared folder, then read the host side.
Run a command inside the container via enroot exec <pid>.
Maps the neutral user parameter onto enroot:
- None -> run as the default (launching) user, or root if the container was started with remap_root.
- “root” / 0 -> run directly (requires create.remap_root=true to be root inside the container).
- other user/uid -> wrap in
suto switch to that user (requires root inside the container).
stdin, when given, is piped to the command’s standard input.
Return the container’s lifecycle status via enroot list -f.
Liveness is keyed off the PID column, not name presence: enroot lists created-but-not-running rootfs with an empty PID. Running -> RUNNING; present without a PID, or absent -> STOPPED; timeout/parse error -> UNKNOWN.
Upload one host file into the sandbox.
Fast path (target under the bind mount): write directly to the host side of the shared folder. Fallback (arbitrary path): stage into the shared folder, then cp inside the container.
Provider-private state stashed on SandboxHandle.raw.
Normalize spec.provider_options['mounts'] into a list of enroot fstab entries.
Accepts a single "src:dst[:type:opts]" string or a list of them. These are
extra per-sandbox mounts, added on top of the staging mount and the
provider-level exec.default_mounts.
Find the container init PID by scanning /proc for its unique cmdline.
Nested inside pyxis, enroot runs as real root without a per-container user
namespace, so enroot list cannot map a PID to the container name. The init is
also reparented out of the enroot start process tree once the container’s PID
namespace is set up, so a tree walk misses it. Instead we tag each container’s init
with its unique name (... # <name>) and find the process whose cmdline is the
init: it starts with sh -c (the provider passes argv0=“sh”, so this holds even
where /bin/sh is dash) and carries both the base init loop and the unique marker.
Scanning all of /proc is reparent-proof; the marker keeps it unambiguous under
concurrency. The enroot start wrapper also carries the marker but its cmdline
starts with the enroot binary, not sh -c , so it is excluded.
Best-effort: did enroot itself fail to run the command (vs the command failing)?
Return /proc/<pid>/cmdline as a space-joined string, or ” if unreadable.
Return the enroot binary path or hard-error if it is not installed.
Map a neutral GPU request onto NVIDIA_VISIBLE_DEVICES for the enroot hook.
Translate a docker image reference into an enroot import URI.
Enroot’s docker scheme separates the registry host with # rather than
/ (docker://[USER@][REGISTRY#]IMAGE[:TAG]). A leading component that
looks like a registry host (contains a . or :, or is localhost)
is treated as the registry; otherwise the reference is a Docker Hub name.
The Docker Hub canonical hostnames are dropped rather than passed as the
REGISTRY fragment: the public registry API is served from
registry-1.docker.io, not the literal docker.io alias, so forwarding
docker://docker.io#repo points enroot at an endpoint that returns
non-JSON and the import fails. Dropping them lets enroot use its configured
Hub default (e.g. docker://swebench/foo:tag).