5 stable releases
3.0.0 | Oct 14, 2024 |
---|---|
2.0.0 | Apr 20, 2024 |
1.0.3 | Dec 19, 2023 |
1.0.1 | Nov 29, 2023 |
#45 in macOS and iOS APIs
36,733 downloads per month
Used in 12 crates
(2 directly)
135KB
3K
SLoC
Supervisor
Watchexec's process supervisor.
- API documentation.
- Licensed under Apache 2.0.
- Status: maintained.
lib.rs
:
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
construct, which is a handle to a single 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
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
, 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
) 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
, 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:
#
#
job.start().await;
job.signal(Signal::User1).await;
job.stop().await;
Because of ordering, it behaves the same as this:
#
#
job.start();
job.signal(Signal::User1);
job.stop().await; // here, all of start(), signal(), and stop() will have run in order
However, this is a different program:
#
#
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");
#
Example
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
Dependencies
~6–15MB
~180K SLoC