//! Event source for signals / notifications sent to the main process. use std::sync::Arc; use async_priority_channel as priority; use tokio::{select, sync::mpsc}; use tracing::{debug, trace}; use watchexec_events::{Event, Priority, Source, Tag}; use watchexec_signals::Signal; use crate::{ error::{CriticalError, RuntimeError}, Config, }; /// Launch the signal event worker. /// /// While you _could_ run several (it won't panic), you **must** only have one (for correctness). /// This may be enforced later. /// /// # Examples /// /// Direct usage: /// /// ```no_run /// use tokio::sync::mpsc; /// use async_priority_channel as priority; /// use watchexec::sources::signal::worker; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { /// let (ev_s, _) = priority::bounded(1024); /// let (er_s, _) = mpsc::channel(64); /// /// worker(Default::default(), er_s, ev_s).await?; /// Ok(()) /// } /// ``` pub async fn worker( config: Arc, errors: mpsc::Sender, events: priority::Sender, ) -> Result<(), CriticalError> { imp_worker(config, errors, events).await } #[cfg(unix)] async fn imp_worker( _config: Arc, errors: mpsc::Sender, events: priority::Sender, ) -> Result<(), CriticalError> { use tokio::signal::unix::{signal, SignalKind}; debug!("launching unix signal worker"); macro_rules! listen { ($sig:ident, $signum:expr) => {{ trace!(kind=%stringify!($sig), "listening for unix signal"); signal($signum).map_err(|err| CriticalError::IoError { about: concat!("setting ", stringify!($sig), " signal listener"), err })? }}; ($sig:ident) => (listen!($sig, 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); // TODO: option to customise set of signals being listened to, so we can safely listen to sigstop only when requested let mut s_tstp = if let Some(signum) = Signal::TerminalSuspend.to_nix().map(|s| s as i32) { listen!(terminal_suspend, SignalKind::from_raw(signum)) } else { signal(SignalKind::from_raw(9)).map_err(|err| CriticalError::IoError { about: concat!("setting unreceivable signal listener"), err })? }; let mut s_stop = if let Some(signum) = Signal::Suspend.to_nix().map(|s| s as i32) { listen!(suspend, SignalKind::from_raw(signum)) } else { signal(SignalKind::from_raw(9)).map_err(|err| CriticalError::IoError { about: concat!("setting unreceivable signal listener"), err })? }; let mut s_cont = if let Some(signum) = Signal::Continue.to_nix().map(|s| s as i32) { listen!(r#continue, SignalKind::from_raw(signum)) } else { signal(SignalKind::from_raw(9)).map_err(|err| CriticalError::IoError { about: concat!("setting unreceivable signal listener"), err })? }; 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, _ = s_tstp.recv() => Signal::TerminalSuspend, _ = s_stop.recv() => Signal::Suspend, _ = s_cont.recv() => Signal::Continue, ); debug!(?sig, "received unix signal"); send_event(errors.clone(), events.clone(), sig).await?; } } #[cfg(windows)] async fn imp_worker( _config: Arc, errors: mpsc::Sender, events: priority::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().map_err(|err| CriticalError::IoError { about: concat!("setting ", stringify!($sig), " signal listener"), err })? }} } 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?; } } async fn send_event( errors: mpsc::Sender, events: priority::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, match sig { Signal::Interrupt | Signal::Terminate => Priority::Urgent, _ => Priority::High, }, ) .await { errors .send(RuntimeError::EventChannelSend { ctx: "signals", err, }) .await?; } Ok(()) }