tokio/runtime/task/
error.rs

1use std::any::Any;
2use std::fmt;
3use std::io;
4
5use super::Id;
6use crate::util::SyncWrapper;
7cfg_rt! {
8    /// Task failed to execute to completion.
9    pub struct JoinError {
10        repr: Repr,
11        id: Id,
12    }
13}
14
15enum Repr {
16    Cancelled,
17    Panic(SyncWrapper<Box<dyn Any + Send + 'static>>),
18}
19
20impl JoinError {
21    pub(crate) fn cancelled(id: Id) -> JoinError {
22        JoinError {
23            repr: Repr::Cancelled,
24            id,
25        }
26    }
27
28    pub(crate) fn panic(id: Id, err: Box<dyn Any + Send + 'static>) -> JoinError {
29        JoinError {
30            repr: Repr::Panic(SyncWrapper::new(err)),
31            id,
32        }
33    }
34
35    /// Returns true if the error was caused by the task being cancelled.
36    ///
37    /// See [the module level docs] for more information on cancellation.
38    ///
39    /// [the module level docs]: crate::task#cancellation
40    pub fn is_cancelled(&self) -> bool {
41        matches!(&self.repr, Repr::Cancelled)
42    }
43
44    /// Returns true if the error was caused by the task panicking.
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// # #[cfg(not(target_family = "wasm"))]
50    /// # {
51    /// use std::panic;
52    ///
53    /// #[tokio::main]
54    /// async fn main() {
55    ///     let err = tokio::spawn(async {
56    ///         panic!("boom");
57    ///     }).await.unwrap_err();
58    ///
59    ///     assert!(err.is_panic());
60    /// }
61    /// # }
62    /// ```
63    pub fn is_panic(&self) -> bool {
64        matches!(&self.repr, Repr::Panic(_))
65    }
66
67    /// Consumes the join error, returning the object with which the task panicked.
68    ///
69    /// # Panics
70    ///
71    /// `into_panic()` panics if the `Error` does not represent the underlying
72    /// task terminating with a panic. Use `is_panic` to check the error reason
73    /// or `try_into_panic` for a variant that does not panic.
74    ///
75    /// # Examples
76    ///
77    /// ```should_panic,ignore-wasm
78    /// use std::panic;
79    ///
80    /// #[tokio::main]
81    /// async fn main() {
82    ///     let err = tokio::spawn(async {
83    ///         panic!("boom");
84    ///     }).await.unwrap_err();
85    ///
86    ///     if err.is_panic() {
87    ///         // Resume the panic on the main task
88    ///         panic::resume_unwind(err.into_panic());
89    ///     }
90    /// }
91    /// ```
92    #[track_caller]
93    pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
94        self.try_into_panic()
95            .expect("`JoinError` reason is not a panic.")
96    }
97
98    /// Consumes the join error, returning the object with which the task
99    /// panicked if the task terminated due to a panic. Otherwise, `self` is
100    /// returned.
101    ///
102    /// # Examples
103    ///
104    /// ```should_panic,ignore-wasm
105    /// use std::panic;
106    ///
107    /// #[tokio::main]
108    /// async fn main() {
109    ///     let err = tokio::spawn(async {
110    ///         panic!("boom");
111    ///     }).await.unwrap_err();
112    ///
113    ///     if let Ok(reason) = err.try_into_panic() {
114    ///         // Resume the panic on the main task
115    ///         panic::resume_unwind(reason);
116    ///     }
117    /// }
118    /// ```
119    pub fn try_into_panic(self) -> Result<Box<dyn Any + Send + 'static>, JoinError> {
120        match self.repr {
121            Repr::Panic(p) => Ok(p.into_inner()),
122            _ => Err(self),
123        }
124    }
125
126    /// Returns a [task ID] that identifies the task which errored relative to
127    /// other currently spawned tasks.
128    ///
129    /// [task ID]: crate::task::Id
130    pub fn id(&self) -> Id {
131        self.id
132    }
133}
134
135impl fmt::Display for JoinError {
136    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match &self.repr {
138            Repr::Cancelled => write!(fmt, "task {} was cancelled", self.id),
139            Repr::Panic(p) => match panic_payload_as_str(p) {
140                Some(panic_str) => {
141                    write!(
142                        fmt,
143                        "task {} panicked with message {:?}",
144                        self.id, panic_str
145                    )
146                }
147                None => {
148                    write!(fmt, "task {} panicked", self.id)
149                }
150            },
151        }
152    }
153}
154
155impl fmt::Debug for JoinError {
156    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match &self.repr {
158            Repr::Cancelled => write!(fmt, "JoinError::Cancelled({:?})", self.id),
159            Repr::Panic(p) => match panic_payload_as_str(p) {
160                Some(panic_str) => {
161                    write!(fmt, "JoinError::Panic({:?}, {:?}, ...)", self.id, panic_str)
162                }
163                None => write!(fmt, "JoinError::Panic({:?}, ...)", self.id),
164            },
165        }
166    }
167}
168
169impl std::error::Error for JoinError {}
170
171impl From<JoinError> for io::Error {
172    fn from(src: JoinError) -> io::Error {
173        io::Error::new(
174            io::ErrorKind::Other,
175            match src.repr {
176                Repr::Cancelled => "task was cancelled",
177                Repr::Panic(_) => "task panicked",
178            },
179        )
180    }
181}
182
183fn panic_payload_as_str(payload: &SyncWrapper<Box<dyn Any + Send>>) -> Option<&str> {
184    // Panic payloads are almost always `String` (if invoked with formatting arguments)
185    // or `&'static str` (if invoked with a string literal).
186    //
187    // Non-string panic payloads have niche use-cases,
188    // so we don't really need to worry about those.
189    if let Some(s) = payload.downcast_ref_sync::<String>() {
190        return Some(s);
191    }
192
193    if let Some(s) = payload.downcast_ref_sync::<&'static str>() {
194        return Some(s);
195    }
196
197    None
198}