systemprompt_loader/subprocess/
mod.rs1use std::process::Command;
46use std::sync::mpsc::{Sender, channel};
47use std::sync::{Mutex, PoisonError};
48
49#[cfg(target_os = "linux")]
50mod linux;
51#[cfg(target_os = "linux")]
52pub use linux::{is_zombie, live_pid_is_subprocess};
53
54#[cfg(target_os = "macos")]
55mod darwin;
56#[cfg(target_os = "macos")]
57pub use darwin::{is_zombie, live_pid_is_subprocess};
58
59#[cfg(not(any(target_os = "linux", target_os = "macos")))]
60mod unsupported;
61#[cfg(not(any(target_os = "linux", target_os = "macos")))]
62pub use unsupported::{is_zombie, live_pid_is_subprocess};
63
64type SpawnReply = Sender<std::io::Result<std::process::Child>>;
65type SpawnRequest = (Command, SpawnReply);
66
67static SPAWNER: Mutex<Option<Sender<SpawnRequest>>> = Mutex::new(None);
68
69pub fn spawn_supervised(cmd: Command) -> std::io::Result<u32> {
70 let child = spawn_owned_supervised(cmd)?;
71 let pid = child.id();
72 drop(child);
73 Ok(pid)
74}
75
76pub fn spawn_owned_supervised(cmd: Command) -> std::io::Result<std::process::Child> {
77 let sender = spawner()?;
78 let (reply_tx, reply_rx) = channel();
79 sender
80 .send((cmd, reply_tx))
81 .map_err(|error| std::io::Error::other(error.to_string()))?;
82 reply_rx
83 .recv()
84 .map_err(|error| std::io::Error::other(error.to_string()))?
85}
86
87fn spawner() -> std::io::Result<Sender<SpawnRequest>> {
88 let mut slot = SPAWNER.lock().unwrap_or_else(PoisonError::into_inner);
89 if let Some(sender) = slot.as_ref() {
90 return Ok(sender.clone());
91 }
92 let sender = start_spawner_thread()?;
93 Ok(slot.insert(sender).clone())
94}
95
96fn start_spawner_thread() -> std::io::Result<Sender<SpawnRequest>> {
97 let (tx, rx) = channel::<SpawnRequest>();
98 std::thread::Builder::new()
99 .name("subprocess-spawner".to_owned())
100 .spawn(move || {
101 while let Ok((mut cmd, reply)) = rx.recv() {
102 let outcome = spawn_on_this_thread(&mut cmd);
103 if let Err(undelivered) = reply.send(outcome)
104 && let Ok(mut child) = undelivered.0
105 {
106 if let Err(error) = child.kill() {
107 tracing::warn!(error = %error, "Failed to stop unclaimed subprocess");
108 }
109 if let Err(error) = child.wait() {
110 tracing::warn!(error = %error, "Failed to reap unclaimed subprocess");
111 }
112 }
113 }
114 })
115 .map(|_handle| tx)
116}
117
118fn spawn_on_this_thread(cmd: &mut Command) -> std::io::Result<std::process::Child> {
119 #[cfg(target_os = "linux")]
120 linux::arm_parent_death_signal(cmd);
121 cmd.spawn()
122}
123
124#[cfg(unix)]
127pub fn place_in_own_process_group(command: &mut Command) {
128 use std::os::unix::process::CommandExt;
129 command.process_group(0);
130}
131
132#[cfg(windows)]
133pub fn place_in_own_process_group(command: &mut Command) {
134 use std::os::windows::process::CommandExt;
135 const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
136 command.creation_flags(CREATE_NEW_PROCESS_GROUP);
137}