1use crate::errors::SignalHandlerSetupError;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
13pub enum SignalHandlerKind {
14 Standard,
17
18 Noop,
20}
21
22impl SignalHandlerKind {
23 pub(crate) fn build(self) -> Result<SignalHandler, SignalHandlerSetupError> {
24 match self {
25 Self::Standard => SignalHandler::new(),
26 Self::Noop => Ok(SignalHandler::noop()),
27 }
28 }
29}
30
31#[derive(Debug)]
33pub(crate) struct SignalHandler {
34 signals: Option<imp::Signals>,
35}
36
37impl SignalHandler {
38 #[cfg(any(unix, windows))]
40 pub(crate) fn new() -> Result<Self, SignalHandlerSetupError> {
41 let signals = imp::Signals::new()?;
42 Ok(Self {
43 signals: Some(signals),
44 })
45 }
46
47 pub(crate) fn noop() -> Self {
49 Self { signals: None }
50 }
51
52 pub(crate) async fn recv(&mut self) -> Option<SignalEvent> {
53 match &mut self.signals {
54 Some(signals) => signals.recv().await,
55 None => None,
56 }
57 }
58}
59
60#[cfg(unix)]
61mod imp {
62 use super::*;
63 use std::io;
64 use tokio::signal::unix::{SignalKind, signal};
65 use tokio_stream::{StreamExt, StreamMap, wrappers::SignalStream};
66
67 #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
68 enum SignalId {
69 Int,
70 Hup,
71 Term,
72 Quit,
73 Tstp,
74 Cont,
75 Info,
76 Usr1,
77 }
78
79 #[derive(Debug)]
81 pub(super) struct Signals {
82 map: StreamMap<SignalId, SignalStream>,
85 sigquit_as_info: bool,
86 }
87
88 impl Signals {
89 pub(super) fn new() -> io::Result<Self> {
90 let mut map = StreamMap::new();
91
92 map.extend([
94 (SignalId::Int, signal_stream(SignalKind::interrupt())?),
95 (SignalId::Hup, signal_stream(SignalKind::hangup())?),
96 (SignalId::Term, signal_stream(SignalKind::terminate())?),
97 (SignalId::Quit, signal_stream(SignalKind::quit())?),
98 (SignalId::Tstp, signal_stream(tstp_kind())?),
99 (SignalId::Cont, signal_stream(cont_kind())?),
100 (SignalId::Usr1, signal_stream(SignalKind::user_defined1())?),
101 ]);
102
103 if let Some(info_kind) = info_kind() {
104 map.insert(SignalId::Info, signal_stream(info_kind)?);
105 }
106
107 let sigquit_as_info =
111 std::env::var("__NEXTEST_SIGQUIT_AS_INFO").is_ok_and(|v| v == "1");
112
113 Ok(Self {
114 map,
115 sigquit_as_info,
116 })
117 }
118
119 pub(super) async fn recv(&mut self) -> Option<SignalEvent> {
120 self.map.next().await.map(|(id, _)| match id {
121 SignalId::Int => SignalEvent::Shutdown(ShutdownEvent::Interrupt),
122 SignalId::Hup => SignalEvent::Shutdown(ShutdownEvent::Hangup),
123 SignalId::Term => SignalEvent::Shutdown(ShutdownEvent::Term),
124 SignalId::Quit => {
125 if self.sigquit_as_info {
126 SignalEvent::Info(SignalInfoEvent::Info)
127 } else {
128 SignalEvent::Shutdown(ShutdownEvent::Quit)
129 }
130 }
131 SignalId::Tstp => SignalEvent::JobControl(JobControlEvent::Stop),
132 SignalId::Cont => SignalEvent::JobControl(JobControlEvent::Continue),
133 SignalId::Info => SignalEvent::Info(SignalInfoEvent::Info),
134 SignalId::Usr1 => SignalEvent::Info(SignalInfoEvent::Usr1),
135 })
136 }
137 }
138
139 fn signal_stream(kind: SignalKind) -> io::Result<SignalStream> {
140 Ok(SignalStream::new(signal(kind)?))
141 }
142
143 fn tstp_kind() -> SignalKind {
144 SignalKind::from_raw(libc::SIGTSTP)
145 }
146
147 fn cont_kind() -> SignalKind {
148 SignalKind::from_raw(libc::SIGCONT)
149 }
150
151 cfg_if::cfg_if! {
154 if #[cfg(any(
155 target_os = "dragonfly",
156 target_os = "freebsd",
157 target_os = "macos",
158 target_os = "netbsd",
159 target_os = "openbsd",
160 target_os = "illumos",
161 ))] {
162 fn info_kind() -> Option<SignalKind> {
163 Some(SignalKind::info())
164 }
165 } else {
166 fn info_kind() -> Option<SignalKind> {
167 None
168 }
169 }
170 }
171}
172
173#[cfg(windows)]
174mod imp {
175 use super::*;
176 use tokio::signal::windows::{CtrlC, ctrl_c};
177
178 #[derive(Debug)]
179 pub(super) struct Signals {
180 ctrl_c: CtrlC,
181 ctrl_c_done: bool,
182 }
183
184 impl Signals {
185 pub(super) fn new() -> std::io::Result<Self> {
186 let ctrl_c = ctrl_c()?;
187 Ok(Self {
188 ctrl_c,
189 ctrl_c_done: false,
190 })
191 }
192
193 pub(super) async fn recv(&mut self) -> Option<SignalEvent> {
194 if self.ctrl_c_done {
195 return None;
196 }
197
198 match self.ctrl_c.recv().await {
199 Some(()) => Some(SignalEvent::Shutdown(ShutdownEvent::Interrupt)),
200 None => {
201 self.ctrl_c_done = true;
202 None
203 }
204 }
205 }
206 }
207}
208
209#[derive(Copy, Clone, Debug, Eq, PartialEq)]
210pub(crate) enum SignalEvent {
211 #[cfg(unix)]
212 JobControl(JobControlEvent),
213 Shutdown(ShutdownEvent),
214 #[cfg_attr(not(unix), expect(dead_code))]
215 Info(SignalInfoEvent),
216}
217
218#[derive(Copy, Clone, Debug, Eq, PartialEq)]
220pub(crate) enum JobControlEvent {
221 #[cfg(unix)]
222 Stop,
223 #[cfg(unix)]
224 Continue,
225}
226
227#[derive(Copy, Clone, Debug, Eq, PartialEq)]
229pub(crate) enum ShutdownEvent {
230 #[cfg(unix)]
231 Hangup,
232 #[cfg(unix)]
233 Term,
234 #[cfg(unix)]
235 Quit,
236 Interrupt,
237}
238
239impl ShutdownEvent {
240 #[cfg(test)]
241 pub(crate) const ALL_VARIANTS: &'static [Self] = &[
242 #[cfg(unix)]
243 Self::Hangup,
244 #[cfg(unix)]
245 Self::Term,
246 #[cfg(unix)]
247 Self::Quit,
248 Self::Interrupt,
249 ];
250}
251
252#[derive(Copy, Clone, Debug, Eq, PartialEq)]
254pub(crate) enum SignalInfoEvent {
255 #[cfg(unix)]
257 Usr1,
258
259 #[cfg(unix)]
261 Info,
262}