Files
sausage/userland/exec-tools/src/timeout.rs
T
surma b9e3e3677d userland: search and exec tools, spawn library (T14)
rg (ripgrep 15.2), grep (ours, on ripgrep's regex crates), find and xargs
(ours), sed (uutils 0.3 wrapper), diff and cmp (uutils diffutils 0.6 with -r),
fd 10.5, jaq 2.3, env and timeout. userland/spawn is a small Command-like
client of the process interface. search-test.nu: 42 cases match native tools.
2026-11-06 17:21:51 +00:00

26 lines
1.3 KiB
Rust

//! Stop a world child after a deadline. Expiration returns status 124.
use sausage_spawn::Command;
use std::time::{Duration, Instant};
fn run() -> Result<i32, Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);
let duration = args.next().ok_or("a duration is required")?;
let (number, multiplier) = match duration.as_bytes().last() {
Some(b's') => (&duration[..duration.len()-1], 1.0),
Some(b'm') => (&duration[..duration.len()-1], 60.0),
Some(b'h') => (&duration[..duration.len()-1], 3600.0),
Some(b'd') => (&duration[..duration.len()-1], 86400.0),
_ => (&duration[..], 1.0),
};
let duration = Duration::try_from_secs_f64(number.parse::<f64>()? * multiplier)?;
let program = args.next().ok_or("a command is required")?;
let child = Command::new(program).args(args).new_group().spawn()?;
if duration.is_zero() { return Ok(child.wait()); }
let deadline = Instant::now() + duration;
loop {
if let Some(code) = child.try_wait() { return Ok(code); }
if Instant::now() >= deadline { child.kill(); child.wait(); return Ok(124); }
std::thread::sleep(Duration::from_millis(10).min(deadline.saturating_duration_since(Instant::now())));
}
}
fn main() { std::process::exit(match run() { Ok(code) => code, Err(e) => { eprintln!("timeout: {e}"); 125 } }); }