问题
I'm creating a terminal text editor in Rust. The editor puts the terminal into raw mode, disabling character echoing and the like, and then restores the original terminal function upon exit.
However, the editor has some bugs, and crashes unexpectedly every now and again due to issues like unsigned variable underflow. When this happens, the cleanup code which would restore the terminal to its original state never runs.
The cleanup function I'd like to run is the following:
fn restore_orig_mode(editor_config: &EditorConfig) -> io::Result<()> {
termios::tcsetattr(STDIN, termios::TCSAFLUSH, &editor_config.orig_termios)
}
回答1:
Try catch_unwind. I haven't used it, so I cannot guarantee it works.
回答2:
In the latest stable Rust, @for1096's answer is the best. In your case, it might be quite simple to apply because your clean-up does not need to use state that is shared with the application code:
use std::panic::catch_unwind;
fn run_editor(){
panic!("Error!");
println!("Running!");
}
fn clean_up(){
println!("Cleaning up!");
}
fn main(){
match catch_unwind(|| run_editor()) {
Ok(_) => println!("Exited successfully"),
Err(_) => clean_up()
}
}
If your clean-up requires accessing shared state with your application, then you will need some additional machinery to convince the compiler that it is safe. For example, if your application looks like this:
// The shared state of your application
struct Editor { /* ... */ }
impl Editor {
fn run(&mut self){
println!("running!");
// panic!("Error!");
}
fn clean_up(&mut self){
println!("cleaning up!");
}
fn new() -> Editor {
Editor { }
}
}
Then, in order to call clean_up
, you would have to manage access to the data, something like this:
use std::panic::catch_unwind;
use std::sync::{Arc, Mutex};
fn main() {
let editor = Arc::new(Mutex::new(Editor::new()));
match catch_unwind(|| editor.lock().unwrap().run()) {
Ok(_) => println!("Exited successfully"),
Err(_) => {
println!("Application panicked.");
let mut editor = match editor.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
editor.clean_up();
}
}
}
Prior to Rust 1.9, you can only handle panics that occur in a child thread. This isn't much different except that you need to clone the Arc
because the original one will need to be move
d into the thread closure.
use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
let editor = Arc::new(Mutex::new(Editor::new()));
// clone before the original is moved into the thread closure
let editor_recovery = editor.clone();
let child = thread::spawn(move || {
editor.lock().unwrap().run();
});
match child.join() {
Ok(_) => println!("Exited successfully"),
Err(_) => {
println!("Application panicked.");
let mut editor = match editor_recovery.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
editor.clean_up();
}
}
}
回答3:
A common solution to this problem in Unix applications and using other languages such as C is to fork()
and have your parent wait for the child. On an error exit by the child, clean up.
This is really the only reliable way to clean up if the clean up is important. For example, your program might be killed by the Linux OOM kill. It will never be able to run a language specific panic, exception, at_exit or anything like that because the operating system simply destroys it.
By having a separate process watching it, that process can handle any special cleanup of files or shared memory.
This solution does not really require using fork()
. The parent could be a shell script or a separate executable.
来源:https://stackoverflow.com/questions/43441047/whats-the-best-way-to-register-a-function-to-run-during-an-unexpected-exit-of-a