From 75243bfdad69c6f6dc9025007fdf14e05462797a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Sat, 16 Oct 2021 01:13:16 +1300 Subject: [PATCH] Rename Signal to MainSignal in preparation for another signal type --- cli/src/config.rs | 20 ++-- lib/examples/demo.rs | 8 +- lib/examples/signal.rs | 6 +- lib/src/event.rs | 6 +- lib/src/signal.rs | 198 +-------------------------------------- lib/src/signal/source.rs | 196 ++++++++++++++++++++++++++++++++++++++ lib/src/watchexec.rs | 2 +- 7 files changed, 220 insertions(+), 216 deletions(-) create mode 100644 lib/src/signal/source.rs diff --git a/cli/src/config.rs b/cli/src/config.rs index 4941ab0..64a40ee 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -12,7 +12,7 @@ use watchexec::{ filter::tagged::TaggedFilterer, fs::Watcher, handler::PrintDisplay, - signal::Signal as InputSignal, + signal::source::MainSignal, }; pub fn new(args: &ArgMatches<'static>) -> Result<(InitConfig, RuntimeConfig, Arc)> { @@ -114,7 +114,7 @@ fn runtime(args: &ArgMatches<'static>) -> Result<(RuntimeConfig, Arc = action.events.iter().flat_map(|e| e.signals()).collect(); + let signals: Vec = action.events.iter().flat_map(|e| e.signals()).collect(); let has_paths = action .events .iter() @@ -122,12 +122,12 @@ fn runtime(args: &ArgMatches<'static>) -> Result<(RuntimeConfig, Arc) -> Result<(RuntimeConfig, Arc Signal::SIGHUP, - InputSignal::Interrupt => Signal::SIGINT, - InputSignal::Quit => Signal::SIGQUIT, - InputSignal::Terminate => Signal::SIGTERM, - InputSignal::User1 => Signal::SIGUSR1, - InputSignal::User2 => Signal::SIGUSR2, + MainSignal::Hangup => Signal::SIGHUP, + MainSignal::Interrupt => Signal::SIGINT, + MainSignal::Quit => Signal::SIGQUIT, + MainSignal::Terminate => Signal::SIGTERM, + MainSignal::User1 => Signal::SIGUSR1, + MainSignal::User2 => Signal::SIGUSR2, }), ); } diff --git a/lib/examples/demo.rs b/lib/examples/demo.rs index 7dc7e3e..635d13f 100644 --- a/lib/examples/demo.rs +++ b/lib/examples/demo.rs @@ -6,7 +6,7 @@ use watchexec::{ config::{InitConfig, RuntimeConfig}, error::ReconfigError, fs::Watcher, - signal::Signal, + signal::source::MainSignal, Watchexec, }; @@ -41,13 +41,13 @@ async fn main() -> Result<()> { .flat_map(|event| event.signals()) .collect::>(); - if sigs.iter().any(|sig| sig == &Signal::Interrupt) { + if sigs.iter().any(|sig| sig == &MainSignal::Interrupt) { action.outcome(Outcome::Exit); - } else if sigs.iter().any(|sig| sig == &Signal::User1) { + } else if sigs.iter().any(|sig| sig == &MainSignal::User1) { eprintln!("Switching to native for funsies"); config.file_watcher(Watcher::Native).keep_action(); w.reconfigure(config)?; - } else if sigs.iter().any(|sig| sig == &Signal::User2) { + } else if sigs.iter().any(|sig| sig == &MainSignal::User2) { eprintln!("Switching to polling for funsies"); config .file_watcher(Watcher::Poll(Duration::from_millis(50))) diff --git a/lib/examples/signal.rs b/lib/examples/signal.rs index 224fa3d..86218fb 100644 --- a/lib/examples/signal.rs +++ b/lib/examples/signal.rs @@ -4,7 +4,7 @@ use miette::Result; use tokio::sync::mpsc; use watchexec::{ event::{Event, Tag}, - signal::{self, Signal}, + signal::{self, source::MainSignal}, }; // Run with: `env RUST_LOG=debug cargo run --example signal`, @@ -21,7 +21,7 @@ async fn main() -> Result<()> { while let Some(e) = ev_r.recv().await { tracing::info!("event: {:?}", e); - if e.tags.contains(&Tag::Signal(Signal::Terminate)) { + if e.tags.contains(&Tag::Signal(MainSignal::Terminate)) { exit(0); } } @@ -34,7 +34,7 @@ async fn main() -> Result<()> { }); tracing::info!("PID is {}", std::process::id()); - signal::worker(er_s.clone(), ev_s.clone()).await?; + signal::source::worker(er_s.clone(), ev_s.clone()).await?; Ok(()) } diff --git a/lib/src/event.rs b/lib/src/event.rs index dccdcc8..3467804 100644 --- a/lib/src/event.rs +++ b/lib/src/event.rs @@ -16,7 +16,7 @@ use std::{ use notify::EventKind; -use crate::signal::Signal; +use crate::signal::source::MainSignal; /// An event, as far as watchexec cares about. #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -36,7 +36,7 @@ pub enum Tag { FileEventKind(EventKind), Source(Source), Process(u32), - Signal(Signal), + Signal(MainSignal), ProcessCompletion(Option), } @@ -104,7 +104,7 @@ impl Event { } /// Return all signals in the event's tags. - pub fn signals(&self) -> impl Iterator + '_ { + pub fn signals(&self) -> impl Iterator + '_ { self.tags.iter().filter_map(|p| match p { Tag::Signal(s) => Some(*s), _ => None, diff --git a/lib/src/signal.rs b/lib/src/signal.rs index 2dce378..78f4d61 100644 --- a/lib/src/signal.rs +++ b/lib/src/signal.rs @@ -1,196 +1,4 @@ -//! Event source for signals / notifications sent to the main process. +//! Signal handling. -use tokio::{select, sync::mpsc}; -use tracing::{debug, trace}; - -use crate::{ - error::{CriticalError, RuntimeError}, - event::{Event, Source, Tag}, -}; - -/// A notification sent to the main (watchexec) process. -/// -/// On Windows, only [`Interrupt`][Signal::Interrupt] and [`Terminate`][Signal::Terminate] will be -/// produced: they are respectively `Ctrl-C` (SIGINT) and `Ctrl-Break` (SIGBREAK). `Ctrl-Close` (the -/// equivalent of `SIGHUP` on Unix, without the semantics of configuration reload) is not supported, -/// and on console close the process will be terminated by the OS. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Signal { - /// Received when the terminal is disconnected. - /// - /// On Unix, this is `SIGHUP`. On Windows, it is not produced. - /// - /// This signal is available because it is a common signal used to reload configuration files, - /// and it is reasonable that either watchexec could make use of it, or that it should be passed - /// on to a sub process. - Hangup, - - /// Received to indicate that the process should stop. - /// - /// On Unix, this is `SIGINT`. On Windows, this is `Ctrl+C`. - /// - /// This signal is generally produced by the user, so it may be handled differently than a - /// termination. - Interrupt, - - /// Received to cause the process to stop and the kernel to dump its core. - /// - /// On Unix, this is `SIGQUIT`. On Windows, it is not produced. - /// - /// This signal is available because it is reasonable that it could be passed on to a sub - /// process, rather than terminate watchexec itself. - Quit, - - /// Received to indicate that the process should stop. - /// - /// On Unix, this is `SIGTERM`. On Windows, this is `Ctrl+Break`. - /// - /// This signal is available for cleanup, but will generally not be passed on to a sub process - /// with no other consequence: it is expected the main process should terminate. - Terminate, - - /// Received for a user or application defined purpose. - /// - /// On Unix, this is `SIGUSR1`. On Windows, it is not produced. - /// - /// This signal is available because it is expected that it most likely should be passed on to a - /// sub process or trigger a particular action within watchexec. - User1, - - /// Received for a user or application defined purpose. - /// - /// On Unix, this is `SIGUSR2`. On Windows, it is not produced. - /// - /// This signal is available because it is expected that it most likely should be passed on to a - /// sub process or trigger a particular action within watchexec. - User2, -} - -/// Launch the signal event worker. -/// -/// While you _can_ run several, you **must** only have one. This may be enforced later. -/// -/// # Examples -/// -/// Direct usage: -/// -/// ```no_run -/// use tokio::sync::mpsc; -/// use watchexec::signal::worker; -/// -/// #[tokio::main] -/// async fn main() -> Result<(), Box> { -/// let (ev_s, _) = mpsc::channel(1024); -/// let (er_s, _) = mpsc::channel(64); -/// -/// worker(er_s, ev_s).await?; -/// Ok(()) -/// } -/// ``` -pub async fn worker( - errors: mpsc::Sender, - events: mpsc::Sender, -) -> Result<(), CriticalError> { - imp_worker(errors, events).await -} - -#[cfg(unix)] -async fn imp_worker( - errors: mpsc::Sender, - events: mpsc::Sender, -) -> Result<(), CriticalError> { - use tokio::signal::unix::{signal, SignalKind}; - - debug!("launching unix signal worker"); - - macro_rules! listen { - ($sig:ident) => {{ - trace!(kind=%stringify!($sig), "listening for unix signal"); - signal(SignalKind::$sig())? - }} - } - - let mut s_hangup = listen!(hangup); - let mut s_interrupt = listen!(interrupt); - let mut s_quit = listen!(quit); - let mut s_terminate = listen!(terminate); - let mut s_user1 = listen!(user_defined1); - let mut s_user2 = listen!(user_defined2); - - loop { - let sig = select!( - _ = s_hangup.recv() => Signal::Hangup, - _ = s_interrupt.recv() => Signal::Interrupt, - _ = s_quit.recv() => Signal::Quit, - _ = s_terminate.recv() => Signal::Terminate, - _ = s_user1.recv() => Signal::User1, - _ = s_user2.recv() => Signal::User2, - ); - - debug!(?sig, "received unix signal"); - send_event(errors.clone(), events.clone(), sig).await?; - } -} - -#[cfg(windows)] -async fn imp_worker( - errors: mpsc::Sender, - events: mpsc::Sender, -) -> Result<(), CriticalError> { - use tokio::signal::windows::{ctrl_break, ctrl_c}; - - debug!("launching windows signal worker"); - - macro_rules! listen { - ($sig:ident) => {{ - trace!(kind=%stringify!($sig), "listening for windows process notification"); - $sig()? - }} - } - - let mut sigint = listen!(ctrl_c); - let mut sigbreak = listen!(ctrl_break); - - loop { - let sig = select!( - _ = sigint.recv() => Signal::Interrupt, - _ = sigbreak.recv() => Signal::Terminate, - ); - - debug!(?sig, "received windows process notification"); - send_event(errors.clone(), events.clone(), sig).await?; - } -} - -// TODO: figure out how to prioritise signals. -async fn send_event( - errors: mpsc::Sender, - events: mpsc::Sender, - sig: Signal, -) -> Result<(), CriticalError> { - let tags = vec![ - Tag::Source(if sig == Signal::Interrupt { - Source::Keyboard - } else { - Source::Os - }), - Tag::Signal(sig), - ]; - - let event = Event { - tags, - metadata: Default::default(), - }; - - trace!(?event, "processed signal into event"); - if let Err(err) = events.send(event).await { - errors - .send(RuntimeError::EventChannelSend { - ctx: "signals", - err, - }) - .await?; - } - - Ok(()) -} +pub mod source; +pub mod process; diff --git a/lib/src/signal/source.rs b/lib/src/signal/source.rs new file mode 100644 index 0000000..5444308 --- /dev/null +++ b/lib/src/signal/source.rs @@ -0,0 +1,196 @@ +//! Event source for signals / notifications sent to the main process. + +use tokio::{select, sync::mpsc}; +use tracing::{debug, trace}; + +use crate::{ + error::{CriticalError, RuntimeError}, + event::{Event, Source, Tag}, +}; + +/// A notification sent to the main (watchexec) process. +/// +/// On Windows, only [`Interrupt`][MainSignal::Interrupt] and [`Terminate`][MainSignal::Terminate] +/// will be produced: they are respectively `Ctrl-C` (SIGINT) and `Ctrl-Break` (SIGBREAK). +/// `Ctrl-Close` (the equivalent of `SIGHUP` on Unix, without the semantics of configuration reload) +/// is not supported, and on console close the process will be terminated by the OS. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MainSignal { + /// Received when the terminal is disconnected. + /// + /// On Unix, this is `SIGHUP`. On Windows, it is not produced. + /// + /// This signal is available because it is a common signal used to reload configuration files, + /// and it is reasonable that either watchexec could make use of it, or that it should be passed + /// on to a sub process. + Hangup, + + /// Received to indicate that the process should stop. + /// + /// On Unix, this is `SIGINT`. On Windows, this is `Ctrl+C`. + /// + /// This signal is generally produced by the user, so it may be handled differently than a + /// termination. + Interrupt, + + /// Received to cause the process to stop and the kernel to dump its core. + /// + /// On Unix, this is `SIGQUIT`. On Windows, it is not produced. + /// + /// This signal is available because it is reasonable that it could be passed on to a sub + /// process, rather than terminate watchexec itself. + Quit, + + /// Received to indicate that the process should stop. + /// + /// On Unix, this is `SIGTERM`. On Windows, this is `Ctrl+Break`. + /// + /// This signal is available for cleanup, but will generally not be passed on to a sub process + /// with no other consequence: it is expected the main process should terminate. + Terminate, + + /// Received for a user or application defined purpose. + /// + /// On Unix, this is `SIGUSR1`. On Windows, it is not produced. + /// + /// This signal is available because it is expected that it most likely should be passed on to a + /// sub process or trigger a particular action within watchexec. + User1, + + /// Received for a user or application defined purpose. + /// + /// On Unix, this is `SIGUSR2`. On Windows, it is not produced. + /// + /// This signal is available because it is expected that it most likely should be passed on to a + /// sub process or trigger a particular action within watchexec. + User2, +} + +/// Launch the signal event worker. +/// +/// While you _can_ run several, you **must** only have one. This may be enforced later. +/// +/// # Examples +/// +/// Direct usage: +/// +/// ```no_run +/// use tokio::sync::mpsc; +/// use watchexec::signal::worker; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Box> { +/// let (ev_s, _) = mpsc::channel(1024); +/// let (er_s, _) = mpsc::channel(64); +/// +/// worker(er_s, ev_s).await?; +/// Ok(()) +/// } +/// ``` +pub async fn worker( + errors: mpsc::Sender, + events: mpsc::Sender, +) -> Result<(), CriticalError> { + imp_worker(errors, events).await +} + +#[cfg(unix)] +async fn imp_worker( + errors: mpsc::Sender, + events: mpsc::Sender, +) -> Result<(), CriticalError> { + use tokio::signal::unix::{signal, SignalKind}; + + debug!("launching unix signal worker"); + + macro_rules! listen { + ($sig:ident) => {{ + trace!(kind=%stringify!($sig), "listening for unix signal"); + signal(SignalKind::$sig())? + }} + } + + let mut s_hangup = listen!(hangup); + let mut s_interrupt = listen!(interrupt); + let mut s_quit = listen!(quit); + let mut s_terminate = listen!(terminate); + let mut s_user1 = listen!(user_defined1); + let mut s_user2 = listen!(user_defined2); + + loop { + let sig = select!( + _ = s_hangup.recv() => MainSignal::Hangup, + _ = s_interrupt.recv() => MainSignal::Interrupt, + _ = s_quit.recv() => MainSignal::Quit, + _ = s_terminate.recv() => MainSignal::Terminate, + _ = s_user1.recv() => MainSignal::User1, + _ = s_user2.recv() => MainSignal::User2, + ); + + debug!(?sig, "received unix signal"); + send_event(errors.clone(), events.clone(), sig).await?; + } +} + +#[cfg(windows)] +async fn imp_worker( + errors: mpsc::Sender, + events: mpsc::Sender, +) -> Result<(), CriticalError> { + use tokio::signal::windows::{ctrl_break, ctrl_c}; + + debug!("launching windows signal worker"); + + macro_rules! listen { + ($sig:ident) => {{ + trace!(kind=%stringify!($sig), "listening for windows process notification"); + $sig()? + }} + } + + let mut sigint = listen!(ctrl_c); + let mut sigbreak = listen!(ctrl_break); + + loop { + let sig = select!( + _ = sigint.recv() => MainSignal::Interrupt, + _ = sigbreak.recv() => MainSignal::Terminate, + ); + + debug!(?sig, "received windows process notification"); + send_event(errors.clone(), events.clone(), sig).await?; + } +} + +// TODO: figure out how to prioritise signals. +async fn send_event( + errors: mpsc::Sender, + events: mpsc::Sender, + sig: MainSignal, +) -> Result<(), CriticalError> { + let tags = vec![ + Tag::Source(if sig == MainSignal::Interrupt { + Source::Keyboard + } else { + Source::Os + }), + Tag::Signal(sig), + ]; + + let event = Event { + tags, + metadata: Default::default(), + }; + + trace!(?event, "processed signal into event"); + if let Err(err) = events.send(event).await { + errors + .send(RuntimeError::EventChannelSend { + ctx: "signals", + err, + }) + .await?; + } + + Ok(()) +} diff --git a/lib/src/watchexec.rs b/lib/src/watchexec.rs index 0567f8b..14ec5b7 100644 --- a/lib/src/watchexec.rs +++ b/lib/src/watchexec.rs @@ -86,7 +86,7 @@ impl Watchexec { action::worker(ac_r, er_s.clone(), ev_s.clone(), ev_r) ); let fs = subtask!(fs, fs::worker(fs_r, er_s.clone(), ev_s.clone())); - let signal = subtask!(signal, signal::worker(er_s.clone(), ev_s.clone())); + let signal = subtask!(signal, signal::source::worker(er_s.clone(), ev_s.clone())); let error_hook = subtask!(error_hook, error_hook(er_r, eh));