watchexec_supervisor/
lib.rs

1//! Watchexec's process supervisor.
2//!
3//! This crate implements the process supervisor for Watchexec. It is responsible for spawning and
4//! managing processes, and for sending events to them.
5//!
6//! You may use this crate to implement your own process supervisor, but keep in mind its direction
7//! will always primarily be driven by the needs of Watchexec itself.
8//!
9//! # Usage
10//!
11//! There is no struct or implementation of a single supervisor, as the particular needs of the
12//! application will dictate how that is designed. Instead, this crate provides a [`Job`](job::Job)
13//! construct, which is a handle to a single [`Command`](command::Command), and manages its
14//! lifecycle. The `Job` API has been modeled after the `systemctl` set of commands for service
15//! control, with operations for starting, stopping, restarting, sending signals, waiting for the
16//! process to complete, etc.
17//!
18//! There are also methods for running hooks within the job's runtime task, and for handling errors.
19//!
20//! # Theory of Operation
21//!
22//! A [`Job`](job::Job) is, properly speaking, a handle which lets one control a Tokio task. That
23//! task is spawned on the Tokio runtime, and so runs in the background. A `Job` takes as input a
24//! [`Command`](command::Command), which describes how to start a single process, through either a
25//! shell command or a direct executable invocation, and if the process should be grouped (using
26//! [`process-wrap`](process_wrap)) or not.
27//!
28//! The job's task runs an event loop on two sources: the process's `wait()` (i.e. when the process
29//! ends) and the job's control queue. The control queue is a hybrid MPSC queue, with three priority
30//! levels and a timer. When the timer is active, the lowest ("Normal") priority queue is disabled.
31//! This is an internal detail which serves to implement graceful stops and restarts. The internals
32//! of the job's task are not available to the API user, actions and queries are performed by
33//! sending messages on this control queue.
34//!
35//! The control queue is executed in priority and in order within priorities. Sending a control to
36//! the task returns a [`Ticket`](job::Ticket), which is a future that resolves when the control has
37//! been processed. Dropping the ticket will not cancel the control. This provides two complementary
38//! ways to orchestrate actions: queueing controls in the desired order if there is no need for
39//! branching flow or for signaling, and sending controls or performing other actions after awaiting
40//! tickets.
41//!
42//! Do note that both of these can be used together. There is no need for the below pattern:
43//!
44//! ```no_run
45//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only
46//! # use std::sync::Arc;
47//! # use watchexec_supervisor::Signal;
48//! # use watchexec_supervisor::command::{Command, Program};
49//! # use watchexec_supervisor::job::{CommandState, start_job};
50//! #
51//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() }));
52//! #
53//! job.start().await;
54//! job.signal(Signal::User1).await;
55//! job.stop().await;
56//! # task.abort();
57//! # }
58//! ```
59//!
60//! Because of ordering, it behaves the same as this:
61//!
62//! ```no_run
63//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only
64//! # use std::sync::Arc;
65//! # use watchexec_supervisor::Signal;
66//! # use watchexec_supervisor::command::{Command, Program};
67//! # use watchexec_supervisor::job::{CommandState, start_job};
68//! #
69//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() }));
70//! #
71//! job.start();
72//! job.signal(Signal::User1);
73//! job.stop().await; // here, all of start(), signal(), and stop() will have run in order
74//! # task.abort();
75//! # }
76//! ```
77//!
78//! However, this is a different program:
79//!
80//! ```no_run
81//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only
82//! # use std::sync::Arc;
83//! # use std::time::Duration;
84//! # use tokio::time::sleep;
85//! # use watchexec_supervisor::Signal;
86//! # use watchexec_supervisor::command::{Command, Program};
87//! # use watchexec_supervisor::job::{CommandState, start_job};
88//! #
89//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() }));
90//! #
91//! job.start().await;
92//! println!("program started!");
93//! sleep(Duration::from_secs(5)).await; // wait until program is fully started
94//!
95//! job.signal(Signal::User1).await;
96//! sleep(Duration::from_millis(150)).await; // wait until program has dumped stats
97//! println!("program stats dumped via USR1 signal!");
98//!
99//! job.stop().await;
100//! println!("program stopped");
101//! #
102//! # task.abort();
103//! # }
104//! ```
105//!
106//! # Example
107//!
108//! ```no_run
109//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only
110//! # use std::sync::Arc;
111//! use watchexec_supervisor::Signal;
112//! use watchexec_supervisor::command::{Command, Program};
113//! use watchexec_supervisor::job::{CommandState, start_job};
114//!
115//! let (job, task) = start_job(Arc::new(Command {
116//!     program: Program::Exec {
117//!         prog: "/bin/date".into(),
118//!         args: Vec::new(),
119//!     }.into(),
120//!     options: Default::default(),
121//! }));
122//!
123//! job.start().await;
124//! job.signal(Signal::User1).await;
125//! job.stop().await;
126//!
127//! job.delete_now().await;
128//!
129//! task.await; // make sure the task is fully cleaned up
130//! # }
131//! ```
132
133#![doc(html_favicon_url = "https://watchexec.github.io/logo:watchexec.svg")]
134#![doc(html_logo_url = "https://watchexec.github.io/logo:watchexec.svg")]
135#![warn(clippy::unwrap_used, missing_docs, rustdoc::unescaped_backticks)]
136#![cfg_attr(not(test), warn(unused_crate_dependencies))]
137#![deny(rust_2018_idioms)]
138
139#[doc(no_inline)]
140pub use watchexec_events::ProcessEnd;
141#[doc(no_inline)]
142pub use watchexec_signals::Signal;
143
144pub mod command;
145pub mod errors;
146pub mod job;
147
148mod flag;