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