Catching panic! when Rust called from C FFI, without spawning threads
Asked Answered
A

2

15

I'm working on a Rust wrapper for the Duktape JavaScript interpreter. In a normal use case, the call stack will look like this:

  1. Rust: Arbitrary application code.
  2. Rust: My library wrapper.
  3. C: The Duktape interpreter.
  4. Rust: My Rust code.
  5. Rust: Arbitrary callbacks into application code.

What happens if (5) calls panic!? According to various Rust developers on IRC, attempting to panic! from inside non-Rust callframes like (3) may result in undefined behavior.

But according the Rust documentation, the only way to catch a panic! is using std::task::try, which spawns an extra thread. There's also rustrt::unwind::try, which cannot be nested twice within a single thread, among other restrictions.

One solution, proposed by Benjamin Herr, is to abort the process if the code in (5) panics. I've packaged his solution as abort_on_panic, and it appears to work, for values of "work" that include "crashing the entire program, but at least not corrupting things subtly":

abort_on_panic!("cannot panic inside this block", {
    panic!("something went wrong!");
});

But is a way to emulate std::task::try without the overhead of thread/task creation?

Adown answered 9/12, 2014 at 17:15 Comment(0)
C
11

As of Rust 1.9.0, you can use panic::catch_unwind to recover the error:

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
        panic!("oh no!");
    });
    assert!(result.is_err());
}

Passing it to the next layer is just as easy with panic::resume_unwind:

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
        panic!("oh no!");
    });

    if let Err(e) = result {
        panic::resume_unwind(e);
    }
}
Chlorpromazine answered 19/3, 2016 at 15:59 Comment(0)
A
4

Editor's note: This answer predates Rust 1.0 and is no longer necessarily accurate. Other answers still contain valuable information.

You cannot 'catch' a panic!. It terminates execution of the current thread. Therefore, without spinning up a new one to isolate, it's going to terminate the thread you're in.

Arabinose answered 11/12, 2014 at 12:57 Comment(2)
Thank you! Is it actually necessary for std::task::try to actually spawn a new OS thread? It seems like it could be implemented to save some runtime state, invoke the "task" in the same OS thread, and restore the runtime state when it finishes. This would maintain the same public API as the current version, but it might save the cost of calling pthread_new everything JavaScript calls out to Rust.Adown
That's a bit higher-level than Rust is.Arabinose

© 2022 - 2024 — McMap. All rights reserved.