2023-11-25 21:33:44 +01:00
|
|
|
use std::{collections::HashMap, path::Path, sync::Arc, time::Duration};
|
|
|
|
use tokio::task::JoinHandle;
|
|
|
|
use watchexec_events::{Event, FileType, ProcessEnd};
|
|
|
|
use watchexec_signals::Signal;
|
|
|
|
use watchexec_supervisor::{
|
|
|
|
command::Command,
|
|
|
|
job::{start_job, Job},
|
|
|
|
};
|
|
|
|
|
|
|
|
use crate::id::Id;
|
|
|
|
|
|
|
|
use super::QuitManner;
|
|
|
|
|
|
|
|
/// The environment given to the action handler.
|
|
|
|
///
|
|
|
|
/// The action handler is the heart of a Watchexec program. Within, you decide what happens when an
|
|
|
|
/// event successfully passes all filters. Watchexec maintains a set of Supervised [`Job`]s, which
|
|
|
|
/// are assigned a unique [`Id`] for lightweight reference. In this action handler, you should
|
|
|
|
/// add commands to be supervised with `create_job()`, or find an already-supervised job with
|
|
|
|
/// `get_job()` or `list_jobs()`. You can interact with jobs directly via their handles, and can
|
|
|
|
/// even store clones of the handles for later use outside the action handler.
|
|
|
|
///
|
|
|
|
/// The action handler is also given the [`Event`]s which triggered the action. These are expected
|
|
|
|
/// to be the way to determine what to do with a job. However, in some applications you might not
|
|
|
|
/// care about them, and that's fine too: for example, you can build a Watchexec which only does
|
|
|
|
/// process supervision, and is triggered entirely by synthetic events. Conversely, you are also not
|
|
|
|
/// obligated to use the job handles: you can build a Watchexec which only does something with the
|
|
|
|
/// events, and never actually starts any processes.
|
|
|
|
///
|
|
|
|
/// There are some important considerations to keep in mind when writing an action handler:
|
|
|
|
///
|
|
|
|
/// 1. The action handler is called with the supervisor set _as of when the handler was called_.
|
|
|
|
/// This is particularly important when multiple action handlers might be running at the same
|
|
|
|
/// time: they might have incomplete views of the supervisor set.
|
|
|
|
///
|
|
|
|
/// 2. The way the action handler communicates with the Watchexec handler is through the return
|
|
|
|
/// value of the handler. That is, when you add a job with `create_job()`, the job is not added
|
|
|
|
/// to the Watchexec instance's supervisor set until the action handler returns. Similarly, when
|
|
|
|
/// using `quit()`, the quit action is not performed until the action handler returns and the
|
|
|
|
/// Watchexec instance is able to see it.
|
|
|
|
///
|
|
|
|
/// 3. The action handler blocks the action main loop. This means that if you have a long-running
|
|
|
|
/// action handler, the Watchexec instance will not be able to process events until the handler
|
|
|
|
/// returns. That will cause events to accumulate and then get dropped once the channel reaches
|
|
|
|
/// capacity, which will impact your ability to receive signals (such as a Ctrl-C), and may spew
|
|
|
|
/// [`EventChannelTrySend` errors](crate::error::RuntimeError::EventChannelTrySend).
|
|
|
|
///
|
|
|
|
/// If you want to do something long-running, you should either ignore that error, and accept
|
|
|
|
/// events may be dropped, or preferrably spawn a task to do it, and return from the action
|
|
|
|
/// handler as soon as possible.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Handler {
|
|
|
|
/// The collected events which triggered the action.
|
|
|
|
pub events: Arc<[Event]>,
|
|
|
|
extant: HashMap<Id, Job>,
|
|
|
|
pub(crate) new: HashMap<Id, (Job, JoinHandle<()>)>,
|
|
|
|
pub(crate) quit: Option<QuitManner>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Handler {
|
|
|
|
pub(crate) fn new(events: Arc<[Event]>, jobs: HashMap<Id, Job>) -> Self {
|
|
|
|
Self {
|
|
|
|
events,
|
|
|
|
extant: jobs,
|
|
|
|
new: HashMap::new(),
|
|
|
|
quit: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new job and return its handle.
|
|
|
|
///
|
|
|
|
/// This starts the [`Job`] immediately, and stores a copy of its handle and [`Id`] in this
|
|
|
|
/// `Action` (and thus in the Watchexec instance, when the action handler returns).
|
|
|
|
pub fn create_job(&mut self, command: Arc<Command>) -> (Id, Job) {
|
|
|
|
let id = Id::default();
|
|
|
|
let (job, task) = start_job(command);
|
|
|
|
self.new.insert(id, (job.clone(), task));
|
|
|
|
(id, job)
|
|
|
|
}
|
|
|
|
|
|
|
|
// exposing this is dangerous as it allows duplicate IDs which may leak jobs
|
|
|
|
fn create_job_with_id(&mut self, id: Id, command: Arc<Command>) -> Job {
|
|
|
|
let (job, task) = start_job(command);
|
|
|
|
self.new.insert(id, (job.clone(), task));
|
|
|
|
job
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get an existing job or create a new one given an Id.
|
|
|
|
///
|
|
|
|
/// This starts the [`Job`] immediately if one with the Id doesn't exist, and stores a copy of
|
|
|
|
/// its handle and [`Id`] in this `Action` (and thus in the Watchexec instance, when the action
|
|
|
|
/// handler returns).
|
|
|
|
pub fn get_or_create_job(&mut self, id: Id, command: impl Fn() -> Arc<Command>) -> Job {
|
|
|
|
self.get_job(id)
|
|
|
|
.unwrap_or_else(|| self.create_job_with_id(id, command()))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a job given its Id.
|
|
|
|
///
|
|
|
|
/// This returns a job handle, if it existed when this handler was called.
|
2024-01-04 10:32:47 +01:00
|
|
|
#[must_use]
|
2023-11-25 21:33:44 +01:00
|
|
|
pub fn get_job(&self, id: Id) -> Option<Job> {
|
|
|
|
self.extant.get(&id).cloned()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// List all jobs currently supervised by Watchexec.
|
|
|
|
///
|
|
|
|
/// This returns an iterator over all jobs, in no particular order, as of when this handler was
|
|
|
|
/// called.
|
|
|
|
pub fn list_jobs(&self) -> impl Iterator<Item = (Id, Job)> + '_ {
|
|
|
|
self.extant.iter().map(|(id, job)| (*id, job.clone()))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Shut down the Watchexec instance immediately.
|
|
|
|
///
|
|
|
|
/// This will kill and drop all jobs without waiting on processes, then quit.
|
|
|
|
///
|
|
|
|
/// Use `graceful_quit()` to wait for processes to finish before quitting.
|
|
|
|
///
|
|
|
|
/// The quit is initiated once the action handler returns, not when this method is called.
|
|
|
|
pub fn quit(&mut self) {
|
|
|
|
self.quit = Some(QuitManner::Abort);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Shut down the Watchexec instance gracefully.
|
|
|
|
///
|
|
|
|
/// This will send graceful stops to all jobs, wait on them to finish, then reap them and quit.
|
|
|
|
///
|
|
|
|
/// Use `quit()` to quit more abruptly.
|
|
|
|
///
|
|
|
|
/// If you want to wait for all other actions to finish and for jobs to get cleaned up, but not
|
|
|
|
/// gracefully delay for processes, you can do:
|
|
|
|
///
|
|
|
|
/// ```no_compile
|
|
|
|
/// action.quit_gracefully(Signal::ForceStop, Duration::ZERO);
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// The quit is initiated once the action handler returns, not when this method is called.
|
|
|
|
pub fn quit_gracefully(&mut self, signal: Signal, grace: Duration) {
|
|
|
|
self.quit = Some(QuitManner::Graceful { signal, grace });
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convenience to get all signals in the event set.
|
|
|
|
pub fn signals(&self) -> impl Iterator<Item = Signal> + '_ {
|
|
|
|
self.events.iter().flat_map(Event::signals)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convenience to get all paths in the event set.
|
|
|
|
///
|
|
|
|
/// An action contains a set of events, and some of those events might relate to watched
|
|
|
|
/// files, and each of *those* events may have one or more paths that were affected.
|
|
|
|
/// To hide this complexity this method just provides any and all paths in the event,
|
|
|
|
/// along with the type of file at that path, if Watchexec knows that.
|
|
|
|
pub fn paths(&self) -> impl Iterator<Item = (&Path, Option<&FileType>)> + '_ {
|
|
|
|
self.events.iter().flat_map(Event::paths)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convenience to get all process completions in the event set.
|
|
|
|
pub fn completions(&self) -> impl Iterator<Item = Option<ProcessEnd>> + '_ {
|
|
|
|
self.events.iter().flat_map(Event::completions)
|
|
|
|
}
|
|
|
|
}
|