src/code_room/container.rs
//! **The container road** (dv2-b1-2, owner 2026-10-15: "have it build in
//! container/containers via draupnir"): every tool the Mímir consumer starts —
//! `cargo-mimir-dump` and the driver under it, `cargo metadata`,
//! `cargo --unit-graph`, `cargo clean`, `rustc --print` — runs in the pinned
//! toolchain IMAGE through draupnir's ONE OCI engine, never bare on the host.
//! A run boots ONE container of the image ([`BoxRoad`], 2026-10-17) and runs
//! every tool in it as its own exec (`ContainerControl::exec_streaming`);
//! when that container cannot boot, each tool gets a container of its own
//! (`BootSpec::container` → `ContainerBoot`, the road before).
//!
//! | setting | meaning |
//! |---|---|
//! | `NORNIR_MIMIR_CONTAINER_IMAGE` | the image (e.g. `nordisk-runa-toolchain:nightly-2026-10-05`): its nightly is the driver's, with clang + mold (the planner's linker set) |
//! | `NORNIR_MIMIR_CONTAINER_DRIVER` | a host dir holding `cargo-mimir-dump` + `mimir-driver` + `mimir-capture` BUILT FOR THE IMAGE (its libc, its nightly; `containers/mimir-driver/Containerfile` builds them); mounted at the same path |
//! | `NORNIR_MIMIR_CONTAINER_BINDS` | more host dirs to mount at the same path, comma-separated (the nornir home is always mounted: the code rooms' clones, the one target dir, the dumps) |
//!
//! Every mount keeps its absolute path, so a path a tool prints inside means
//! the same file outside. cargo's registry lives in
//! `<nornir home>/mimir-cargo-home` (a crate is downloaded once). The tool runs
//! as the server's own uid:gid on a rootful engine (what it writes is the
//! server's), as root on a rootless one (mapped to the invoking user).
//!
//! When the image is named and the road does not work — no engine socket, the
//! image missing, the driver not loading inside — the consumer says so in one
//! line and reads on the HOST road ([`super::driver::Settings::probe`]).
use std::collections::BTreeSet;
use std::os::unix::process::ExitStatusExt;
use std::path::PathBuf;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
use mimir_ingest::{ToolCall, ToolRunner};
pub const ENV_IMAGE: &str = "NORNIR_MIMIR_CONTAINER_IMAGE";
pub const ENV_DRIVER_DIR: &str = "NORNIR_MIMIR_CONTAINER_DRIVER";
pub const ENV_BINDS: &str = "NORNIR_MIMIR_CONTAINER_BINDS";
/// The road's settings.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContainerRoad {
pub image: String,
/// Host dir of the image-built driver pair (mounted).
pub driver_dir: PathBuf,
/// Host dirs mounted at the same path.
pub binds: Vec<PathBuf>,
/// cargo's home inside (and outside: it is under a mounted dir).
pub cargo_home: PathBuf,
}
impl ContainerRoad {
/// From the environment: `None` when no image is named (the host road).
pub fn from_env() -> Option<Self> {
let image = std::env::var(ENV_IMAGE).ok().map(|v| v.trim().to_string()).filter(|v| !v.is_empty())?;
let home = crate::config::nornir_home();
let driver_dir = std::env::var(ENV_DRIVER_DIR).ok().filter(|v| !v.trim().is_empty()).map(PathBuf::from).unwrap_or_else(|| home.join("mimir-driver-container"));
let mut binds = vec![home.clone()];
binds.extend(std::env::var(ENV_BINDS).unwrap_or_default().split(',').map(str::trim).filter(|b| !b.is_empty()).map(PathBuf::from));
binds.push(driver_dir.clone());
binds.dedup();
Some(ContainerRoad { image, driver_dir, binds, cargo_home: home.join("mimir-cargo-home") })
}
/// `cargo-mimir-dump` as the image sees it.
pub fn cargo_mimir_dump(&self) -> PathBuf {
self.driver_dir.join("cargo-mimir-dump")
}
/// The uid:gid a tool runs as on `engine_url` (dwarves' `work_user` rule).
pub fn user_for(engine_url: &str, status: &str) -> String {
let path = engine_url.strip_prefix("unix://").unwrap_or(engine_url);
if draupnir::container::is_user_scope_socket(std::path::Path::new(path)) {
return "0:0".to_string();
}
let id = |key: &str| status.lines().find_map(|l| l.strip_prefix(key)).and_then(|v| v.split_whitespace().next()).unwrap_or("0").to_string();
format!("{}:{}", id("Uid:"), id("Gid:"))
}
/// Where a tool run's exact stdout lands (under a mounted dir).
pub fn capture_file(&self, name: &str) -> PathBuf {
self.cargo_home.with_file_name("mimir-capture").join(format!("{name}.out"))
}
/// The draupnir spec of one tool run: the tool under `mimir-capture` (its
/// stdout byte for byte into [`Self::capture_file`]; the engine's log
/// frames cut long lines).
pub fn spec(&self, call: &ToolCall, name: String, user: &str) -> draupnir::BootSpec {
let mut argv = vec![self.driver_dir.join("mimir-capture").display().to_string(), self.capture_file(&name).display().to_string(), "--".to_string(), call.program.clone()];
argv.extend(call.args.iter().cloned());
let mut spec = draupnir::BootSpec::container(name, self.image.clone()).with_cmd(argv).with_user(user.to_string());
for b in &self.binds {
spec = spec.with_bind(format!("{0}:{0}", b.display()));
}
let home = self.cargo_home.display().to_string();
spec = spec.with_env("CARGO_HOME", home.clone()).with_env("HOME", home);
for (k, v) in road_env(std::env::var("CARGO_BUILD_JOBS").ok()) {
spec = spec.with_env(k, v);
}
for (k, v) in &call.envs {
spec = spec.with_env(k.clone(), v.clone());
}
if let Some(d) = &call.cwd {
spec = spec.with_working_dir(d.display().to_string());
}
spec
}
}
/// What every container this road boots is called: draupnir's `draupnir-`
/// before our `mimir-<pid>-<seq>`.
pub const NAME_PREFIX: &str = "draupnir-mimir-";
/// The containers this process has running right now (full engine names).
static LIVE: Mutex<BTreeSet<String>> = Mutex::new(BTreeSet::new());
/// One running container in [`LIVE`], out again when dropped.
pub struct Live(String);
impl Live {
pub fn track(name: String) -> Self {
LIVE.lock().unwrap_or_else(|p| p.into_inner()).insert(name.clone());
Live(name)
}
}
impl Drop for Live {
fn drop(&mut self) {
LIVE.lock().unwrap_or_else(|p| p.into_inner()).remove(&self.0);
}
}
/// The containers this process has running right now.
pub fn live() -> Vec<String> {
LIVE.lock().unwrap_or_else(|p| p.into_inner()).iter().cloned().collect()
}
/// The server pid a road container's name carries (`draupnir-mimir-<pid>-<seq>`).
pub fn owner_pid(name: &str) -> Option<u32> {
let (pid, seq) = name.strip_prefix(NAME_PREFIX)?.split_once('-')?;
if pid.is_empty() || seq.is_empty() || !pid.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
pid.parse().ok()
}
/// Is `name` a road container nobody reads any more? Its server is gone, or
/// it carries OUR pid but we did not boot it (the pid reused after a
/// restart). A live other process's (a CLI build) is not.
pub fn is_leftover(name: &str, own_pid: u32, alive: &dyn Fn(u32) -> bool, live: &BTreeSet<String>) -> bool {
match owner_pid(name) {
None => false,
Some(p) if p == own_pid => !live.contains(name),
Some(p) => !alive(p),
}
}
/// Does process `pid` exist (in this pid namespace)?
pub fn pid_alive(pid: u32) -> bool {
std::path::Path::new("/proc").join(pid.to_string()).exists()
}
/// Stop + remove these containers, all at once. draupnir's engine drives a
/// runtime of its own, so each runs on a thread in no runtime.
fn stop_all(names: &[String]) {
use draupnir::container::ContainerControl;
std::thread::scope(|s| {
for n in names {
s.spawn(move || {
let m = draupnir::Machine { id: n.clone(), spec_name: n.trim_start_matches("draupnir-").to_string(), kind: draupnir::Kind::Container, power: draupnir::PowerState::On };
draupnir::container::ContainerBoot::new().stop(&m);
});
}
});
}
/// Stop every container this process has running (server shutdown).
pub fn stop_live() -> Vec<String> {
let names = live();
stop_all(&names);
names
}
/// **Startup step**: stop + remove the road containers earlier servers left
/// running ([`is_leftover`]), and say so in one line. Nothing when the road
/// is not configured.
pub fn reap_at_start() {
if ContainerRoad::from_env().is_none() {
return;
}
let me = std::process::id();
let listed = std::thread::scope(|s| {
s.spawn(|| {
use draupnir::container::ContainerControl;
draupnir::container::ContainerBoot::new().list_containers(NAME_PREFIX)
})
.join()
});
let listed = match listed {
Ok(Ok(l)) => l,
Ok(Err(e)) => return eprintln!("mimir container road: could not list leftover build containers: {e}"),
Err(_) => return eprintln!("mimir container road: could not list leftover build containers: the engine call panicked"),
};
let own: BTreeSet<String> = live().into_iter().collect();
let gone: Vec<String> = listed.into_iter().map(|(n, _)| n).filter(|n| is_leftover(n, me, &pid_alive, &own)).collect();
stop_all(&gone);
if gone.is_empty() {
eprintln!("mimir container road: no leftover build containers");
} else {
eprintln!("mimir container road: stopped {} leftover build container(s) of earlier servers: {}", gone.len(), gone.join(", "));
}
}
/// Wait for SIGTERM / SIGINT, stop the live containers, then `exit(128 +
/// signal)` — what the unhandled signal did, minus the containers it left
/// compiling. The handlers are in place when this returns.
pub fn stop_on_terminate(exit: impl FnOnce(i32) + Send + 'static) -> std::io::Result<impl std::future::Future<Output = ()> + Send> {
use tokio::signal::unix::{SignalKind, signal};
let mut term = signal(SignalKind::terminate())?;
let mut int = signal(SignalKind::interrupt())?;
Ok(async move {
let sig = tokio::select! {
_ = term.recv() => libc::SIGTERM,
_ = int.recv() => libc::SIGINT,
};
// off the runtime: the engine blocks on a runtime of its own
let stopped = std::thread::spawn(stop_live).join().unwrap_or_default();
if !stopped.is_empty() {
eprintln!("mimir container road: signal {sig}, stopped {} build container(s): {}", stopped.len(), stopped.join(", "));
}
exit(128 + sig);
})
}
// ── ONE container per run (owner 2026-10-17: "66 containers starting, that
// must not be very fast — … just one long-lived, fix it now") ──────────────
//
// Planning a 21-repo workspace started ~66 containers (the probe, `cargo
// metadata`, one `--unit-graph` per repo, `cargo clean`, the std dump, …),
// each a create + start + 200 ms state poll + log read + remove. Now the run
// boots ONE container of the image with the road's mounts, user and env (its
// main process only waits), and every tool runs inside it as its own exec
// (draupnir's `exec_streaming`), concurrently when the callers are. The box
// lives as long as the run's [`BoxRoad`] (the consumer's `DriverDumpers`):
// dropped, it is stopped; SIGTERM stops it ([`stop_live`]); a later server's
// boot reaper takes it if this process died ([`reap_at_start`]). A box that
// died is booted again on the next call. A box that cannot be booted at all
// sends the run back to one container per call, said in one line.
/// The env a call's every process carries (cargo, rustc, build scripts all
/// inherit it): `NORNIR_MIMIR_CALL=<server pid>-<call seq>`, unique per call —
/// what [`BoxRoad::kill`] finds that call's processes by inside the box, and
/// only them.
pub const CALL_TAG_ENV: &str = "NORNIR_MIMIR_CALL";
/// The box's main process: waits, reaps the orphans calls leave behind (it is
/// PID 1), ends on SIGTERM.
pub const BOX_MAIN: &str = "trap 'exit 0' TERM INT; while :; do sleep 5 & wait; done";
/// SIGKILL every process in the box whose environment holds `$1` exactly (a
/// call's [`CALL_TAG_ENV`] pair) — that call's tree and nobody else's — until
/// none is left (a process forked between two scans is caught on the next).
pub const KILL_CALL: &str = r#"tag="$1"; n=0
while :; do
hit=
for d in /proc/[0-9]*; do
p=${d#/proc/}
[ "$p" = "$$" ] && continue
if grep -qxzF -- "$tag" "$d/environ" 2>/dev/null; then kill -9 "$p" 2>/dev/null && hit=1; fi
done
[ -z "$hit" ] && exit 0
n=$((n+1)); [ "$n" -ge 50 ] && exit 1
sleep 0.1
done"#;
/// What the road needs of an OCI engine, by container NAME (the daemon-side
/// `draupnir-…` one). [`DraupnirEngine`] is the production one; a test drives
/// a fake that runs the processes on this box.
pub trait BoxEngine: Send + Sync {
/// Boot `spec` detached; the daemon-side name.
fn boot(&self, spec: &draupnir::BootSpec) -> Result<String, String>;
/// Is the container `name` running?
fn running(&self, name: &str) -> bool;
/// One process inside the running `name`, its output to `sink` as it comes.
fn exec(&self, name: &str, spec: &draupnir::container::ExecSpec, sink: &mut dyn FnMut(draupnir::container::LogStream, &[u8])) -> Result<draupnir::container::ExecRun, String>;
/// Stop + remove `name` (idempotent).
fn stop(&self, name: &str);
/// The per-call road: boot `spec`, run it to its end, remove it.
fn run_once(&self, spec: &draupnir::BootSpec, sink: &mut dyn FnMut(draupnir::container::LogStream, &str)) -> Result<draupnir::container::RunOutcome, String>;
}
/// draupnir's ONE OCI engine. It drives a runtime of its own, so every call
/// runs on a thread that is in no runtime (the consumer calls from inside the
/// lake's), output handed back over a channel as it comes.
#[derive(Clone, Default)]
pub struct DraupnirEngine(draupnir::container::ContainerBoot);
fn machine_of(name: &str) -> draupnir::Machine {
draupnir::Machine { id: name.to_string(), spec_name: name.trim_start_matches("draupnir-").to_string(), kind: draupnir::Kind::Container, power: draupnir::PowerState::On }
}
/// `f` on a fresh thread (no tokio runtime there); its sends reach `each` on
/// this one while it runs.
fn off_runtime<T: Send, M: Send>(f: impl FnOnce(std::sync::mpsc::Sender<M>) -> T + Send, mut each: impl FnMut(M)) -> T {
let (tx, rx) = std::sync::mpsc::channel::<M>();
std::thread::scope(|s| {
let worker = s.spawn(move || f(tx));
for m in rx {
each(m);
}
worker.join().unwrap_or_else(|p| std::panic::resume_unwind(p))
})
}
impl BoxEngine for DraupnirEngine {
fn boot(&self, spec: &draupnir::BootSpec) -> Result<String, String> {
use draupnir::Boot;
off_runtime(|_: std::sync::mpsc::Sender<()>| self.0.boot(spec).map(|m| m.id).map_err(|e| e.to_string()), |_| {})
}
fn running(&self, name: &str) -> bool {
use draupnir::container::{ContainerControl, ContainerState};
off_runtime(|_: std::sync::mpsc::Sender<()>| matches!(self.0.container_state(&machine_of(name)), ContainerState::Running), |_| {})
}
fn exec(&self, name: &str, spec: &draupnir::container::ExecSpec, sink: &mut dyn FnMut(draupnir::container::LogStream, &[u8])) -> Result<draupnir::container::ExecRun, String> {
use draupnir::container::ContainerControl;
off_runtime(
|tx| self.0.exec_streaming(&machine_of(name), spec, &mut |st, b| drop(tx.send((st, b.to_vec())))).map_err(|e| e.to_string()),
|(st, b): (draupnir::container::LogStream, Vec<u8>)| sink(st, &b),
)
}
fn stop(&self, name: &str) {
use draupnir::container::ContainerControl;
off_runtime(|_: std::sync::mpsc::Sender<()>| self.0.stop(&machine_of(name)), |_| {})
}
fn run_once(&self, spec: &draupnir::BootSpec, sink: &mut dyn FnMut(draupnir::container::LogStream, &str)) -> Result<draupnir::container::RunOutcome, String> {
use draupnir::container::RunOptions;
off_runtime(
|tx| self.0.run_to_completion_streaming(spec, &RunOptions::default(), &mut |st, l| drop(tx.send((st, l.to_string())))).map_err(|e| e.to_string()),
|(st, l): (draupnir::container::LogStream, String)| sink(st, &l),
)
}
}
/// Which road a [`BoxRoad`] is on.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BoxMode {
/// No box yet (booted at the first call).
Idle,
/// Every call is an exec in this container (its daemon-side name).
Up(String),
/// The box could not be booted: one container per call.
PerCall,
}
/// The run's ONE container and the calls running in it.
pub struct BoxRoad<E: BoxEngine = DraupnirEngine> {
pub road: ContainerRoad,
engine: E,
/// uid:gid every process runs as.
user: String,
/// The box (and its [`Live`] entry) — held only to read or (re)boot it.
state: Mutex<(BoxMode, Option<Live>)>,
/// Running calls: seq → (program, the container it runs in, its tag).
calls: Mutex<std::collections::BTreeMap<u64, (String, String, String)>>,
/// How many containers this road has booted (the box, its restarts, and
/// every per-call one).
boots: AtomicU64,
}
impl BoxRoad<DraupnirEngine> {
/// The road over draupnir's engine, running as the engine wants
/// ([`ContainerRoad::user_for`]).
pub fn new(road: ContainerRoad) -> Self {
let user = ContainerRoad::user_for(&draupnir::container::engine_socket_url(), &std::fs::read_to_string("/proc/self/status").unwrap_or_default());
Self::with_engine(road, DraupnirEngine::default(), user)
}
}
/// One running call in [`BoxRoad::calls`], out again when dropped.
struct CallGuard<'a>(&'a Mutex<std::collections::BTreeMap<u64, (String, String, String)>>, u64);
impl Drop for CallGuard<'_> {
fn drop(&mut self) {
self.0.lock().unwrap_or_else(|p| p.into_inner()).remove(&self.1);
}
}
/// Hands a byte stream to `line` one line at a time (no `\n` / `\r\n`), the
/// last unterminated one at [`Lines::finish`].
struct Lines(Vec<u8>);
impl Lines {
fn push(&mut self, b: &[u8], line: &mut dyn FnMut(&str)) {
// only the new bytes are searched: a one-line JSON of megabytes
// (`cargo metadata`) arrives in thousands of chunks
let seen = self.0.len();
self.0.extend_from_slice(b);
let mut start = 0;
let mut from = seen;
while let Some(i) = self.0[from..].iter().position(|&c| c == b'\n') {
let end = from + i;
line(String::from_utf8_lossy(&self.0[start..end]).trim_end_matches('\r'));
start = end + 1;
from = start;
}
self.0.drain(..start);
}
fn finish(&mut self, line: &mut dyn FnMut(&str)) {
if !self.0.is_empty() {
line(String::from_utf8_lossy(&self.0).trim_end_matches('\r'));
self.0.clear();
}
}
}
impl<E: BoxEngine> BoxRoad<E> {
pub fn with_engine(road: ContainerRoad, engine: E, user: String) -> Self {
BoxRoad { road, engine, user, state: Mutex::new((BoxMode::Idle, None)), calls: Mutex::new(Default::default()), boots: AtomicU64::new(0) }
}
/// The road right now.
pub fn mode(&self) -> BoxMode {
self.state.lock().unwrap_or_else(|p| p.into_inner()).0.clone()
}
/// Containers booted so far.
pub fn boots(&self) -> u64 {
self.boots.load(Ordering::SeqCst)
}
/// The calls running now: (seq, program).
pub fn calls(&self) -> Vec<(u64, String)> {
self.calls.lock().unwrap_or_else(|p| p.into_inner()).iter().map(|(k, (p, _, _))| (*k, p.clone())).collect()
}
/// The box's spec: the image, every mount at its own path, cargo's home,
/// the uid; its main process only waits ([`BOX_MAIN`]).
pub fn box_spec(&self, name: String) -> draupnir::BootSpec {
let mut spec = draupnir::BootSpec::container(name, self.road.image.clone()).with_cmd(vec!["sh".to_string(), "-c".to_string(), BOX_MAIN.to_string()]).with_user(self.user.clone());
for b in &self.road.binds {
spec = spec.with_bind(format!("{0}:{0}", b.display()));
}
let home = self.road.cargo_home.display().to_string();
let mut spec = spec.with_env("CARGO_HOME", home.clone()).with_env("HOME", home);
for (k, v) in road_env(std::env::var("CARGO_BUILD_JOBS").ok()) {
spec = spec.with_env(k, v);
}
spec
}
/// The exec of one call: the tool under `mimir-capture` (stdout byte for
/// byte into its capture file), the call's env + its tag, its tree.
pub fn exec_spec(&self, call: &ToolCall, name: &str, tag: &str) -> draupnir::container::ExecSpec {
let mut argv = vec![self.road.driver_dir.join("mimir-capture").display().to_string(), self.road.capture_file(name).display().to_string(), "--".to_string(), call.program.clone()];
argv.extend(call.args.iter().cloned());
let mut spec = draupnir::container::ExecSpec::new(argv).with_user(self.user.clone());
for (k, v) in &call.envs {
spec = spec.with_env(k.clone(), v.clone());
}
spec = spec.with_env(CALL_TAG_ENV, tag);
if let Some(d) = &call.cwd {
spec = spec.with_working_dir(d.display().to_string());
}
spec
}
/// The box to exec into: booted now when there is none (or `dead` names
/// the one that died — removed, a new one booted). `None`: the run is on
/// one container per call.
fn ensure_box(&self, dead: Option<&str>) -> Option<String> {
static BOX_SEQ: AtomicU64 = AtomicU64::new(0);
let mut st = self.state.lock().unwrap_or_else(|p| p.into_inner());
match (&st.0, dead) {
(BoxMode::PerCall, _) => return None,
(BoxMode::Up(n), Some(d)) if n == d => {
self.engine.stop(n);
*st = (BoxMode::Idle, None);
}
(BoxMode::Up(n), _) => return Some(n.clone()),
(BoxMode::Idle, _) => {}
}
let restart = dead.is_some();
let spec_name = format!("mimir-{}-box{}", std::process::id(), BOX_SEQ.fetch_add(1, Ordering::SeqCst));
let full = draupnir::container::ContainerBoot::container_name_for(&spec_name);
let live = Live::track(full.clone());
self.boots.fetch_add(1, Ordering::SeqCst);
match self.engine.boot(&self.box_spec(spec_name)) {
Ok(name) => {
if restart {
eprintln!("mimir container road: the run's container died, booted {name} in its place");
}
*st = (BoxMode::Up(name.clone()), Some(live));
Some(name)
}
Err(e) => {
// a box that failed to start may stay behind in state `created`
self.engine.stop(&full);
eprintln!("mimir container road: could not boot the run's one container ({e}); one container per tool call instead");
*st = (BoxMode::PerCall, None);
None
}
}
}
/// **Stop call `seq`** — its processes only (found by its tag), the
/// other calls in the box run on; on the per-call road, its container.
/// `false` when no such call runs.
pub fn kill(&self, seq: u64) -> bool {
let Some((_, container, tag)) = self.calls.lock().unwrap_or_else(|p| p.into_inner()).get(&seq).cloned() else {
return false;
};
if tag.is_empty() {
self.engine.stop(&container);
} else {
self.kill_tag(&container, &tag);
}
true
}
fn kill_tag(&self, container: &str, tag: &str) {
let spec = draupnir::container::ExecSpec::new(vec!["sh".into(), "-c".into(), KILL_CALL.into(), "sh".into(), format!("{CALL_TAG_ENV}={tag}")]).with_user(self.user.clone());
if let Err(e) = self.engine.exec(container, &spec, &mut |_, _| {}) {
eprintln!("mimir container road: could not stop call {tag} in {container}: {e}");
}
}
/// One call as an exec in the box. `Err((e, retry))`: `retry` when the
/// box is not running and nothing of the call was handed on yet.
fn run_in_box(&self, name: &str, call: &ToolCall, line: &mut dyn FnMut(&str)) -> Result<std::process::Output, (std::io::Error, bool)> {
use draupnir::container::LogStream;
static SEQ: AtomicU64 = AtomicU64::new(0);
let seq = SEQ.fetch_add(1, Ordering::SeqCst);
let call_name = format!("mimir-{}-{seq}", std::process::id());
let tag = format!("{}-{seq}", std::process::id());
let capture = self.road.capture_file(&call_name);
if let Some(d) = capture.parent() {
std::fs::create_dir_all(d).map_err(|e| (e, false))?;
}
self.calls.lock().unwrap_or_else(|p| p.into_inner()).insert(seq, (call.program.clone(), name.to_string(), tag.clone()));
let _guard = CallGuard(&self.calls, seq);
let spec = self.exec_spec(call, &call_name, &tag);
let mut lines = Lines(Vec::new());
let mut said = false;
let run = self.engine.exec(name, &spec, &mut |st, b| {
if matches!(st, LogStream::Stdout) && !b.is_empty() {
said = true;
lines.push(b, line);
}
});
lines.finish(line);
let stdout_file = std::fs::read(&capture).ok();
let _ = std::fs::remove_file(&capture);
match run {
Ok(run) => {
// a process the engine reported no code for is no success
let code = run.exit_code.unwrap_or(255).clamp(0, 255) as i32;
Ok(std::process::Output { status: std::process::ExitStatus::from_raw(code << 8), stdout: stdout_file.unwrap_or(run.stdout), stderr: run.stderr })
}
Err(e) => {
let alive = self.engine.running(name);
// the transport broke under a live box: the call's processes
// must not compile on, read by nobody
if alive {
self.kill_tag(name, &tag);
}
Err((std::io::Error::other(format!("{} in {} ({name}): {e}", call.program, self.road.image)), !alive && !said))
}
}
}
/// One call in a container of its own (the road before the box, and its
/// fallback).
fn run_per_call(&self, call: &ToolCall, line: &mut dyn FnMut(&str)) -> std::io::Result<std::process::Output> {
use draupnir::container::LogStream;
static SEQ: AtomicU64 = AtomicU64::new(0);
let seq = SEQ.fetch_add(1, Ordering::SeqCst);
let name = format!("mimir-{}-{seq}", std::process::id());
let capture = self.road.capture_file(&name);
if let Some(d) = capture.parent() {
std::fs::create_dir_all(d)?;
}
let full = draupnir::container::ContainerBoot::container_name_for(&name);
let _live = Live::track(full.clone());
// the call map: per-call seqs live beside the box's, so mark them apart
let key = u64::MAX - seq;
self.calls.lock().unwrap_or_else(|p| p.into_inner()).insert(key, (call.program.clone(), full.clone(), String::new()));
let _guard = CallGuard(&self.calls, key);
let spec = self.road.spec(call, name, &self.user);
self.boots.fetch_add(1, Ordering::SeqCst);
let out = self.engine.run_once(&spec, &mut |st, l| {
if matches!(st, LogStream::Stdout) {
line(l);
}
});
// a container that failed to start stays behind in state `created`
if out.is_err() {
self.engine.stop(&full);
}
let out = out.map_err(|e| std::io::Error::other(format!("{} in {}: {e}", call.program, self.road.image)))?;
let join = |lines: Vec<String>| {
let mut s = lines.join("\n");
if !s.is_empty() {
s.push('\n');
}
s.into_bytes()
};
// a container that vanished before it reported a code is no success
let code = out.exit_code.unwrap_or(255).clamp(0, 255) as i32;
// the exact stdout is the capture file's; the log copy only when there is none
let stdout = match std::fs::read(&capture) {
Ok(b) => {
let _ = std::fs::remove_file(&capture);
b
}
Err(_) => join(out.stdout),
};
Ok(std::process::Output { status: std::process::ExitStatus::from_raw(code << 8), stdout, stderr: join(out.stderr) })
}
/// Stop the box (the run is over). Idempotent.
pub fn shut(&self) {
let mut st = self.state.lock().unwrap_or_else(|p| p.into_inner());
if let BoxMode::Up(n) = &st.0 {
self.engine.stop(n);
}
*st = (BoxMode::Idle, None);
}
}
impl<E: BoxEngine> Drop for BoxRoad<E> {
fn drop(&mut self) {
self.shut();
}
}
impl<E: BoxEngine> ToolRunner for BoxRoad<E> {
fn run(&self, call: &ToolCall, line: &mut dyn FnMut(&str)) -> std::io::Result<std::process::Output> {
std::fs::create_dir_all(&self.road.cargo_home)?;
let mut dead: Option<String> = None;
loop {
let Some(name) = self.ensure_box(dead.as_deref()) else {
return self.run_per_call(call, line);
};
match self.run_in_box(&name, call, line) {
Ok(o) => return Ok(o),
// the box died before the call said anything: once more in a new one
Err((_, true)) if dead.is_none() => dead = Some(name),
Err((e, _)) => return Err(e),
}
}
}
fn describe(&self) -> String {
format!("container {} (draupnir OCI, one container per run)", self.road.image)
}
}
/// What the road's containers inherit from the server beyond the call's own
/// env: the host's cargo job bound (`CARGO_BUILD_JOBS`). Without it a dump's
/// compile in the container runs one rustc per core, and on a shared box the
/// MIR redump of 21 members drove the server's host into the OOM killer.
pub fn road_env(jobs: Option<String>) -> Vec<(String, String)> {
jobs.filter(|j| !j.trim().is_empty()).map(|j| vec![("CARGO_BUILD_JOBS".to_string(), j)]).unwrap_or_default()
}
#[cfg(test)]
mod tests {
/// RED before: the road passed no job bound, so a dump's compile in the
/// container used every core of the host.
#[test]
fn the_road_carries_the_hosts_cargo_job_bound() {
assert_eq!(super::road_env(Some("4".into())), vec![("CARGO_BUILD_JOBS".to_string(), "4".to_string())]);
assert!(super::road_env(None).is_empty());
assert!(super::road_env(Some(" ".into())).is_empty());
}
use super::*;
/// One tool run → one draupnir container spec: the image, the argv, the
/// tool's environment + cargo's home, every mount at its own path, the
/// tree as the working dir, the uid the engine wants.
#[test]
fn a_tool_run_is_one_container_with_its_tree_mounts_and_env() {
let road = ContainerRoad {
image: "img:1".into(),
driver_dir: "/n/drv".into(),
binds: vec!["/n".into(), "/b".into()],
cargo_home: "/n/mimir-cargo-home".into(),
};
let call = ToolCall { program: "cargo".into(), args: vec!["metadata".into()], envs: vec![("RUSTUP_TOOLCHAIN".into(), "nightly-x".into())], cwd: Some("/n/ws/builds/mimir_v2/clones/skade".into()) };
let s = road.spec(&call, "mimir-1-0".into(), "103:103");
let dbg = format!("{s:?}");
for want in ["img:1", "/n/drv/mimir-capture", "/n/mimir-capture/mimir-1-0.out", "\"--\"", "\"cargo\"", "\"metadata\"", "/n:/n", "/b:/b", "RUSTUP_TOOLCHAIN", "CARGO_HOME", "/n/mimir-cargo-home", "/n/ws/builds/mimir_v2/clones/skade", "103:103"] {
assert!(dbg.contains(want), "{want} missing from {dbg}");
}
assert_eq!(road.cargo_mimir_dump(), PathBuf::from("/n/drv/cargo-mimir-dump"));
// rootful engine: the server's own ids; the status file says them
assert_eq!(ContainerRoad::user_for("unix:///run/podman/podman.sock", "Uid:\t103\t103\t103\t103\nGid:\t103\t103\t103\t103\n"), "103:103");
}
/// Only `draupnir-mimir-<pid>-<seq>` names belong to the road; their pid
/// is the server that booted them.
#[test]
fn a_road_container_name_carries_its_server_pid() {
assert_eq!(owner_pid("draupnir-mimir-2866104-11"), Some(2866104));
assert_eq!(owner_pid("draupnir-mimir-7-0"), Some(7));
for other in ["draupnir-cargo-build-pid1-korp-start", "draupnir-mimir-x-1", "draupnir-mimir-12", "mimir-12-0", "draupnir-mimir--3"] {
assert_eq!(owner_pid(other), None, "{other}");
}
}
/// The live bug (2026-10-16): after a restart `draupnir-mimir-2866104-11`
/// of the dead server kept compiling. A container is a leftover when its
/// server is gone, or when it carries OUR pid but we did not boot it (a
/// pid reused after a restart). A live other process's (a CLI build) is
/// not; nor is ours; nor anything not the road's.
#[test]
fn leftovers_are_containers_whose_server_is_gone() {
let me = 4242;
let alive = |p: u32| p == 4242 || p == 5000;
let mut live = BTreeSet::new();
live.insert("draupnir-mimir-4242-3".to_string());
assert!(is_leftover("draupnir-mimir-2866104-11", me, &alive, &live));
assert!(is_leftover("draupnir-mimir-4242-9", me, &alive, &live));
assert!(!is_leftover("draupnir-mimir-4242-3", me, &alive, &live));
assert!(!is_leftover("draupnir-mimir-5000-0", me, &alive, &live));
assert!(!is_leftover("draupnir-korp-start", me, &alive, &live));
}
/// A running container is in the registry for exactly as long as its
/// guard lives — what SIGTERM stops.
#[test]
fn a_running_container_is_tracked_until_it_ends() {
let name = format!("{NAME_PREFIX}{}-test-live", std::process::id());
{
let _g = Live::track(name.clone());
assert!(live().contains(&name));
}
assert!(!live().contains(&name));
}
/// SIGTERM to the server: the live containers are stopped and the
/// process ends with 128 + 15, as an unhandled SIGTERM would.
#[test]
fn sigterm_stops_and_exits_143() {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let (tx, rx) = std::sync::mpsc::channel();
rt.block_on(async {
let wait = stop_on_terminate(move |code| tx.send(code).unwrap()).unwrap();
let task = tokio::spawn(wait);
unsafe { libc::kill(std::process::id() as i32, libc::SIGTERM) };
tokio::time::timeout(std::time::Duration::from_secs(10), task).await.unwrap().unwrap();
});
assert_eq!(rx.recv().unwrap(), 143);
}
/// Against the real engine (`NORNIR_MIMIR_CONTAINER_IMAGE` names an image
/// with `sleep`): a dead server's container is reaped at start, ours is
/// not; ours goes on stop.
#[test]
#[ignore = "needs an OCI engine + NORNIR_MIMIR_CONTAINER_IMAGE"]
fn the_engine_reaps_leftovers_and_stops_ours() {
use draupnir::Boot;
use draupnir::container::{ContainerBoot, ContainerControl};
let image = std::env::var(ENV_IMAGE).expect("NORNIR_MIMIR_CONTAINER_IMAGE");
let boot = |n: &str| {
let spec = draupnir::BootSpec::container(n.to_string(), image.clone()).with_cmd(vec!["sleep".to_string(), "600".to_string()]);
std::thread::scope(|s| s.spawn(|| ContainerBoot::new().boot(&spec).unwrap()).join().unwrap());
ContainerBoot::container_name_for(n)
};
let names = || -> Vec<String> {
std::thread::scope(|s| s.spawn(|| ContainerBoot::new().list_containers(NAME_PREFIX).unwrap()).join().unwrap()).into_iter().map(|(n, _)| n).collect()
};
// beyond any pid_max: a server that cannot be alive
let dead = boot("mimir-4194999-0");
let ours = boot(&format!("mimir-{}-777", std::process::id()));
let _g = Live::track(ours.clone());
assert!(names().contains(&dead) && names().contains(&ours));
reap_at_start();
assert!(!names().contains(&dead), "the dead server's container is still there");
assert!(names().contains(&ours), "ours was reaped");
assert!(stop_live().contains(&ours));
assert!(!names().contains(&ours), "ours outlived the stop");
}
/// A tool whose container cannot start (the driver dir missing) leaves no
/// container behind (the e2e of 2026-10-16 found the probe's
/// `draupnir-mimir-<pid>-0` corpse in state created).
#[test]
#[ignore = "needs an OCI engine + NORNIR_MIMIR_CONTAINER_IMAGE"]
fn a_container_that_cannot_start_leaves_nothing() {
use draupnir::container::{ContainerBoot, ContainerControl};
let image = std::env::var(ENV_IMAGE).expect("NORNIR_MIMIR_CONTAINER_IMAGE");
let tmp = tempfile::tempdir().unwrap();
let road = BoxRoad::new(ContainerRoad { image, driver_dir: tmp.path().join("no-driver"), binds: vec![tmp.path().to_path_buf()], cargo_home: tmp.path().join("mimir-cargo-home") });
let call = ToolCall { program: "rustc".into(), args: vec!["--version".into()], envs: vec![], cwd: None };
// the box boots (its main needs no driver), the call fails in it; on
// the per-call road the container fails to start
let _ = road.run(&call, &mut |_| {});
assert!(road.run_per_call(&call, &mut |_| {}).is_err());
drop(road);
let mine = format!("{NAME_PREFIX}{}-", std::process::id());
let left: Vec<String> = std::thread::scope(|s| s.spawn(|| ContainerBoot::new().list_containers(&mine).unwrap()).join().unwrap()).into_iter().map(|(n, _)| n).collect();
assert!(left.is_empty(), "left behind: {left:?}");
}
// ── the ONE box, against a fake engine that runs every process on this
// box (an exec is a local child; `mimir-capture` is played by the fake) ──
use draupnir::container::{ExecRun, ExecSpec, LogStream, RunOutcome};
#[derive(Default)]
struct Fake {
/// containers up (daemon-side names)
up: Mutex<BTreeSet<String>>,
boots: AtomicU64,
execs: AtomicU64,
once: AtomicU64,
refuse_boot: bool,
}
/// Run `argv` here with `env` in `dir`, chunks to `sink`; a leading
/// `mimir-capture <file> --` is played (stdout also into the file).
fn local(argv: &[String], env: &[(String, String)], dir: Option<&str>, sink: &mut dyn FnMut(LogStream, &[u8])) -> ExecRun {
use std::io::Read;
let (capture, argv) = if argv[0].ends_with("mimir-capture") { (Some(argv[1].clone()), &argv[3..]) } else { (None, argv) };
let mut c = std::process::Command::new(&argv[0]);
c.args(&argv[1..]).envs(env.iter().map(|(k, v)| (k, v))).stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::piped());
if let Some(d) = dir {
c.current_dir(d);
}
let mut child = c.spawn().unwrap();
let (mut out, mut err) = (child.stdout.take().unwrap(), child.stderr.take().unwrap());
let errs = std::thread::spawn(move || {
let mut b = Vec::new();
err.read_to_end(&mut b).unwrap();
b
});
let mut run = ExecRun::default();
let mut buf = [0u8; 8192];
loop {
let n = out.read(&mut buf).unwrap();
if n == 0 {
break;
}
sink(LogStream::Stdout, &buf[..n]);
run.stdout.extend_from_slice(&buf[..n]);
}
run.stderr = errs.join().unwrap();
let st = child.wait().unwrap();
run.exit_code = Some(st.code().unwrap_or_else(|| 128 + st.signal().unwrap_or(0)) as i64);
if let Some(f) = capture {
std::fs::write(f, &run.stdout).unwrap();
}
run
}
impl BoxEngine for Fake {
fn boot(&self, spec: &draupnir::BootSpec) -> Result<String, String> {
self.boots.fetch_add(1, Ordering::SeqCst);
if self.refuse_boot {
return Err("no such image".into());
}
let n = draupnir::container::ContainerBoot::container_name_for(&spec.name);
self.up.lock().unwrap().insert(n.clone());
Ok(n)
}
fn running(&self, name: &str) -> bool {
self.up.lock().unwrap().contains(name)
}
fn exec(&self, name: &str, spec: &ExecSpec, sink: &mut dyn FnMut(LogStream, &[u8])) -> Result<ExecRun, String> {
if !self.running(name) {
return Err(format!("container {name} is not running"));
}
self.execs.fetch_add(1, Ordering::SeqCst);
Ok(local(&spec.argv, &spec.env, spec.working_dir.as_deref(), sink))
}
fn stop(&self, name: &str) {
self.up.lock().unwrap().remove(name);
}
fn run_once(&self, spec: &draupnir::BootSpec, sink: &mut dyn FnMut(LogStream, &str)) -> Result<RunOutcome, String> {
self.boots.fetch_add(1, Ordering::SeqCst);
self.once.fetch_add(1, Ordering::SeqCst);
let env: Vec<(String, String)> = spec.env.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let run = local(&spec.cmd, &env, spec.working_dir.as_deref(), &mut |_, _| {});
let lines = |b: &[u8]| String::from_utf8_lossy(b).lines().map(str::to_string).collect::<Vec<_>>();
for l in lines(&run.stdout) {
sink(LogStream::Stdout, &l);
}
Ok(RunOutcome { exit_code: run.exit_code, stdout: lines(&run.stdout), stderr: lines(&run.stderr) })
}
}
fn fake_road(fake: Fake) -> (tempfile::TempDir, BoxRoad<Fake>) {
let tmp = tempfile::tempdir().unwrap();
let road = ContainerRoad { image: "img:1".into(), driver_dir: tmp.path().join("drv"), binds: vec![tmp.path().to_path_buf()], cargo_home: tmp.path().join("mimir-cargo-home") };
(tmp, BoxRoad::with_engine(road, fake, "0:0".into()))
}
fn sh(script: &str) -> ToolCall {
ToolCall { program: "sh".into(), args: vec!["-c".into(), script.into()], envs: vec![("CALL_ENV".into(), "x".into())], cwd: None }
}
/// The owner's 66: many tool calls — some at once — boot exactly ONE
/// container; each comes back with its own output.
#[test]
fn many_tool_calls_boot_one_container() {
let (_t, road) = fake_road(Fake::default());
for i in 0..12 {
let o = road.run(&sh(&format!("echo call-{i} $CALL_ENV")), &mut |_| {}).unwrap();
assert_eq!(o.stdout, format!("call-{i} x\n").into_bytes());
}
std::thread::scope(|s| {
for i in 0..8 {
let road = &road;
s.spawn(move || assert_eq!(road.run(&sh(&format!("sleep 0.2; echo {i}")), &mut |_| {}).unwrap().stdout, format!("{i}\n").into_bytes()));
}
});
assert_eq!(road.boots(), 1, "one container for 20 calls");
assert_eq!(road.engine.boots.load(Ordering::SeqCst), 1);
assert_eq!((road.engine.execs.load(Ordering::SeqCst), road.engine.once.load(Ordering::SeqCst)), (20, 0));
assert!(matches!(road.mode(), BoxMode::Up(_)));
assert_eq!(road.describe(), "container img:1 (draupnir OCI, one container per run)");
}
/// Exit codes and a line over 64 KB come back exactly: stdout byte for
/// byte, the line whole to the live callback, stderr apart.
#[test]
fn exit_codes_and_long_lines_come_back_whole() {
let (_t, road) = fake_road(Fake::default());
let mut lines = Vec::new();
let o = road.run(&sh("head -c 100000 /dev/zero | tr '\\0' a; echo; printf 'last'; echo oops >&2; exit 3"), &mut |l| lines.push(l.to_string())).unwrap();
assert_eq!(o.status.code(), Some(3));
assert_eq!(o.stdout.len(), 100_005);
assert!(o.stdout[..100_000].iter().all(|&b| b == b'a') && o.stdout.ends_with(b"\nlast"));
assert_eq!(o.stderr, b"oops\n");
assert_eq!(lines.len(), 2);
assert_eq!((lines[0].len(), lines[1].as_str()), (100_000, "last"));
assert_eq!(road.run(&sh("exit 0"), &mut |_| {}).unwrap().status.code(), Some(0));
assert_eq!(road.run(&sh("kill -9 $$"), &mut |_| {}).unwrap().status.code(), Some(137));
}
/// Stopping one call kills ITS processes (its children too) and nothing
/// else: the call beside it in the same container ends normally.
#[test]
fn killing_one_call_leaves_the_others_running() {
let (t, road) = fake_road(Fake::default());
let mark = t.path().join("child-lived");
let (a, b, victim) = std::thread::scope(|s| {
let road = &road;
let mark = &mark;
let wait_for = |n: usize| {
let t0 = std::time::Instant::now();
while road.calls().len() < n {
assert!(t0.elapsed().as_secs() < 10, "{n} calls never ran");
std::thread::sleep(std::time::Duration::from_millis(20));
}
road.calls()
};
let a = s.spawn(move || road.run(&sh(&format!("(sleep 3; touch {}) & sleep 30; echo never", mark.display())), &mut |_| {}).unwrap());
let victim = wait_for(1)[0].0;
let b = s.spawn(move || road.run(&sh("sleep 2; echo ok"), &mut |_| {}).unwrap());
wait_for(2);
std::thread::sleep(std::time::Duration::from_millis(200));
assert!(road.kill(victim));
(a.join().unwrap(), b.join().unwrap(), victim)
});
assert_eq!(a.status.code(), Some(137), "the killed call");
assert!(a.stdout.is_empty());
assert_eq!((b.status.code(), b.stdout.as_slice()), (Some(0), &b"ok\n"[..]), "the other call");
std::thread::sleep(std::time::Duration::from_secs(2));
assert!(!mark.exists(), "the killed call's child lived on");
assert!(!road.kill(victim), "a finished call is no call");
assert_eq!(road.boots(), 1);
}
/// A box that died is booted again at the next call; the run's end stops
/// the box and takes it out of the live list.
#[test]
fn a_dead_box_is_rebooted_and_the_end_stops_it() {
let (_t, road) = fake_road(Fake::default());
road.run(&sh("true"), &mut |_| {}).unwrap();
let BoxMode::Up(first) = road.mode() else { panic!("no box") };
assert!(live().contains(&first));
road.engine.stop(&first); // killed from outside
let o = road.run(&sh("echo again"), &mut |_| {}).unwrap();
assert_eq!(o.stdout, b"again\n");
let BoxMode::Up(second) = road.mode() else { panic!("no box") };
assert_ne!(first, second);
assert_eq!(road.boots(), 2);
assert!(!live().contains(&first) && live().contains(&second));
let up = || road.engine.up.lock().unwrap().clone();
assert_eq!(up().into_iter().collect::<Vec<_>>(), vec![second.clone()]);
road.shut();
assert!(up().is_empty(), "the run's end left the box up");
assert!(!live().contains(&second));
}
/// A box that cannot boot sends the run to one container per call — and
/// the calls still answer.
#[test]
fn no_box_falls_back_to_a_container_per_call() {
let (_t, road) = fake_road(Fake { refuse_boot: true, ..Fake::default() });
for i in 0..3 {
let o = road.run(&sh(&format!("echo {i}; exit {i}")), &mut |_| {}).unwrap();
assert_eq!((o.status.code(), o.stdout), (Some(i), format!("{i}\n").into_bytes()));
}
assert_eq!(road.mode(), BoxMode::PerCall);
assert_eq!(road.engine.once.load(Ordering::SeqCst), 3);
assert_eq!(road.engine.boots.load(Ordering::SeqCst), 1 + 3, "one failed box, then one per call");
}
/// The box: the image, every mount at its own path, cargo's home, the uid,
/// a main that only waits; a call: the tool under mimir-capture with its
/// env, its tag and its tree.
#[test]
fn the_box_and_a_call_carry_the_roads_mounts_env_and_user() {
let road = ContainerRoad { image: "img:1".into(), driver_dir: "/n/drv".into(), binds: vec!["/n".into(), "/b".into()], cargo_home: "/n/mimir-cargo-home".into() };
let b = BoxRoad::with_engine(road, Fake::default(), "103:103".into());
let dbg = format!("{:?}", b.box_spec("mimir-1-box0".into()));
for want in ["img:1", "/n:/n", "/b:/b", "CARGO_HOME", "/n/mimir-cargo-home", "103:103", "while :; do sleep 5 & wait; done"] {
assert!(dbg.contains(want), "{want} missing from {dbg}");
}
let call = ToolCall { program: "cargo".into(), args: vec!["metadata".into()], envs: vec![("RUSTUP_TOOLCHAIN".into(), "nightly-x".into())], cwd: Some("/n/ws/clones/skade".into()) };
let e = b.exec_spec(&call, "mimir-1-7", "1-7");
assert_eq!(e.argv, ["/n/drv/mimir-capture", "/n/mimir-capture/mimir-1-7.out", "--", "cargo", "metadata"]);
assert_eq!(e.env, [("RUSTUP_TOOLCHAIN".to_string(), "nightly-x".to_string()), (CALL_TAG_ENV.to_string(), "1-7".to_string())]);
assert_eq!((e.user.as_deref(), e.working_dir.as_deref()), (Some("103:103"), Some("/n/ws/clones/skade")));
}
/// Against the real engine (`NORNIR_MIMIR_CONTAINER_IMAGE`, and
/// `NORNIR_MIMIR_CONTAINER_DRIVER` holding a `mimir-capture` built for the
/// image): many calls are ONE container; exit codes and a 200 KB line come
/// back exact; one call killed, the other lives; a killed box is booted
/// again; the end stops it.
#[test]
#[ignore = "needs an OCI engine + NORNIR_MIMIR_CONTAINER_IMAGE + NORNIR_MIMIR_CONTAINER_DRIVER (mimir-capture)"]
fn the_engine_runs_a_run_in_one_container() {
let road = ContainerRoad::from_env().expect("NORNIR_MIMIR_CONTAINER_IMAGE");
let mine = format!("{NAME_PREFIX}{}-", std::process::id());
let ours = || -> Vec<String> {
use draupnir::container::{ContainerBoot, ContainerControl};
std::thread::scope(|s| s.spawn(|| ContainerBoot::new().list_containers(&mine).unwrap()).join().unwrap()).into_iter().map(|(n, _)| n).collect()
};
let b = BoxRoad::new(road);
let t = std::time::Instant::now();
for i in 0..10 {
let o = b.run(&sh(&format!("echo {i}; exit {i}")), &mut |_| {}).unwrap();
assert_eq!((o.status.code(), o.stdout), (Some(i), format!("{i}\n").into_bytes()));
}
eprintln!("10 calls in one box: {:?}", t.elapsed());
assert_eq!((b.boots(), ours().len()), (1, 1));
let o = b.run(&sh("head -c 200000 /dev/zero | tr '\\0' a; echo; echo err >&2; exit 4"), &mut |_| {}).unwrap();
assert_eq!((o.status.code(), o.stdout.len(), o.stderr.as_slice()), (Some(4), 200_001, &b"err\n"[..]));
let (a, c) = std::thread::scope(|s| {
let b = &b;
let wait_for = |n: usize| {
while b.calls().len() < n {
std::thread::sleep(std::time::Duration::from_millis(20));
}
b.calls()
};
let a = s.spawn(move || b.run(&sh("sleep 60; echo never"), &mut |_| {}).unwrap());
let victim = wait_for(1)[0].0;
let c = s.spawn(move || b.run(&sh("sleep 3; echo ok"), &mut |_| {}).unwrap());
wait_for(2);
std::thread::sleep(std::time::Duration::from_millis(500));
assert!(b.kill(victim));
(a.join().unwrap(), c.join().unwrap())
});
assert_eq!(a.status.code(), Some(137));
assert_eq!((c.status.code(), c.stdout.as_slice()), (Some(0), &b"ok\n"[..]));
let BoxMode::Up(first) = b.mode() else { panic!("no box") };
b.engine.stop(&first);
assert_eq!(b.run(&sh("echo back"), &mut |_| {}).unwrap().stdout, b"back\n");
assert_eq!((b.boots(), ours().len()), (2, 1));
drop(b);
assert!(ours().is_empty(), "the end left {:?}", ours());
}
/// **MEASURE** the planning step's tool calls, one container per call vs
/// the run's one box: per repo of `NORNIR_ONEBOX_BENCH_REPOS` (comma-
/// separated trees) `cargo metadata` and `cargo check --unit-graph`, after
/// the probe's `rustc -vV` + `rustc --print sysroot`; the same calls in the
/// same order on both roads, the answers compared.
#[test]
#[ignore = "bench: needs an OCI engine + the image + the driver dir + NORNIR_ONEBOX_BENCH_REPOS"]
fn bench_planning_per_call_vs_one_box() {
let road = ContainerRoad::from_env().expect("NORNIR_MIMIR_CONTAINER_IMAGE");
let tc = std::env::var("NORNIR_ONEBOX_BENCH_TOOLCHAIN").unwrap_or_else(|_| mimir_ingest::pinned_toolchain().to_string());
let repos: Vec<PathBuf> = std::env::var("NORNIR_ONEBOX_BENCH_REPOS").expect("NORNIR_ONEBOX_BENCH_REPOS").split(',').filter(|s| !s.is_empty()).map(PathBuf::from).collect();
let env = vec![("RUSTUP_TOOLCHAIN".to_string(), tc)];
let mut calls = vec![
ToolCall { program: "rustc".into(), args: vec!["-vV".into()], envs: env.clone(), cwd: None },
ToolCall { program: "rustc".into(), args: vec!["--print".into(), "sysroot".into()], envs: env.clone(), cwd: None },
];
for r in &repos {
let m = r.join("Cargo.toml").display().to_string();
calls.push(ToolCall { program: "cargo".into(), args: vec!["metadata".into(), "--format-version".into(), "1".into()], envs: env.clone(), cwd: Some(r.clone()) });
calls.push(ToolCall { program: "cargo".into(), args: ["check", "--manifest-path", &m, "--workspace", "--all-targets", "--unit-graph", "-Z", "unstable-options"].map(String::from).to_vec(), envs: env.clone(), cwd: Some(r.clone()) });
}
let b = BoxRoad::new(road);
// per call, one box, and both again: the first round warms cargo's registry
let mut answers: Vec<Vec<std::process::Output>> = Vec::new();
let mut rows = Vec::new();
for round in 0..2 {
for one_box in [false, true] {
let (t, before) = (std::time::Instant::now(), b.boots());
let outs: Vec<_> = calls.iter().map(|c| if one_box { b.run(c, &mut |_| {}) } else { b.run_per_call(c, &mut |_| {}) }.unwrap()).collect();
rows.push(format!("round {round} {}: {:.2?} with {} container start(s)", if one_box { "one box" } else { "per call" }, t.elapsed(), b.boots() - before));
answers.push(outs);
}
}
for r in &rows {
eprintln!("BENCH {r}");
}
// the warm round answers alike on both roads (round 0 fills the
// registry, so its first answers may differ)
for ((c, p), o) in calls.iter().zip(&answers[2]).zip(&answers[3]) {
assert_eq!((p.status.code(), p.stdout.len()), (o.status.code(), o.stdout.len()), "{} {:?} in {:?} answered apart", c.program, c.args, c.cwd);
}
let ok = answers[3].iter().filter(|o| o.status.success()).count();
eprintln!("BENCH planning {} repos, {} calls ({ok} ok)", repos.len(), calls.len());
}
/// A one-line JSON of megabytes in thousands of chunks (`cargo metadata`
/// through the attach stream) is split in linear time: the bench of
/// 2026-10-17 found the box SLOWER than a container per call (85 s vs
/// 27 s for 44 calls) while every chunk rescanned the whole line.
#[test]
fn a_huge_line_in_small_chunks_is_split_in_linear_time() {
let mut l = Lines(Vec::new());
let mut got: Vec<usize> = Vec::new();
let chunk = vec![b'x'; 4096];
let t = std::time::Instant::now();
for _ in 0..4096 {
l.push(&chunk, &mut |s| got.push(s.len()));
}
l.push(b"\nend\r\n", &mut |s| got.push(s.len()));
l.finish(&mut |s| got.push(s.len()));
assert_eq!(got, vec![4096 * 4096, 3]);
assert!(t.elapsed() < std::time::Duration::from_secs(2), "16 MB in 4 KB chunks took {:?}", t.elapsed());
}
}