relay_supervisor
An OTP supervisor where data from older children can be used when starting younger children.
For example, if you have a database connection process and a web server process that needs a database connection, you can have the database connection as the older child and have the supervisor pass the connection value to the younger web server child.
import app/database
import app/web_server
import relay_supervisor as relay
pub fn start(credentials: String, hostname: String) -> _ {
// The `new` function takes a callback that is run on the supervisor
// when it starts. It builds up the sequence of children that is to be
// started, and passes state between them
relay.new(fn(children) {
// Add the database process as the first child.
//
// The initial state value of `children` is `Nil`, so `providing` is
// used to pass the database credentials in as the start argument.
//
// The database child's returned start data is a database connection,
// and the `returning` function is used to make that the new state, so
// it can be used by the following children.
let children =
children
|> relay.add(
relay.child(database.template)
|> relay.providing(fn(_state) { credentials })
|> relay.returning(fn(_state, db) { db })
)
// Add the web server as the next child.
//
// It takes the database connection and a hostname in a tuple as its
// argument, so the `providing` function is used again. If only the
// database connection was taken then we could omit this function
// call, as the default behaviour is to use the state as the argument
// to the process' start function.
//
// We don't use the web server's returned start data at all, so we
// don't need the `returning` function.
let children =
children
|> relay.add(
relay.child(web_server.template)
|> relay.providing(fn(db) { #(db, hostname) })
)
children
})
}
A supervisor of this style may be added to the gleam_otp package in
future, depending on how useful and successful this module is found to be
in real-world Gleam code.
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(state)
A recipe for the starting of a child and how it takes and updates the state of the sequence of children it belongs to.
This is used with the add, providing, and returning functions.
pub opaque type ChildBuilder(state_in, state_out)
How the supervisor should react when one of its children terminates.
Unlike some other supervisors, the one-for-one strategy is not supported. This is because the relay supervisor is stateful; If an older child is restarted then the state the previous instance created for its younger siblings may no longer be up-to-date, so they have have to be restarted to provide the new state.
pub type Strategy {
OneForAll
RestForOne
}
Constructors
-
OneForAllIf one child process terminates and is to be restarted, all other child processes are terminated and then all child processes are restarted.
-
RestForOneIf one child process terminates and is to be restarted, the ‘rest’ of the child processes (that is, the child processes after the terminated child process in the start order) are terminated. Then the terminated child process and all child processes after it are restarted.
A reference to the running supervisor. In future this could be used to send commands to the supervisor to perform certain actions, but today no such APIs have been exposed.
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
A template used by the supervisor to start a child process.
pub type Template(argument, return) {
Template(
start: fn(argument) -> Result(
actor.Started(return),
actor.StartError,
),
child_type: supervision.ChildType,
)
}
Constructors
-
Template( start: fn(argument) -> Result( actor.Started(return), actor.StartError, ), child_type: supervision.ChildType, )Arguments
- start
-
A function that starts the process.
- child_type
-
Whether the child is a supervisor or not.
Values
pub fn add(
children: Children(state_in),
builder: ChildBuilder(state_in, state_out),
) -> Children(state_out)
Add a child to the sequence of children to start, updating the state for younger children.
pub fn child(
template: Template(state_in, state_out),
) -> ChildBuilder(state_in, state_out)
Prepare a new child from a template, for inclusion in the sequence of children.
pub fn new(
children: fn(Children(Nil)) -> Children(state),
) -> Builder(state)
Create a new supervisor builder, ready for further configuration before
being used to start a new supervisor with the start function.
The function argument is run on the newly created supervisor.
pub fn providing(
builder: ChildBuilder(state_in_1, state_out),
provider: fn(state_in_2) -> state_in_1,
) -> ChildBuilder(state_in_2, state_out)
Construct from the children state the argument used to start this child.
The callback will run on the supervisor immediately before each time this child is started and restarted.
pub fn restart_tolerance(
builder: Builder(state),
intensity intensity: Int,
period period: Int,
) -> Builder(state)
To prevent a supervisor from getting into an infinite loop of child process terminations and restarts, supervisors have a maximum restart tolerance.
Intensity is the maximum number of restarts permitted, and period is the number of seconds the intensity is tracked within.
If more than intensity restarts occur within period 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 returning(
builder: ChildBuilder(state_in, state_out_1),
state_updater: fn(state_in, state_out_1) -> state_out_2,
) -> ChildBuilder(state_in, state_out_2)
Construct from the old state and the child’s returned state data the new state, to be used when starting younger children of the supervisor.
The callback will run on the supervisor immediately after each time this child is started and restarted.
The most recent state from each child is stored in the memory of the supervisor, so avoid returning large amounts of data if it not required by later children.
pub fn start(
builder: Builder(state),
) -> Result(actor.Started(Supervisor), actor.StartError)
Start a new supervisor process with the configuration and children 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.
If any child fails to start the supervisor first terminates all already started child processes with reason shutdown and then terminate itself and returns an error.
pub fn supervised(
builder: Builder(state),
) -> supervision.ChildSpecification(Supervisor)
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 this to starting unsupervised supervisors with the
start function.
If any child fails to start the supervisor first terminates all already started child processes with reason shutdown and then terminate itself and returns an error.