How can AssertUnwindSafe be used with the CatchUnwind future

Viewed 673

I want to be able to pass an mutable reference to function, but catch unwinds that may come from that function. The purpose is for use in writing some test wrappers (setup, teardown), not general error handling.

If I were using typical sync code, I can get this to compile and work...

struct MyStruct {
    n: u32
}

fn my_func(s: &mut MyStruct) {
    s.n += 1;
    panic!("Oh no!");
}

fn main() {
    let mut ctx = MyStruct { n: 1 };
    let mut wrapper = std::panic::AssertUnwindSafe(&mut ctx);
    let result = std::panic::catch_unwind(move || {
        my_func(*wrapper);
    });
    
    // Do some cleanup of `ctx` here.

    if let Err(err) = result {
        std::panic::resume_unwind(err);
    }
}

However, I haven't been able to figure out how to do this using futures and async/await. In that case, I would be trying to call a function that has been declared as async. I have tried various things like the code below:

async fn run_async(s: &mut MyStruct) {
    s.n += 1;
    panic!("Oh no!");
}

#[tokio::main]
async fn main() {
    let mut ctx = MyStruct { n : 1 };
    let wrapper = std::panic::AssertUnwindSafe(&mut ctx);
    let result = async move {
        run_async(*wrapper).catch_unwind().await
    }.await;
    
    println!("{:?}", result);
}

However, I typically will end up with an error such as:

the type &mut MyStruct may not be safely transferred across an unwind boundary`.

I was under the belief that AssertUnwindSafe was supposed to help with these problems, as they did with the sync code. But there is obviously something I'm not understanding at the intersection of AssertUnwindSafe and the async/await.

1 Answers

With std::panic::catch_unwind, the closure provided must be UnwindSafe, and using a mutable reference inside will make the closure not implement UnwindSafe. This is why wrapping the reference and moving that instead works.

However, with futures::future::FutureExt::catch_unwind, the future provided must be UnwindSafe, and the future generated by run_async doesn't care if the reference came from an AssertUnwindSafe wrapper or not since you're unwrapping it before you call it. So, you should assert that the future itself is safe:

use futures::future::FutureExt;

struct MyStruct {
    n: i32
}

async fn run_async(s: &mut MyStruct) {
    s.n += 1;
    panic!("Oh no!");
}

#[tokio::main]
async fn main() {
    let mut ctx = MyStruct { n : 1 };
    let result = async move {
        // AssertUnwindSafe moved to the future
        std::panic::AssertUnwindSafe(run_async(&mut ctx)).catch_unwind().await
    }.await;
    
    println!("{:?}", result);
}
Related