nextest_runner/runner/
unix.rs1use super::{InternalTerminateReason, ShutdownRequest, TerminateChildResult, UnitContext};
5use crate::{
6 errors::ConfigureHandleInheritanceError,
7 reporter::events::{
8 UnitState, UnitTerminateMethod, UnitTerminateReason, UnitTerminateSignal,
9 UnitTerminatingState,
10 },
11 runner::{RunUnitQuery, RunUnitRequest, SignalRequest},
12 signal::{JobControlEvent, ShutdownEvent},
13 test_command::ChildAccumulator,
14 time::StopwatchStart,
15};
16use libc::{SIGCONT, SIGHUP, SIGINT, SIGKILL, SIGQUIT, SIGSTOP, SIGTERM, SIGTSTP};
17use std::{convert::Infallible, os::unix::process::CommandExt, time::Duration};
18use tokio::{process::Child, sync::mpsc::UnboundedReceiver};
19
20pub(super) fn configure_handle_inheritance_impl(
22 _no_capture: bool,
23) -> Result<(), ConfigureHandleInheritanceError> {
24 Ok(())
25}
26
27pub(super) fn set_process_group(cmd: &mut std::process::Command) {
31 cmd.process_group(0);
32}
33
34#[derive(Debug)]
35pub(super) struct Job(());
36
37impl Job {
38 pub(super) fn create() -> Result<Self, Infallible> {
39 Ok(Self(()))
40 }
41}
42
43pub(super) fn assign_process_to_job(
44 _child: &tokio::process::Child,
45 _job: Option<&Job>,
46) -> Result<(), Infallible> {
47 Ok(())
48}
49
50pub(super) fn job_control_child(child: &Child, event: JobControlEvent) {
51 if let Some(pid) = child.id() {
52 let pid = pid as i32;
53 let signal = match event {
55 JobControlEvent::Stop => SIGTSTP,
56 JobControlEvent::Continue => SIGCONT,
57 };
58 unsafe {
59 libc::kill(-pid, signal);
62 }
63 } else {
64 }
66}
67
68pub(super) fn raise_stop() {
70 unsafe { libc::raise(SIGSTOP) };
72}
73
74#[expect(clippy::too_many_arguments)]
80pub(super) async fn terminate_child<'a>(
81 cx: &UnitContext<'a>,
82 child: &mut Child,
83 child_acc: &mut ChildAccumulator,
84 reason: InternalTerminateReason,
85 stopwatch: &mut StopwatchStart,
86 req_rx: &mut UnboundedReceiver<RunUnitRequest<'a>>,
87 _job: Option<&Job>,
88 grace_period: Duration,
89) -> TerminateChildResult {
90 let Some(pid) = child.id() else {
91 return TerminateChildResult::Exited;
92 };
93
94 let pid_i32 = pid as i32;
95 let (term_reason, term_method) = to_terminate_reason_and_method(&reason, grace_period);
96
97 #[allow(clippy::infallible_destructuring_match)]
99 let term_signal = match term_method {
100 UnitTerminateMethod::Signal(term_signal) => term_signal,
101 #[cfg(test)]
102 UnitTerminateMethod::Fake => {
103 unreachable!("fake method is only used for reporter tests")
104 }
105 };
106
107 unsafe {
108 libc::kill(-pid_i32, term_signal.signal())
111 };
112
113 if term_signal == UnitTerminateSignal::Kill {
114 return TerminateChildResult::Killed;
116 }
117
118 let mut sleep = std::pin::pin!(crate::time::pausable_sleep(grace_period));
119 let mut waiting_stopwatch = crate::time::stopwatch();
120
121 loop {
122 tokio::select! {
123 () = child_acc.fill_buf(), if !child_acc.fds.is_done() => {}
124 _ = child.wait() => {
125 break TerminateChildResult::Exited;
127 }
128 recv = req_rx.recv() => {
129 let req = recv.expect("a RecvError should never happen here");
133
134 match req {
135 RunUnitRequest::Signal(SignalRequest::Stop(sender)) => {
136 stopwatch.pause();
137 sleep.as_mut().pause();
138 waiting_stopwatch.pause();
139
140 job_control_child(child, JobControlEvent::Stop);
141 let _ = sender.send(());
142 }
143 RunUnitRequest::Signal(SignalRequest::Continue) => {
144 if !sleep.is_paused() {
146 stopwatch.resume();
147 sleep.as_mut().resume();
148 waiting_stopwatch.resume();
149 }
150 job_control_child(child, JobControlEvent::Continue);
151 }
152 RunUnitRequest::Signal(SignalRequest::Shutdown(_)) => {
153 unsafe {
156 libc::kill(-pid_i32, SIGKILL);
158 }
159 break TerminateChildResult::Killed;
160 }
161 RunUnitRequest::OtherCancel => {
162 }
165 RunUnitRequest::Query(RunUnitQuery::GetInfo(sender)) => {
166 let waiting_snapshot = waiting_stopwatch.snapshot();
167 _ = sender.send(
168 cx.info_response(
169 UnitState::Terminating(UnitTerminatingState {
170 pid,
171 time_taken: stopwatch.snapshot().active,
172 reason: term_reason,
173 method: term_method,
174 waiting_duration: waiting_snapshot.active,
175 remaining: grace_period
176 .checked_sub(waiting_snapshot.active)
177 .unwrap_or_default(),
178 }),
179 child_acc.snapshot_in_progress(cx.packet().kind().waiting_on_message()),
180 )
181 );
182 }
183 }
184 }
185 _ = &mut sleep => {
186 unsafe {
188 libc::kill(-pid_i32, SIGKILL);
190 }
191 break TerminateChildResult::Killed;
192 }
193 }
194 }
195}
196
197fn to_terminate_reason_and_method(
198 reason: &InternalTerminateReason,
199 grace_period: Duration,
200) -> (UnitTerminateReason, UnitTerminateMethod) {
201 match reason {
202 InternalTerminateReason::Timeout => (
203 UnitTerminateReason::Timeout,
204 timeout_terminate_method(grace_period),
205 ),
206 InternalTerminateReason::Signal(req) => (
207 UnitTerminateReason::Signal,
208 shutdown_terminate_method(*req, grace_period),
209 ),
210 }
211}
212
213fn timeout_terminate_method(grace_period: Duration) -> UnitTerminateMethod {
214 if grace_period.is_zero() {
215 UnitTerminateMethod::Signal(UnitTerminateSignal::Kill)
216 } else {
217 UnitTerminateMethod::Signal(UnitTerminateSignal::Term)
218 }
219}
220
221fn shutdown_terminate_method(req: ShutdownRequest, grace_period: Duration) -> UnitTerminateMethod {
222 if grace_period.is_zero() {
223 return UnitTerminateMethod::Signal(UnitTerminateSignal::Kill);
224 }
225
226 match req {
227 ShutdownRequest::Once(ShutdownEvent::Hangup) => {
228 UnitTerminateMethod::Signal(UnitTerminateSignal::Hangup)
229 }
230 ShutdownRequest::Once(ShutdownEvent::Term) => {
231 UnitTerminateMethod::Signal(UnitTerminateSignal::Term)
232 }
233 ShutdownRequest::Once(ShutdownEvent::Quit) => {
234 UnitTerminateMethod::Signal(UnitTerminateSignal::Quit)
235 }
236 ShutdownRequest::Once(ShutdownEvent::Interrupt) => {
237 UnitTerminateMethod::Signal(UnitTerminateSignal::Interrupt)
238 }
239 ShutdownRequest::Twice => UnitTerminateMethod::Signal(UnitTerminateSignal::Kill),
240 }
241}
242
243impl UnitTerminateSignal {
244 fn signal(self) -> libc::c_int {
245 match self {
246 UnitTerminateSignal::Interrupt => SIGINT,
247 UnitTerminateSignal::Term => SIGTERM,
248 UnitTerminateSignal::Hangup => SIGHUP,
249 UnitTerminateSignal::Quit => SIGQUIT,
250 UnitTerminateSignal::Kill => SIGKILL,
251 }
252 }
253}