1use crate::errors::SignalHandlerSetupError;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
13pub enum SignalHandlerKind {
14 Standard,
17
18 DebuggerMode,
22
23 Noop,
25}
26
27impl SignalHandlerKind {
28 pub(crate) fn build(self) -> Result<SignalHandler, SignalHandlerSetupError> {
29 match self {
30 Self::Standard => SignalHandler::new(),
31 Self::DebuggerMode => SignalHandler::debugger_mode(),
32 Self::Noop => Ok(SignalHandler::noop()),
33 }
34 }
35}
36
37#[derive(Debug)]
39pub(crate) struct SignalHandler {
40 signals: Option<imp::Signals>,
41}
42
43impl SignalHandler {
44 #[cfg(any(unix, windows))]
46 pub(crate) fn new() -> Result<Self, SignalHandlerSetupError> {
47 let signals = imp::Signals::new()?;
48 Ok(Self {
49 signals: Some(signals),
50 })
51 }
52
53 #[cfg(any(unix, windows))]
55 pub(crate) fn debugger_mode() -> Result<Self, SignalHandlerSetupError> {
56 let signals = imp::Signals::debugger_mode()?;
57 Ok(Self {
58 signals: Some(signals),
59 })
60 }
61
62 pub(crate) fn noop() -> Self {
64 Self { signals: None }
65 }
66
67 pub(crate) async fn recv(&mut self) -> Option<SignalEvent> {
68 match &mut self.signals {
69 Some(signals) => signals.recv().await,
70 None => None,
71 }
72 }
73}
74
75#[cfg(unix)]
76mod imp {
77 use super::*;
78 use std::io;
79 use tokio::signal::unix::{SignalKind, signal};
80 use tokio_stream::{StreamExt, StreamMap, wrappers::SignalStream};
81
82 #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
83 enum SignalId {
84 Int,
85 Hup,
86 Term,
87 Quit,
88 Tstp,
89 Cont,
90 Info,
91 Usr1,
92 }
93
94 #[derive(Debug)]
96 pub(super) struct Signals {
97 map: StreamMap<SignalId, SignalStream>,
100 sigquit_as_info: bool,
101 }
102
103 impl Signals {
104 pub(super) fn new() -> io::Result<Self> {
105 let mut map = StreamMap::new();
106
107 map.extend([
109 (SignalId::Int, signal_stream(SignalKind::interrupt())?),
110 (SignalId::Hup, signal_stream(SignalKind::hangup())?),
111 (SignalId::Term, signal_stream(SignalKind::terminate())?),
112 (SignalId::Quit, signal_stream(SignalKind::quit())?),
113 (SignalId::Tstp, signal_stream(tstp_kind())?),
114 (SignalId::Cont, signal_stream(cont_kind())?),
115 (SignalId::Usr1, signal_stream(SignalKind::user_defined1())?),
116 ]);
117
118 if let Some(info_kind) = info_kind() {
119 map.insert(SignalId::Info, signal_stream(info_kind)?);
120 }
121
122 let sigquit_as_info =
126 std::env::var("__NEXTEST_SIGQUIT_AS_INFO").is_ok_and(|v| v == "1");
127
128 Ok(Self {
129 map,
130 sigquit_as_info,
131 })
132 }
133
134 pub(super) fn debugger_mode() -> io::Result<Self> {
143 use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
144
145 let ignore_action =
148 SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty());
149
150 unsafe {
151 let _ = sigaction(Signal::SIGINT, &ignore_action);
152 let _ = sigaction(Signal::SIGQUIT, &ignore_action);
153 }
154
155 let mut map = StreamMap::new();
156
157 map.extend([
161 (SignalId::Hup, signal_stream(SignalKind::hangup())?),
162 (SignalId::Term, signal_stream(SignalKind::terminate())?),
163 (SignalId::Tstp, signal_stream(tstp_kind())?),
164 (SignalId::Cont, signal_stream(cont_kind())?),
165 ]);
166
167 Ok(Self {
168 map,
169 sigquit_as_info: false,
170 })
171 }
172
173 pub(super) async fn recv(&mut self) -> Option<SignalEvent> {
174 self.map.next().await.map(|(id, _)| match id {
175 SignalId::Int => {
176 SignalEvent::Shutdown(ShutdownEvent::Signal(ShutdownSignalEvent::Interrupt))
177 }
178 SignalId::Hup => {
179 SignalEvent::Shutdown(ShutdownEvent::Signal(ShutdownSignalEvent::Hangup))
180 }
181 SignalId::Term => {
182 SignalEvent::Shutdown(ShutdownEvent::Signal(ShutdownSignalEvent::Term))
183 }
184 SignalId::Quit => {
185 if self.sigquit_as_info {
186 SignalEvent::Info(SignalInfoEvent::Info)
187 } else {
188 SignalEvent::Shutdown(ShutdownEvent::Signal(ShutdownSignalEvent::Quit))
189 }
190 }
191 SignalId::Tstp => SignalEvent::JobControl(JobControlEvent::Stop),
192 SignalId::Cont => SignalEvent::JobControl(JobControlEvent::Continue),
193 SignalId::Info => SignalEvent::Info(SignalInfoEvent::Info),
194 SignalId::Usr1 => SignalEvent::Info(SignalInfoEvent::Usr1),
195 })
196 }
197 }
198
199 fn signal_stream(kind: SignalKind) -> io::Result<SignalStream> {
200 Ok(SignalStream::new(signal(kind)?))
201 }
202
203 fn tstp_kind() -> SignalKind {
204 SignalKind::from_raw(libc::SIGTSTP)
205 }
206
207 fn cont_kind() -> SignalKind {
208 SignalKind::from_raw(libc::SIGCONT)
209 }
210
211 cfg_if::cfg_if! {
214 if #[cfg(any(
215 target_os = "dragonfly",
216 target_os = "freebsd",
217 target_os = "macos",
218 target_os = "netbsd",
219 target_os = "openbsd",
220 target_os = "illumos",
221 ))] {
222 fn info_kind() -> Option<SignalKind> {
223 Some(SignalKind::info())
224 }
225 } else {
226 fn info_kind() -> Option<SignalKind> {
227 None
228 }
229 }
230 }
231}
232
233#[cfg(windows)]
234mod imp {
235 use super::*;
236 use tokio::signal::windows::{CtrlC, ctrl_c};
237
238 #[derive(Debug)]
239 pub(super) struct Signals {
240 ctrl_c: CtrlC,
241 ctrl_c_done: bool,
242 }
243
244 impl Signals {
245 pub(super) fn new() -> std::io::Result<Self> {
246 let ctrl_c = ctrl_c()?;
247 Ok(Self {
248 ctrl_c,
249 ctrl_c_done: false,
250 })
251 }
252
253 pub(super) fn debugger_mode() -> std::io::Result<Self> {
256 let ctrl_c = ctrl_c()?;
259 Ok(Self {
260 ctrl_c,
261 ctrl_c_done: true,
262 })
263 }
264
265 pub(super) async fn recv(&mut self) -> Option<SignalEvent> {
266 if self.ctrl_c_done {
267 return None;
268 }
269
270 match self.ctrl_c.recv().await {
271 Some(()) => Some(SignalEvent::Shutdown(ShutdownEvent::Signal(
272 ShutdownSignalEvent::Interrupt,
273 ))),
274 None => {
275 self.ctrl_c_done = true;
276 None
277 }
278 }
279 }
280 }
281}
282
283#[derive(Copy, Clone, Debug, Eq, PartialEq)]
284pub(crate) enum SignalEvent {
285 #[cfg(unix)]
286 JobControl(JobControlEvent),
287 Shutdown(ShutdownEvent),
288 #[cfg_attr(not(unix), expect(dead_code))]
289 Info(SignalInfoEvent),
290}
291
292#[derive(Copy, Clone, Debug, Eq, PartialEq)]
294pub(crate) enum JobControlEvent {
295 #[cfg(unix)]
296 Stop,
297 #[cfg(unix)]
298 Continue,
299}
300
301#[derive(Copy, Clone, Debug, Eq, PartialEq)]
303pub(crate) enum ShutdownSignalEvent {
304 #[cfg(unix)]
305 Hangup,
306 #[cfg(unix)]
307 Term,
308 #[cfg(unix)]
309 Quit,
310 Interrupt,
311}
312
313impl ShutdownSignalEvent {
314 #[cfg(test)]
315 pub(crate) const ALL_VARIANTS: &'static [Self] = &[
316 #[cfg(unix)]
317 Self::Hangup,
318 #[cfg(unix)]
319 Self::Term,
320 #[cfg(unix)]
321 Self::Quit,
322 Self::Interrupt,
323 ];
324}
325
326#[derive(Copy, Clone, Debug, Eq, PartialEq)]
328pub(crate) enum ShutdownEvent {
329 Signal(ShutdownSignalEvent),
331 TestFailureImmediate,
333}
334
335impl ShutdownEvent {
336 #[cfg(unix)]
338 pub(crate) const TERMINATE: Self = Self::Signal(ShutdownSignalEvent::Term);
339
340 #[cfg(not(unix))]
342 pub(crate) const TERMINATE: Self = Self::Signal(ShutdownSignalEvent::Interrupt);
343}
344
345#[derive(Copy, Clone, Debug, Eq, PartialEq)]
347pub(crate) enum SignalInfoEvent {
348 #[cfg(unix)]
350 Usr1,
351
352 #[cfg(unix)]
354 Info,
355}