tokio-rs/tokio · error
`JoinError` reason is not a panic.
Error message
`JoinError` reason is not a panic.
What it means
JoinError::into_panic calls try_into_panic().expect('JoinError reason is not a panic.'). It panics (double panic risk) when called on a JoinError that is NOT a panic — i.e. it represents a cancelled task. into_panic assumes the underlying task panicked.
Source
Thrown at tokio/src/runtime/task/error.rs:95
/// ```should_panic,ignore-wasm
/// use std::panic;
///
/// #[tokio::main]
/// async fn main() {
/// let err = tokio::spawn(async {
/// panic!("boom");
/// }).await.unwrap_err();
///
/// if err.is_panic() {
/// // Resume the panic on the main task
/// panic::resume_unwind(err.into_panic());
/// }
/// }
/// ```
#[track_caller]
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
self.try_into_panic()
.expect("`JoinError` reason is not a panic.")
}
/// Consumes the join error, returning the object with which the task
/// panicked if the task terminated due to a panic. Otherwise, `self` is
/// returned.
///
/// # Examples
///
/// ```should_panic,ignore-wasm
/// use std::panic;
///
/// #[tokio::main]
/// async fn main() {
/// let err = tokio::spawn(async {
/// panic!("boom");
/// }).await.unwrap_err();
///
/// if let Ok(reason) = err.try_into_panic() {View on GitHub (pinned to 625954f365)
Solutions
- Check e.is_panic() before calling e.into_panic().
- Prefer try_into_panic() which returns Result and won't panic.
- Match on the error kind (cancelled vs panic) explicitly.
- Treat cancellation separately from panic recovery in your error path.
Example fix
// before
let panic_payload = err.into_panic(); // panics if cancelled
// after
if err.is_panic() {
std::panic::resume_unwind(err.into_panic());
} else {
// cancelled
return;
} Defensive patterns
Strategy: type-guard
Validate before calling
// Guard with is_panic before calling into_panic:
if err.is_panic() { /* safe to into_panic */ } Type guard
fn into_panic_safe(e: tokio::task::JoinError) -> Result<Box<dyn std::any::Any + Send>, tokio::task::JoinError> {
e.try_into_panic().map_err(|e| e)
} Try / catch
match err.try_into_panic() {
Ok(payload) => std::panic::resume_unwind(payload),
Err(e) => { /* cancelled */ return; }
} Prevention
- Prefer try_into_panic() over into_panic().
- Always check is_panic() first.
- Distinguish cancellation from panic in error handling.
When it happens
Trigger: Calling err.into_panic() on a JoinError that is actually Repr::Cancelled (task was aborted), not Repr::Panic.
Common situations: Unconditionally calling into_panic() after handle.await.err() without checking is_panic(); confusing cancellation with panic in error handling.
Related errors
- task was cancelled
- task panicked
- There must be more than one worker
- Can't get a task id when not inside a task
- cannot create LocalSet during thread shutdown
AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11).
Data as JSON: /api/errors/eb438c3eaa29c600.
Report an issue: GitHub.