3

If using set_hook, we can get lots of information, especially the stack trace - which is very helpful. However, with catch_unwind, I only get a Result which contains almost no useful information at all. Therefore, I wonder how to get panic information (especially stack trace) with Rust's catch_unwind?

I am in a multi-threading environment, where there are many threads concurrently running and any can panic. I guess I should use set_hook together with catch_unwind, and also uses some thread-local variables, but I am not sure about whether it is feasible and the details.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
ch271828n
  • 15,854
  • 5
  • 53
  • 88
  • Does this answer your question? [Retrieving backtrace from a panic in hook in Rust?](https://stackoverflow.com/questions/54917373/retrieving-backtrace-from-a-panic-in-hook-in-rust) – Kitsu Oct 16 '21 at 06:40
  • @Kitsu No. I want to have backtrace *at the code of* catch_unwind, instead of at the code inside set_hook. – ch271828n Oct 16 '21 at 06:52
  • Actually there will be backtrace for catch_unwind, you just need to filter some items: [link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3c1532af5cd6a3ae07c9212d95f66532). – Kitsu Oct 16 '21 at 06:58
  • @Kitsu Sorry I mean, in your example, `main() {let _ = std::panic::catch_unwind(f); GET_BACKTRACE_HERE; }`. I want backtrace at `GET_BACKTRACE_HERE` https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=80d112cb128b4de4062c890b1c4ae1fe – ch271828n Oct 16 '21 at 07:00
  • Actually backtrace at hook contains it as sub-bactrace: ``` 11: playground::g at src/main.rs:8:13 12: playground::main at src/main.rs:16:5 ``` For release mode it's inlined, so you can add `#[inline(never)]` to have the similar result. – Kitsu Oct 16 '21 at 07:03
  • @Kitsu I know. The problem is: we do have backtrace at the `set_hook`'s callback, but how can I transfer that value to the `GET_BACKTRACE_HERE` location? I may use ThreadLocal, but is that safe? (e.g. when multithreading, or multi-panic happens? I am not familiar with panics) – ch271828n Oct 16 '21 at 07:07
  • 1
    Oh, now I got it. That's interesting one, I think thread-local should work: [link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0c79bfd969899cc391a3da79ec1643d7). – Kitsu Oct 16 '21 at 07:22
  • Thank you! You can make it an answer and I will accept it. – ch271828n Oct 16 '21 at 23:08

1 Answers1

4

You can get a backtrace in the panic hook by using the backtrace crate, however that doesn't help in catch_unwind since the stack has already been unwound from where the panic occurred.

What you can do is smuggle the backtrace from the panic hook into the catcher by storing it in a thread local variable. This should be reliable since a panic while panicking is an automatic process abort (so you can't overwrite one that hasn't been caught yet), and panics do not propagate across thread boundaries (joining a panicked thread returns a Result<_, Box<dyn Any>> with the panic as the error).

Here's a full example:

use std::cell::RefCell;
use backtrace::Backtrace;

thread_local! {
    static Backtrace: RefCell<Option<Backtrace>> = RefCell::new(None);
}

fn f() {
    panic!("xyz");
}

fn g() {
    if let Err(err) = std::panic::catch_unwind(f) {
        let b = Backtrace.with(|b| b.borrow_mut().take()).unwrap();
        println!("at panic:\n{:?}", b);
    }
}

fn main() {
    std::panic::set_hook(Box::new(|_| {
        let trace = Backtrace::new();
        Backtrace.with(move |b| b.borrow_mut().replace(trace));
    }));
    
    g();
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Does this work with async? – Brandon Ros Nov 18 '22 at 21:08
  • @BrandonRos [Yes](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=57f69f5707c879f4a01dd6156fff0fbf). The only change would be to use [`.catch_unwind()`](https://docs.rs/futures/latest/futures/future/trait.FutureExt.html#method.catch_unwind) from the [futures](https://crates.io/crates/futures) crate instead of `std::panic::catch_unwind`. – kmdreko Nov 18 '22 at 21:33
  • I've got that part, but when the result is an Err, I'm having a hard time getting what seems to be `std::panic::PanicInfo` out of it since it is `Box`. I'm trying to use `catch_unwind` to get the error message/stack trace of what went wrong. Any tips? – Brandon Ros Nov 18 '22 at 22:28
  • 1
    @BrandonRos The error message can best be retrieved by downcasting the `Box` into either `&str` or `String`, though it can still be something else if triggered by `panic_any()` ([docs](https://doc.rust-lang.org/std/macro.panic.html)). And as the focus of this question and answer, the backtrace can't be retrieved from the result but must be accessed out-of-band from the `Backtrace` thread-local variable. – kmdreko Nov 19 '22 at 00:28
  • I was hoping the full PanicInfo would be downcastable instead of just the “payload” field from it. – Brandon Ros Nov 20 '22 at 01:17