gleam/otp/factory_supervisor
A supervisor where child processes are started dynamically from a pre-specified template, so new processes can be created as needed while the program is running.
When the supervisor is shut down it shuts down all its children concurrently and in no specified order.
For further detail see the Erlang documentation, particularly the parts
about the simple_one_for_one
restart strategy, which is the Erlang
equivilent of the factory supervisor.
https://www.erlang.org/doc/apps/stdlib/supervisor.html.
Usage
Add the factory supervisor to your supervision tree using the supervised
function and a name created at the start of the program. The new
function takes a “template function”, which is a function that takes one
argument and starts a linked child process.
You most likely want to give the factory supervisor a name, and to pass that name to any other processes that will want to cause new child processes to be started under the factory supervisor. In this example a web server is used.
import gleam/erlang/process.{type Name}
import gleam/otp/actor.{type StartResult}
import gleam/otp/factory_supervisor as factory
import gleam/otp/static_supervisor as supervisor
import my_app
/// This function starts the application's supervision tree.
///
/// It takes a name as an argument that is used by the factory supervisor.
///
pub fn start_supervision_tree(reporters_name: Name(_)) -> StartResult(_) {
// Define a named factory supervisor that can create new child processes
// using the `my_app.start_reporter_actor` function, which is defined
// elsewhere in the program.
let reporter_factory_supervisor =
factory.worker_child(my_app.start_reporter_actor)
|> factory.named(reporters_name)
|> factory.supervised
// This web server process takes the name, so it can contact the factory
// supervisor to command it to start new processes as needed.
let web_server = my_app.supervised_web_server(reporters_name)
// Create the top-level static supervisor with the supervisor and web
// server as its children
supervisor.new(supervisor.RestForOne)
|> supervisor.add(reporter_factory_supervisor)
|> supervisor.add(web_server)
|> supervisor.start
}
Any process with the name of the factory supervisor can use the
get_by_name
function to get a reference to the supervisor, and then use
the start_child
function to have it start new child processes.
Remember! Each process name created with process.new_name
is unique.
Two names created by calling the function twice are different names, even
if the same string is given as an argument. You must create the name value
at the start of your program and then pass it down into application code
and library code that uses names.
import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/otp/factory_supervisor
import my_app
/// In our example this function is called each time a HTTP request is
/// received by the web server.
pub fn handle_request(req: Request(_), reporters: Name(_)) -> Response(_) {
// Get a reference to the supervisor using the name
let supervisor = factory_supervisor.get_by_name(reporters)
// Start a new child process under the supervisor, passing the request path
// to use as the argument for the child-starting template function.
let start_result = factory_supervisor.start_child(supervisor, request.path)
// A response is sent to the HTTP client.
// The child starting template function returns a result, with the error case
// being used when children fail to start. Because of this the `start_child`
// function also returns a result, so it must be handled too.
case start_result {
Ok(_) -> response.new(200)
Error(_) -> response.new(500)
}
}
Types
A builder for configuring and starting a supervisor. See each of the functions that take this type for details of the configuration possible.
pub opaque type Builder(child_argument, child_data)
The message type of a factory supervisor. This message type is not used directly, but if you are using a name with a factory supervisor then this will be the message type of the name.
pub type Message(child_argument, child_data)
A reference to the running supervisor
This supervisor wrap Erlang/OTP’s supervisor
module, and as such it does
not use subjects for message sending. If it was implemented in Gleam a
subject might be used instead of this type.
pub opaque type Supervisor(child_argument, child_data)
Values
pub fn get_by_name(
name: process.Name(Message(child_argument, child_data)),
) -> Supervisor(child_argument, child_data)
Get a reference to a supervisor using its registered name.
If no supervisor has been started using this name then functions using this reference will fail.
Panics
Functions using the Supervisor
reference returned by this function
will panic if there is no factory supervisor registered with the name
when they are called. Always make sure your supervisors are themselves
supervised.
pub fn named(
builder: Builder(child_argument, child_data),
name: process.Name(Message(child_argument, child_data)),
) -> Builder(child_argument, child_data)
Provide a name for the supervisor to be registered with when started, enabling it be more easily contacted by other processes. This is useful for enabling processes that can take over from an older one that has exited due to a failure.
If the name is already registered to another process then the factory supervisor will fail to start.
pub fn restart_strategy(
builder: Builder(argument, data),
restart_strategy: supervision.Restart,
) -> Builder(argument, data)
Configure the strategy for restarting children when they exit. See the
documentation for the supervision.Restart
for details.
If not set the default strategy is supervision.Transient
, so children
will be restarted if they terminate abnormally.
pub fn restart_tolerance(
builder: Builder(child_argument, child_data),
intensity intensity: Int,
period period: Int,
) -> Builder(child_argument, child_data)
To prevent a supervisor from getting into an infinite loop of child process terminations and restarts, a maximum restart tolerance is defined using two integer values specified with keys intensity and period in the above map. Assuming the values MaxR for intensity and MaxT for period, then, if more than MaxR restarts occur within MaxT seconds, the supervisor terminates all child processes and then itself. The termination reason for the supervisor itself in that case will be shutdown.
Intensity defaults to 2 and period defaults to 5.
pub fn start(
builder: Builder(child_argument, child_data),
) -> Result(
actor.Started(Supervisor(child_argument, child_data)),
actor.StartError,
)
Start a new supervisor process with the configuration and child template specified within the builder.
Typically you would use the supervised
function to add your supervisor to
a supervision tree instead of using this function directly.
The supervisor will be linked to the parent process that calls this function.
pub fn start_child(
supervisor: Supervisor(child_argument, child_data),
argument: child_argument,
) -> Result(actor.Started(child_data), actor.StartError)
Start a new child using the supervisor’s child template and the given argument. The start result of the child is returned.
pub fn supervised(
builder: Builder(child_argument, child_data),
) -> supervision.ChildSpecification(
Supervisor(child_argument, child_data),
)
Create a ChildSpecification
that adds this supervisor as the child of
another, making it fault tolerant and part of the application’s supervision
tree. You should prefer to starting unsupervised supervisors with the
start
function.
If any child fails to start the supevisor first terminates all already started child processes with reason shutdown and then terminate itself and returns an error.
pub fn supervisor_child(
template: fn(child_argument) -> Result(
actor.Started(child_data),
actor.StartError,
),
) -> Builder(child_argument, child_data)
Configure a supervisor with a template that will start children that are also supervisors.
You should only use this if the child processes are also supervisors.
Supervisor children have an unlimited amount of time to shutdown, there is no timeout.
pub fn timeout(
builder: Builder(argument, data),
ms ms: Int,
) -> Builder(argument, data)
Configure the amount of milliseconds a child has to shut down before being brutal killed by the supervisor.
If not set the default for a child is 5000ms.
This will be ignored if the child is a supervisor itself.
pub fn worker_child(
template: fn(child_argument) -> Result(
actor.Started(child_data),
actor.StartError,
),
) -> Builder(child_argument, child_data)
Configure a supervisor with a child-starting template function.
You should use this unless the child processes are also supervisors.
The default shutdown timeout is 5000ms. This can be changed with the
timeout
function.