use std::process::Command;
use std::sync::mpsc::{Sender, channel};
use std::sync::{Mutex, PoisonError};
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "linux")]
pub use linux::{is_zombie, live_pid_is_subprocess};
#[cfg(target_os = "macos")]
mod darwin;
#[cfg(target_os = "macos")]
pub use darwin::{is_zombie, live_pid_is_subprocess};
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
mod unsupported;
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub use unsupported::{is_zombie, live_pid_is_subprocess};
type SpawnReply = Sender<std::io::Result<std::process::Child>>;
type SpawnRequest = (Command, SpawnReply);
static SPAWNER: Mutex<Option<Sender<SpawnRequest>>> = Mutex::new(None);
pub fn spawn_supervised(cmd: Command) -> std::io::Result<u32> {
let child = spawn_owned_supervised(cmd)?;
let pid = child.id();
drop(child);
Ok(pid)
}
pub fn spawn_owned_supervised(cmd: Command) -> std::io::Result<std::process::Child> {
let sender = spawner()?;
let (reply_tx, reply_rx) = channel();
sender
.send((cmd, reply_tx))
.map_err(|error| std::io::Error::other(error.to_string()))?;
reply_rx
.recv()
.map_err(|error| std::io::Error::other(error.to_string()))?
}
fn spawner() -> std::io::Result<Sender<SpawnRequest>> {
let mut slot = SPAWNER.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(sender) = slot.as_ref() {
return Ok(sender.clone());
}
let sender = start_spawner_thread()?;
Ok(slot.insert(sender).clone())
}
fn start_spawner_thread() -> std::io::Result<Sender<SpawnRequest>> {
let (tx, rx) = channel::<SpawnRequest>();
std::thread::Builder::new()
.name("subprocess-spawner".to_owned())
.spawn(move || {
while let Ok((mut cmd, reply)) = rx.recv() {
let outcome = spawn_on_this_thread(&mut cmd);
if let Err(undelivered) = reply.send(outcome)
&& let Ok(mut child) = undelivered.0
{
if let Err(error) = child.kill() {
tracing::warn!(error = %error, "Failed to stop unclaimed subprocess");
}
if let Err(error) = child.wait() {
tracing::warn!(error = %error, "Failed to reap unclaimed subprocess");
}
}
}
})
.map(|_handle| tx)
}
fn spawn_on_this_thread(cmd: &mut Command) -> std::io::Result<std::process::Child> {
#[cfg(target_os = "linux")]
linux::arm_parent_death_signal(cmd);
cmd.spawn()
}
#[cfg(unix)]
pub fn place_in_own_process_group(command: &mut Command) {
use std::os::unix::process::CommandExt;
command.process_group(0);
}
#[cfg(windows)]
pub fn place_in_own_process_group(command: &mut Command) {
use std::os::windows::process::CommandExt;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
command.creation_flags(CREATE_NEW_PROCESS_GROUP);
}