watchexec_supervisor/lib.rs
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
//! 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
//! [`process-wrap`](process_wrap)) 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;