Crate signal_hook[][src]

Library for easier and safe Unix signal handling

Unix signals are inherently hard to handle correctly, for several reasons:

The goal of the library

The aim is to subscriptions to signals a „structured“ resource, in a similar way memory allocation is ‒ parts of the program can independently subscribe and it’s the same part of the program that can give them up, independently of what the other parts do. Therefore, it is possible to register multiple actions to the same signal.

Another goal is to shield applications away from differences between platforms. Various Unix systems have little quirks and differences that need to be worked around and that’s not something every application should be dealing with. We even try to provide some support for Windows, but we lack the expertise in that area, so that one is not complete and is a bit rough (if you know how it works there and are willing to either contribute the code or consult, please get in touch).

Furthermore, it provides implementation of certain common signal-handling patterns, usable from safe Rust, without the application author needing to learn about all the traps.

Note that despite everything, there are still some quirks around signal handling that are not possible to paper over and need to be considered. Also, there are some signal use cases that are inherently unsafe and they are not covered by this crate.

Anatomy of the crate

The crate is split into several modules.

The easiest way to handle signals is using the Signals iterator thing. It can register for a set of signals and produce them one by one, in a blocking manner. You can reserve a thread for handling them as they come. If you want something asynchronous, there are adaptor crates for the most common asynchronous runtimes. The module also contains ways to build iterators that produce a bit more information that just the signal number.

The flag module contains routines to set a flag based on incoming signals and to do certain actions inside the signal handlers based on the flags (the flags can also be manipulated by the rest of the application). This allows building things like checking if a signal happened on each loop iteration or making sure application shuts down on the second CTRL+C if it got stuck in graceful shutdown requested by the first.

The consts module contains some constants, most importantly the signal numbers themselves (these are just re-exports from libc and if your OS has some extra ones, you can use them too, this is just for convenience).

And last, there is the low_level module. It contains routines to directly register and unregister arbitrary actions. Some of the patters in the above modules return a SigId, which can be used with the low_level::unregister to remove the action. There are also some other utilities that are more suited to build other abstractions with than to use directly.

Certain parts of the library can be enabled or disabled with use flags:

Limitations

Signal masks

As the library uses sigaction under the hood, signal masking works as expected (eg. with pthread_sigmask). This means, signals will not be delivered if the signal is masked in all program’s threads.

By the way, if you do want to modify the signal mask (or do other Unix-specific magic), the nix crate offers safe interface to many low-level functions, including pthread_sigmask.

Portability

It should work on any POSIX.1-2001 system, which are all the major big OSes with the notable exception of Windows.

Non-standard signals are also supported. Pass the signal value directly from libc or use the numeric value directly.

use std::sync::Arc;
use std::sync::atomic::{AtomicBool};
let term = Arc::new(AtomicBool::new(false));
let _ = signal_hook::flag::register(libc::SIGINT, Arc::clone(&term));

This crate includes a limited support for Windows, based on signal/raise in the CRT. There are differences in both API and behavior:

Moreover, signals won’t work as you expected. SIGTERM isn’t actually used and not all Ctrl-Cs are turned into SIGINT.

Patches to improve Windows support in this library are welcome.

Features

There are several feature flags that control how much is available as part of the crate, some enabled by default.

Examples

Using a flag to terminate a loop-based application

use std::io::Error;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

fn main() -> Result<(), Error> {
    let term = Arc::new(AtomicBool::new(false));
    signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&term))?;
    while !term.load(Ordering::Relaxed) {
        // Do some time-limited stuff here
        // (if this could block forever, then there's no guarantee the signal will have any
        // effect).
    }
    Ok(())
}

A complex signal handling with a background thread

This also handles the double CTRL+C situation (eg. the second CTRL+C kills) and resetting the terminal on SIGTSTP (CTRL+Z, curses-based applications should do something like this).

use std::io::Error;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use signal_hook::consts::signal::*;
use signal_hook::consts::TERM_SIGNALS;
use signal_hook::flag;
// A friend of the Signals iterator, but can be customized by what we want yielded about each
// signal.
use signal_hook::iterator::SignalsInfo;
use signal_hook::iterator::exfiltrator::WithOrigin;
use signal_hook::low_level;


fn main() -> Result<(), Error> {
    // Make sure double CTRL+C and similar kills
    let term_now = Arc::new(AtomicBool::new(false));
    for sig in TERM_SIGNALS {
        // When terminated by a second term signal, exit with exit code 1.
        // This will do nothing the first time (because term_now is false).
        flag::register_conditional_shutdown(*sig, 1, Arc::clone(&term_now))?;
        // But this will "arm" the above for the second time, by setting it to true.
        // The order of registering these is important, if you put this one first, it will
        // first arm and then terminate ‒ all in the first round.
        flag::register(*sig, Arc::clone(&term_now))?;
    }

    // Subscribe to all these signals with information about where they come from. We use the
    // extra info only for logging in this example (it is not available on all the OSes or at
    // all the occasions anyway, it may return `Unknown`).
    let mut sigs = vec![
        // Some terminal handling
        SIGTSTP, SIGCONT, SIGWINCH,
        // Reload of configuration for daemons ‒ um, is this example for a TUI app or a daemon
        // O:-)? You choose...
        SIGHUP,
        // Application-specific action, to print some statistics.
        SIGUSR1,
    ];
    sigs.extend(TERM_SIGNALS);
    let mut signals = SignalsInfo::<WithOrigin>::new(&sigs)?;

    // This is the actual application that'll start in its own thread. We'll control it from
    // this thread based on the signals, but it keeps running.
    // This is called after all the signals got registered, to avoid the short race condition
    // in the first registration of each signal in multi-threaded programs.
    let app = App::run_background();

    // Consume all the incoming signals. This happens in "normal" Rust thread, not in the
    // signal handlers. This means that we are allowed to do whatever we like in here, without
    // restrictions, but it also means the kernel believes the signal already got delivered, we
    // handle them in delayed manner. This is in contrast with eg the above
    // `register_conditional_shutdown` where the shutdown happens *inside* the handler.
    let mut has_terminal = true;
    for info in &mut signals {
        // Will print info about signal + where it comes from.
        eprintln!("Received a signal {:?}", info);
        match info.signal {
            SIGTSTP => {
                // Restore the terminal to non-TUI mode
                if has_terminal {
                    app.restore_term();
                    has_terminal = false;
                    // And actually stop ourselves.
                    low_level::emulate_default_handler(SIGTSTP)?;
                }
            }
            SIGCONT => {
                if !has_terminal {
                    app.claim_term();
                    has_terminal = true;
                }
            }
            SIGWINCH => app.resize_term(),
            SIGHUP => app.reload_config(),
            SIGUSR1 => app.print_stats(),
            term_sig => { // These are all the ones left
                eprintln!("Terminating");
                assert!(TERM_SIGNALS.contains(&term_sig));
                break;
            }
        }
    }

    // If during this another termination signal comes, the trick at the top would kick in and
    // terminate early. But if it doesn't, the application shuts down gracefully.
    app.wait_for_stop();

    Ok(())
}

Asynchronous runtime support

If you are looking for integration with an asynchronous runtime take a look at one of the following adapter crates:

Feel free to open a pull requests if you want to add support for runtimes not mentioned above.

Porting from previous versions

There were some noisy changes when going from 0.2 version to the 0.3 version. In particular:

Modules

consts

The low-level constants.

flag

Module for actions setting flags.

iterator

An iterator over incoming signals.

low_level

Some low level utilities

Structs

SigId

An ID of registered action.