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    /// use std::panic;
50    ///
51    /// #[tokio::main]
52    /// async fn main() {
53    ///     let err = tokio::spawn(async {
54    ///         panic!("boom");
55    ///     }).await.unwrap_err();
56    ///
57    ///     assert!(err.is_panic());
58    /// }
59    /// ```
60    pub fn is_panic(&self) -> bool {
61        matches!(&self.repr, Repr::Panic(_))
62    }
63
64    /// Consumes the join error, returning the object with which the task panicked.
65    ///
66    /// # Panics
67    ///
68    /// `into_panic()` panics if the `Error` does not represent the underlying
69    /// task terminating with a panic. Use `is_panic` to check the error reason
70    /// or `try_into_panic` for a variant that does not panic.
71    ///
72    /// # Examples
73    ///
74    /// ```should_panic
75    /// use std::panic;
76    ///
77    /// #[tokio::main]
78    /// async fn main() {
79    ///     let err = tokio::spawn(async {
80    ///         panic!("boom");
81    ///     }).await.unwrap_err();
82    ///
83    ///     if err.is_panic() {
84    ///         // Resume the panic on the main task
85    ///         panic::resume_unwind(err.into_panic());
86    ///     }
87    /// }
88    /// ```
89    #[track_caller]
90    pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
91        self.try_into_panic()
92            .expect("`JoinError` reason is not a panic.")
93    }
94
95    /// Consumes the join error, returning the object with which the task
96    /// panicked if the task terminated due to a panic. Otherwise, `self` is
97    /// returned.
98    ///
99    /// # Examples
100    ///
101    /// ```should_panic
102    /// use std::panic;
103    ///
104    /// #[tokio::main]
105    /// async fn main() {
106    ///     let err = tokio::spawn(async {
107    ///         panic!("boom");
108    ///     }).await.unwrap_err();
109    ///
110    ///     if let Ok(reason) = err.try_into_panic() {
111    ///         // Resume the panic on the main task
112    ///         panic::resume_unwind(reason);
113    ///     }
114    /// }
115    /// ```
116    pub fn try_into_panic(self) -> Result<Box<dyn Any + Send + 'static>, JoinError> {
117        match self.repr {
118            Repr::Panic(p) => Ok(p.into_inner()),
119            _ => Err(self),
120        }
121    }
122
123    /// Returns a [task ID] that identifies the task which errored relative to
124    /// other currently spawned tasks.
125    ///
126    /// [task ID]: crate::task::Id
127    pub fn id(&self) -> Id {
128        self.id
129    }
130}
131
132impl fmt::Display for JoinError {
133    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
134        match &self.repr {
135            Repr::Cancelled => write!(fmt, "task {} was cancelled", self.id),
136            Repr::Panic(p) => match panic_payload_as_str(p) {
137                Some(panic_str) => {
138                    write!(
139                        fmt,
140                        "task {} panicked with message {:?}",
141                        self.id, panic_str
142                    )
143                }
144                None => {
145                    write!(fmt, "task {} panicked", self.id)
146                }
147            },
148        }
149    }
150}
151
152impl fmt::Debug for JoinError {
153    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match &self.repr {
155            Repr::Cancelled => write!(fmt, "JoinError::Cancelled({:?})", self.id),
156            Repr::Panic(p) => match panic_payload_as_str(p) {
157                Some(panic_str) => {
158                    write!(fmt, "JoinError::Panic({:?}, {:?}, ...)", self.id, panic_str)
159                }
160                None => write!(fmt, "JoinError::Panic({:?}, ...)", self.id),
161            },
162        }
163    }
164}
165
166impl std::error::Error for JoinError {}
167
168impl From<JoinError> for io::Error {
169    fn from(src: JoinError) -> io::Error {
170        io::Error::new(
171            io::ErrorKind::Other,
172            match src.repr {
173                Repr::Cancelled => "task was cancelled",
174                Repr::Panic(_) => "task panicked",
175            },
176        )
177    }
178}
179
180fn panic_payload_as_str(payload: &SyncWrapper<Box<dyn Any + Send>>) -> Option<&str> {
181    // Panic payloads are almost always `String` (if invoked with formatting arguments)
182    // or `&'static str` (if invoked with a string literal).
183    //
184    // Non-string panic payloads have niche use-cases,
185    // so we don't really need to worry about those.
186    if let Some(s) = payload.downcast_ref_sync::<String>() {
187        return Some(s);
188    }
189
190    if let Some(s) = payload.downcast_ref_sync::<&'static str>() {
191        return Some(s);
192    }
193
194    None
195}