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 c_int
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    pub const SIGIO: c_int = sig!(Io, 29);
138    pub const SIGSYS: c_int = sig!(Sys, 31);
139}
140
141macro_rules! define_signal_enum {
142    (
143        $(#[$outer:meta])*
144        pub enum Signal {
145            $(
146                $(#[$inner:meta])*
147                $name:ident = $value:ident,
148            )*
149        }
150    ) => {
151        $(#[$outer])*
152        #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
153        #[repr(i32)]
154        pub enum Signal {
155            $(
156                $(#[$inner])*
157                $name = signum::$value,
158            )*
159        }
160
161        impl Signal {
162            /// Returns the signal number.
163            fn number(self) -> std::os::raw::c_int {
164                match self {
165                    $(
166                        Signal::$name => signum::$value,
167                    )*
168                }
169            }
170
171            /// Parse a signal from its number.
172            #[cfg(unix)]
173            fn from_number(number: std::os::raw::c_int) -> Option<Self> {
174                match number {
175                    $(
176                        signum::$value => Some(Signal::$name),
177                    )*
178                    _ => None,
179                }
180            }
181        }
182    }
183}
184
185define_signal_enum! {
186    // Copied from https://github.com/bytecodealliance/rustix/blob/main/src/backend/linux_raw/process/types.rs#L81-L161
187
188    /// The signal types that we are able to listen for.
189    pub enum Signal {
190        /// `SIGHUP`
191        Hup = SIGHUP,
192        /// `SIGINT`
193        Int = SIGINT,
194        /// `SIGQUIT`
195        Quit = SIGQUIT,
196        /// `SIGILL`
197        Ill = SIGILL,
198        /// `SIGTRAP`
199        Trap = SIGTRAP,
200        /// `SIGABRT`, aka `SIGIOT`
201        #[doc(alias = "Iot")]
202        #[doc(alias = "Abrt")]
203        Abort = SIGABRT,
204        /// `SIGBUS`
205        Bus = SIGBUS,
206        /// `SIGFPE`
207        Fpe = SIGFPE,
208        /// `SIGKILL`
209        Kill = SIGKILL,
210        /// `SIGUSR1`
211        Usr1 = SIGUSR1,
212        /// `SIGSEGV`
213        Segv = SIGSEGV,
214        /// `SIGUSR2`
215        Usr2 = SIGUSR2,
216        /// `SIGPIPE`
217        Pipe = SIGPIPE,
218        /// `SIGALRM`
219        #[doc(alias = "Alrm")]
220        Alarm = SIGALRM,
221        /// `SIGTERM`
222        Term = SIGTERM,
223        /// `SIGCHLD`
224        #[doc(alias = "Chld")]
225        Child = SIGCHLD,
226        /// `SIGCONT`
227        Cont = SIGCONT,
228        /// `SIGSTOP`
229        Stop = SIGSTOP,
230        /// `SIGTSTP`
231        Tstp = SIGTSTP,
232        /// `SIGTTIN`
233        Ttin = SIGTTIN,
234        /// `SIGTTOU`
235        Ttou = SIGTTOU,
236        /// `SIGURG`
237        Urg = SIGURG,
238        /// `SIGXCPU`
239        Xcpu = SIGXCPU,
240        /// `SIGXFSZ`
241        Xfsz = SIGXFSZ,
242        /// `SIGVTALRM`
243        #[doc(alias = "Vtalrm")]
244        Vtalarm = SIGVTALRM,
245        /// `SIGPROF`
246        Prof = SIGPROF,
247        /// `SIGWINCH`
248        Winch = SIGWINCH,
249        /// `SIGIO`, aka `SIGPOLL`
250        #[doc(alias = "Poll")]
251        Io = SIGIO,
252        /// `SIGSYS`, aka `SIGUNUSED`
253        #[doc(alias = "Unused")]
254        Sys = SIGSYS,
255    }
256}
257
258/// Wait for a specific set of signals.
259///
260/// See the [module-level documentation](index.html) for more details.
261pub struct Signals {
262    /// The strategy used to read the signals.
263    notifier: sys::Notifier,
264
265    /// The map between signal numbers and signal IDs.
266    signal_ids: HashMap<Signal, SigId>,
267}
268
269impl Drop for Signals {
270    fn drop(&mut self) {
271        for signal in self.signal_ids.values() {
272            registry::unregister(*signal);
273        }
274    }
275}
276
277impl fmt::Debug for Signals {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        struct RegisteredSignals<'a>(&'a HashMap<Signal, SigId>);
280
281        impl fmt::Debug for RegisteredSignals<'_> {
282            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283                f.debug_set().entries(self.0.keys()).finish()
284            }
285        }
286
287        f.debug_struct("Signals")
288            .field("notifier", &self.notifier)
289            .field("signal_ids", &RegisteredSignals(&self.signal_ids))
290            .finish()
291    }
292}
293
294impl Signals {
295    /// Create a new `Signals` instance with a set of signals.
296    pub fn new<B>(signals: impl IntoIterator<Item = B>) -> io::Result<Self>
297    where
298        B: Borrow<Signal>,
299    {
300        let mut this = Self {
301            notifier: sys::Notifier::new()?,
302            signal_ids: HashMap::new(),
303        };
304
305        // Add the signals to the set of signals to wait for.
306        this.add_signals(signals)?;
307
308        Ok(this)
309    }
310
311    /// Add signals to the set of signals to wait for.
312    ///
313    /// One signal cannot be added twice. If a signal that has already been added is passed to this
314    /// method, it will be ignored.
315    ///
316    /// Registering a signal prevents the default behavior of that signal from occurring. For
317    /// example, if you register `SIGINT`, pressing `Ctrl+C` will no longer terminate the process.
318    /// To run the default signal handler, use [`signal_hook::low_level::emulate_default_handler`]
319    /// instead.
320    ///
321    /// [`signal_hook::low_level::emulate_default_handler`]: https://docs.rs/signal-hook/latest/signal_hook/low_level/fn.emulate_default_handler.html
322    pub fn add_signals<B>(&mut self, signals: impl IntoIterator<Item = B>) -> io::Result<()>
323    where
324        B: Borrow<Signal>,
325    {
326        for signal in signals {
327            let signal = signal.borrow();
328
329            // If we've already registered this signal, skip it.
330            if self.signal_ids.contains_key(signal) {
331                continue;
332            }
333
334            // Get the closure to call when the signal is received.
335            let closure = self.notifier.add_signal(*signal)?;
336
337            let id = unsafe {
338                // SAFETY: Closure is guaranteed to be signal-safe.
339                registry::register(signal.number(), closure)?
340            };
341
342            // Add the signal ID to the map.
343            self.signal_ids.insert(*signal, id);
344        }
345
346        Ok(())
347    }
348
349    /// Remove signals from the set of signals to wait for.
350    ///
351    /// This function can be used to opt out of listening to signals previously registered via
352    /// [`add_signals`](Self::add_signals) or [`new`](Self::new). If a signal that has not been
353    /// registered is passed to this method, it will be ignored.
354    pub fn remove_signals<B>(&mut self, signals: impl IntoIterator<Item = B>) -> io::Result<()>
355    where
356        B: Borrow<Signal>,
357    {
358        for signal in signals {
359            let signal = signal.borrow();
360
361            // If we haven't registered this signal, skip it.
362            let id = match self.signal_ids.remove(signal) {
363                Some(id) => id,
364                None => continue,
365            };
366
367            // Remove the signal from the notifier.
368            self.notifier.remove_signal(*signal)?;
369
370            // Use `signal-hook-registry` to unregister the signal.
371            registry::unregister(id);
372        }
373
374        Ok(())
375    }
376}
377
378#[cfg(unix)]
379impl AsRawFd for Signals {
380    fn as_raw_fd(&self) -> RawFd {
381        self.notifier.as_raw_fd()
382    }
383}
384
385#[cfg(unix)]
386impl AsFd for Signals {
387    fn as_fd(&self) -> BorrowedFd<'_> {
388        self.notifier.as_fd()
389    }
390}
391
392impl Unpin for Signals {}
393
394impl Stream for Signals {
395    type Item = io::Result<Signal>;
396
397    #[inline]
398    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
399        Pin::new(&mut &*self).poll_next(cx)
400    }
401
402    #[inline]
403    fn size_hint(&self) -> (usize, Option<usize>) {
404        // This stream is expected to never end.
405        (usize::MAX, None)
406    }
407}
408
409impl Stream for &Signals {
410    type Item = io::Result<Signal>;
411
412    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
413        let signal = ready!(self.notifier.poll_next(cx))?;
414        Poll::Ready(Some(Ok(signal)))
415    }
416
417    #[inline]
418    fn size_hint(&self) -> (usize, Option<usize>) {
419        // This stream is expected to never end.
420        (usize::MAX, None)
421    }
422}