rightkit_child_process/
registry.rs1use crate::{process_handle::ProcessHandle, TerminationOptions};
17use std::{
18 io,
19 sync::{Arc, Mutex, MutexGuard, Weak},
20};
21
22#[cfg(unix)]
23type PlatformChild = crate::unix_child::UnixChild;
24#[cfg(windows)]
25type PlatformChild = crate::spawn_windows::WindowsChild;
26
27struct Entry {
28 key: usize,
30 pid: u32,
31 child: Weak<Mutex<PlatformChild>>,
32 #[cfg(windows)]
33 process: ProcessHandle,
34 #[cfg(windows)]
35 job: Weak<crate::process_windows::KillOnCloseJob>,
36}
37
38static OWNED: Mutex<Vec<Entry>> = Mutex::new(Vec::new());
39
40fn owned() -> MutexGuard<'static, Vec<Entry>> {
41 OWNED.lock().unwrap_or_else(|poison| poison.into_inner())
42}
43
44fn key_of(child: &Arc<Mutex<PlatformChild>>) -> usize {
45 Arc::as_ptr(child) as usize
46}
47
48#[cfg(unix)]
49pub(crate) fn register(child: &Arc<Mutex<PlatformChild>>, pid: u32) {
50 let mut list = owned();
51 list.retain(|entry| entry.child.strong_count() > 0);
52 list.push(Entry {
53 key: key_of(child),
54 pid,
55 child: Arc::downgrade(child),
56 });
57}
58
59#[cfg(windows)]
60pub(crate) fn register(
61 child: &Arc<Mutex<PlatformChild>>,
62 handle: &ProcessHandle,
63 job: Weak<crate::process_windows::KillOnCloseJob>,
64 pid: u32,
65) {
66 let Ok(process) = handle.try_clone() else {
69 return;
70 };
71 let mut list = owned();
72 list.retain(|entry| entry.child.strong_count() > 0);
73 list.push(Entry {
74 key: key_of(child),
75 pid,
76 child: Arc::downgrade(child),
77 process,
78 job,
79 });
80}
81
82pub(crate) fn forget(child: &Arc<Mutex<PlatformChild>>) {
84 let key = key_of(child);
85 owned().retain(|entry| entry.key != key);
86}
87
88pub fn owned_count() -> usize {
91 let mut list = owned();
92 list.retain(|entry| entry.child.strong_count() > 0);
93 list.len()
94}
95
96#[derive(Debug, Default)]
98pub struct TerminateAllReport {
99 pub terminated: Vec<u32>,
101 pub already_gone: Vec<u32>,
104 pub failed: Vec<(u32, io::Error)>,
106}
107
108pub fn terminate_all_owned(options: TerminationOptions) -> TerminateAllReport {
124 #[cfg(unix)]
125 {
126 terminate_all_unix(options)
127 }
128 #[cfg(windows)]
129 {
130 terminate_all_windows(options)
131 }
132}
133
134#[cfg(unix)]
135fn terminate_all_unix(options: TerminationOptions) -> TerminateAllReport {
136 use crate::{
137 owned::{await_tree_shared, signal_shared},
138 unix_child::{SignalOutcome, SignalTarget},
139 };
140 use std::time::{Duration, Instant, SystemTime};
141
142 const REAP_BUDGET: Duration = Duration::from_secs(2);
143
144 let live: Vec<(u32, Arc<Mutex<PlatformChild>>)> = {
145 let mut list = owned();
146 list.retain(|entry| entry.child.strong_count() > 0);
147 list.iter()
148 .filter_map(|entry| entry.child.upgrade().map(|child| (entry.pid, child)))
149 .collect()
150 };
151 let mut report = TerminateAllReport::default();
152
153 let mut asked: Vec<(u32, Arc<Mutex<PlatformChild>>, ProcessHandle)> = Vec::new();
155 for (pid, child) in live {
156 let handle = match ProcessHandle::new(&child, SystemTime::now()) {
157 Ok(handle) => handle,
158 Err(error) => {
159 report.failed.push((pid, error));
160 continue;
161 }
162 };
163 match signal_shared(&child, SignalTarget::Group, libc::SIGTERM) {
164 Ok(SignalOutcome::Sent) => asked.push((pid, child, handle)),
165 Ok(_) => report.already_gone.push(pid),
166 Err(error) => report.failed.push((pid, error)),
167 }
168 }
169
170 let deadline = Instant::now().checked_add(options.grace);
172 for (pid, child, handle) in &asked {
173 let left = deadline.map_or(Duration::ZERO, |at| {
174 at.saturating_duration_since(Instant::now())
175 });
176 if let Err(error) = await_tree_shared(child, handle, left, true) {
177 report.failed.push((*pid, error));
178 }
179 }
180
181 let reap_deadline = Instant::now().checked_add(REAP_BUDGET);
183 for (pid, child, handle) in &asked {
184 match signal_shared(child, SignalTarget::Group, libc::SIGKILL) {
185 Ok(_) => {
186 let left = reap_deadline.map_or(Duration::ZERO, |at| {
187 at.saturating_duration_since(Instant::now())
188 });
189 let _ = handle.wait_timeout(left);
191 if !report.failed.iter().any(|(failed, _)| failed == pid) {
192 report.terminated.push(*pid);
193 }
194 }
195 Err(error) => report.failed.push((*pid, error)),
196 }
197 }
198 report
199}
200
201#[cfg(windows)]
202fn terminate_all_windows(options: TerminationOptions) -> TerminateAllReport {
203 let live: Vec<(
204 u32,
205 ProcessHandle,
206 Option<Arc<crate::process_windows::KillOnCloseJob>>,
207 )> = {
208 let mut list = owned();
209 list.retain(|entry| entry.child.strong_count() > 0);
210 list.iter()
211 .filter_map(|entry| {
212 Some((
213 entry.pid,
214 entry.process.try_clone().ok()?,
215 entry.job.upgrade(),
216 ))
217 })
218 .collect()
219 };
220 let mut report = TerminateAllReport::default();
221 for (pid, process, job) in live {
222 if job.is_none() && matches!(process.try_wait(), Ok(Some(_))) {
223 report.already_gone.push(pid);
224 continue;
225 }
226 match crate::process_windows::terminate(&process, job.as_deref(), options.windows_exit_code)
227 {
228 Ok(()) => report.terminated.push(pid),
229 Err(error) => report.failed.push((pid, error)),
230 }
231 }
232 report
233}