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(
33 child: Child,
34 stdin_thread: Option<std::thread::JoinHandle<()>>,
35 io_threads: Vec<std::thread::JoinHandle<()>>,
36 ) -> Self {
37 let _ = stdin_thread;
46 #[cfg(unix)]
47 let pid = child.id();
48 #[cfg(windows)]
49 let pid = child.id();
50 Self {
51 inner: Arc::new(Mutex::new(ChildInner {
52 child: Some(child),
53 io_threads,
54 reaped: false,
55 exit_status: None,
56 killed: AtomicBool::new(false),
57 })),
58 pid: Arc::new(AtomicU32::new(pid)),
59 }
60 }
61}
62
63impl BackgroundHandle for ChildHandle {
64 fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
65 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
66 if let Some(ref mut child) = guard.child {
67 match child.try_wait()? {
68 Some(status) => {
69 guard.reaped = true;
70 guard.exit_status = Some(status);
71 self.pid.store(0, Ordering::SeqCst);
73 for thread in guard.io_threads.drain(..) {
77 let _ = thread.join();
78 }
79 guard.child = None;
80 Ok(Some(status))
81 }
82 None => Ok(None),
83 }
84 } else if guard.reaped {
85 Ok(Some(
87 guard
88 .exit_status
89 .unwrap_or_else(|| exit_status_from_code(0)),
90 ))
91 } else {
92 Ok(None)
94 }
95 }
96
97 fn wait(&mut self) -> Result<ExitStatus> {
98 let child_opt = {
101 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
102 guard.child.take()
103 };
104
105 if let Some(mut child) = child_opt {
106 let status = child.wait()?;
107 self.pid.store(0, Ordering::SeqCst);
109 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
113 for thread in guard.io_threads.drain(..) {
114 let _ = thread.join();
115 }
116 guard.reaped = true;
117 guard.exit_status = Some(status);
118 Ok(status)
119 } else {
120 let guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
122 Ok(guard
123 .exit_status
124 .unwrap_or_else(|| exit_status_from_code(0)))
125 }
126 }
127
128 fn kill(&mut self) -> Result<()> {
129 let pid = self.pid.load(Ordering::SeqCst);
130 if pid == 0 {
131 return Ok(());
132 }
133 #[cfg(unix)]
136 {
137 unsafe {
138 libc::kill(pid as i32, libc::SIGKILL);
139 }
140 }
141 #[cfg(windows)]
142 {
143 use windows_sys::Win32::Foundation::CloseHandle;
149 use windows_sys::Win32::System::Threading::{
150 OpenProcess, PROCESS_TERMINATE, TerminateProcess,
151 };
152 unsafe {
153 let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
154 if !handle.is_null() {
155 TerminateProcess(handle, 1);
156 CloseHandle(handle);
157 }
158 }
159 }
160 self.inner
161 .lock()
162 .unwrap_or_else(|e| e.into_inner())
163 .killed
164 .store(true, Ordering::SeqCst);
165 Ok(())
166 }
167}
168
169impl Drop for ChildHandle {
170 fn drop(&mut self) {
171 if Arc::strong_count(&self.inner) > 1 {
173 return;
174 }
175 let mut guard = self.inner.lock().unwrap_or_else(|e| e.into_inner());
176 if guard.reaped {
177 return;
178 }
179 if let Some(ref mut child) = guard.child
180 && matches!(child.try_wait(), Ok(None))
181 {
182 let _ = child.kill();
183 let _ = child.wait();
184 }
185 guard.reaped = true;
186 }
190}
191
192fn exit_status_from_code(code: i32) -> ExitStatus {
194 #[cfg(unix)]
195 {
196 use std::os::unix::process::ExitStatusExt;
197 ExitStatus::from_raw(code << 8)
198 }
199 #[cfg(windows)]
200 {
201 use std::os::windows::process::ExitStatusExt;
202 ExitStatus::from_raw(code as u32)
203 }
204}