use std::any::Any; use std::fmt; use std::io; use super::Id; use crate::util::SyncWrapper; cfg_rt! { /// Task failed to execute to completion. pub struct JoinError { repr: Repr, id: Id, } } enum Repr { Cancelled, Panic(SyncWrapper>), } impl JoinError { pub(crate) fn cancelled(id: Id) -> JoinError { JoinError { repr: Repr::Cancelled, id, } } pub(crate) fn panic(id: Id, err: Box) -> JoinError { JoinError { repr: Repr::Panic(SyncWrapper::new(err)), id, } } /// Returns true if the error was caused by the task being cancelled. /// /// See [the module level docs] for more information on cancellation. /// /// [the module level docs]: crate::task#cancellation pub fn is_cancelled(&self) -> bool { matches!(&self.repr, Repr::Cancelled) } /// Returns true if the error was caused by the task panicking. /// /// # Examples /// /// ``` /// use std::panic; /// /// #[tokio::main] /// async fn main() { /// let err = tokio::spawn(async { /// panic!("boom"); /// }).await.unwrap_err(); /// /// assert!(err.is_panic()); /// } /// ``` pub fn is_panic(&self) -> bool { matches!(&self.repr, Repr::Panic(_)) } /// Consumes the join error, returning the object with which the task panicked. /// /// # Panics /// /// `into_panic()` panics if the `Error` does not represent the underlying /// task terminating with a panic. Use `is_panic` to check the error reason /// or `try_into_panic` for a variant that does not panic. /// /// # Examples /// /// ```should_panic /// 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 { 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 /// 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() { /// // Resume the panic on the main task /// panic::resume_unwind(reason); /// } /// } /// ``` pub fn try_into_panic(self) -> Result, JoinError> { match self.repr { Repr::Panic(p) => Ok(p.into_inner()), _ => Err(self), } } /// Returns a [task ID] that identifies the task which errored relative to /// other currently spawned tasks. /// /// [task ID]: crate::task::Id pub fn id(&self) -> Id { self.id } } impl fmt::Display for JoinError { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.repr { Repr::Cancelled => write!(fmt, "task {} was cancelled", self.id), Repr::Panic(p) => match panic_payload_as_str(p) { Some(panic_str) => { write!( fmt, "task {} panicked with message {:?}", self.id, panic_str ) } None => { write!(fmt, "task {} panicked", self.id) } }, } } } impl fmt::Debug for JoinError { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.repr { Repr::Cancelled => write!(fmt, "JoinError::Cancelled({:?})", self.id), Repr::Panic(p) => match panic_payload_as_str(p) { Some(panic_str) => { write!(fmt, "JoinError::Panic({:?}, {:?}, ...)", self.id, panic_str) } None => write!(fmt, "JoinError::Panic({:?}, ...)", self.id), }, } } } impl std::error::Error for JoinError {} impl From for io::Error { fn from(src: JoinError) -> io::Error { io::Error::new( io::ErrorKind::Other, match src.repr { Repr::Cancelled => "task was cancelled", Repr::Panic(_) => "task panicked", }, ) } } fn panic_payload_as_str(payload: &SyncWrapper>) -> Option<&str> { // Panic payloads are almost always `String` (if invoked with formatting arguments) // or `&'static str` (if invoked with a string literal). // // Non-string panic payloads have niche use-cases, // so we don't really need to worry about those. if let Some(s) = payload.downcast_ref_sync::() { return Some(s); } if let Some(s) = payload.downcast_ref_sync::<&'static str>() { return Some(s); } None }