Skip to content

thread::scope clobbers the panic payload #139017

@SludgePhD

Description

@SludgePhD

I tried this code:

use std::{panic::catch_unwind, thread};

fn main() {
    let res = catch_unwind(|| {
        thread::scope(|scope| {
            scope.spawn(|| panic!("my panic message with useful information"));
        });
    });
    let payload = res.unwrap_err();
    let message = payload.downcast_ref::<&str>().unwrap();
    dbg!(message);
}

I expected to see this happen: the panic payload from the scoped thread should be forwarded to the main thread using panic::resume_unwind. The payload passed to panic! inside the scope should be retrievable there, and there should not be any additional panics.

Instead, this happened: a second panic with no link to the original is caused by the thread::scope implementation, and the payload is a meaningless "a scoped thread panicked" message.

Meta

rustc --version --verbose:

rustc 1.85.1

beta and nightly also behave like this

Activity

added
needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.
on Mar 27, 2025
added
T-libsRelevant to the library team, which will review and decide on the PR/issue.
A-threadArea: `std::thread`
A-panicArea: Panicking machinery
on Mar 27, 2025
theemathas

theemathas commented on Mar 28, 2025

@theemathas
Contributor

The scope() documentation states:

If you want to handle panics from spawned threads, join them before the end of the scope.

You can get the payload with the following code:

use std::thread;

fn main() {
    let res = thread::scope(|scope| -> thread::Result<()> {
        let join_handle = scope.spawn(|| panic!("my panic message with useful information"));
        join_handle.join()?;
        // Maybe join other threads here
        Ok(())
    });
    let payload = res.unwrap_err();
    let message = payload.downcast_ref::<&str>().unwrap();
    dbg!(message);
}
SludgePhD

SludgePhD commented on Mar 28, 2025

@SludgePhD
Author

Sure, but there is no good reason for the behavior in the first place. Structured concurrency primitives like thread::scope have the opportunity to perfectly forward panics across thread boundaries, it would be a waste not to use it.

hanna-kruppe

hanna-kruppe commented on Mar 28, 2025

@hanna-kruppe
Contributor

What do you expect to happen the implicit join at the end of the scope finds more than one panicked thread?

SludgePhD

SludgePhD commented on Mar 28, 2025

@SludgePhD
Author

Then any of the panics can be forwarded (which is the same behavior as rayon's). The code running in the threads also has to ensure that it doesn't cause any knock-on panics for this strategy to yield a "useful" panic payload, of course.

removed
needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.
on May 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-panicArea: Panicking machineryA-threadArea: `std::thread`C-bugCategory: This is a bug.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @hanna-kruppe@theemathas@lolbinarycat@jieyouxu@rustbot

        Issue actions

          `thread::scope` clobbers the panic payload · Issue #139017 · rust-lang/rust