Finish handlers by implementing the error hook
This commit is contained in:
parent
0f37e42243
commit
816313303a
|
@ -8,7 +8,7 @@ use tokio::process::Command;
|
||||||
/// conventions. Also `Cmd` is only available in Windows, while `Powershell` is
|
/// conventions. Also `Cmd` is only available in Windows, while `Powershell` is
|
||||||
/// also available on unices (provided the end-user has it installed, of course).
|
/// also available on unices (provided the end-user has it installed, of course).
|
||||||
///
|
///
|
||||||
/// See [`Config.cmd`][crate::config::Config] for the semantics of `None` vs the
|
/// See [`Config.cmd`] for the semantics of `None` vs the
|
||||||
/// other options.
|
/// other options.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Shell {
|
pub enum Shell {
|
||||||
|
@ -55,7 +55,7 @@ impl Default for Shell {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shell {
|
impl Shell {
|
||||||
/// Obtain a [`Command`] given the cmd vec from [`Config`][crate::config::Config].
|
/// Obtain a [`Command`] given a list of command parts.
|
||||||
///
|
///
|
||||||
/// Behaves as described in the enum documentation.
|
/// Behaves as described in the enum documentation.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
|
|
||||||
/// Configuration for [`Watchexec`][crate::Watchexec].
|
use crate::{error::RuntimeError, handler::Handler};
|
||||||
|
|
||||||
|
/// Runtime configuration for [`Watchexec`][crate::Watchexec].
|
||||||
///
|
///
|
||||||
/// This is used both for constructing the instance and to reconfigure it at runtime, though note
|
/// This is used both when constructing the instance (as initial configuration) and to reconfigure
|
||||||
/// that some fields are only applied at construction time.
|
/// it at runtime via [`Watchexec::reconfig()`][crate::Watchexec::reconfig()].
|
||||||
///
|
///
|
||||||
/// Use [`ConfigBuilder`] to build a new one, or modify an existing one. This struct is marked
|
/// Use [`RuntimeConfigBuilder`] to build a new one, or modify an existing one. This struct is
|
||||||
/// non-exhaustive such that new options may be added without breaking change.
|
/// marked non-exhaustive such that new options may be added without breaking change.
|
||||||
#[derive(Builder, Clone, Debug)]
|
#[derive(Builder, Clone, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Config {
|
pub struct RuntimeConfig {
|
||||||
/// Working data for the filesystem event source.
|
/// Working data for the filesystem event source.
|
||||||
///
|
///
|
||||||
/// This notably includes the path set to be watched.
|
/// This notably includes the path set to be watched.
|
||||||
|
@ -22,13 +26,35 @@ pub struct Config {
|
||||||
/// filtering, etc.
|
/// filtering, etc.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub action: crate::action::WorkingData,
|
pub action: crate::action::WorkingData,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialisation configuration for [`Watchexec`][crate::Watchexec].
|
||||||
|
///
|
||||||
|
/// This is used only for constructing the instance.
|
||||||
|
///
|
||||||
|
/// Use [`InitConfigBuilder`] to build a new one, or modify an existing one. This struct is marked
|
||||||
|
/// non-exhaustive such that new options may be added without breaking change. Note that this
|
||||||
|
/// builder uses a different style (consuming `self`) for technical reasons (cannot be `Clone`d).
|
||||||
|
#[derive(Builder)]
|
||||||
|
#[builder(pattern = "owned")]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct InitConfig {
|
||||||
|
/// Runtime error handler.
|
||||||
|
///
|
||||||
|
/// This is run on every runtime error that occurs within watchexec. By default the placeholder
|
||||||
|
/// `()` handler is used, which discards all errors.
|
||||||
|
///
|
||||||
|
/// If the handler errors, [_that_ error][crate::error::RuntimeError::Handler] is immediately
|
||||||
|
/// given to the handler. If that second handler call errors as well, its error is ignored.
|
||||||
|
///
|
||||||
|
/// Only used at construction time, cannot be changed via reconfiguration.
|
||||||
|
#[builder(default = "Box::new(()) as _")]
|
||||||
|
pub error_handler: Box<dyn Handler<RuntimeError> + Send>,
|
||||||
|
|
||||||
/// Internal: the buffer size of the channel which carries runtime errors.
|
/// Internal: the buffer size of the channel which carries runtime errors.
|
||||||
///
|
///
|
||||||
/// The default (64) is usually fine. If you expect a much larger throughput of runtime errors,
|
/// The default (64) is usually fine. If you expect a much larger throughput of runtime errors,
|
||||||
/// adjusting this value may help. (Fixing whatever is causing the errors may also help.)
|
/// or if your `error_handler` is slow, adjusting this value may help.
|
||||||
///
|
|
||||||
/// Only used at construction time, cannot be changed via reconfiguration.
|
|
||||||
#[builder(default = "64")]
|
#[builder(default = "64")]
|
||||||
pub error_channel_size: usize,
|
pub error_channel_size: usize,
|
||||||
|
|
||||||
|
@ -36,8 +62,15 @@ pub struct Config {
|
||||||
///
|
///
|
||||||
/// The default (1024) is usually fine. If you expect a much larger throughput of events,
|
/// The default (1024) is usually fine. If you expect a much larger throughput of events,
|
||||||
/// adjusting this value may help.
|
/// adjusting this value may help.
|
||||||
///
|
|
||||||
/// Only used at construction time, cannot be changed via reconfiguration.
|
|
||||||
#[builder(default = "1024")]
|
#[builder(default = "1024")]
|
||||||
pub event_channel_size: usize,
|
pub event_channel_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for InitConfig {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("InitConfig")
|
||||||
|
.field("error_channel_size", &self.error_channel_size)
|
||||||
|
.field("event_channel_size", &self.event_channel_size)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
//! - for closures,
|
//! - for closures,
|
||||||
//! - for std and tokio channels,
|
//! - for std and tokio channels,
|
||||||
//! - for printing to writers, in `Debug` and `Display` (where supported) modes (generally used for
|
//! - for printing to writers, in `Debug` and `Display` (where supported) modes (generally used for
|
||||||
//! debugging and testing, as they don't allow any other output customisation).
|
//! debugging and testing, as they don't allow any other output customisation),
|
||||||
|
//! - for `()`, as placeholder.
|
||||||
//!
|
//!
|
||||||
//! The implementation for [`FnMut`] only supports fns that return a [`Future`]. Unfortunately
|
//! The implementation for [`FnMut`] only supports fns that return a [`Future`]. Unfortunately
|
||||||
//! it's not possible to provide an implementation for fns that don't return a `Future` as well,
|
//! it's not possible to provide an implementation for fns that don't return a `Future` as well,
|
||||||
|
@ -86,7 +87,6 @@
|
||||||
use std::{error::Error, future::Future, io::Write, marker::PhantomData};
|
use std::{error::Error, future::Future, io::Write, marker::PhantomData};
|
||||||
|
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
use tracing::{event, Level};
|
|
||||||
|
|
||||||
/// A callable that can be used to hook into watchexec.
|
/// A callable that can be used to hook into watchexec.
|
||||||
pub trait Handler<T> {
|
pub trait Handler<T> {
|
||||||
|
@ -160,6 +160,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Handler<T> for () {
|
||||||
|
fn handle(&mut self, _data: T) -> Result<(), Box<dyn Error>> {
|
||||||
|
Ok(()).map_err(|e: std::convert::Infallible| Box::new(e) as _)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Handler<T> for std::sync::mpsc::Sender<T>
|
impl<T> Handler<T> for std::sync::mpsc::Sender<T>
|
||||||
where
|
where
|
||||||
T: Send + 'static,
|
T: Send + 'static,
|
||||||
|
|
|
@ -6,22 +6,26 @@
|
||||||
//! This library is powered by [Tokio](https://tokio.rs), minimum version 1.10. This requirement may
|
//! This library is powered by [Tokio](https://tokio.rs), minimum version 1.10. This requirement may
|
||||||
//! change (upwards) in the future without breaking change.
|
//! change (upwards) in the future without breaking change.
|
||||||
//!
|
//!
|
||||||
//! The main way to use this crate involves constructing a [`Watchexec`] around a [`Config`] and
|
//! The main way to use this crate involves constructing a [`Watchexec`] around an
|
||||||
//! running it. The config may contain some instances of [`Handler`][handler::Handler]s, hooking
|
//! [`InitConfig`][config::InitConfig] and a [`RuntimeConfig`][config::RuntimeConfig], then running
|
||||||
//! into watchexec at various points.
|
//! it. [`Handler`][handler::Handler]s are used to hook into watchexec at various points. The
|
||||||
|
//! runtime config can be changed at any time with the [`reconfig()`][Watchexec::reconfig()] method.
|
||||||
//!
|
//!
|
||||||
//! ```ignore // TODO: implement and switch to no_run
|
//! ```ignore // TODO: implement and switch to no_run
|
||||||
//! use watchexec::{Watchexec, ConfigBuilder, Handler as _};
|
//! use watchexec::{Watchexec, InitConfigBuilder, RuntimeConfigBuilder, Handler as _};
|
||||||
//!
|
//!
|
||||||
//! #[tokio::main]
|
//! #[tokio::main]
|
||||||
//! async fn main() {
|
//! async fn main() {
|
||||||
//! let mut config = ConfigBuilder::new()
|
//! let init = InitConfigBuilder::new()
|
||||||
|
//! .error_handler(PrintDebug(std::io::stderr()));
|
||||||
|
//!
|
||||||
|
//! let mut runtime = RuntimeConfigBuilder::new()
|
||||||
//! config.pathset(["watchexec.conf"]);
|
//! config.pathset(["watchexec.conf"]);
|
||||||
//!
|
//!
|
||||||
//! let conf = YourConfigFormat::load_from_file("watchexec.conf").await?;
|
//! let conf = YourConfigFormat::load_from_file("watchexec.conf").await?;
|
||||||
//! conf.apply(&mut config);
|
//! conf.apply(&mut runtime);
|
||||||
//!
|
//!
|
||||||
//! let we = Watchexec::new(config.build().unwrap()).unwrap();
|
//! let we = Watchexec::new(init.build().unwrap(), runtime.build().unwrap()).unwrap();
|
||||||
//! let w = we.clone();
|
//! let w = we.clone();
|
||||||
//!
|
//!
|
||||||
//! let c = config.clone();
|
//! let c = config.clone();
|
||||||
|
@ -29,8 +33,8 @@
|
||||||
//! if e.path().map(|p| p.ends_with("watchexec.conf")).unwrap_or(false) {
|
//! if e.path().map(|p| p.ends_with("watchexec.conf")).unwrap_or(false) {
|
||||||
//! let conf = YourConfigFormat::load_from_file("watchexec.conf").await?;
|
//! let conf = YourConfigFormat::load_from_file("watchexec.conf").await?;
|
||||||
//!
|
//!
|
||||||
//! conf.apply(&mut config);
|
//! conf.apply(&mut runtime);
|
||||||
//! w.reconfigure(config.build());
|
//! w.reconfigure(runtime.build().unwrap());
|
||||||
//! // tada! self-reconfiguring watchexec on config file change!
|
//! // tada! self-reconfiguring watchexec on config file change!
|
||||||
//! }
|
//! }
|
||||||
//! });
|
//! });
|
||||||
|
@ -62,12 +66,10 @@ pub mod handler;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
|
|
||||||
// the core experience
|
// the core experience
|
||||||
mod config;
|
pub mod config;
|
||||||
mod watchexec;
|
mod watchexec;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use crate::watchexec::Watchexec;
|
pub use crate::watchexec::Watchexec;
|
||||||
#[doc(inline)]
|
|
||||||
pub use config::{Config, ConfigBuilder};
|
|
||||||
|
|
||||||
// the *action* is debounced, not the events
|
// the *action* is debounced, not the events
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use std::{mem::take, sync::Arc};
|
use std::{
|
||||||
|
fmt,
|
||||||
|
mem::{replace, take},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use atomic_take::AtomicTake;
|
use atomic_take::AtomicTake;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
@ -11,35 +15,48 @@ use tokio::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action,
|
action,
|
||||||
config::Config,
|
config::{InitConfig, RuntimeConfig},
|
||||||
error::{CriticalError, ReconfigError},
|
error::{CriticalError, ReconfigError, RuntimeError},
|
||||||
fs, signal,
|
fs,
|
||||||
|
handler::Handler,
|
||||||
|
signal,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Watchexec {
|
pub struct Watchexec {
|
||||||
handle: Arc<AtomicTake<JoinHandle<Result<(), CriticalError>>>>,
|
handle: Arc<AtomicTake<JoinHandle<Result<(), CriticalError>>>>,
|
||||||
start_lock: Arc<Notify>,
|
start_lock: Arc<Notify>,
|
||||||
|
|
||||||
action_watch: watch::Sender<action::WorkingData>,
|
action_watch: watch::Sender<action::WorkingData>,
|
||||||
fs_watch: watch::Sender<fs::WorkingData>,
|
fs_watch: watch::Sender<fs::WorkingData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Watchexec {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Watchexec").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Watchexec {
|
impl Watchexec {
|
||||||
/// TODO
|
/// TODO
|
||||||
///
|
///
|
||||||
/// Returns an [`Arc`] for convenience; use [`try_unwrap`][Arc::try_unwrap()] to get the value
|
/// Returns an [`Arc`] for convenience; use [`try_unwrap`][Arc::try_unwrap()] to get the value
|
||||||
/// directly if needed.
|
/// directly if needed.
|
||||||
pub fn new(mut config: Config) -> Result<Arc<Self>, CriticalError> {
|
pub fn new(
|
||||||
let (fs_s, fs_r) = watch::channel(take(&mut config.fs));
|
mut init: InitConfig,
|
||||||
let (ac_s, ac_r) = watch::channel(take(&mut config.action));
|
mut runtime: RuntimeConfig,
|
||||||
|
) -> Result<Arc<Self>, CriticalError> {
|
||||||
|
let (fs_s, fs_r) = watch::channel(take(&mut runtime.fs));
|
||||||
|
let (ac_s, ac_r) = watch::channel(take(&mut runtime.action));
|
||||||
|
|
||||||
let notify = Arc::new(Notify::new());
|
let notify = Arc::new(Notify::new());
|
||||||
let start_lock = notify.clone();
|
let start_lock = notify.clone();
|
||||||
let handle = spawn(async move {
|
let handle = spawn(async move {
|
||||||
notify.notified().await;
|
notify.notified().await;
|
||||||
|
|
||||||
let (er_s, er_r) = mpsc::channel(config.error_channel_size);
|
let (er_s, er_r) = mpsc::channel(init.error_channel_size);
|
||||||
let (ev_s, ev_r) = mpsc::channel(config.event_channel_size);
|
let (ev_s, ev_r) = mpsc::channel(init.event_channel_size);
|
||||||
|
|
||||||
|
let eh = replace(&mut init.error_handler, Box::new(()) as _);
|
||||||
|
|
||||||
macro_rules! subtask {
|
macro_rules! subtask {
|
||||||
($task:expr) => {
|
($task:expr) => {
|
||||||
|
@ -51,18 +68,21 @@ impl Watchexec {
|
||||||
let fs = subtask!(fs::worker(fs_r, er_s.clone(), ev_s.clone()));
|
let fs = subtask!(fs::worker(fs_r, er_s.clone(), ev_s.clone()));
|
||||||
let signal = subtask!(signal::worker(er_s.clone(), ev_s.clone()));
|
let signal = subtask!(signal::worker(er_s.clone(), ev_s.clone()));
|
||||||
|
|
||||||
|
let error_hook = subtask!(error_hook(er_r, eh));
|
||||||
|
|
||||||
try_join!(action, fs, signal).map(drop)
|
try_join!(action, fs, signal).map(drop)
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Arc::new(Self {
|
Ok(Arc::new(Self {
|
||||||
handle: Arc::new(AtomicTake::new(handle)),
|
handle: Arc::new(AtomicTake::new(handle)),
|
||||||
start_lock,
|
start_lock,
|
||||||
|
|
||||||
action_watch: ac_s,
|
action_watch: ac_s,
|
||||||
fs_watch: fs_s,
|
fs_watch: fs_s,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reconfig(&self, config: Config) -> Result<(), ReconfigError> {
|
pub fn reconfig(&self, config: RuntimeConfig) -> Result<(), ReconfigError> {
|
||||||
self.action_watch.send(config.action)?;
|
self.action_watch.send(config.action)?;
|
||||||
self.fs_watch.send(config.fs)?;
|
self.fs_watch.send(config.fs)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -88,3 +108,21 @@ fn flatten(join_res: Result<Result<(), CriticalError>, JoinError>) -> Result<(),
|
||||||
.map_err(CriticalError::MainTaskJoin)
|
.map_err(CriticalError::MainTaskJoin)
|
||||||
.and_then(|x| x)
|
.and_then(|x| x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn error_hook(
|
||||||
|
mut errors: mpsc::Receiver<RuntimeError>,
|
||||||
|
mut handler: Box<dyn Handler<RuntimeError> + Send>,
|
||||||
|
) -> Result<(), CriticalError> {
|
||||||
|
while let Some(err) = errors.recv().await {
|
||||||
|
if let Err(e) = handler.handle(err) {
|
||||||
|
handler
|
||||||
|
.handle(RuntimeError::Handler {
|
||||||
|
ctx: "error hook",
|
||||||
|
err: e.to_string(),
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue