148 lines
6.0 KiB
Rust
148 lines
6.0 KiB
Rust
//! Watchexec's process supervisor.
|
|
//!
|
|
//! This crate implements the process supervisor for Watchexec. It is responsible for spawning and
|
|
//! managing processes, and for sending events to them.
|
|
//!
|
|
//! You may use this crate to implement your own process supervisor, but keep in mind its direction
|
|
//! will always primarily be driven by the needs of Watchexec itself.
|
|
//!
|
|
//! # Usage
|
|
//!
|
|
//! There is no struct or implementation of a single supervisor, as the particular needs of the
|
|
//! application will dictate how that is designed. Instead, this crate provides a [`Job`](job::Job)
|
|
//! construct, which is a handle to a single [`Command`](command::Command), and manages its
|
|
//! lifecycle. The `Job` API has been modeled after the `systemctl` set of commands for service
|
|
//! control, with operations for starting, stopping, restarting, sending signals, waiting for the
|
|
//! process to complete, etc.
|
|
//!
|
|
//! There are also methods for running hooks within the job's runtime task, and for handling errors.
|
|
//!
|
|
//! # Theory of Operation
|
|
//!
|
|
//! A [`Job`](job::Job) is, properly speaking, a handle which lets one control a Tokio task. That
|
|
//! task is spawned on the Tokio runtime, and so runs in the background. A `Job` takes as input a
|
|
//! [`Command`](command::Command), which describes how to start a single process, through either a
|
|
//! shell command or a direct executable invocation, and if the process should be grouped (using
|
|
//! [`command-group`](command_group)) or not.
|
|
//!
|
|
//! The job's task runs an event loop on two sources: the process's `wait()` (i.e. when the process
|
|
//! ends) and the job's control queue. The control queue is a hybrid MPSC queue, with three priority
|
|
//! levels and a timer. When the timer is active, the lowest ("Normal") priority queue is disabled.
|
|
//! This is an internal detail which serves to implement graceful stops and restarts. The internals
|
|
//! of the job's task are not available to the API user, actions and queries are performed by
|
|
//! sending messages on this control queue.
|
|
//!
|
|
//! The control queue is executed in priority and in order within priorities. Sending a control to
|
|
//! the task returns a [`Ticket`](job::Ticket), which is a future that resolves when the control has
|
|
//! been processed. Dropping the ticket will not cancel the control. This provides two complementary
|
|
//! ways to orchestrate actions: queueing controls in the desired order if there is no need for
|
|
//! branching flow or for signaling, and sending controls or performing other actions after awaiting
|
|
//! tickets.
|
|
//!
|
|
//! Do note that both of these can be used together. There is no need for the below pattern:
|
|
//!
|
|
//! ```no_run
|
|
//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only
|
|
//! # use std::sync::Arc;
|
|
//! # use watchexec_supervisor::Signal;
|
|
//! # use watchexec_supervisor::command::{Command, Program};
|
|
//! # use watchexec_supervisor::job::{CommandState, start_job};
|
|
//! #
|
|
//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() }));
|
|
//! #
|
|
//! job.start().await;
|
|
//! job.signal(Signal::User1).await;
|
|
//! job.stop().await;
|
|
//! # task.abort();
|
|
//! # }
|
|
//! ```
|
|
//!
|
|
//! Because of ordering, it behaves the same as this:
|
|
//!
|
|
//! ```no_run
|
|
//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only
|
|
//! # use std::sync::Arc;
|
|
//! # use watchexec_supervisor::Signal;
|
|
//! # use watchexec_supervisor::command::{Command, Program};
|
|
//! # use watchexec_supervisor::job::{CommandState, start_job};
|
|
//! #
|
|
//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() }));
|
|
//! #
|
|
//! job.start();
|
|
//! job.signal(Signal::User1);
|
|
//! job.stop().await; // here, all of start(), signal(), and stop() will have run in order
|
|
//! # task.abort();
|
|
//! # }
|
|
//! ```
|
|
//!
|
|
//! However, this is a different program:
|
|
//!
|
|
//! ```no_run
|
|
//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only
|
|
//! # use std::sync::Arc;
|
|
//! # use std::time::Duration;
|
|
//! # use tokio::time::sleep;
|
|
//! # use watchexec_supervisor::Signal;
|
|
//! # use watchexec_supervisor::command::{Command, Program};
|
|
//! # use watchexec_supervisor::job::{CommandState, start_job};
|
|
//! #
|
|
//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() }));
|
|
//! #
|
|
//! job.start().await;
|
|
//! println!("program started!");
|
|
//! sleep(Duration::from_secs(5)).await; // wait until program is fully started
|
|
//!
|
|
//! job.signal(Signal::User1).await;
|
|
//! sleep(Duration::from_millis(150)).await; // wait until program has dumped stats
|
|
//! println!("program stats dumped via USR1 signal!");
|
|
//!
|
|
//! job.stop().await;
|
|
//! println!("program stopped");
|
|
//! #
|
|
//! # task.abort();
|
|
//! # }
|
|
//! ```
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```no_run
|
|
//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only
|
|
//! # use std::sync::Arc;
|
|
//! use watchexec_supervisor::Signal;
|
|
//! use watchexec_supervisor::command::{Command, Program};
|
|
//! use watchexec_supervisor::job::{CommandState, start_job};
|
|
//!
|
|
//! let (job, task) = start_job(Arc::new(Command {
|
|
//! program: Program::Exec {
|
|
//! prog: "/bin/date".into(),
|
|
//! args: Vec::new(),
|
|
//! }.into(),
|
|
//! options: Default::default(),
|
|
//! }));
|
|
//!
|
|
//! job.start().await;
|
|
//! job.signal(Signal::User1).await;
|
|
//! job.stop().await;
|
|
//!
|
|
//! job.delete_now().await;
|
|
//!
|
|
//! task.await; // make sure the task is fully cleaned up
|
|
//! # }
|
|
//! ```
|
|
|
|
#![doc(html_favicon_url = "https://watchexec.github.io/logo:watchexec.svg")]
|
|
#![doc(html_logo_url = "https://watchexec.github.io/logo:watchexec.svg")]
|
|
#![warn(clippy::unwrap_used, missing_docs, rustdoc::unescaped_backticks)]
|
|
#![deny(rust_2018_idioms)]
|
|
|
|
#[doc(no_inline)]
|
|
pub use watchexec_events::ProcessEnd;
|
|
#[doc(no_inline)]
|
|
pub use watchexec_signals::Signal;
|
|
|
|
pub mod command;
|
|
pub mod errors;
|
|
pub mod job;
|
|
|
|
mod flag;
|