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