Return value from catch_unwind is a useless "Any"

Ref: Rust Playground

In a multi-thread program, I want to catch panics in threads, log the backtrace info, and shut down the rest of the program cleanly. Normal behavior on panics in threads is that the offending thread prints messages to standard output, while other threads continue happily on.

Thread panics can be caught with catch_unwind, which gives a chance to do something to stop the rest of the system. Calling resume_unwind in the catch_unwind handler continues the normal unwind. That works fine.

But the argument passed to catch_unwind, which should contain error info, is a generic Result. Inside the Err is an <Any<something>>. That Any cannot be fomatted with "Debug". See the attached Playground. It would make sense if if were a PanicInfo, but it doesn't seem to be.

How do I decode that error value into something useful?

(What I'd really like to do is catch the error, capture the backtrace as a string, put that into the program's log file, and shut down the rest of the program cleanly. More support for that seems to be coming, but is still churning in nightly.)

It's almost always String or &'static str. Use the downcasting methods.

1 Like

The Err value of the Result is the value that was passed to panic!, not any of the info available in the panic hook. If you need a backtrace, consider setting your own panic hook.

1 Like

Tried that. Works for explicit panic: Rust Playground

That returns an Any<&str>

Now to try some other things, such as subscript out of range.

That returns Any<String>

Divide by zero:

Returns Any<&str>

So Any<&str> and Any<String> cover the common cases. Any other error types that need coverage?

This should be documented.

This will commonly, but not always, be a &'static str or String.

PanicInfo::payload

1 Like

I've always considered it wise to copy the standard library's behavior for extracting the message from the payload object:

As you see, it only covers &'static str and String payloads.

That's useful. Returning "Box" as an actual string is an amusing fallback. I've never seen that show up in a traceback, so if anything uses it, it's rare.

You can't access the traits of an Any, so you can't just use the Debug trait to get a string.

It's surprising how much low-level machinery I have to build to get a multi-thread GUI program to close down properly on a thread panic. I had to switch some mutexes to parking_lot to get fairness, which meant that I lost mutex poisoning. Releasing the lock on a panic, which parking_lot does, really isn't a good practice. Is there a good alternative to parking_lot which has both fairness and poisoning?

Goal is, some thread panics, everything winds down, user gets a popup dialog to send a bug report, program exits cleanly.

The problem is that it might not implement Debug. And supporting conditional trait downcasting like this is very non-trivial.

Anyway, panics raised by the usual macros/functions only raise String/&'static strs (depending on whether a literal or a format string was passed). AFAIK arbitrary data can only be included by using std::panic::panic_any or std::panic::resume_unwind.

You can build you own poison detection on top of parking_lot's Mutex, for example see this playground.

1 Like

Turns out that parking_lot is not compatible with catch_unwind. Parking_lot mutexes are not UnwindSafe.

Worse, I have that problem in a major crate. Way down at the interface between Wgpu and Vulkan graphics, there's a parking_lot unsafe cell:

the type `UnsafeCell<HashMap<wgpu_hal::vulkan::FramebufferKey, ash::vk::definitions::Framebuffer, BuildHasherDefault<fxhash::FxHasher>>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> libclient/src/viewer/movement.rs:167:21
    |
167 |                     catch_panic(&client_base_shared_link.abort_flag, || {   // causes other threads to quit on panic                        
    |                     ^^^^^^^^^^^ `UnsafeCell<HashMap<wgpu_hal::vulkan::FramebufferKey, ash::vk::definitions::Framebuffer, BuildHasherDefault<fxhash::FxHasher>>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `ClientBaseShared`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<HashMap<wgpu_hal::vulkan::FramebufferKey, ash::vk::definitions::Framebuffer, BuildHasherDefault<fxhash::FxHasher>>>`
    = note: required because it appears within the type `parking_lot::lock_api::Mutex<parking_lot::RawMutex, HashMap<wgpu_hal::vulkan::FramebufferKey, ash::vk::definitions::Framebuffer, BuildHasherDefault<fxhash::FxHasher>>>`
    = note: required because it appears within the type `wgpu_hal::vulkan::DeviceShared`
    = note: required because it appears within the type `generateddefs::smallvec::alloc::sync::ArcInner<wgpu_hal::vulkan::DeviceShared>`
    = note: required because it appears within the type `PhantomData<generateddefs::smallvec::alloc::sync::ArcInner<wgpu_hal::vulkan::DeviceShared>>`
    = note: required because it appears within the type `Arc<wgpu_hal::vulkan::DeviceShared>`
    = note: required because it appears within the type `wgpu_hal::vulkan::Swapchain`
    = note: required because it appears within the type `Option<wgpu_hal::vulkan::Swapchain>`
    = note: required because it appears within the type `wgpu_hal::vulkan::Surface`
    = note: required because it appears within the type `wgpu_core::instance::HalSurface<wgpu_hal::vulkan::Api>`
    = note: required because it appears within the type `Option<wgpu_core::instance::HalSurface<wgpu_hal::vulkan::Api>>`
    = note: required because it appears within the type `wgpu_core::instance::Surface`
    = note: required because it appears within the type `PhantomData<wgpu_core::instance::Surface>`
    = note: required because it appears within the type `wgpu_core::id::Id<wgpu_core::instance::Surface>`
    = note: required because it appears within the type `wgpu_core::id::Valid<wgpu_core::id::Id<wgpu_core::instance::Surface>>`
    = note: required because it appears within the type `wgpu_core::resource::TextureInner<wgpu_hal::empty::Api>`
    = note: required because it appears within the type `wgpu_core::resource::Texture<wgpu_hal::empty::Api>`
    = note: required because it appears within the type `PhantomData<wgpu_core::resource::Texture<wgpu_hal::empty::Api>>`
    = note: required because it appears within the type `wgpu_core::id::Id<wgpu_core::resource::Texture<wgpu_hal::empty::Api>>`
    = note: required because it appears within the type `wgpu_core::init_tracker::texture::TextureInitTrackerAction`
    = note: required because it appears within the type `PhantomData<wgpu_core::init_tracker::texture::TextureInitTrackerAction>`
    = note: required because it appears within the type `Unique<wgpu_core::init_tracker::texture::TextureInitTrackerAction>`
    = note: required because it appears within the type `generateddefs::smallvec::alloc::raw_vec::RawVec<wgpu_core::init_tracker::texture::TextureInitTrackerAction>`
    = note: required because it appears within the type `Vec<wgpu_core::init_tracker::texture::TextureInitTrackerAction>`
    = note: required because it appears within the type `wgpu_core::binding_model::BindGroup<wgpu_hal::empty::Api>`
    = note: required because it appears within the type `PhantomData<wgpu_core::binding_model::BindGroup<wgpu_hal::empty::Api>>`
    = note: required because it appears within the type `wgpu_core::id::Id<wgpu_core::binding_model::BindGroup<wgpu_hal::empty::Api>>`
    = note: required because it appears within the type `wgpu::BindGroup`
    = note: required because it appears within the type `rend3::util::mipmap::MipmapGenerator`
    = note: required because it appears within the type `rend3::renderer::Renderer`
    = note: required because it appears within the type `generateddefs::smallvec::alloc::sync::ArcInner<rend3::renderer::Renderer>`
    = note: required because it appears within the type `PhantomData<generateddefs::smallvec::alloc::sync::ArcInner<rend3::renderer::Renderer>>`
    = note: required because it appears within the type `Arc<rend3::renderer::Renderer>`
    = note: required because it appears within the type `libscene::render::renderbase::RenderBase`
    = note: required because it appears within the type `RenderScene`
    = note: required because it appears within the type `generateddefs::smallvec::alloc::sync::ArcInner<RenderScene>`
    = note: required because it appears within the type `PhantomData<generateddefs::smallvec::alloc::sync::ArcInner<RenderScene>>`
    = note: required because it appears within the type `Arc<RenderScene>`
    = note: required because it appears within the type `Option<Arc<RenderScene>>`
note: required because it appears within the type `ClientBaseShared`
   --> libclient/src/base/clientbase.rs:208:12

So I can't use catch_unwind in any thread that has a handle to the graphics context.

Parking_lot seems to have chosen speed over safety, which is a problem.

So how do people do a clean shutdown of GUI programs in serious trouble?

If you're handling the poisoning yourself you can just silence the unwind safety error with AssertUnwindSafe.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.