Signal handling

Processeslike command line applicationsneed to react to signals sent by the operating system.The most common example is probably Ctrl+C,the signal that typically tells a process to terminate.To handle signals in Rust programsyou need to consider how you can receive these signalsas well as how you can react to them.

Note:If your applications does not need to gracefully shutdown,the default handling is fine(i.e. exit immediatelyand let the OS cleanup resources like open file handles).In that case:No need to do what this chapter tells you!

However,for applications that need to clean up after themselves,this chapter is very relevant!For example,if your application needs toproperly close network connections(saying “good bye” to the processes at the other end),remove temporary files,or reset system settings,read on.

Differences between operating systems

On Unix systems(like Linux, macOS, and FreeBSD)a process can receive signals.It can either react to themin a default (OS-provided) way,catch the signal and handle them in a program-defined way,or ignore the signal entirely.

Windows does not have signals.You can use Console Handlersto define callbacks that get executed when an event occurs.There is also structured exception handlingwhich handles all the various types of system exceptions such as division by zero, invalid access exceptions, stack overflow, and so on

First off: Handling Ctrl+C

The ctrlc crate does just what the name suggests:It allows you to react to the user pressing Ctrl+C,in a cross-platform way.The main way to use the crate is this:

  1. use std::{thread, time::Duration};
  2. fn main() {
  3. ctrlc::set_handler(move || {
  4. println!("received Ctrl+C!");
  5. })
  6. .expect("Error setting Ctrl-C handler");
  7. // Following code does the actual work, and can be interrupted by pressing
  8. // Ctrl-C. As an example: Let's wait a few seconds.
  9. thread::sleep(Duration::from_secs(2));
  10. }

This is, of course, not that helpful:It only prints a message but otherwise doesn’t stop the program.

In a real-world program,it’s a good idea to instead set a variable in the signal handlerthat you then check in various places in your program.For example,you can set an Arc<AtomicBool>(a boolean shareable between threads)in your signal handler,and in hot loops,or when waiting for a thread,you periodically check its valueand break when it becomes true.

Handling other types of signals

The ctrlc crate only handles Ctrl+C,or, what on Unix systems would be called SIGINT (the “interrupt” signal).To react to more Unix signals,you should have a look at signal-hook.Its design is described in this blog post,and it is currently the library with the widest community support.

Here’s a simple example:

  1. use signal_hook::{iterator::Signals, SIGINT};
  2. use std::{error::Error, thread, time::Duration};
  3. fn main() -> Result<(), Box<Error>> {
  4. let signals = Signals::new(&[SIGINT])?;
  5. thread::spawn(move || {
  6. for sig in signals.forever() {
  7. println!("Received signal {:?}", sig);
  8. }
  9. });
  10. // Following code does the actual work, and can be interrupted by pressing
  11. // Ctrl-C. As an example: Let's wait a few seconds.
  12. thread::sleep(Duration::from_secs(2));
  13. Ok(())
  14. }

Using channels

Instead of setting a variableand having other parts of the program check it,you can use channels:You create a channel into which the signal handler emits a valuewhenever the signal is received.In your application code you usethis and other channelsas synchronization points between threads.Using crossbeam-channel it would look something like this:

  1. use std::time::Duration;
  2. use crossbeam_channel::{bounded, tick, Receiver, select};
  3. fn ctrl_channel() -> Result<Receiver<()>, ctrlc::Error> {
  4. let (sender, receiver) = bounded(100);
  5. ctrlc::set_handler(move || {
  6. let _ = sender.send(());
  7. })?;
  8. Ok(receiver)
  9. }
  10. fn main() -> Result<(), exitfailure::ExitFailure> {
  11. let ctrl_c_events = ctrl_channel()?;
  12. let ticks = tick(Duration::from_secs(1));
  13. loop {
  14. select! {
  15. recv(ticks) -> _ => {
  16. println!("working!");
  17. }
  18. recv(ctrl_c_events) -> _ => {
  19. println!();
  20. println!("Goodbye!");
  21. break;
  22. }
  23. }
  24. }
  25. Ok(())
  26. }

Using futures and streams

If you are using tokio,you are most likely already writing your applicationwith asynchronous patterns and an event-driven design.Instead of using crossbeam’s channels directly,you can enable signal-hook’s tokio-support feature.This allows you to call .into_async()on signal-hook’s Signals typesto get a new type that implements futures::Stream.

What to do when you receive another Ctrl+C while you’re handling the first Ctrl+C

Most users will press Ctrl+C,and then give your program a few seconds to exit,or tell them what’s going on.If that doesn’t happen,they will press Ctrl+C again.The typical behavior is to have the application quit immediately.