Docs: command
This commit is contained in:
parent
fcf6a2154a
commit
17b83fda08
|
@ -235,16 +235,7 @@ async fn apply_outcome(
|
|||
}
|
||||
|
||||
(Some(p), Outcome::Signal(sig)) => {
|
||||
#[cfg(unix)]
|
||||
if let Some(sig) = sig.to_nix() {
|
||||
p.signal(sig).await;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if let SubSignal::Terminate = sig {
|
||||
p.kill().await;
|
||||
}
|
||||
// else: https://github.com/watchexec/watchexec/issues/219
|
||||
p.signal(sig).await;
|
||||
}
|
||||
|
||||
(Some(p), Outcome::Wait) => {
|
||||
|
|
|
@ -6,21 +6,33 @@ use tracing::{debug, trace};
|
|||
|
||||
use crate::error::RuntimeError;
|
||||
|
||||
/// Low-level wrapper around a process child, be it grouped or ungrouped.
|
||||
#[derive(Debug)]
|
||||
pub enum Process {
|
||||
/// The initial state of the process, before it's spawned.
|
||||
None,
|
||||
|
||||
/// A grouped process that's been spawned.
|
||||
Grouped(AsyncGroupChild),
|
||||
|
||||
/// An ungrouped process that's been spawned.
|
||||
Ungrouped(Child),
|
||||
|
||||
/// The cached exit status of the process.
|
||||
Done(ExitStatus),
|
||||
}
|
||||
|
||||
impl Default for Process {
|
||||
/// Returns [`Process::None`].
|
||||
fn default() -> Self {
|
||||
Process::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Process {
|
||||
/// Sends a Unix signal to the process.
|
||||
///
|
||||
/// Does nothing if the process is not running.
|
||||
#[cfg(unix)]
|
||||
pub fn signal(&mut self, sig: Signal) -> Result<(), RuntimeError> {
|
||||
use command_group::UnixChildExt;
|
||||
|
@ -39,6 +51,12 @@ impl Process {
|
|||
.map_err(RuntimeError::Process)
|
||||
}
|
||||
|
||||
/// Kills the process.
|
||||
///
|
||||
/// Does nothing if the process is not running.
|
||||
///
|
||||
/// Note that this has different behaviour for grouped and ungrouped processes due to Tokio's
|
||||
/// API: it waits on ungrouped processes, but not for grouped processes.
|
||||
pub async fn kill(&mut self) -> Result<(), RuntimeError> {
|
||||
match self {
|
||||
Self::None | Self::Done(_) => Ok(()),
|
||||
|
@ -54,6 +72,15 @@ impl Process {
|
|||
.map_err(RuntimeError::Process)
|
||||
}
|
||||
|
||||
/// Checks the status of the process.
|
||||
///
|
||||
/// Returns `true` if the process is still running.
|
||||
///
|
||||
/// This takes `&mut self` as it transitions the [`Process`] state to [`Process::Done`] if it
|
||||
/// finds the process has ended, such that it will cache the exit status. Otherwise that status
|
||||
/// would be lost.
|
||||
///
|
||||
/// Does nothing and returns `false` immediately if the `Process` is `Done` or `None`.
|
||||
pub fn is_running(&mut self) -> Result<bool, RuntimeError> {
|
||||
match self {
|
||||
Self::None | Self::Done(_) => Ok(false),
|
||||
|
@ -81,6 +108,16 @@ impl Process {
|
|||
.map_err(RuntimeError::Process)
|
||||
}
|
||||
|
||||
/// Waits for the process to exit, and returns its exit status.
|
||||
///
|
||||
/// This takes `&mut self` as it transitions the [`Process`] state to [`Process::Done`] if it
|
||||
/// finds the process has ended, such that it will cache the exit status.
|
||||
///
|
||||
/// This makes it possible to call `wait` on a process multiple times, without losing the exit
|
||||
/// status.
|
||||
///
|
||||
/// Returns immediately with the cached exit status if the `Process` is `Done`, and with `None`
|
||||
/// if the `Process` is `None`.
|
||||
pub async fn wait(&mut self) -> Result<Option<ExitStatus>, RuntimeError> {
|
||||
match self {
|
||||
Self::None => Ok(None),
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::{
|
|||
Arc,
|
||||
};
|
||||
|
||||
use command_group::{AsyncCommandGroup, Signal};
|
||||
use command_group::AsyncCommandGroup;
|
||||
use tokio::{
|
||||
process::Command,
|
||||
select, spawn,
|
||||
|
@ -18,6 +18,7 @@ use tracing::{debug, error, trace};
|
|||
use crate::{
|
||||
error::RuntimeError,
|
||||
event::{Event, Source, Tag},
|
||||
signal::process::SubSignal,
|
||||
};
|
||||
|
||||
use super::Process;
|
||||
|
@ -25,10 +26,14 @@ use super::Process;
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
enum Intervention {
|
||||
Kill,
|
||||
#[cfg(unix)]
|
||||
Signal(Signal),
|
||||
Signal(SubSignal),
|
||||
}
|
||||
|
||||
/// A task which supervises a process.
|
||||
///
|
||||
/// This spawns a process from a [`Command`] and waits for it to complete while handling
|
||||
/// interventions to it: orders to terminate it, or to send a signal to it. It also immediately
|
||||
/// issues a [`Tag::ProcessCompletion`] event when the process completes.
|
||||
#[derive(Debug)]
|
||||
pub struct Supervisor {
|
||||
id: u32,
|
||||
|
@ -43,6 +48,7 @@ pub struct Supervisor {
|
|||
}
|
||||
|
||||
impl Supervisor {
|
||||
/// Spawns the command, the supervision task, and returns a new control object.
|
||||
pub fn spawn(
|
||||
errors: Sender<RuntimeError>,
|
||||
events: Sender<Event>,
|
||||
|
@ -100,12 +106,27 @@ impl Supervisor {
|
|||
}
|
||||
#[cfg(unix)]
|
||||
Intervention::Signal(sig) => {
|
||||
if let Err(err) = process.signal(sig) {
|
||||
if let Some(sig) = sig.to_nix() {
|
||||
if let Err(err) = process.signal(sig) {
|
||||
error!(%err, "while sending signal to process");
|
||||
errors.send(err).await.ok();
|
||||
trace!("continuing to watch command");
|
||||
}
|
||||
} else {
|
||||
let err = RuntimeError::UnsupportedSignal(sig);
|
||||
error!(%err, "while sending signal to process");
|
||||
errors.send(err).await.ok();
|
||||
trace!("continuing to watch command");
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Intervention::Signal(sig) => {
|
||||
// https://github.com/watchexec/watchexec/issues/219
|
||||
let err = RuntimeError::UnsupportedSignal(sig);
|
||||
error!(%err, "while sending signal to process");
|
||||
errors.send(err).await.ok();
|
||||
trace!("continuing to watch command");
|
||||
}
|
||||
}
|
||||
}
|
||||
else => break,
|
||||
|
@ -156,29 +177,58 @@ impl Supervisor {
|
|||
})
|
||||
}
|
||||
|
||||
/// Get the PID of the process or process group.
|
||||
///
|
||||
/// This always successfully returns a PID, even if the process has already exited, as the PID
|
||||
/// is held as soon as the process spawns. Take care not to use this for process manipulation
|
||||
/// once the process has exited, as the ID may have been reused already.
|
||||
pub fn id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub async fn signal(&self, signal: Signal) {
|
||||
trace!(?signal, "sending signal intervention");
|
||||
self.intervene.send(Intervention::Signal(signal)).await.ok();
|
||||
/// Issues a signal to the process.
|
||||
///
|
||||
/// On Windows, this currently only supports [`SubSignal::ForceStop`].
|
||||
///
|
||||
/// While this is async, it returns once the signal intervention has been sent internally, not
|
||||
/// when the signal has been delivered.
|
||||
pub async fn signal(&self, signal: SubSignal) {
|
||||
if cfg!(windows) {
|
||||
if let SubSignal::ForceStop = signal {
|
||||
self.intervene.send(Intervention::Kill).await.ok();
|
||||
}
|
||||
// else: https://github.com/watchexec/watchexec/issues/219
|
||||
} else {
|
||||
trace!(?signal, "sending signal intervention");
|
||||
self.intervene.send(Intervention::Signal(signal)).await.ok();
|
||||
}
|
||||
// only errors on channel closed, and that only happens if the process is dead
|
||||
}
|
||||
|
||||
/// Stops the process.
|
||||
///
|
||||
/// While this is async, it returns once the signal intervention has been sent internally, not
|
||||
/// when the signal has been delivered.
|
||||
pub async fn kill(&self) {
|
||||
trace!("sending kill intervention");
|
||||
self.intervene.send(Intervention::Kill).await.ok();
|
||||
// only errors on channel closed, and that only happens if the process is dead
|
||||
}
|
||||
|
||||
/// Returns true if the supervisor is still running.
|
||||
///
|
||||
/// This is almost always equivalent to whether the _process_ is still running, but may not be
|
||||
/// 100% in sync.
|
||||
pub fn is_running(&self) -> bool {
|
||||
let ongoing = self.ongoing.load(Ordering::SeqCst);
|
||||
trace!(?ongoing, "supervisor state");
|
||||
ongoing
|
||||
}
|
||||
|
||||
/// Returns only when the supervisor completes.
|
||||
///
|
||||
/// This is almost always equivalent to waiting for the _process_ to complete, but may not be
|
||||
/// 100% in sync.
|
||||
pub async fn wait(&mut self) -> Result<(), RuntimeError> {
|
||||
if !self.ongoing.load(Ordering::SeqCst) {
|
||||
trace!("supervisor already completed");
|
||||
|
|
|
@ -13,6 +13,7 @@ use crate::{
|
|||
action,
|
||||
event::Event,
|
||||
fs::{self, Watcher},
|
||||
signal::process::SubSignal,
|
||||
};
|
||||
|
||||
/// Errors which are not recoverable and stop watchexec execution.
|
||||
|
@ -169,6 +170,14 @@ pub enum RuntimeError {
|
|||
#[diagnostic(code(watchexec::runtime::process_doa))]
|
||||
ProcessDeadOnArrival,
|
||||
|
||||
/// Error received when a [`SubSignal`] is unsupported
|
||||
///
|
||||
/// This may happen if the signal is not supported on the current platform, or if Watchexec
|
||||
/// doesn't support sending the signal.
|
||||
#[error("unsupported signal: {0:?}")]
|
||||
#[diagnostic(code(watchexec::runtime::unsupported_signal))]
|
||||
UnsupportedSignal(SubSignal),
|
||||
|
||||
/// Error received when clearing the screen.
|
||||
#[error("clear screen: {0}")]
|
||||
#[diagnostic(code(watchexec::runtime::clearscreen))]
|
||||
|
|
Loading…
Reference in New Issue