saluki_core/runtime/mod.rs
1//! Runtime system.
2//!
3//! This module contains the core components of the runtime system, including supervisors and processes. It's directly
4//! inspired by [Erlang/OTP](https://www.erlang.org/docs/28/system/design_principles#supervision-trees).
5//!
6//! To quote the Erlang/OTP documentation:
7//!
8//! > Workers are processes that perform computations and other actual work. Supervisors are processes that monitor
9//! > workers. A supervisor can restart a worker if something goes wrong. The supervision tree is a hierarchical
10//! > arrangement of code into supervisors and workers, which makes it possible to design and program fault-tolerant
11//! > software.
12//!
13//! # Processes
14//!
15//! An asynchronous system is composed of independent units of computation running concurrently, such as a set of tasks
16//! executing on a thread pool. We refer to these as **processes.** In other systems, these might be called _actors_,
17//! _tasks_, _fibers_, _virtual threads_, _goroutines_, or something else. Processes are lightweight and able to be
18//! (generally) created and destroyed cheaply.
19//!
20//! Processes have a few key attributes and invariants:
21//!
22//! - every process is a future that runs as an independent asynchronous task on a Tokio runtime
23//! - every process has a unique numerical identifier and a semi-unique name
24//!
25//! Unlike Erlang processes, Saluki processes don't have an inherent mailbox or message passing capabilities. As well,
26//! processes can't run by themselves. They must be _supervised_.
27//!
28//! # Supervisors
29//!
30//! Supervisors are themselves processes whose only job is to _supervise_ other processes, also called _workers_. In a
31//! supervisor, workers are added and configured through a common convention that allows defining how the worker is
32//! created (or recreated on failure), how many times it can be restarted, and more. Supervisors themselves can also be
33//! workers, and so nested _supervision trees_ can be constructed.
34//!
35//! Supervisors include a number of configurable settings that allow customizing the behavior of how workers are
36//! managed, which in turn allows building fault-tolerant systems: we can restart workers for transient failures, give
37//! up for permanent failures, and so on.
38//!
39//! # Spawning children
40//!
41//! Usually, you declare children in advance with [`Supervisor::add_worker`]. A process can also start new children
42//! while it runs. There are three ways to do this. They differ in the owner of the new child:
43//!
44//! - [`spawn`], which makes the new child a child of the current process. It is the equivalent of [`tokio::spawn`] for
45//! supervision. It is the recommended way to spawn for code that runs under supervision.
46//! - [`spawn_sibling`], which gives the new child to the nearest supervisor above the current process. The child is a
47//! sibling of the current process only if that supervisor runs the process directly, and not in a scope.
48//! - [`SupervisorHandle`], which names a supervisor explicitly.
49//!
50//! For all three ways, a [`ChildBuilder`] describes the child. To get a builder, use [`worker`] or [`supervisable`].
51//! For a named supervisor, use the methods with the same names on [`SupervisorHandle`]. The builder is the only way to
52//! configure a child. It produces a [`ChildSpecification`], which has no settings of its own. Because of this, you
53//! cannot use the specification to make a combination of settings that the builder does not offer.
54//!
55//! A child that is itself a [`Supervisor`] can be handed to any of the above directly, which is all most callers need.
56//! [`nested_supervisor`] and [`SupervisorHandle::nested_supervisor`] give access to the settings that are not available
57//! that way: whether the subtree is needed or hosted, and whether it stops as soon as its owner is told to stop.
58//!
59//! All three ways to spawn are synchronous and cannot fail. As with [`tokio::spawn`], an accepted child does not always
60//! run. If its owner stops before it reaches the child, the owner never starts the child.
61//!
62//! # Restart policy
63//!
64//! You do not select the restart policy of a child directly. Two properties of the child decide it:
65//!
66//! - Whether it can start again. A child reports this through [`Supervisable::can_restart`]. A [`Supervisable`] builds
67//! its work each time it starts, so it can usually start again. A child that consumes something when it starts is
68//! _one-shot_. Examples are a child built from a closure, which consumes its body, and a [`Supervisable`] that takes
69//! a connection.
70//! - Whether its owner can work without it. The owner of a _needed_ child cannot work without it. The owner of a
71//! _hosted_ child only runs it. See [`ChildBuilder::needed`] and [`ChildBuilder::hosted`].
72//!
73//! Together, these two properties decide what occurs when a child stops without a stop request:
74//!
75//! | | Needed | Hosted |
76//! | --- | --- | --- |
77//! | Can start again | always restarted ([`Permanent`]) | restarted if it failed ([`Transient`]) |
78//! | One-shot | its owner fails | removed, and its owner continues |
79//!
80//! [`Permanent`]: RestartType::Permanent
81//! [`Transient`]: RestartType::Transient
82//!
83//! A child that stops because of a stop request is never restarted. A child of a scope that stops after the owner of
84//! the scope is told to stop counts as stopped by request. If a child stops more often than the restart limit of its
85//! owner allows, the owner fails. This applies to needed and hosted children. The next level of the supervision tree
86//! then handles the failure.
87//!
88//! A child that can start again, and that you declare in advance with [`Supervisor::add_worker`], is needed by default.
89//! This is because it is part of the purpose of its supervisor. Every other child is hosted by default.
90//!
91//! # Scopes
92//!
93//! A supervisor owns its children. Every supervised worker also owns the children that it spawns, and keeps them in its
94//! [scope].
95//!
96//! Each of these children runs as a separate task. The worker does not finish until all of these children finish. When
97//! the body of the worker returns, the scope stops them, each according to its own shutdown strategy, as a supervisor
98//! does.
99//!
100//! A value can also own a scope, so that the background tasks of a primitive stop when the primitive stops. See
101//! [`scope::nested`]. You can also create a scope before the process that will own it exists, with [`UnadoptedScope`].
102//! This lets work spawned during the construction of something belong to the process that later runs that thing.
103//!
104//! # Where to spawn a child
105//!
106//! Spawn most children into the scope of the current process with [`spawn`]. Use a different way only when one of these
107//! conditions is true:
108//!
109//! - A value, not a process, must own the child. Spawn the child into a [`scope::nested`] guard.
110//! - A process other than the one that spawns the child must own it. Get the scope of the owner with
111//! [`scope::current`], and spawn the child with [`ChildBuilder::spawn_in`].
112//! - The child must continue to run after restarts of the process that spawns it. Make both of them children of a
113//! [`Supervisor`], and spawn the child through its [`SupervisorHandle`].
114//! - Children need their own restart strategy, resource group, or runtime. A scope restarts its children at most once
115//! in each five-second period, for all of its children together. Put these children under a [`Supervisor`].
116//!
117//! If a worker body only waits for a stop request while its children do the work, use a [`Supervisor`] instead.
118//!
119//! # Supervision trees
120//!
121//! As supervisors can be nested, this allows building a tree of supervisors (hence _supervision trees_) where leaf
122//! supervisors manage workers specific to a certain area, and parent supervisors manage the leaf supervisors. For
123//! example, for a server application serving multiple API endpoints, each endpoint might be managed by a separate
124//! supervisor: a worker for accepting connections, a worker for each connection, and so on. Above those supervisors, a
125//! parent supervisor manages each leaf supervisor, and potentially other workers that provide necessary services
126//! utilized by each endpoint, such as logging, metrics, or other infrastructure services.
127//!
128//! As every supervisor can define its own specific restart strategy, and behavior, this allows for more granular
129//! grouping and control over which set of workers must be restarted if a related worker fails, and how those failures
130//! propagate up and down the supervision tree.
131//!
132//! # Inspecting the tree
133//!
134//! A supervision tree is also a description of what a process is doing, which makes it worth being able to read at
135//! runtime. [`Supervisor::tree_handle`] returns a read-only handle whose [`snapshot`][SupervisionTreeHandle::snapshot]
136//! walks the tree from that supervisor downward, reporting each node's name, process, restart policy, restart count,
137//! lifetime, and resource usage, with its children nested beneath it. A handle can be taken before the supervisor
138//! starts and stays valid across every restart of it, which makes it suitable for handing to a worker that serves an
139//! operator-facing diagnostics endpoint.
140//!
141//! # Examples
142//!
143//! See the `basic_supervisor` example which shows how supervisors and workers are composed together, as well as how
144//! failed workers and supervisors are restarted.
145
146mod process;
147pub use self::process::get_sanitized_name;
148pub use self::process::Id as ProcessId;
149#[cfg(test)]
150pub(crate) use self::process::Name;
151
152pub mod state;
153
154mod dedicated;
155pub use self::dedicated::{RuntimeConfiguration, RuntimeMode};
156
157pub(crate) mod halt;
158
159mod restart;
160pub use self::restart::{RestartMode, RestartStrategy, RestartType};
161
162mod supervisable;
163pub use self::supervisable::{InitializationError, ShutdownStrategy, Supervisable, SupervisorFuture};
164
165mod supervisor;
166#[cfg(any(test, feature = "test-util"))]
167pub(crate) use self::supervisor::ExitObserver;
168pub use self::supervisor::{
169 AutoShutdown, ChildId, ChildSpecification, ChildState, LoweredChild, Supervisor, SupervisorError, SupervisorHandle,
170 SupervisorSpec, WorkerSpec,
171};
172
173mod tree;
174pub use self::tree::{
175 NodeKind, NodeSnapshot, NodeState, ResourceUsage, SupervisionSettings, SupervisionTreeAPIHandler,
176 SupervisionTreeHandle, SupervisionTreeState, SupervisionTreeWorker, TreeSnapshot, TreeTotals, UnixMillis,
177 SUPERVISION_TREE_ROUTE,
178};
179
180mod spawn;
181pub use self::spawn::{spawn, spawn_sibling};
182
183pub mod scope;
184pub use self::scope::{Scope, ScopeError, ScopeGuard, UnadoptedScope};
185
186mod builder;
187pub use self::builder::{
188 nested_supervisor, supervisable, worker, worker_with_shutdown, BuilderState, ChildBuilder, NestedSupervisorBuilder,
189 OneShot, Restartable,
190};
191
192mod workers;
193pub use self::workers::{FnWorker, IntoWorkerResult};
194
195mod worker_state;
196
197#[cfg(any(test, feature = "test-util"))]
198pub mod test_util;