Change on_error to let the handler raise a CriticalError

This commit is contained in:
Félix Saparelli 2022-01-31 02:55:47 +13:00
parent 8ebcf083b8
commit 427e4a0d08
6 changed files with 83 additions and 21 deletions

View File

@ -7,7 +7,7 @@ use watchexec::{
error::ReconfigError,
fs::Watcher,
signal::source::MainSignal,
Watchexec,
ErrorHook, Watchexec,
};
// Run with: `env RUST_LOG=debug cargo run --example print_out`
@ -16,8 +16,8 @@ async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let mut init = InitConfig::default();
init.on_error(|err| async move {
eprintln!("Watchexec Runtime Error: {}", err);
init.on_error(|err: ErrorHook| async move {
eprintln!("Watchexec Runtime Error: {}", err.error);
Ok::<(), std::convert::Infallible>(())
});

View File

@ -31,7 +31,7 @@ impl ProcessHolder {
trace!("replacing supervisor");
if let Some(_old) = self.0.write().await.replace(new) {
trace!("replaced supervisor");
// TODO: figure out what to do with old
// TODO: figure out what to do with old
} else {
trace!("not replaced: no supervisor");
}

View File

@ -5,10 +5,10 @@ use std::{fmt, path::Path, sync::Arc, time::Duration};
use crate::{
action::{Action, PostSpawn, PreSpawn},
command::Shell,
error::RuntimeError,
filter::Filterer,
fs::Watcher,
handler::{Handler, HandlerLock},
ErrorHook,
};
/// Runtime configuration for [`Watchexec`][crate::Watchexec].
@ -127,18 +127,20 @@ pub struct InitConfig {
/// If the handler errors, [_that_ error][crate::error::RuntimeError::Handler] is immediately
/// given to the handler. If this second handler call errors as well, its error is ignored.
///
/// Also see the [`ErrorHook`] documentation for returning critical errors from this handler.
///
/// # Examples
///
/// ```
/// # use std::convert::Infallible;
/// # use watchexec::config::InitConfig;
/// # use watchexec::{config::InitConfig, ErrorHook};
/// let mut init = InitConfig::default();
/// init.on_error(|err| async move {
/// tracing::error!("{}", err);
/// init.on_error(|err: ErrorHook| async move {
/// tracing::error!("{}", err.error);
/// Ok::<(), Infallible>(())
/// });
/// ```
pub error_handler: Box<dyn Handler<RuntimeError> + Send>,
pub error_handler: Box<dyn Handler<ErrorHook> + Send>,
/// Internal: the buffer size of the channel which carries runtime errors.
///
@ -167,7 +169,7 @@ impl InitConfig {
/// Set the runtime error handler.
///
/// See the [documentation on the field](InitConfig#structfield.error_handler) for more details.
pub fn on_error(&mut self, handler: impl Handler<RuntimeError> + Send + 'static) -> &mut Self {
pub fn on_error(&mut self, handler: impl Handler<ErrorHook> + Send + 'static) -> &mut Self {
self.error_handler = Box::new(handler) as _;
self
}

View File

@ -116,4 +116,4 @@ pub mod handler;
mod watchexec;
#[doc(inline)]
pub use crate::watchexec::Watchexec;
pub use crate::watchexec::{ErrorHook, Watchexec};

View File

@ -6,6 +6,7 @@ use std::{
use atomic_take::AtomicTake;
use futures::FutureExt;
use once_cell::sync::OnceCell;
use tokio::{
spawn,
sync::{mpsc, watch, Notify},
@ -170,7 +171,7 @@ fn flatten(join_res: Result<Result<(), CriticalError>, JoinError>) -> Result<(),
async fn error_hook(
mut errors: mpsc::Receiver<RuntimeError>,
mut handler: Box<dyn Handler<RuntimeError> + Send>,
mut handler: Box<dyn Handler<ErrorHook> + Send>,
) -> Result<(), CriticalError> {
while let Some(err) = errors.recv().await {
if matches!(err, RuntimeError::Exit) {
@ -179,15 +180,74 @@ async fn error_hook(
}
error!(%err, "runtime error");
if let Err(err) = handler.handle(err) {
let hook = ErrorHook::new(err);
let crit = hook.critical.clone();
if let Err(err) = handler.handle(hook) {
error!(%err, "error while handling error");
handler
.handle(rte("error hook", err))
.unwrap_or_else(|err| {
error!(%err, "error while handling error of handling error");
});
let rehook = ErrorHook::new(rte("error hook", err));
let recrit = rehook.critical.clone();
handler.handle(rehook).unwrap_or_else(|err| {
error!(%err, "error while handling error of handling error");
});
ErrorHook::handle_crit(recrit, "error handler error handler")?;
} else {
ErrorHook::handle_crit(crit, "error handler")?;
}
}
Ok(())
}
/// The environment given to the error handler.
///
/// This deliberately does not implement Clone to make it hard to move it out of the handler, which
/// you should not do.
///
/// The [`ErrorHook::critical()`] method should be used to send a [`CriticalError`], which will
/// terminate watchexec. This is useful to e.g. upgrade certain errors to be fatal.
///
/// Note that returning errors from the error handler does not result in critical errors.
#[derive(Debug)]
pub struct ErrorHook {
/// The runtime error for which this handler was called.
pub error: RuntimeError,
critical: Arc<OnceCell<CriticalError>>,
}
impl ErrorHook {
fn new(error: RuntimeError) -> Self {
Self {
error,
critical: Default::default(),
}
}
fn handle_crit(
crit: Arc<OnceCell<CriticalError>>,
name: &'static str,
) -> Result<(), CriticalError> {
match Arc::try_unwrap(crit) {
Err(err) => {
error!(?err, "{} hook has an outstanding ref", name);
Ok(())
}
Ok(crit) => {
if let Some(crit) = crit.into_inner() {
debug!(%crit, "{} output a critical error", name);
Err(crit)
} else {
Ok(())
}
}
}
}
/// Set a critical error to be emitted.
///
/// This takes `self` and `ErrorHook` is not `Clone`, so it's only possible to call it once.
/// Regardless, if you _do_ manage to call it twice, it will do nothing beyond the first call.
pub fn critical(self, critical: CriticalError) {
self.critical.set(critical).ok();
}
}

View File

@ -4,7 +4,7 @@ use miette::Result;
use tokio::time::sleep;
use watchexec::{
config::{InitConfig, RuntimeConfig},
Watchexec,
ErrorHook, Watchexec,
};
#[tokio::main]
@ -12,8 +12,8 @@ async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let mut init = InitConfig::default();
init.on_error(|err| async move {
eprintln!("Watchexec Runtime Error: {}", err);
init.on_error(|err: ErrorHook| async move {
eprintln!("Watchexec Runtime Error: {}", err.error);
Ok::<(), std::convert::Infallible>(())
});