1 use std::any::Any;
2 use std::fmt;
3 use std::io;
4 
5 use super::Id;
6 use crate::util::SyncWrapper;
7 cfg_rt! {
8     /// Task failed to execute to completion.
9     pub struct JoinError {
10         repr: Repr,
11         id: Id,
12     }
13 }
14 
15 enum Repr {
16     Cancelled,
17     Panic(SyncWrapper<Box<dyn Any + Send + 'static>>),
18 }
19 
20 impl JoinError {
cancelled(id: Id) -> JoinError21     pub(crate) fn cancelled(id: Id) -> JoinError {
22         JoinError {
23             repr: Repr::Cancelled,
24             id,
25         }
26     }
27 
panic(id: Id, err: Box<dyn Any + Send + 'static>) -> JoinError28     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
is_cancelled(&self) -> bool40     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     /// ```
is_panic(&self) -> bool60     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]
into_panic(self) -> Box<dyn Any + Send + 'static>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     /// ```
try_into_panic(self) -> Result<Box<dyn Any + Send + 'static>, JoinError>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
id(&self) -> Id127     pub fn id(&self) -> Id {
128         self.id
129     }
130 }
131 
132 impl fmt::Display for JoinError {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result133     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 
152 impl fmt::Debug for JoinError {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result153     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 
166 impl std::error::Error for JoinError {}
167 
168 impl From<JoinError> for io::Error {
from(src: JoinError) -> io::Error169     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 
panic_payload_as_str(payload: &SyncWrapper<Box<dyn Any + Send>>) -> Option<&str>180 fn 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 }
196