Running a program
nuclei::process::Command is shaped like std's: arguments, environment, working directory and standard streams, then output (collect what it printed), status (only the exit) or spawn (a Child to talk to). The examples run Unix programs.
use nuclei::process::Command;
#[nuclei::main]
async fn main() -> std::io::Result<()> {
// Run a program to its end and collect what it printed.
let output = Command::new("echo").arg("hello").output().await?;
assert!(output.status.success());
assert_eq!(output.stdout, b"hello\n");
// Or just its exit status, its streams left as they are.
let status = Command::new("true").status().await?;
assert_eq!(status.code(), Some(0));
Ok(())
}A shard's driver watches each child's exit (a pidfd on Linux, a process event on kqueue, a wait on the process handle on Windows) and reaps it, so a wait holds no thread, and a child whose Child was dropped leaves no zombie behind.
Talking to a child
Piped streams are async: write to stdin, read stdout and stderr with the IO helpers. Dropping stdin ends the child's input; wait closes it first too, so a child that reads to the end can exit.
use nuclei::io::{AsyncReadExt, AsyncWriteExt};
use nuclei::process::{Command, Stdio};
#[nuclei::main]
async fn main() -> std::io::Result<()> {
let mut child = Command::new("sort")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
// The pipes are async streams.
let mut stdin = child.stdin.take().expect("piped");
stdin.write_all(b"pear\napple\nfig\n").await?;
// Dropping stdin ends the child's input, so sort can finish.
drop(stdin);
let mut stdout = child.stdout.take().expect("piped");
let mut sorted = String::new();
stdout.read_to_string(&mut sorted).await?;
assert_eq!(sorted, "apple\nfig\npear\n");
assert!(child.wait().await?.success());
Ok(())
}Ending a child
use nuclei::process::Command;
#[nuclei::main]
async fn main() -> std::io::Result<()> {
// kill ends the child and waits for it to go.
let mut child = Command::new("sleep").arg("60").spawn()?;
child.kill().await?;
assert!(!child.wait().await?.success());
// start_kill only starts it; wait to see it go.
let mut child = Command::new("sleep").arg("60").spawn()?;
child.start_kill()?;
child.wait().await?;
// kill_on_drop: dropping the Child ends the process too.
let child = Command::new("sleep").arg("60").kill_on_drop(true).spawn()?;
drop(child);
Ok(())
}kill().awaitends the child (SIGKILL on Unix, TerminateProcess on Windows) and waits for its exit;start_kill()only starts it.kill_on_drop(true)ends the child when itsChildis dropped before it exits. By default a dropped child runs on.try_wait()gives the status if the child exited, without waiting.- For what
Commanddoes not mirror,as_std_mutreaches std's command underneath,spawn_withstarts the child through your own code, andinto_stdtakes std's command back.
Ctrl-C
nuclei::signal::ctrl_c() completes when the process gets a Ctrl-C: SIGINT on Unix, the console's CTRL_C_EVENT on Windows. Listening starts with its first poll, and replaces the default action, which would end the process.
use std::time::Duration;
use nuclei::time::sleep;
#[nuclei::main]
async fn main() {
nuclei::select! {
_ = nuclei::signal::ctrl_c() => println!("stopping on Ctrl-C"),
_ = sleep(Duration::from_millis(100)) => println!("the work finished first"),
}
}the work finished firstUnix signals
signal::unix::signal(kind) listens to one signal from then on, as a stream: recv().await ends once it was delivered, once or more, since the last recv. SignalKind names the usual ones (hangup, terminate, user_defined1, ...) and from_raw any other.
use nuclei::process::Command;
use nuclei::signal::unix::{SignalKind, signal};
#[nuclei::main]
async fn main() -> std::io::Result<()> {
// Listening starts here; the signal's default action is replaced.
let mut reload = signal(SignalKind::user_defined1())?;
let mut hangup = signal(SignalKind::hangup())?;
// Send this process SIGUSR1, as an operator would with `kill -USR1`.
let pid = std::process::id().to_string();
Command::new("kill").args(["-USR1", &pid]).status().await?;
nuclei::select! {
_ = reload.recv() => println!("reloading"),
_ = hangup.recv() => println!("hung up"),
}
Ok(())
}reloadingWindows console events
signal::windows has ctrl_c, ctrl_break, ctrl_close, ctrl_logoff and ctrl_shutdown, each a CtrlEvent whose recv().await ends at the next such event.