1use anyhow::Result;
2#[allow(clippy::disallowed_types, clippy::disallowed_methods)]
3use std::process::{Child, ExitStatus};
4use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
5use std::sync::{Arc, Mutex};
6
7use crate::contract::BackgroundHandle;
8
9#[allow(clippy::disallowed_types, clippy::disallowed_methods)]
13struct ChildInner {
14 child: Option<Child>,
15 io_threads: Vec<std::thread::JoinHandle<()>>,
16 reaped: bool,
17 exit_status: Option<ExitStatus>,
18 killed: AtomicBool,
19}
20
21#[derive(Clone)]
22#[allow(clippy::disallowed_types, clippy::disallowed_methods)]
23pub struct ChildHandle {
24 inner: Arc<Mutex<ChildInner>>,
25 pid: Arc<AtomicU32>,
28}
29
30impl ChildHandle {
31 #[allow(clippy::disallowed_types, clippy::disallowed_methods)]
32 pub(crate) fn new(child: Child, io_threads: Vec<std::thread::JoinHandle<()>>) -> Self {
33 #[cfg(unix)]
34 let pid = child.id();
35 #[cfg(windows)]
36 let pid = child.id();
37 Self {
38 inner: Arc::new(Mutex::new(ChildInner {
39 child: Some(child),
40 io_threads,
41 reaped: false,
42 exit_status: None,
43 killed: AtomicBool::new(false),
44 })),
45 pid: Arc::new(AtomicU32::new(pid)),
46 }
47 }
48}
49
50impl BackgroundHandle for ChildHandle {
51 fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
52 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
53 if let Some(ref mut child) = guard.child {
54 match child.try_wait()? {
55 Some(status) => {
56 guard.reaped = true;
57 guard.exit_status = Some(status);
58 self.pid.store(0, Ordering::SeqCst);
60 for thread in guard.io_threads.drain(..) {
61 let _ = thread.join();
62 }
63 guard.child = None;
64 Ok(Some(status))
65 }
66 None => Ok(None),
67 }
68 } else if guard.reaped {
69 Ok(Some(
71 guard
72 .exit_status
73 .unwrap_or_else(|| exit_status_from_code(0)),
74 ))
75 } else {
76 Ok(None)
78 }
79 }
80
81 fn wait(&mut self) -> Result<ExitStatus> {
82 let child_opt = {
85 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
86 guard.child.take()
87 };
88
89 if let Some(mut child) = child_opt {
90 let status = child.wait()?;
91 self.pid.store(0, Ordering::SeqCst);
93 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
95 for thread in guard.io_threads.drain(..) {
96 let _ = thread.join();
97 }
98 guard.reaped = true;
99 guard.exit_status = Some(status);
100 Ok(status)
101 } else {
102 let guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
104 Ok(guard
105 .exit_status
106 .unwrap_or_else(|| exit_status_from_code(0)))
107 }
108 }
109
110 fn kill(&mut self) -> Result<()> {
111 let pid = self.pid.load(Ordering::SeqCst);
112 if pid == 0 {
113 return Ok(());
114 }
115 #[cfg(unix)]
118 {
119 unsafe {
120 libc::kill(pid as i32, libc::SIGKILL);
121 }
122 }
123 #[cfg(windows)]
124 {
125 use windows_sys::Win32::Foundation::CloseHandle;
131 use windows_sys::Win32::System::Threading::{
132 OpenProcess, PROCESS_TERMINATE, TerminateProcess,
133 };
134 unsafe {
135 let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
136 if !handle.is_null() {
137 TerminateProcess(handle, 1);
138 CloseHandle(handle);
139 }
140 }
141 }
142 self.inner
143 .lock()
144 .unwrap_or_else(|e| e.into_inner())
145 .killed
146 .store(true, Ordering::SeqCst);
147 Ok(())
148 }
149}
150
151impl Drop for ChildHandle {
152 fn drop(&mut self) {
153 if Arc::strong_count(&self.inner) > 1 {
155 return;
156 }
157 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
158 if guard.reaped {
159 return;
160 }
161 if let Some(ref mut child) = guard.child
162 && matches!(child.try_wait(), Ok(None))
163 {
164 let _ = child.kill();
165 let _ = child.wait();
166 }
167 guard.reaped = true;
168 }
172}
173
174fn exit_status_from_code(code: i32) -> ExitStatus {
176 #[cfg(unix)]
177 {
178 use std::os::unix::process::ExitStatusExt;
179 ExitStatus::from_raw(code << 8)
180 }
181 #[cfg(windows)]
182 {
183 use std::os::windows::process::ExitStatusExt;
184 ExitStatus::from_raw(code as u32)
185 }
186}