1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
use std::{
fmt,
sync::{Arc, Weak},
time::Duration,
};
use once_cell::sync::OnceCell;
use tokio::{
process::Command as TokioCommand,
sync::{Mutex, OwnedMutexGuard},
};
use crate::{command::Command, event::Event, filter::Filterer, handler::HandlerLock};
use super::Outcome;
/// The configuration of the [action][crate::action] worker.
///
/// This is marked non-exhaustive so new configuration can be added without breaking.
#[derive(Clone)]
#[non_exhaustive]
pub struct WorkingData {
/// How long to wait for events to build up before executing an action.
///
/// This is sometimes called "debouncing." We debounce on the trailing edge: an action is
/// triggered only after that amount of time has passed since the first event in the cycle. The
/// action is called with all the collected events in the cycle.
pub throttle: Duration,
/// The main handler to define: what to do when an action is triggered.
///
/// This handler is called with the [`Action`] environment, which has a certain way of returning
/// the desired outcome, check out the [`Action::outcome()`] method. The handler checks for the
/// outcome as soon as the handler returns, which means that if the handler returns before the
/// outcome is set, you'll get unexpected results. For this reason, it's a bad idea to use ex. a
/// channel as the handler.
///
/// If this handler is not provided, it defaults to a no-op, which does absolutely nothing, not
/// even quit. Hence, you really need to provide a handler.
///
/// It is possible to change the handler or any other configuration inside the previous handler.
/// It's useful to know that the handlers are updated from this working data before any of them
/// run in any given cycle, so changing the pre-spawn and post-spawn handlers from this handler
/// will not affect the running action.
pub action_handler: HandlerLock<Action>,
/// A handler triggered before a command is spawned.
///
/// This handler is called with the [`PreSpawn`] environment, which provides mutable access to
/// the [`Command`](TokioCommand) which is about to be run. See the notes on the
/// [`PreSpawn::command()`] method for important information on what you can do with it.
///
/// Returning an error from the handler will stop the action from processing further, and issue
/// a [`RuntimeError`][crate::error::RuntimeError] to the error channel.
pub pre_spawn_handler: HandlerLock<PreSpawn>,
/// A handler triggered immediately after a command is spawned.
///
/// This handler is called with the [`PostSpawn`] environment, which provides details on the
/// spawned command, including its PID.
///
/// Returning an error from the handler will drop the [`Child`][tokio::process::Child], which
/// will terminate the command without triggering any of the normal Watchexec behaviour, and
/// issue a [`RuntimeError`][crate::error::RuntimeError] to the error channel.
pub post_spawn_handler: HandlerLock<PostSpawn>,
/// Commands to execute.
///
/// These will be run in order, and an error will stop early.
pub commands: Vec<Command>,
/// Whether to use process groups (on Unix) or job control (on Windows) to run the command.
///
/// This makes use of [command_group] under the hood.
///
/// If you want to known whether a spawned command was run in a process group, you should use
/// the value in [`PostSpawn`] instead of reading this one, as it may have changed in the
/// meantime.
pub grouped: bool,
/// The filterer implementation to use when filtering events.
///
/// The default is a no-op, which will always pass every event.
pub filterer: Arc<dyn Filterer>,
}
impl fmt::Debug for WorkingData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WorkingData")
.field("throttle", &self.throttle)
.field("commands", &self.commands)
.field("grouped", &self.grouped)
.field("filterer", &self.filterer)
.finish_non_exhaustive()
}
}
impl Default for WorkingData {
fn default() -> Self {
Self {
throttle: Duration::from_millis(50),
action_handler: Default::default(),
pre_spawn_handler: Default::default(),
post_spawn_handler: Default::default(),
commands: Vec::new(),
grouped: true,
filterer: Arc::new(()),
}
}
}
/// The environment given to the action handler.
///
/// This deliberately does not implement Clone to make it hard to move it out of the handler, which
/// you should not do.
///
/// The [`Action::outcome()`] method is the only way to set the outcome of the action, and it _must_
/// be called before the handler returns.
#[derive(Debug)]
pub struct Action {
/// The collected events which triggered the action.
pub events: Arc<[Event]>,
pub(super) outcome: Arc<OnceCell<Outcome>>,
}
impl Action {
pub(super) fn new(events: Arc<[Event]>) -> Self {
Self {
events,
outcome: Default::default(),
}
}
/// Set the action's outcome.
///
/// This takes `self` and `Action` 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.
///
/// See the [`Action`] documentation about handlers to learn why it's a bad idea to clone or
/// send it elsewhere, and what kind of handlers you cannot use.
pub fn outcome(self, outcome: Outcome) {
self.outcome.set(outcome).ok();
}
}
/// The environment given to the pre-spawn handler.
///
/// This deliberately does not implement Clone to make it hard to move it out of the handler, which
/// you should not do.
///
/// The [`PreSpawn::command()`] method is the only way to mutate the command, and the mutex guard it
/// returns _must_ be dropped before the handler returns.
#[derive(Debug)]
#[non_exhaustive]
pub struct PreSpawn {
/// The command which is about to be spawned.
pub command: Command,
/// The collected events which triggered the action this command issues from.
pub events: Arc<[Event]>,
to_spawn_w: Weak<Mutex<TokioCommand>>,
}
impl PreSpawn {
pub(crate) fn new(
command: Command,
to_spawn: TokioCommand,
events: Arc<[Event]>,
) -> (Self, Arc<Mutex<TokioCommand>>) {
let arc = Arc::new(Mutex::new(to_spawn));
(
Self {
command,
events,
to_spawn_w: Arc::downgrade(&arc),
},
arc.clone(),
)
}
/// Get write access to the command that will be spawned.
///
/// Keeping the lock alive beyond the end of the handler may cause the command to be cancelled,
/// but note no guarantees are made on this behaviour. Just don't do it. See the [`Action`]
/// documentation about handlers for more.
///
/// This will always return `Some()` under normal circumstances.
pub async fn command(&self) -> Option<OwnedMutexGuard<TokioCommand>> {
if let Some(arc) = self.to_spawn_w.upgrade() {
Some(arc.lock_owned().await)
} else {
None
}
}
}
/// The environment given to the post-spawn handler.
///
/// This is Clone, as there's nothing (except returning an error) that can be done to the command
/// now that it's spawned, as far as Watchexec is concerned. Nevertheless, you should return from
/// this handler quickly, to avoid holding up anything else.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct PostSpawn {
/// The command the process was spawned with.
pub command: Command,
/// The collected events which triggered the action the command issues from.
pub events: Arc<[Event]>,
/// The process ID or the process group ID.
pub id: u32,
/// Whether the command was run in a process group.
pub grouped: bool,
}