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
//! [`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;