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}