Skip to main content

async_signal/
lib.rs

1//! Asynchronous signal handling.
2//!
3//! This crate provides the [`Signals`] type, which can be used to listen for POSIX signals asynchronously.
4//! It can be seen as an asynchronous version of [`signal_hook::iterator::Signals`].
5//!
6//! [`signal_hook::iterator::Signals`]: https://docs.rs/signal-hook/latest/signal_hook/iterator/struct.Signals.html
7//!
8//! # Implementation
9//!
10//! This crate uses the [`signal_hook_registry`] crate to register a listener for each signal. That
11//! listener will then send a message through a Unix socket to the [`Signals`] type, which will
12//! receive it and notify the user. Asynchronous notification is done through the [`async-io`] crate.
13//!
14//! Note that the internal pipe has a limited capacity. Once it has reached capacity, additional
15//! signals will be dropped.
16//!
17//! On Windows, a different implementation that only supports `SIGINT` is used. This implementation
18//! uses a channel to notify the user.
19//!
20//! [`signal_hook_registry`]: https://crates.io/crates/signal-hook-registry
21//! [`async-io`]: https://crates.io/crates/async-io
22//!
23//! # Examples
24//!
25//! ```no_run
26//! use async_signal::{Signal, Signals};
27//! use futures_lite::prelude::*;
28//! use signal_hook::low_level;
29//!
30//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
31//! # async_io::block_on(async {
32//! // Register the signals we want to receive.
33//! let mut signals = Signals::new(&[
34//!     Signal::Term,
35//!     Signal::Quit,
36//!     Signal::Int,
37//! ])?;
38//!
39//! // Wait for a signal to be received.
40//! while let Some(signal) = signals.next().await {
41//!     // Print the signal.
42//!     eprintln!("Received signal {:?}", signal);
43//!
44//!     // After printing it, do whatever the signal was supposed to do in the first place.
45//!     low_level::emulate_default_handler(signal.unwrap() as i32).unwrap();
46//! }
47//! # Ok(())
48//! # })
49//! # }
50//! ```
51
52#![doc(
53    html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
54)]
55#![doc(
56    html_logo_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png"
57)]
58
59cfg_if::cfg_if! {
60    if #[cfg(windows)] {
61        mod channel;
62        use channel as sys;
63    } else {
64        mod pipe;
65        use pipe as sys;
66    }
67}
68
69cfg_if::cfg_if! {
70    if #[cfg(unix)] {
71        use signal_hook_registry as registry;
72    } else if #[cfg(windows)] {
73        mod windows_registry;
74        use windows_registry as registry;
75    }
76}
77
78use futures_core::ready;
79use futures_core::stream::Stream;
80use registry::SigId;
81
82use std::borrow::Borrow;
83use std::collections::HashMap;
84use std::fmt;
85use std::io;
86use std::pin::Pin;
87use std::task::{Context, Poll};
88
89#[cfg(unix)]
90use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
91
92mod signum {
93    pub(crate) use std::os::raw::c_int;
94
95    macro_rules! sig {
96        ($rustix_name:ident, $raw_value:literal) => {{
97            #[cfg(unix)]
98            {
99                rustix::process::Signal::$rustix_name.as_raw()
100            }
101
102            #[cfg(windows)]
103            {
104                $raw_value
105            }
106        }};
107    }
108
109    // Define these ourselves.
110    pub const SIGHUP: c_int = sig!(HUP, 1);
111    pub const SIGINT: c_int = sig!(INT, 2);
112    pub const SIGQUIT: c_int = sig!(QUIT, 3);
113    pub const SIGILL: c_int = sig!(ILL, 4);
114    pub const SIGTRAP: c_int = sig!(TRAP, 5);
115    pub const SIGABRT: c_int = sig!(ABORT, 6);
116    pub const SIGFPE: c_int = sig!(FPE, 8);
117    pub const SIGKILL: c_int = sig!(KILL, 9);
118    pub const SIGSEGV: c_int = sig!(SEGV, 11);
119    pub const SIGPIPE: c_int = sig!(PIPE, 13);
120    pub const SIGALRM: c_int = sig!(ALARM, 14);
121    pub const SIGTERM: c_int = sig!(TERM, 15);
122    pub const SIGTTIN: c_int = sig!(TTIN, 21);
123    pub const SIGTTOU: c_int = sig!(TTOU, 22);
124    pub const SIGXCPU: c_int = sig!(XCPU, 24);
125    pub const SIGXFSZ: c_int = sig!(XFSZ, 25);
126    pub const SIGVTALRM: c_int = sig!(VTALARM, 26);
127    pub const SIGPROF: c_int = sig!(PROF, 27);
128    pub const SIGWINCH: c_int = sig!(WINCH, 28);
129    pub const SIGCHLD: c_int = sig!(CHILD, 17);
130    pub const SIGBUS: c_int = sig!(BUS, 7);
131    pub const SIGUSR1: c_int = sig!(USR1, 10);
132    pub const SIGUSR2: c_int = sig!(USR2, 12);
133    pub const SIGCONT: c_int = sig!(CONT, 18);
134    pub const SIGSTOP: c_int = sig!(STOP, 19);
135    pub const SIGTSTP: c_int = sig!(TSTP, 20);
136    pub const SIGURG: c_int = sig!(URG, 23);
137    #[cfg(not(target_os = "haiku"))]
138    pub const SIGIO: c_int = sig!(IO, 29);
139    pub const SIGSYS: c_int = sig!(SYS, 31);
140}
141
142macro_rules! define_signal_enum {
143    (
144        $(#[$outer:meta])*
145        pub enum Signal {
146            $(
147                $(#[cfg($($inner_cfg:tt)*)])?
148                $(#[doc $($inner_doc:tt)*])*
149                $name:ident = $value:ident,
150            )*
151        }
152    ) => {
153        $(#[$outer])*
154        #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
155        #[repr(i32)]
156        pub enum Signal {
157            $(
158                $(#[cfg($($inner_cfg)*)])?
159                $(#[doc $($inner_doc)*])*
160                $name = signum::$value,
161            )*
162        }
163
164        impl Signal {
165            /// Returns the signal number.
166            fn number(self) -> std::os::raw::c_int {
167                match self {
168                    $(
169                        $(#[cfg($($inner_cfg)*)])?
170                        Signal::$name => signum::$value,
171                    )*
172                }
173            }
174
175            /// Parse a signal from its number.
176            #[cfg(unix)]
177            fn from_number(number: std::os::raw::c_int) -> Option<Self> {
178                match number {
179                    $(
180                        $(#[cfg($($inner_cfg)*)])?
181                        signum::$value => Some(Signal::$name),
182                    )*
183                    _ => None,
184                }
185            }
186        }
187    }
188}
189
190define_signal_enum! {
191    // Copied from https://github.com/bytecodealliance/rustix/blob/main/src/backend/linux_raw/process/types.rs#L81-L161
192
193    /// The signal types that we are able to listen for.
194    pub enum Signal {
195        /// `SIGHUP`
196        Hup = SIGHUP,
197        /// `SIGINT`
198        Int = SIGINT,
199        /// `SIGQUIT`
200        Quit = SIGQUIT,
201        /// `SIGILL`
202        Ill = SIGILL,
203        /// `SIGTRAP`
204        Trap = SIGTRAP,
205        /// `SIGABRT`, aka `SIGIOT`
206        #[doc(alias = "Iot")]
207        #[doc(alias = "Abrt")]
208        Abort = SIGABRT,
209        /// `SIGBUS`
210        Bus = SIGBUS,
211        /// `SIGFPE`
212        Fpe = SIGFPE,
213        /// `SIGKILL`
214        Kill = SIGKILL,
215        /// `SIGUSR1`
216        Usr1 = SIGUSR1,
217        /// `SIGSEGV`
218        Segv = SIGSEGV,
219        /// `SIGUSR2`
220        Usr2 = SIGUSR2,
221        /// `SIGPIPE`
222        Pipe = SIGPIPE,
223        /// `SIGALRM`
224        #[doc(alias = "Alrm")]
225        Alarm = SIGALRM,
226        /// `SIGTERM`
227        Term = SIGTERM,
228        /// `SIGCHLD`
229        #[doc(alias = "Chld")]
230        Child = SIGCHLD,
231        /// `SIGCONT`
232        Cont = SIGCONT,
233        /// `SIGSTOP`
234        Stop = SIGSTOP,
235        /// `SIGTSTP`
236        Tstp = SIGTSTP,
237        /// `SIGTTIN`
238        Ttin = SIGTTIN,
239        /// `SIGTTOU`
240        Ttou = SIGTTOU,
241        /// `SIGURG`
242        Urg = SIGURG,
243        /// `SIGXCPU`
244        Xcpu = SIGXCPU,
245        /// `SIGXFSZ`
246        Xfsz = SIGXFSZ,
247        /// `SIGVTALRM`
248        #[doc(alias = "Vtalrm")]
249        Vtalarm = SIGVTALRM,
250        /// `SIGPROF`
251        Prof = SIGPROF,
252        /// `SIGWINCH`
253        Winch = SIGWINCH,
254        #[cfg(not(target_os = "haiku"))]
255        /// `SIGIO`, aka `SIGPOLL`
256        #[doc(alias = "Poll")]
257        Io = SIGIO,
258        /// `SIGSYS`, aka `SIGUNUSED`
259        #[doc(alias = "Unused")]
260        Sys = SIGSYS,
261    }
262}
263
264/// Wait for a specific set of signals.
265///
266/// See the [module-level documentation](index.html) for more details.
267pub struct Signals {
268    /// The strategy used to read the signals.
269    notifier: sys::Notifier,
270
271    /// The map between signal numbers and signal IDs.
272    signal_ids: HashMap<Signal, SigId>,
273}
274
275impl Drop for Signals {
276    fn drop(&mut self) {
277        for signal in self.signal_ids.values() {
278            registry::unregister(*signal);
279        }
280    }
281}
282
283impl fmt::Debug for Signals {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        struct RegisteredSignals<'a>(&'a HashMap<Signal, SigId>);
286
287        impl fmt::Debug for RegisteredSignals<'_> {
288            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289                f.debug_set().entries(self.0.keys()).finish()
290            }
291        }
292
293        f.debug_struct("Signals")
294            .field("notifier", &self.notifier)
295            .field("signal_ids", &RegisteredSignals(&self.signal_ids))
296            .finish()
297    }
298}
299
300impl Signals {
301    /// Create a new `Signals` instance with a set of signals.
302    pub fn new<B>(signals: impl IntoIterator<Item = B>) -> io::Result<Self>
303    where
304        B: Borrow<Signal>,
305    {
306        let mut this = Self {
307            notifier: sys::Notifier::new()?,
308            signal_ids: HashMap::new(),
309        };
310
311        // Add the signals to the set of signals to wait for.
312        this.add_signals(signals)?;
313
314        Ok(this)
315    }
316
317    /// Add signals to the set of signals to wait for.
318    ///
319    /// One signal cannot be added twice. If a signal that has already been added is passed to this
320    /// method, it will be ignored.
321    ///
322    /// Registering a signal prevents the default behavior of that signal from occurring. For
323    /// example, if you register `SIGINT`, pressing `Ctrl+C` will no longer terminate the process.
324    /// To run the default signal handler, use [`signal_hook::low_level::emulate_default_handler`]
325    /// instead.
326    ///
327    /// [`signal_hook::low_level::emulate_default_handler`]: https://docs.rs/signal-hook/latest/signal_hook/low_level/fn.emulate_default_handler.html
328    pub fn add_signals<B>(&mut self, signals: impl IntoIterator<Item = B>) -> io::Result<()>
329    where
330        B: Borrow<Signal>,
331    {
332        for signal in signals {
333            let signal = signal.borrow();
334
335            // If we've already registered this signal, skip it.
336            if self.signal_ids.contains_key(signal) {
337                continue;
338            }
339
340            // Get the closure to call when the signal is received.
341            let closure = self.notifier.add_signal(*signal)?;
342
343            let id = unsafe {
344                // SAFETY: Closure is guaranteed to be signal-safe.
345                registry::register(signal.number(), closure)?
346            };
347
348            // Add the signal ID to the map.
349            self.signal_ids.insert(*signal, id);
350        }
351
352        Ok(())
353    }
354
355    /// Remove signals from the set of signals to wait for.
356    ///
357    /// This function can be used to opt out of listening to signals previously registered via
358    /// [`add_signals`](Self::add_signals) or [`new`](Self::new). If a signal that has not been
359    /// registered is passed to this method, it will be ignored.
360    pub fn remove_signals<B>(&mut self, signals: impl IntoIterator<Item = B>) -> io::Result<()>
361    where
362        B: Borrow<Signal>,
363    {
364        for signal in signals {
365            let signal = signal.borrow();
366
367            // If we haven't registered this signal, skip it.
368            let id = match self.signal_ids.remove(signal) {
369                Some(id) => id,
370                None => continue,
371            };
372
373            // Remove the signal from the notifier.
374            self.notifier.remove_signal(*signal)?;
375
376            // Use `signal-hook-registry` to unregister the signal.
377            registry::unregister(id);
378        }
379
380        Ok(())
381    }
382}
383
384#[cfg(unix)]
385impl AsRawFd for Signals {
386    fn as_raw_fd(&self) -> RawFd {
387        self.notifier.as_raw_fd()
388    }
389}
390
391#[cfg(unix)]
392impl AsFd for Signals {
393    fn as_fd(&self) -> BorrowedFd<'_> {
394        self.notifier.as_fd()
395    }
396}
397
398impl Unpin for Signals {}
399
400impl Stream for Signals {
401    type Item = io::Result<Signal>;
402
403    #[inline]
404    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
405        Pin::new(&mut &*self).poll_next(cx)
406    }
407
408    #[inline]
409    fn size_hint(&self) -> (usize, Option<usize>) {
410        // This stream is expected to never end.
411        (usize::MAX, None)
412    }
413}
414
415impl Stream for &Signals {
416    type Item = io::Result<Signal>;
417
418    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
419        let signal = ready!(self.notifier.poll_next(cx))?;
420        Poll::Ready(Some(Ok(signal)))
421    }
422
423    #[inline]
424    fn size_hint(&self) -> (usize, Option<usize>) {
425        // This stream is expected to never end.
426        (usize::MAX, None)
427    }
428}