Exclude component compilation from command deadlines

This commit is contained in:
2026-11-07 21:39:23 +00:00
parent 361cfffb32
commit 0ed5e7befe
4 changed files with 336 additions and 72 deletions
+73 -68
View File
@@ -47,6 +47,7 @@ mod bindings {
}
use bindings::sausage::process::host::{Host, HostChild, Request, Stdio};
mod cache;
mod deadline;
mod http;
pub use http::Router;
pub mod net;
@@ -83,6 +84,7 @@ struct Entry {
command: u32,
group: u32,
cancelled: Arc<AtomicBool>,
deadline: Option<Arc<deadline::Deadline>>,
tag: sausage_vfs::journal::Tag,
abort: AbortHandle,
}
@@ -329,16 +331,17 @@ impl Runtime {
conversations: Vec<String>,
inherited_io: Option<Io>,
tool_call: Option<String>,
deadline: Option<Arc<deadline::Deadline>>,
) -> anyhow::Result<Started> {
anyhow::ensure!(!request.argv.is_empty(), "empty argv");
let cancelled = if parent == 0 {
Arc::new(AtomicBool::new(false))
let (cancelled, deadline) = if parent == 0 {
(Arc::new(AtomicBool::new(false)), deadline)
} else {
self.entries
.lock()
.unwrap()
.get(&parent)
.map(|entry| entry.cancelled.clone())
.map(|entry| (entry.cancelled.clone(), entry.deadline.clone()))
.ok_or_else(|| anyhow::anyhow!("parent exited"))?
};
if !request.env.iter().any(|(k, _)| k == "PATH") {
@@ -366,7 +369,9 @@ impl Runtime {
let bytes = file
.read(0, usize::try_from(file.stat().await?.size)?)
.await?;
let compilation = deadline.as_ref().map(|deadline| deadline.pause());
let pre = self.compiler.get(bytes.clone()).await?;
drop(compilation);
self.cache.lock().unwrap().insert(bytes);
let pid = self.next.fetch_add(1, Ordering::Relaxed);
let tag = if parent == 0 {
@@ -528,6 +533,7 @@ impl Runtime {
command,
group,
cancelled: cancelled.clone(),
deadline,
tag,
abort: task.abort_handle(),
},
@@ -583,6 +589,7 @@ impl Host for State {
self.conversations.clone(),
Some(self.stdio.clone()),
None,
None,
)
.await
{
@@ -707,79 +714,77 @@ impl Exec for Arc<Runtime> {
options: Options,
output: &mut (dyn for<'chunk> FnMut(Stream, &'chunk [u8]) + Send),
) -> anyhow::Result<i32> {
let deadline = options
.timeout
.map(|duration| tokio::time::Instant::now() + duration);
let argv = match command {
Command::Shell(script) => vec![
"/bin/sh".into(),
"--noprofile".into(),
"--norc".into(),
"-c".into(),
script,
],
Command::Argv(argv) => argv,
};
let env = options.env;
let start = self.start(
Request {
program: argv.first().cloned().unwrap_or_default(),
argv,
env,
cwd: options.cwd,
group: None,
stdin: Stdio::Pipe,
stdout: Stdio::Pipe,
stderr: Stdio::Pipe,
},
0,
0,
0,
options.conversations,
None,
options.tool_call,
);
let mut child = if let Some(deadline) = deadline {
tokio::time::timeout_at(deadline, start)
.await
.map_err(|_| anyhow::anyhow!("command timed out"))??
} else {
start.await?
};
let _guard = GroupGuard {
runtime: self.clone(),
group: child.pid,
};
drop(child.input);
let mut stdout = child.output.take().unwrap();
let mut stderr = child.error.take().unwrap();
let deadline = options.timeout.map(deadline::Deadline::new);
let run = async {
let (mut out_open, mut err_open) = (true, true);
let (mut out, mut err) = ([0; 8192], [0; 8192]);
while out_open || err_open {
tokio::select! {
n = stdout.read(&mut out), if out_open => { let n = n?; out_open = n != 0; if n != 0 { output(Stream::Stdout, &out[..n]); } }
n = stderr.read(&mut err), if err_open => { let n = n?; err_open = n != 0; if n != 0 { output(Stream::Stderr, &err[..n]); } }
let argv = match command {
Command::Shell(script) => vec![
"/bin/sh".into(),
"--noprofile".into(),
"--norc".into(),
"-c".into(),
script,
],
Command::Argv(argv) => argv,
};
let env = options.env;
let mut child = self
.start(
Request {
program: argv.first().cloned().unwrap_or_default(),
argv,
env,
cwd: options.cwd,
group: None,
stdin: Stdio::Pipe,
stdout: Stdio::Pipe,
stderr: Stdio::Pipe,
},
0,
0,
0,
options.conversations,
None,
options.tool_call,
deadline.clone(),
)
.await?;
let _guard = GroupGuard {
runtime: self.clone(),
group: child.pid,
};
drop(child.input);
let mut stdout = child.output.take().unwrap();
let mut stderr = child.error.take().unwrap();
let run = async {
let (mut out_open, mut err_open) = (true, true);
let (mut out, mut err) = ([0; 8192], [0; 8192]);
while out_open || err_open {
tokio::select! {
n = stdout.read(&mut out), if out_open => { let n = n?; out_open = n != 0; if n != 0 { output(Stream::Stdout, &out[..n]); } }
n = stderr.read(&mut err), if err_open => { let n = n?; err_open = n != 0; if n != 0 { output(Stream::Stderr, &err[..n]); } }
}
}
}
loop {
if let Some(code) = *child.code.borrow_and_update() {
return Ok(code);
loop {
if let Some(code) = *child.code.borrow_and_update() {
return Ok(code);
}
child.code.changed().await?;
}
child.code.changed().await?;
};
let result = run.await;
if let Some(journal) = self.world.root.journal() {
journal.retain().await?;
}
result
};
let result = if let Some(deadline) = deadline {
tokio::time::timeout_at(deadline, run)
.await
.map_err(|_| anyhow::anyhow!("command timed out"))?
if let Some(deadline) = &deadline {
tokio::select! {
result = run => result,
_ = deadline.expired() => anyhow::bail!("command timed out"),
}
} else {
run.await
};
if let Some(journal) = self.world.root.journal() {
journal.retain().await?;
}
result
}
}
/// Construct the harness's shared executor over the same open world.