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}