2023-11-25 21:33:44 +01:00
|
|
|
use std::{fmt, future::Future, sync::Arc};
|
2021-08-18 15:12:50 +02:00
|
|
|
|
2022-06-11 08:43:11 +02:00
|
|
|
use async_priority_channel as priority;
|
2021-08-19 16:55:34 +02:00
|
|
|
use atomic_take::AtomicTake;
|
2023-11-25 21:33:44 +01:00
|
|
|
use futures::TryFutureExt;
|
2022-02-12 07:59:02 +01:00
|
|
|
use miette::Diagnostic;
|
2022-01-30 14:55:47 +01:00
|
|
|
use once_cell::sync::OnceCell;
|
2021-08-18 15:12:50 +02:00
|
|
|
use tokio::{
|
|
|
|
spawn,
|
2023-11-25 21:33:44 +01:00
|
|
|
sync::{mpsc, Notify},
|
|
|
|
task::{JoinHandle, JoinSet},
|
2021-08-18 15:12:50 +02:00
|
|
|
};
|
2021-08-21 16:48:00 +02:00
|
|
|
use tracing::{debug, error, trace};
|
2023-11-25 21:33:44 +01:00
|
|
|
use watchexec_events::{Event, Priority};
|
2021-08-18 15:12:50 +02:00
|
|
|
|
|
|
|
use crate::{
|
2023-11-25 21:33:44 +01:00
|
|
|
action::{self, ActionHandler},
|
|
|
|
changeable::ChangeableFn,
|
|
|
|
error::{CriticalError, RuntimeError},
|
|
|
|
sources::{fs, keyboard, signal},
|
|
|
|
Config,
|
2021-08-18 15:12:50 +02:00
|
|
|
};
|
|
|
|
|
2021-10-16 16:01:55 +02:00
|
|
|
/// The main watchexec runtime.
|
|
|
|
///
|
|
|
|
/// All this really does is tie the pieces together in one convenient interface.
|
|
|
|
///
|
|
|
|
/// It creates the correct channels, spawns every available event sources, the action worker, the
|
|
|
|
/// error hook, and provides an interface to change the runtime configuration during the runtime,
|
|
|
|
/// inject synthetic events, and wait for graceful shutdown.
|
2021-08-18 15:12:50 +02:00
|
|
|
pub struct Watchexec {
|
2023-11-25 21:33:44 +01:00
|
|
|
/// The configuration of this Watchexec instance.
|
|
|
|
///
|
|
|
|
/// Configuration can be changed at any time using the provided methods on [`Config`].
|
|
|
|
///
|
|
|
|
/// Treat this field as readonly: replacing it with a different instance of `Config` will not do
|
|
|
|
/// anything except potentially lose you access to the actual Watchexec config. In normal use
|
|
|
|
/// you'll have obtained `Watchexec` behind an `Arc` so that won't be an issue.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// Change the action handler:
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// # use watchexec::Watchexec;
|
|
|
|
/// let wx = Watchexec::default();
|
|
|
|
/// wx.config.on_action(|mut action| {
|
|
|
|
/// if action.signals().next().is_some() {
|
|
|
|
/// action.quit();
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// action
|
|
|
|
/// });
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Set paths to be watched:
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// # use watchexec::Watchexec;
|
|
|
|
/// let wx = Watchexec::new(|mut action| {
|
|
|
|
/// if action.signals().next().is_some() {
|
|
|
|
/// action.quit();
|
|
|
|
/// } else {
|
|
|
|
/// for event in action.events.iter() {
|
|
|
|
/// println!("{event:?}");
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// action
|
|
|
|
/// }).unwrap();
|
|
|
|
///
|
|
|
|
/// wx.config.pathset(["."]);
|
|
|
|
/// ```
|
|
|
|
pub config: Arc<Config>,
|
2021-08-18 15:12:50 +02:00
|
|
|
start_lock: Arc<Notify>,
|
2022-06-11 08:43:11 +02:00
|
|
|
event_input: priority::Sender<Event, Priority>,
|
2023-11-25 21:33:44 +01:00
|
|
|
handle: Arc<AtomicTake<JoinHandle<Result<(), CriticalError>>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Watchexec {
|
|
|
|
/// Instantiate with default config.
|
|
|
|
///
|
|
|
|
/// Note that this will panic if the constructor errors.
|
|
|
|
///
|
|
|
|
/// Prefer calling `new()` instead.
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::with_config(Default::default()).expect("Use Watchexec::new() to avoid this panic")
|
|
|
|
}
|
2021-08-18 15:12:50 +02:00
|
|
|
}
|
|
|
|
|
2021-08-21 10:46:44 +02:00
|
|
|
impl fmt::Debug for Watchexec {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.debug_struct("Watchexec").finish_non_exhaustive()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-18 15:12:50 +02:00
|
|
|
impl Watchexec {
|
2023-11-25 21:33:44 +01:00
|
|
|
/// Instantiates a new `Watchexec` runtime given an initial action handler.
|
2021-08-19 16:55:34 +02:00
|
|
|
///
|
|
|
|
/// Returns an [`Arc`] for convenience; use [`try_unwrap`][Arc::try_unwrap()] to get the value
|
2023-11-25 21:33:44 +01:00
|
|
|
/// directly if needed, or use `new_with_config`.
|
2022-06-07 11:19:16 +02:00
|
|
|
///
|
2023-11-25 21:33:44 +01:00
|
|
|
/// Look at the [`Config`] documentation for more on the required action handler.
|
2022-06-07 11:19:16 +02:00
|
|
|
/// Watchexec will subscribe to most signals sent to the process it runs in and send them, as
|
|
|
|
/// [`Event`]s, to the action handler. At minimum, you should check for interrupt/ctrl-c events
|
2023-11-25 21:33:44 +01:00
|
|
|
/// and call `action.quit()` in your handler, otherwise hitting ctrl-c will do nothing.
|
2021-08-21 10:46:44 +02:00
|
|
|
pub fn new(
|
2023-11-25 21:33:44 +01:00
|
|
|
action_handler: impl (Fn(ActionHandler) -> ActionHandler) + Send + Sync + 'static,
|
2021-08-21 10:46:44 +02:00
|
|
|
) -> Result<Arc<Self>, CriticalError> {
|
2023-11-25 21:33:44 +01:00
|
|
|
let config = Config::default();
|
|
|
|
config.on_action(action_handler);
|
|
|
|
Self::with_config(config).map(Arc::new)
|
|
|
|
}
|
2021-08-21 16:48:00 +02:00
|
|
|
|
2023-11-25 21:33:44 +01:00
|
|
|
/// Instantiates a new `Watchexec` runtime given an initial async action handler.
|
|
|
|
///
|
|
|
|
/// This is the same as [`new`](fn@Self::new) except the action handler is async.
|
|
|
|
pub fn new_async(
|
|
|
|
action_handler: impl (Fn(ActionHandler) -> Box<dyn Future<Output = ActionHandler> + Send + Sync>)
|
|
|
|
+ Send
|
|
|
|
+ Sync
|
|
|
|
+ 'static,
|
|
|
|
) -> Result<Arc<Self>, CriticalError> {
|
|
|
|
let config = Config::default();
|
|
|
|
config.on_action_async(action_handler);
|
|
|
|
Self::with_config(config).map(Arc::new)
|
|
|
|
}
|
2021-08-22 17:11:58 +02:00
|
|
|
|
2023-11-25 21:33:44 +01:00
|
|
|
/// Instantiates a new `Watchexec` runtime with a config.
|
|
|
|
///
|
|
|
|
/// This is generally not needed: the config can be changed after instantiation (before and
|
|
|
|
/// after _starting_ Watchexec with `main()`). The only time this should be used is to set the
|
|
|
|
/// "unchangeable" configuration items for internal details like buffer sizes for queues, or to
|
|
|
|
/// obtain Self unwrapped by an Arc like `new()` does.
|
|
|
|
pub fn with_config(config: Config) -> Result<Self, CriticalError> {
|
|
|
|
debug!(?config, pid=%std::process::id(), version=%env!("CARGO_PKG_VERSION"), "initialising");
|
|
|
|
let config = Arc::new(config);
|
|
|
|
let outer_config = config.clone();
|
2021-08-24 12:20:44 +02:00
|
|
|
|
2023-11-25 21:33:44 +01:00
|
|
|
let notify = Arc::new(Notify::new());
|
|
|
|
let start_lock = notify.clone();
|
2021-08-18 15:12:50 +02:00
|
|
|
|
2024-04-20 06:58:17 +02:00
|
|
|
let (ev_s, ev_r) =
|
|
|
|
priority::bounded(config.event_channel_size.try_into().unwrap_or(u64::MAX));
|
2023-11-25 21:33:44 +01:00
|
|
|
let event_input = ev_s.clone();
|
2022-12-02 00:19:04 +01:00
|
|
|
|
2021-08-21 16:48:00 +02:00
|
|
|
trace!("creating main task");
|
2021-08-18 15:12:50 +02:00
|
|
|
let handle = spawn(async move {
|
2021-08-21 16:48:00 +02:00
|
|
|
trace!("waiting for start lock");
|
2021-08-18 15:12:50 +02:00
|
|
|
notify.notified().await;
|
2021-08-21 16:48:00 +02:00
|
|
|
debug!("starting main task");
|
2021-08-18 15:12:50 +02:00
|
|
|
|
2023-11-25 21:33:44 +01:00
|
|
|
let (er_s, er_r) = mpsc::channel(config.error_channel_size);
|
2021-08-21 10:46:44 +02:00
|
|
|
|
2023-11-25 21:33:44 +01:00
|
|
|
let mut tasks = JoinSet::new();
|
2021-08-18 15:12:50 +02:00
|
|
|
|
2024-01-01 06:01:14 +01:00
|
|
|
tasks.spawn(action::worker(config.clone(), er_s.clone(), ev_r).map_ok(|()| "action"));
|
|
|
|
tasks.spawn(fs::worker(config.clone(), er_s.clone(), ev_s.clone()).map_ok(|()| "fs"));
|
2023-11-25 21:33:44 +01:00
|
|
|
tasks.spawn(
|
2024-01-01 06:01:14 +01:00
|
|
|
signal::worker(config.clone(), er_s.clone(), ev_s.clone()).map_ok(|()| "signal"),
|
2021-09-02 19:22:15 +02:00
|
|
|
);
|
2023-11-25 21:33:44 +01:00
|
|
|
tasks.spawn(
|
2024-01-04 10:32:47 +01:00
|
|
|
keyboard::worker(config.clone(), er_s.clone(), ev_s.clone())
|
|
|
|
.map_ok(|()| "keyboard"),
|
2022-12-02 00:19:04 +01:00
|
|
|
);
|
2024-01-01 06:01:14 +01:00
|
|
|
tasks.spawn(error_hook(er_r, config.error_handler.clone()).map_ok(|()| "error"));
|
2021-08-18 15:12:50 +02:00
|
|
|
|
2023-11-25 21:33:44 +01:00
|
|
|
while let Some(Ok(res)) = tasks.join_next().await {
|
|
|
|
match res {
|
|
|
|
Ok("action") => {
|
|
|
|
debug!("action worker exited, ending watchexec");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Ok(task) => {
|
|
|
|
debug!(task, "worker exited");
|
|
|
|
}
|
|
|
|
Err(CriticalError::Exit) => {
|
2021-08-22 16:36:58 +02:00
|
|
|
trace!("got graceful exit request via critical error, erasing the error");
|
2023-11-25 21:33:44 +01:00
|
|
|
// Close event channel to signal worker task to stop
|
|
|
|
ev_s.close();
|
2021-08-22 16:36:58 +02:00
|
|
|
}
|
2023-11-25 21:33:44 +01:00
|
|
|
Err(e) => {
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
debug!("main task graceful exit");
|
|
|
|
tasks.shutdown().await;
|
|
|
|
Ok(())
|
2021-08-18 15:12:50 +02:00
|
|
|
});
|
|
|
|
|
2021-08-21 16:48:00 +02:00
|
|
|
trace!("done with setup");
|
2023-11-25 21:33:44 +01:00
|
|
|
Ok(Self {
|
|
|
|
config: outer_config,
|
2021-08-18 15:12:50 +02:00
|
|
|
start_lock,
|
2021-08-24 12:20:44 +02:00
|
|
|
event_input,
|
2023-11-25 21:33:44 +01:00
|
|
|
handle: Arc::new(AtomicTake::new(handle)),
|
|
|
|
})
|
2021-08-18 15:12:50 +02:00
|
|
|
}
|
|
|
|
|
2021-08-24 12:20:44 +02:00
|
|
|
/// Inputs an [`Event`] directly.
|
|
|
|
///
|
|
|
|
/// This can be useful for testing, for custom event sources, or for one-off action triggers
|
|
|
|
/// (for example, on start).
|
|
|
|
///
|
|
|
|
/// Hint: use [`Event::default()`] to send an empty event (which won't be filtered).
|
2022-06-11 08:43:11 +02:00
|
|
|
pub async fn send_event(&self, event: Event, priority: Priority) -> Result<(), CriticalError> {
|
|
|
|
self.event_input.send(event, priority).await?;
|
2021-08-24 12:20:44 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-08-19 16:55:34 +02:00
|
|
|
/// Start watchexec and obtain the handle to its main task.
|
|
|
|
///
|
|
|
|
/// This must only be called once.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
/// Panics if called twice.
|
|
|
|
pub fn main(&self) -> JoinHandle<Result<(), CriticalError>> {
|
2021-08-21 16:48:00 +02:00
|
|
|
trace!("notifying start lock");
|
2021-08-18 15:12:50 +02:00
|
|
|
self.start_lock.notify_one();
|
2021-08-21 16:48:00 +02:00
|
|
|
|
|
|
|
debug!("handing over main task handle");
|
2021-08-19 16:55:34 +02:00
|
|
|
self.handle
|
|
|
|
.take()
|
|
|
|
.expect("Watchexec::main was called twice")
|
2021-08-18 15:12:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-21 10:46:44 +02:00
|
|
|
async fn error_hook(
|
|
|
|
mut errors: mpsc::Receiver<RuntimeError>,
|
2023-11-25 21:33:44 +01:00
|
|
|
handler: ChangeableFn<ErrorHook, ()>,
|
2021-08-21 10:46:44 +02:00
|
|
|
) -> Result<(), CriticalError> {
|
|
|
|
while let Some(err) = errors.recv().await {
|
2021-08-22 16:32:08 +02:00
|
|
|
if matches!(err, RuntimeError::Exit) {
|
|
|
|
trace!("got graceful exit request via runtime error, upgrading to crit");
|
|
|
|
return Err(CriticalError::Exit);
|
|
|
|
}
|
|
|
|
|
2021-08-21 16:48:00 +02:00
|
|
|
error!(%err, "runtime error");
|
2023-11-25 21:33:44 +01:00
|
|
|
let payload = ErrorHook::new(err);
|
|
|
|
let crit = payload.critical.clone();
|
|
|
|
handler.call(payload);
|
|
|
|
ErrorHook::handle_crit(crit)?;
|
2021-08-21 10:46:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-01-30 14:55:47 +01:00
|
|
|
|
|
|
|
/// 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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-25 21:33:44 +01:00
|
|
|
fn handle_crit(crit: Arc<OnceCell<CriticalError>>) -> Result<(), CriticalError> {
|
2022-01-30 14:55:47 +01:00
|
|
|
match Arc::try_unwrap(crit) {
|
|
|
|
Err(err) => {
|
2023-11-25 21:33:44 +01:00
|
|
|
error!(?err, "error handler hook has an outstanding ref");
|
2022-01-30 14:55:47 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2023-01-06 14:53:49 +01:00
|
|
|
Ok(crit) => crit.into_inner().map_or_else(
|
|
|
|
|| Ok(()),
|
|
|
|
|crit| {
|
2023-11-25 21:33:44 +01:00
|
|
|
debug!(%crit, "error handler output a critical error");
|
2022-01-30 14:55:47 +01:00
|
|
|
Err(crit)
|
2023-01-06 14:53:49 +01:00
|
|
|
},
|
|
|
|
),
|
2022-01-30 14:55:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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();
|
|
|
|
}
|
2022-01-30 15:16:48 +01:00
|
|
|
|
|
|
|
/// Elevate the current runtime error to critical.
|
|
|
|
///
|
|
|
|
/// This is a shorthand method for `ErrorHook::critical(CriticalError::Elevated(error))`.
|
|
|
|
pub fn elevate(self) {
|
|
|
|
let Self { error, critical } = self;
|
2022-02-12 07:59:02 +01:00
|
|
|
critical
|
|
|
|
.set(CriticalError::Elevated {
|
|
|
|
help: error.help().map(|h| h.to_string()),
|
|
|
|
err: error,
|
|
|
|
})
|
|
|
|
.ok();
|
2022-01-30 15:16:48 +01:00
|
|
|
}
|
2022-01-30 14:55:47 +01:00
|
|
|
}
|