Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a shutdown hook to a Rust program?

I'm writing a Rust program which needs to save some data at the end of its execution, whatever happens.

In the Java world, I would do that with a shutdown hook. There is a crate, aptly called shutdown_hooks, but it seems only able to register extern "C" functions (and I'm not totally sure it will run on panic!(...)).

How can I implement a shutdown hook that triggers on normal exit as well on panic?

like image 318
Riduidel Avatar asked Mar 26 '26 20:03

Riduidel


1 Answers

In the general case, it's impossible. Even ignoring effects from outside of the program (as mentioned by mcarton), whoever compiles the final binary can choose if a panic actually triggers stack unwinding or if it simply aborts the program. In the latter case, there's nothing you can do.

In the case of unwinding panic or normal exit, you can implement Drop and use the conventional aspects of RAII:

struct Cleanup;

impl Drop for Cleanup {
    fn drop(&mut self) {
        eprintln!("Doing some final cleanup");
    }
}

fn main() {
    let _cleanup = Cleanup;

    panic!("Oh no!");
}
thread 'main' panicked at 'Oh no!', src/main.rs:12:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Doing some final cleanup

It appears that Java's shutdown hooks allow running multiple pieces of code in parallel in threads. You could do something similar with some small modifications:

use std::{
    sync::{Arc, Condvar, Mutex},
    thread,
};

#[derive(Debug, Default)]
struct Cleanup {
    hooks: Vec<thread::JoinHandle<()>>,
    run: Arc<Mutex<bool>>,
    go: Arc<Condvar>,
}

impl Cleanup {
    fn add(&mut self, f: impl FnOnce() + Send + 'static) {
        let run = self.run.clone();
        let go = self.go.clone();

        let t = thread::spawn(move || {
            let mut run = run.lock().unwrap();

            while !*run {
                run = go.wait(run).unwrap();
            }

            f();
        });
        self.hooks.push(t);
    }
}

impl Drop for Cleanup {
    fn drop(&mut self) {
        eprintln!("Starting final cleanup");

        *self.run.lock().unwrap() = true;
        self.go.notify_all();

        for h in self.hooks.drain(..) {
            h.join().unwrap();
        }

        eprintln!("Final cleanup complete");
    }
}

fn main() {
    let mut cleanup = Cleanup::default();

    cleanup.add(|| {
        eprintln!("Cleanup #1");
    });

    cleanup.add(|| {
        eprintln!("Cleanup #2");
    });

    panic!("Oh no!");
}

See also:

  • What's the best way to register a function to run during an unexpected exit of a Rust program?
  • Is there a way to tell the Rust compiler to call drop on partially-initialized array elements when handling a panic?
like image 161
Shepmaster Avatar answered Mar 29 '26 10:03

Shepmaster



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!