diff options
author | har7an <99636919+har7an@users.noreply.github.com> | 2022-10-06 06:46:18 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-06 06:46:18 +0000 |
commit | 6715f4629c664885e0800da76cd73ca470705b27 (patch) | |
tree | 08eb83fd3212df743f83ec8a9ed399956f926d51 /zellij-utils | |
parent | 46edc590ecda91cfb47456e46be4c618b35c8cc3 (diff) |
Server: Remove `panic`s in `tab` module (#1748)
* utils/errors: Add `ToAnyhow` trait
for converting `Result` types that don't satisfy `anyhow`s trait
constraints (`Display + Send + Sync + 'static`) conveniently.
An example of such a Result is the `SendError` returned from
`send_to_plugins`, which sends `PluginInstruction`s as message type.
One of the enum variants can contain a `mpsc::Sender`, which is `!Sync`
and hence makes the whole `SendError` be `!Sync` in this case. Add an
implementation for this case that takes the message and converts it into
an error containing the message formatted as string, with the additional
`ErrorContext` as anyhow context.
* server/tab: Remove calls to `unwrap()`
and apply error reporting via `anyhow` instead. Make all relevant
functions return `Result`s where previously a panic could occur and
attach error context.
* server/screen: Modify `update_tab!`
to accept an optional 4th parameter, a literal "?". If present, this
will append a `?` to the given closure verbatim to handle/propagate
errors from within the generated macro code.
* server/screen: Handle new `Result`s from `Tab`
and apply appropriate error context and propagate errors further up.
* server/tab/unit: `unwrap` on new `Result`s
* server/unit: Unwrap `Results` in screen tests
* server/tab: Better message for ad-hoc errors
created with `anyhow!`. Since these errors don't have an underlying
cause, we describe the cause in the macro instead and then attach the
error context as usual before `?`ing the error back up.
* utils/cargo: Activate `anyhow`s "backtrace" feature
to capture error backtraces at the error origins (i.e. where we first
receive an error and convert it to a `anyhow::Error`). Since we
propagate error back up the call stack now, the place where we `unwrap`
on errors doesn't match the place where the error originated. Hence, the
callstack, too, is quite misleading since it contains barely any
references of the functions that triggered the error.
As a consequence, we have 2 backtraces now when zellij crashes: One from
`anyhow` (that is implicitly attached to anyhows error reports), and one
from the custom panic handler (which is displayed through `miette`).
* utils/errors: Separate stack traces
in the output of miette. Since we record backtraces with `anyhow` now,
we end up having two backtraces in the output: One from the `anyhow`
error and one from the actual call to `panic`. Adds a comment explaining
the situation and another "section" to the error output of miette: We
print the backtrace from anyhow as "Stack backtrace", and the output
from the panic handler as "Panic backtrace". We keep both for the
(hopefully unlikely) case that the anyhow backtrace isn't existent, so
we still have at least something to work with.
* server/screen: Remove calls to `fatal`
and leave the `panic`ing to the calling function instead.
* server/screen: Remove needless macro
which extended `active_tab!` by passing the client IDs to the closure.
However, this isn't necessary because closures capture their environment
already, and the client_id needn't be mutable.
* server/screen: Handle unused result
* server/screen: Reintroduce arcane macro
that defaults to some default client_id if it isn't valid (e.g. when the
ScreenInstruction is sent via CLI).
* server/tab/unit: Unwrap new results
Diffstat (limited to 'zellij-utils')
-rw-r--r-- | zellij-utils/Cargo.toml | 2 | ||||
-rw-r--r-- | zellij-utils/src/errors.rs | 42 |
2 files changed, 42 insertions, 2 deletions
diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 02fad102e..0c2ad1e97 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.45" +anyhow = { version = "1.0.45", features = ["backtrace"] } backtrace = "0.3.55" rmp-serde = "1.1.0" clap = { version = "3.2.2", features = ["derive", "env"] } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index ec9d4de3c..f729c036c 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -22,6 +22,8 @@ use thiserror::Error as ThisError; pub mod prelude { pub use super::FatalError; pub use super::LoggableError; + #[cfg(not(target_family = "wasm"))] + pub use super::ToAnyhow; pub use anyhow::anyhow; pub use anyhow::bail; pub use anyhow::Context; @@ -38,10 +40,18 @@ pub trait ErrorInstruction { struct Panic(String); impl Panic { + // We already capture a backtrace with `anyhow` using the `backtrace` crate in the background. + // The advantage is that this is the backtrace of the real errors source (i.e. where we first + // encountered the error and turned it into an `anyhow::Error`), whereas the backtrace recorded + // here is the backtrace leading to the call to any `panic`ing function. Since now we propagate + // errors up before `unwrap`ing them (e.g. in `zellij_server::screen::screen_thread_main`), the + // former is what we really want to diagnose. + // We still keep the second one around just in case the first backtrace isn't meaningful or + // non-existent in the first place (Which really shouldn't happen, but you never know). fn show_backtrace(&self) -> String { if let Ok(var) = std::env::var("RUST_BACKTRACE") { if !var.is_empty() && var != "0" { - return format!("\n{:?}", backtrace::Backtrace::new()); + return format!("\n\nPanic backtrace:\n{:?}", backtrace::Backtrace::new()); } } "".into() @@ -513,4 +523,34 @@ mod not_wasm { Ok(()) } } + + /// Helper trait to convert error types that don't satisfy `anyhow`s trait requirements to + /// anyhow errors. + pub trait ToAnyhow<U> { + fn to_anyhow(self) -> crate::anyhow::Result<U>; + } + + /// `SendError` doesn't satisfy `anyhow`s trait requirements due to `T` possibly being a + /// `PluginInstruction` type, which wraps an `mpsc::Send` and isn't `Sync`. Due to this, in turn, + /// the whole error type isn't `Sync` and doesn't work with `anyhow` (or pretty much any other + /// error handling crate). + /// + /// Takes the `SendError` and creates an `anyhow` error type with the message that was sent + /// (formatted as string), attaching the [`ErrorContext`] as anyhow context to it. + impl<T: std::fmt::Debug, U> ToAnyhow<U> + for Result<U, crate::channels::SendError<(T, ErrorContext)>> + { + fn to_anyhow(self) -> crate::anyhow::Result<U> { + match self { + Ok(val) => crate::anyhow::Ok(val), + Err(e) => { + let (msg, context) = e.into_inner(); + Err( + crate::anyhow::anyhow!("failed to send message to client: {:#?}", msg) + .context(context.to_string()), + ) + }, + } + } + } } |