Change on_error to let the handler raise a CriticalError
This commit is contained in:
parent
8ebcf083b8
commit
427e4a0d08
|
@ -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>(())
|
||||
});
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -116,4 +116,4 @@ pub mod handler;
|
|||
mod watchexec;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::watchexec::Watchexec;
|
||||
pub use crate::watchexec::{ErrorHook, Watchexec};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>(())
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue