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}