🎓 A1Learner
🔍

Rust Handling Signals

A signal is a message the system sends to a running program. Ctrl-C sends the signal named SIGINT.

Install a tiny handler

The ctrlc crate installs the handler. The handler does one thing only. It sets a flag.

ctrlc::set_handler(move || {
    flag.store(true, Ordering::SeqCst);
})
.expect("handler should install");

Share the flag

The handler and the work loop are separate code paths. An Arc<AtomicBool> is the switch both sides can touch.

let stop = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&stop);

The loop watches the flag

Each round checks the switch. A set flag ends the loop. Normal code then does the tidying, never the handler.

while !stop.load(Ordering::SeqCst) {
    thread::sleep(Duration::from_millis(200));
}

Leave through the front door

After the loop, print the tidy-up lines. Return success. The shell sees a normal, clean exit:

println!("Ctrl-C seen; tidying up");
println!("done");
ExitCode::SUCCESS

Example

// Cargo.toml: ctrlc = "3"
use std::process::ExitCode;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

fn main() -> ExitCode {
    let stop = Arc::new(AtomicBool::new(false));
    let flag = Arc::clone(&stop);
    ctrlc::set_handler(move || {
        flag.store(true, Ordering::SeqCst);
    })
    .expect("handler should install");
    println!("working; press Ctrl-C to stop");
    while !stop.load(Ordering::SeqCst) {
        thread::sleep(Duration::from_millis(200));
    }
    println!("Ctrl-C seen; tidying up");
    println!("done");
    ExitCode::SUCCESS
}

Key points

  • Handling a signal means catching it and answering in normal code.
  • SIGINT arrives on Ctrl-C.
  • SIGTERM arrives on a plain kill or a system shutdown.
  • SIGTERM has no key.
  • A handler must be tiny.
  • It can interrupt at any moment, even mid-print.
  • Set a flag and get out.
  • With no handler, Ctrl-C kills the program at once.
  • The shell reports exit code 130.
  • Some tools need a second pattern.
  • The handler sends on a channel, and the main code waits on that channel.
  • The system uses signals to message a running program.

In this example, the program prints its working line and waits in a sleep loop. The handler holds one clone of the flag. The loop holds the other. Send the program SIGINT, by Ctrl-C or by kill. The handler sets the flag. The loop notices within one sleep round. The program prints its two tidy-up lines and returns success. The exit code is 0, verified under a real terminal and by direct signal.

Note

Rust ignores SIGPIPE by default, as the Broken Pipes lesson showed.

SIGINT keeps its killing default until you install a handler.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why should a signal handler only set a flag?