nemo_gym.sandbox.providers.enroot

View as Markdown

Enroot provider package.

Submodules

Package Contents

Classes

NameDescription
EnrootCreateConfigSettings for creating an Enroot sandbox container.
EnrootCreateErrorRaised when Enroot cannot create a sandbox.
EnrootCreateVerificationErrorRaised when a newly-created sandbox cannot execute a probe command.
EnrootExecConfigSettings for running commands inside an Enroot sandbox.
EnrootProbeConfigPost-create probe settings: a test command confirming the sandbox is usable.
EnrootProviderSandbox provider backed by the local Enroot CLI.

API

class nemo_gym.sandbox.providers.enroot.EnrootCreateConfig(
mount_point: str = DEFAULT_MOUNT_POINT,
base_dir: str | None = None,
data_path: str | None = None,
cache_path: str | None = None,
runtime_path: str | None = None,
sqsh_cache_dir: str | None = None,
rw: bool = True,
remap_root: bool = False,
bypass_entrypoint: bool = True,
init_command: str = DEFAULT_INIT_COMMAND,
import_timeout_s: float | None = 1800,
create_timeout_s: float | None = 600,
start_timeout_s: float | None = 600,
start_poll_s: float = 0.5,
extra_import_args: list[str] = list(),
extra_create_args: list[str] = list(),
extra_start_args: list[str] = list()
)
Dataclass

Settings for creating an Enroot sandbox container.

base_dir
str | None = None
bypass_entrypoint
bool = True
cache_path
str | None = None
create_timeout_s
float | None = 600
data_path
str | None = None
extra_create_args
list[str] = field(default_factory=list)
extra_import_args
list[str] = field(default_factory=list)
extra_start_args
list[str] = field(default_factory=list)
import_timeout_s
float | None = 1800
init_command
str = DEFAULT_INIT_COMMAND
mount_point
str = DEFAULT_MOUNT_POINT
remap_root
bool = False
runtime_path
str | None = None
rw
bool = True
sqsh_cache_dir
str | None = None
start_poll_s
float = 0.5
start_timeout_s
float | None = 600
nemo_gym.sandbox.providers.enroot.EnrootCreateConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.enroot.EnrootCreateError()

Bases: SandboxCreateError

Raised when Enroot cannot create a sandbox.

class nemo_gym.sandbox.providers.enroot.EnrootCreateVerificationError()

Bases: SandboxCreateVerificationError

Raised when a newly-created sandbox cannot execute a probe command.

class nemo_gym.sandbox.providers.enroot.EnrootExecConfig(
default_timeout_s: float | None = 180,
default_mounts: list[str] = list(),
extra_exec_args: list[str] = list(),
concurrency: int = 32,
exec_shell: str = 'sh'
)
Dataclass

Settings for running commands inside an Enroot sandbox.

concurrency
int = 32
default_mounts
list[str] = field(default_factory=list)
default_timeout_s
float | None = 180
exec_shell
str = 'sh'
extra_exec_args
list[str] = field(default_factory=list)
nemo_gym.sandbox.providers.enroot.EnrootExecConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.enroot.EnrootProbeConfig(
command: str | None = READY_PROBE_COMMAND,
expected_stdout: str | None = READY_PROBE_EXPECTED,
timeout_s: int = 30,
deadline_s: float | None = None,
stable_count: int = 1,
stable_delay_s: float = 0.0
)
Dataclass

Post-create probe settings: a test command confirming the sandbox is usable.

command
str | None = READY_PROBE_COMMAND
deadline_s
float | None = None
expected_stdout
str | None = READY_PROBE_EXPECTED
stable_count
int = 1
stable_delay_s
float = 0.0
timeout_s
int = 30
nemo_gym.sandbox.providers.enroot.EnrootProbeConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.enroot.EnrootProvider(
exec: nemo_gym.sandbox.providers.enroot.provider.EnrootExecConfig | collections.abc.Mapping[str, typing.Any] | None = None,
create: nemo_gym.sandbox.providers.enroot.provider.EnrootCreateConfig | collections.abc.Mapping[str, typing.Any] | None = None,
probe: nemo_gym.sandbox.providers.enroot.provider.EnrootProbeConfig | collections.abc.Mapping[str, typing.Any] | None = None
)

Sandbox provider backed by the local Enroot CLI.

_binary
= _require_enroot()
_create_config
= _coerce_config(create, EnrootCreateConfig)
_enroot_env
_exec_config
= _coerce_config(exec, EnrootExecConfig)
_import_locks
dict[str, Lock] = {}
_probe
= _coerce_config(probe, EnrootProbeConfig)
_semaphore
= asyncio.Semaphore(self._exec_config.concurrency)
_sqsh_cache_dir
= Path(cfg.sqsh_cache_dir or base / 'sqsh')
name
= 'enroot'
nemo_gym.sandbox.providers.enroot.EnrootProvider._await_container_pid(
err_f: typing.IO[bytes]
) -> int
async

Poll enroot list until the container’s init PID appears, or raise.

nemo_gym.sandbox.providers.enroot.EnrootProvider._cleanup_failed_create_handle(
) -> None
async

Best-effort teardown of a sandbox that failed to start or verify.

nemo_gym.sandbox.providers.enroot.EnrootProvider._ensure_image(
image: str
) -> pathlib.Path
async

Return a local squashfs path for image, importing (and caching) if needed.

nemo_gym.sandbox.providers.enroot.EnrootProvider._kill_start_group(
) -> None

Best-effort SIGTERM then SIGKILL of the detached start’s process group.

nemo_gym.sandbox.providers.enroot.EnrootProvider._lookup_container(
name: str
) -> tuple[int | None, bool]
async

Return (pid, present). pid is the running init PID or None; present is whether the rootfs exists at all (running or not).

nemo_gym.sandbox.providers.enroot.EnrootProvider._pid_has_container_marker(
pid: int,
name: str
) -> bool
staticmethod

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.

nemo_gym.sandbox.providers.enroot.EnrootProvider._read_temp(
handle: typing.IO[bytes]
) -> str
staticmethod
nemo_gym.sandbox.providers.enroot.EnrootProvider._resolve_image(
image: str
) -> tuple[str | None, pathlib.Path | None]

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.

nemo_gym.sandbox.providers.enroot.EnrootProvider._run(
argv: list[str],
timeout_s: float | None,
stdin: bytes | None = None
) -> tuple[int, str, str]
async

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.

nemo_gym.sandbox.providers.enroot.EnrootProvider._start_detached(
argv: list[str]
) -> tuple[typing.Any, typing.IO[bytes], typing.IO[bytes]]
async

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.

nemo_gym.sandbox.providers.enroot.EnrootProvider._verify_created_handle(
) -> None
async

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_count consecutive times, or the deadline elapses.
nemo_gym.sandbox.providers.enroot.EnrootProvider.aclose() -> None
async

No provider-wide resources to close.

nemo_gym.sandbox.providers.enroot.EnrootProvider.close(
) -> None
async

Kill the container init, remove the rootfs, and clean up the staging dir.

nemo_gym.sandbox.providers.enroot.EnrootProvider.create(
async

Import/create the rootfs, launch a detached init, and return a ready handle.

nemo_gym.sandbox.providers.enroot.EnrootProvider.download_file(
source_path: str,
target_path: pathlib.Path
) -> None
async

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.

nemo_gym.sandbox.providers.enroot.EnrootProvider.exec(
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = None,
user: str | int | None = None,
stdin: bytes | None = None
async

Run a command inside the container via enroot exec &lt;pid&gt;.

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 su to switch to that user (requires root inside the container).

stdin, when given, is piped to the command’s standard input.

nemo_gym.sandbox.providers.enroot.EnrootProvider.status(
async

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.

nemo_gym.sandbox.providers.enroot.EnrootProvider.upload_file(
source_path: pathlib.Path,
target_path: str
) -> None
async

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.