I'm writing a console application that needs to do some cleanup when it exits. It opens an alternate screen buffer, changes bg/fg colors, a bunch of things like that that I'd like to revert when the user terminates the app. I understand that there's no 100% reliable way to do this but I'd like it to work in as many cases as possible. Note that this also needs to work on Windows.
What I have so far is the following:
A scope guard in my main function that handles the case of a graceful termination.
A panic handler that will clean up on every panic (my application is single-threaded right now and never tries to recover from a panic). Not sure if this is necessary or if I can rely on unwind dropping my scope guard instead.
The ctrlc library to handle the most common termination signals. The handler will prevent shutdown and instead set a termination flag in my application to make it exit gracefully.
Is the panic handler the correct approach? Is there any way to intercept a call to exit()? Anything else that I'm missing?
I'd just rely on the scope guard. That'll run when the thread unwinds if the code panics, and then as long as you set up the logic to also gracefully exit on a signal you should be set.
You can register code to run on calls to exit() with atexit(), but there are pretty huge caveats there and I wouldn't recommend it.
pub struct App {
_private: (),
}
impl App {
pub fn new() -> Self {
println!("Init the world");
App { _private: () }
}
}
impl Drop for App {
fn drop(&mut self) {
println!("Drop the world");
}
}
In your main.rs you do
fn main() {
let app = app::App::new();
}
The good thing about this approach is your App provides a scope:
you can store there all things that must live as long as your app lives
drop is called even in case of panic, but not when it panics itself (so called "double panic" which kills the app). Of course, your app can still be killed with TerminateProcess/kill 9, but I wouldn't bother.
Yes, thank you, that's what I meant by a "scope guard". This is exactly what I'm doing right now.
There's no way for a process to handle kill -9, that's why I said there's no 100% reliable way to do this. But both kill and ctrl+c can be easily handled by the ctrlc crate, which is what I'm doing. I think these are both pretty common ways for a process to get terminated and the ctrlc crate provides a very low effort way of handling them.
A side note: I tried removing the panic hook and immediatelly realized why I put it there in the first place. The default panic hook will first print the error message and then unwind and call my cleanup function. The cleanup function will then revert to the primary screen buffer, which means the error message will get immediatelly lost. My custom panic hook calls the cleanup function first and only then prints the error message so that the user / developer can actually read it.