Skip to main content

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}