brokerd: start pipe threads safely and collect output within a grace period

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-23 16:07:15 -07:00
parent c87aff0793
commit 93539dbead
4 changed files with 200 additions and 51 deletions
+24 -4
View File
@@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use crate::config::Runner;
use crate::pipes::Io;
use crate::pipes::{GRACE, Io};
use crate::podman;
use crate::runner::{RunError, RunOutput, RunSpec, Runtime};
@@ -20,6 +20,7 @@ pub const COULD_NOT_RUN: &str = "the tool could not run";
pub const CANNOT_START: &str = "the tool runner could not start the container";
pub const KILLED: &str = "the tool was stopped: it ran out of memory or was killed";
pub const TIMED_OUT: &str = "the tool ran past its time limit";
pub const OUTPUT_OPEN: &str = "the tool left its output open";
pub const UNEXPECTED: &str = "the tool failed with an unexpected status";
/// How often a running container is checked.
pub const POLL: Duration = Duration::from_millis(50);
@@ -144,10 +145,29 @@ impl Podman {
};
let started = Instant::now();
let cap = usize::try_from(self.runner.output_cap).unwrap_or(usize::MAX);
let io = Io::start(&mut child, input, cap);
let io = match Io::start(&mut child, input, cap, STDERR_KEPT) {
Ok(io) => io,
Err(e) => {
// The container may be running: stop it before answering.
self.podman(&["kill", name]);
self.podman(&["rm", "-f", name]);
let _ = child.kill();
let _ = child.wait();
(self.log)(&format!(
"brokerd: cannot start a thread for {name}: {e}\n{RUNBOOK}"
));
return Err(RunError::Unavailable(CANNOT_START.to_string()));
}
};
let status = self.wait(&mut child, name, started, limit);
let (out, truncated, err) = io.finish();
self.answer(name, status, out, truncated, &err)
let done = io.finish(GRACE);
if done.open && status.is_some() {
(self.log)(&format!(
"brokerd: {name} ended but something still holds its output; it was abandoned"
));
return Err(RunError::Failed(OUTPUT_OPEN.to_string()));
}
self.answer(name, status, done.out, done.truncated, &done.err)
}
/// Step 3: spawn `podman` with `args` and all three standard streams piped. On failure, log