procspawn/
error.rs

1use std::fmt;
2use std::io;
3
4use ipc_channel::ipc::{IpcError, TryRecvError};
5use ipc_channel::{Error as BincodeError, ErrorKind as BincodeErrorKind};
6use serde::{Deserialize, Serialize};
7
8/// Represents a panic caugh across processes.
9///
10/// This contains the marshalled panic information so that it can be used
11/// for other purposes.
12///
13/// This is similar to `std::panic::PanicInfo` but can cross process boundaries.
14#[derive(Serialize, Deserialize)]
15pub struct PanicInfo {
16    msg: String,
17    pub(crate) location: Option<Location>,
18    #[cfg(feature = "backtrace")]
19    pub(crate) backtrace: Option<backtrace::Backtrace>,
20}
21
22/// Location of a panic.
23///
24/// This is similar to `std::panic::Location` but can cross process boundaries.
25#[derive(Serialize, Deserialize, Debug)]
26pub struct Location {
27    file: String,
28    line: u32,
29    column: u32,
30}
31
32impl Location {
33    pub(crate) fn from_std(loc: &std::panic::Location) -> Location {
34        Location {
35            file: loc.file().into(),
36            line: loc.line(),
37            column: loc.column(),
38        }
39    }
40
41    /// Returns the name of the source file from which the panic originated.
42    pub fn file(&self) -> &str {
43        &self.file
44    }
45
46    /// Returns the line number from which the panic originated.
47    pub fn line(&self) -> u32 {
48        self.line
49    }
50
51    /// Returns the column from which the panic originated.
52    pub fn column(&self) -> u32 {
53        self.column
54    }
55}
56
57impl PanicInfo {
58    /// Creates a new panic object.
59    pub(crate) fn new(s: &str) -> PanicInfo {
60        PanicInfo {
61            msg: s.into(),
62            location: None,
63            #[cfg(feature = "backtrace")]
64            backtrace: None,
65        }
66    }
67
68    /// Returns the message of the panic.
69    pub fn message(&self) -> &str {
70        self.msg.as_str()
71    }
72
73    /// Returns the panic location.
74    pub fn location(&self) -> Option<&Location> {
75        self.location.as_ref()
76    }
77
78    /// Returns a reference to the backtrace.
79    ///
80    /// Typically this backtrace is already resolved because it's currently
81    /// not possible to cross the process boundaries with unresolved backtraces.
82    #[cfg(feature = "backtrace")]
83    pub fn backtrace(&self) -> Option<&backtrace::Backtrace> {
84        self.backtrace.as_ref()
85    }
86}
87
88impl fmt::Debug for PanicInfo {
89    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90        f.debug_struct("PanicInfo")
91            .field("message", &self.message())
92            .field("location", &self.location())
93            .field("backtrace", &{
94                #[cfg(feature = "backtrace")]
95                {
96                    self.backtrace()
97                }
98                #[cfg(not(feature = "backtrace"))]
99                {
100                    None::<()>
101                }
102            })
103            .finish()
104    }
105}
106
107impl fmt::Display for PanicInfo {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        write!(f, "{}", self.msg)
110    }
111}
112
113/// Encapsulates errors of the procspawn crate.
114///
115/// In particular it gives access to remotely captured panics.
116#[derive(Debug)]
117pub struct SpawnError {
118    kind: SpawnErrorKind,
119}
120
121#[derive(Debug)]
122enum SpawnErrorKind {
123    Bincode(BincodeError),
124    Io(io::Error),
125    Panic(PanicInfo),
126    IpcChannelClosed(io::Error),
127    Cancelled,
128    TimedOut,
129    Consumed,
130}
131
132impl SpawnError {
133    /// If a panic ocurred this returns the captured panic info.
134    pub fn panic_info(&self) -> Option<&PanicInfo> {
135        if let SpawnErrorKind::Panic(ref info) = self.kind {
136            Some(info)
137        } else {
138            None
139        }
140    }
141
142    /// True if this error comes from a panic.
143    pub fn is_panic(&self) -> bool {
144        self.panic_info().is_some()
145    }
146
147    /// True if this error indicates a cancellation.
148    pub fn is_cancellation(&self) -> bool {
149        matches!(self.kind, SpawnErrorKind::Cancelled)
150    }
151
152    /// True if this error indicates a timeout.
153    pub fn is_timeout(&self) -> bool {
154        matches!(self.kind, SpawnErrorKind::TimedOut)
155    }
156
157    /// True if this means the remote side closed.
158    pub fn is_remote_close(&self) -> bool {
159        matches!(self.kind, SpawnErrorKind::IpcChannelClosed(..))
160    }
161
162    pub(crate) fn new_remote_close() -> SpawnError {
163        SpawnError {
164            kind: SpawnErrorKind::IpcChannelClosed(io::Error::new(
165                io::ErrorKind::ConnectionReset,
166                "remote closed",
167            )),
168        }
169    }
170
171    pub(crate) fn new_cancelled() -> SpawnError {
172        SpawnError {
173            kind: SpawnErrorKind::Cancelled,
174        }
175    }
176
177    pub(crate) fn new_timeout() -> SpawnError {
178        SpawnError {
179            kind: SpawnErrorKind::TimedOut,
180        }
181    }
182
183    pub(crate) fn new_consumed() -> SpawnError {
184        SpawnError {
185            kind: SpawnErrorKind::Consumed,
186        }
187    }
188}
189
190impl std::error::Error for SpawnError {
191    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
192        match self.kind {
193            SpawnErrorKind::Bincode(ref err) => Some(err),
194            SpawnErrorKind::Io(ref err) => Some(err),
195            SpawnErrorKind::Panic(_) => None,
196            SpawnErrorKind::Cancelled => None,
197            SpawnErrorKind::TimedOut => None,
198            SpawnErrorKind::Consumed => None,
199            SpawnErrorKind::IpcChannelClosed(ref err) => Some(err),
200        }
201    }
202}
203
204impl fmt::Display for SpawnError {
205    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206        match self.kind {
207            SpawnErrorKind::Bincode(_) => write!(f, "process spawn error: bincode error"),
208            SpawnErrorKind::Io(_) => write!(f, "process spawn error: i/o error"),
209            SpawnErrorKind::Panic(ref p) => write!(f, "process spawn error: panic: {}", p),
210            SpawnErrorKind::Cancelled => write!(f, "process spawn error: call cancelled"),
211            SpawnErrorKind::TimedOut => write!(f, "process spawn error: timed out"),
212            SpawnErrorKind::Consumed => write!(f, "process spawn error: result already consumed"),
213            SpawnErrorKind::IpcChannelClosed(_) => write!(
214                f,
215                "process spawn error: remote side closed (might have panicked on serialization)"
216            ),
217        }
218    }
219}
220
221impl From<BincodeError> for SpawnError {
222    fn from(err: BincodeError) -> SpawnError {
223        // unwrap nested IO errors
224        if let BincodeErrorKind::Io(io_err) = *err {
225            return SpawnError::from(io_err);
226        }
227        SpawnError {
228            kind: SpawnErrorKind::Bincode(err),
229        }
230    }
231}
232
233impl From<TryRecvError> for SpawnError {
234    fn from(err: TryRecvError) -> SpawnError {
235        match err {
236            TryRecvError::Empty => SpawnError::new_remote_close(),
237            TryRecvError::IpcError(err) => SpawnError::from(err),
238        }
239    }
240}
241
242impl From<IpcError> for SpawnError {
243    fn from(err: IpcError) -> SpawnError {
244        // unwrap nested IO errors
245        match err {
246            IpcError::Io(err) => SpawnError::from(err),
247            IpcError::Bincode(err) => SpawnError::from(err),
248            IpcError::Disconnected => SpawnError::new_remote_close(),
249        }
250    }
251}
252
253impl From<io::Error> for SpawnError {
254    fn from(err: io::Error) -> SpawnError {
255        if let io::ErrorKind::ConnectionReset = err.kind() {
256            return SpawnError {
257                kind: SpawnErrorKind::IpcChannelClosed(err),
258            };
259        }
260        SpawnError {
261            kind: SpawnErrorKind::Io(err),
262        }
263    }
264}
265
266impl From<PanicInfo> for SpawnError {
267    fn from(panic: PanicInfo) -> SpawnError {
268        SpawnError {
269            kind: SpawnErrorKind::Panic(panic),
270        }
271    }
272}