How can I cause a panic on a thread to immediately

2019-06-21 10:27发布

问题:

In Rust, a panic terminates the current thread but is not sent back to the main thread. The solution we are told is to use join. However, this blocks the currently executing thread. So if my main thread spawns 2 threads, I cannot join both of them and immediately get a panic back.

let jh1 = thread::spawn(|| { println!("thread 1"); sleep(1000000); };
let jh2 = thread::spawn(|| { panic!("thread 2") };

In the above, if I join on thread 1 and then on thread 2 I will be waiting for 1 before ever receiving a panic from either thread

Although in some cases I desire the current behavior, my goal is to default to Go's behavior where I can spawn a thread and have it panic on that thread and then immediately end the main thread. (The Go specification also documents a protect function, so it is easy to achieve Rust behavior in Go).

回答1:

good point, in go the main thread doesn't get unwound, the program just crashes, but the original panic is reported. This is in fact the behavior I want (although ideally resources would get cleaned up properly everywhere).

This you can achieve with the unstable std::panic::set_handler(). You can set a handler which prints the panic info and then exits the whole process, something like this:

#![feature(std_panic, panic_handler)]

use std::thread;
use std::panic;
use std::process;

fn main() {
    // take_handler() returns the default handler in case when a custom one is not set
    let orig_handler = panic::take_handler();
    panic::set_handler(move |panic_info| {
        // invoke the default handler and exit the process
        orig_handler(panic_info);
        process::exit(1);
    });

    thread::spawn(move || {
        panic!("something bad happened");
    }).join();

    // this line won't ever be invoked because of process::exit()
    println!("Won't be printed");
}

Try commenting set_handler() out, and you'll see that println!() line gets executed.

However, this approach, due to process::exit() usage, will not allow resources allocated by other threads to be freed. In fact, I'm not sure that Go runtime allows this as well; it is likely that it uses the same approach with aborting the process.



回答2:

I tried to force my code to stop processing when any of threads panicked. The only more-or-less clear solution without using unstable features was to use Drop trait implemented on some struct. This can lead to a resource leak, but in my scenario I'm ok with this.

use std::process;
use std::thread;
use std::time::Duration;


static THREAD_ERROR_CODE: i32 = 0x1;
static NUM_THREADS: u32 = 17;
static PROBE_SLEEP_MILLIS: u64 = 500;

struct PoisonPill;

impl Drop for PoisonPill {
    fn drop(&mut self) {
        if thread::panicking() {
            println!("dropped while unwinding");
            process::exit(THREAD_ERROR_CODE);
        }
    }
}

fn main() {
    let mut thread_handles = vec![];

    for i in 0..NUM_THREADS {
        thread_handles.push(thread::spawn(move || {
            let b = PoisonPill;
            thread::sleep(Duration::from_millis(PROBE_SLEEP_MILLIS));
            if i % 2 == 0 {
                println!("kill {}", i);
                panic!();
            }
            println!("this is thread number {}", i);
        }));
    }

    for handle in thread_handles {
        let _ = handle.join();
    }
}

No matter how b = PoisonPill leaves it's scope, normal or after panic!, its Drop method kicks in. You can distinguish if the caller panicked using thread::panicking and take some action — in my case killing the process.



标签: rust