Parent (parent v0.12.1) View Source
Functions for implementing a parent process.
A parent process is a process that manages the lifecycle of its children. Typically the simplest
approach is to use higher-level abstractions, such as Parent.Supervisor
or Parent.GenServer
.
The common behaviour for every parent process is implemented in this module, and therefore it is
described in this document.
Overview
A parent process has the following properties:
- It traps exits and uses the
shutdown: :infinity
shutdown strategy. - It keeps track of its children.
- It presents itself to the rest of the OTP as a supervisor, which means that generic code walking the supervision tree, such as OTP release handler, will also iterate the parent's subtree.
- Before terminating, it stops its children synchronously, in the reverse startup order.
You can interact with the parent process from other processes using functions from the
Parent.Client
module. If you want to manipulate the parent from the inside, you can use the
functions from this module.
Initialization
A parent process has to be initialized using initialize/1
. This function takes the following
initialization options:
:max_restarts
and:max_seconds
- same as withSupervisor
, with the same defaults:registry?
- If true, the parent will manage its own ETS-based child registry. See the "Child discovery" section for details.
When using higher-level abstractions, these options are typically passed throguh start functions,
such as Parent.Supervisor.start_link/2
.
Child specification
Child specification describes how the parent starts and manages a child. This specification is
passed to functions such as start_child/2
, Parent.Client.start_child/2
, or
Parent.Supervisor.start_link/2
to start a child process.
The specification is a map which is a superset of the Supervisor child
specifications. All the
fields that are shared with Supervisor
have the same effect.
It's worth noting that the :id
field is optional. If not provided, the child will be anonymous,
and you can only manage it via its pid. Therefore, the minimum required child specification
is %{start: mfa_or_zero_arity_fun}
.
Also, just like with Supervisor
, you can provide module | {module, arg}
when starting a
child. See Supervisor.child_spec/1
for details.
To modify a child specification, Parent.child_spec/2
can be used.
Bound children
You can bind the lifecycle of each child to the lifecycles of its older siblings. This is roughly
similar to the :rest_for_one
supervisor strategy.
For example, if you want to start two children, a consumer and a producer, and bind the producer's lifecycle to the consumer, you need the following child specifications:
consumer_spec = %{
id: :consumer,
# ...
}
producer_spec = %{
id: :producer,
binds_to: [:consumer]
}
This will make sure that if the consumer stops, the producer is taken down as well.
For this to work, you need to start the consumer before the producer. In other words, a child can only be bound to its older siblings.
It's worth noting that bindings are transitive. If a child A is bound to the child B, which is in turns bound to child C, then child A also depends on child C. If child C stops, B and A will be stopped to.
Shutdown groups
A shutdown group is a mechanism that roughly emulates the :one_for_all
supervisor strategy.
For example, to set up a two-way lifecycle dependency between the consumer and the producer, we
can use the following specifications:
consumer_spec = %{
id: :consumer,
shutdown_group: :consumer_and_producer
# ...
}
producer_spec = %{
id: :producer,
shutdown_group: :consumer_and_producer
}
In this case, when any child of the group terminates, the other children will be taken down as
well. All children belonging to the same shutdown group must use the same :restart
and
:ephemeral
settings.
Note that a child can be a member of some shutdown group, and at the same time bound to other older siblings.
Lifecycle dependency consequences
A lifecycle dependency means that a child is taken down when its dependency stops. This will
happen irrespective of how the child has been stopped. Even if you manually stop the child using
functions such as shutdown_child/1
or Parent.Client.shutdown_child/2
, the siblings bound to
it will be taken down.
In general, parent doesn't permit the state which violates the binding settings. If the process A is bound to the process B, parent will not allow A to keep running if B stops.
Handling child termination
When a child terminates, depending on its :restart
and :ephemeral
settings, parent will do
one of the following things:
- restart the child (possibly giving up if restart limit has been exceeded)
- set the child's pid to
:undefined
- remove the child from its internal structures
The :restart
option controls when a child will be automatically restarted by its parent:
:permanent
- A child is automatically restarted if it stops. This is the default value.:transient
- A child is automatically restarted only if it exits abnormally.:temporary
- A child is not automatically restarted.
The :ephemeral
option controls what to do when a non-running child is will not be restarted.
Such situation can happen when a temporary child terminates, when a transient child stops
normally, or if the child's start function returns :ignore
.
If the child is not marked as ephemeral (default), parent will keep the child in its internal
structures, setting its pid to :undefined
. Functions such as Parent.children/0
will include
the non-running child in the result, and such child can be restarted using
Parent.restart_child/1
. This mimics the behaviour of "static supervisors" (i.e. one_for_one,
rest_for_one, one_for_all).
If the child is marked as ephemeral, parent will remove the child from its internal structures.
This mimics the behaviour of DynamicSupervisor
.
In all of these cases, parent will perform the same action on bound siblings, ignoring their
:restart
and :ephemeral
settings. For example, if a permanent child is restarted, parent
will restart all of its bound siblings (including the temporary ones). Likewise, if a temporary
child stops, parent will stop all of its bound siblings, including the permanent ones. If an
ephemeral child stops, parent will remove all of its bound siblings, including the non-ephemeral
ones.
If a child is not restarted, its ephemeral bound siblings will be removed. This is the only case
where parent honors the :ephemeral
status of bound siblings.
Restart flow
Process restarts can be triggered by the following situations:
- a child terminated and it will be restarted due to its
:restart
setting restart_child/1
has been invoked (manual restart)return_children/1
has been invoked (returning removed children)
In all these situations the flow is the same. Parent will first synchronously stop the bound dependencies of the child (in the reverse startup order). Then it will attempt to restart the child and its siblings. This is done by starting processes synchronously, one by one, in the startup order. If all processes are started successfully, restart has succeeded.
If some process fails to start, the parent treats it as a crash. It will take down all the bound siblings, while proceeding to start other children which are not depending on the failed process.
Therefore, a restart may partially succeed, with some children not being started. In this case, the parent will retry to restart such children, according to their specification. Temporary children which fail to restart will be considered as stopped, and parent will not attempt to restart them again.
A failed attempt to restart a child is considered as a crash and contributes to the restart intensity. Thus, if a child repeatedly fails to restart, the parent will give up at some point, according to restart intensity settings.
The restarted children keep their original startup order with respect to non-restarted children. For example, suppose that four children are running: A, B, C, and D, and children B and D are restarted. If the parent process then stops, it will take the children down in the order D, C, B, and A.
Maximum restart frequency
Similarly to Supervisor
, a parent process keeps track of the amount of restarts, and
self-terminates if maximum threshold (defaults to 3 restarts in 5 seconds) is exceeded.
In addition, you can provide child specific thresholds by including :max_restarts
and
:max_seconds
options in child specification. Note that :max_restarts
can be set to
:infinity
(both for the parent and each child). This can be useful if you want to disable the
parent global limit, and use child-specific limits.
Finally, it's worth noting that if termination of one child causes the restart of multiple children, parent will treat this as a single restart event when calculating the restart frequency and considering possible self-termination.
Child timeout
You can optionally include the :timeout
option in the child specification to ask the parent to
terminate the child if it doesn't stop in the given time. The child will be terminated according
to its shutdown specification. In the case of non-forceful termination, the :timeout
exit
signal will be used.
A non-temporary child which timeouts will be restarted.
Child discovery
Children can be discovered by other processes using functions such as Parent.Client.child_pid/2
,
or Parent.Client.children/1
. By default, these functions will perform a synchronous call into
the parent process. This should work fine as long as the parent is not pressured by various
events, such as frequent children stopping and starting, or some other custom logic.
In such cases you can consider setting the registry?
option to true
when initializing the
parent process. When this option is set, parent will create an ETS table which will be used by
the discovery functions.
In addition, parent supports maintaining the child-specific meta information. You can set this
information by providing the :meta
field in the child specification, update it through
functions such as update_child_meta/2
or Parent.Client.update_child_meta/3
, and query it
through Parent.Client.child_meta/2
.
Building custom parent processes
If available parent behaviours don't fit your purposes, you can consider building your own behaviour or a concrete parent process. In this case, the functions of this module will provide the necessary plumbing.
The basic idea is presented in the following sketch:
defp init_process do
Parent.initialize(parent_opts)
start_some_children()
loop()
end
defp loop() do
receive do
msg ->
case Parent.handle_message(msg) do
# parent handled the message
:ignore -> loop()
# parent handled the message and returned some useful information
{:stopped_children, stopped_children} -> handle_stopped_children(stopped_children)
# not a parent message
nil -> custom_handle_message(msg)
end
end
end
More specifically, to build a parent process you need to do the following:
- Invoke
initialize/0
when the process is started. - Use functions such as
start_child/2
to work with child processes. - When a message is received, invoke
handle_message/1
before handling the message yourself. - If you receive a shutdown exit message from your parent, stop the process.
- Before terminating, invoke
shutdown_all/1
to stop all the children. - Use
:infinity
as the shutdown strategy for the parent process, and:supervisor
for its type. - If the process is a
GenServer
, handle supervisor calls (seesupervisor_which_children/0
andsupervisor_count_children/0
). - Implement
format_status/2
(seeParent.GenServer
for details) where applicable.
If the parent process is powered by a non-interactive code (e.g. Task
), make sure
to receive messages sent to that process, and handle them properly (see points 3 and 4).
You can take a look at the code of Parent.GenServer
for specific details.
Link to this section Summary
Functions
Returns true if the child process is still running, false otherwise.
Returns the id of a child process with the given pid.
Returns the meta associated with the given child id.
Returns the pid of a child process with the given id.
Builds and overrides a child specification
Returns the list of running child processes in the startup order.
Should be invoked by the parent process for each incoming message.
Initializes the state of the parent process.
Returns true if the parent state is initialized.
Returns the count of children.
Restarts the child and its siblings.
Starts new instances of stopped children.
Terminates all running child processes.
Shuts down the child and all siblings depending on it, and removes them from the parent state.
Synchronously starts all children.
Starts the child described by the specification.
Should be invoked by the behaviour when handling :count_children
GenServer call.
Should be invoked by the behaviour when handling :get_childspec
GenServer call.
Should be invoked by the behaviour when handling :which_children
GenServer call.
Updates the meta of the given child process.
Link to this section Types
Specs
child() :: %{id: child_id(), pid: pid() | :undefined, meta: child_meta()}
Specs
child_id() :: term()
Specs
child_meta() :: term()
Specs
Specs
child_spec() :: %{ :start => start(), optional(:id) => child_id(), optional(:modules) => [module()] | :dynamic, optional(:type) => :worker | :supervisor, optional(:meta) => child_meta(), optional(:shutdown) => shutdown(), optional(:timeout) => pos_integer() | :infinity, optional(:restart) => :temporary | :transient | :permanent, optional(:max_restarts) => non_neg_integer() | :infinity, optional(:max_seconds) => pos_integer(), optional(:binds_to) => [child_ref()], optional(:shutdown_group) => shutdown_group(), optional(:ephemeral?) => boolean() }
Specs
handle_message_response() :: {:stopped_children, stopped_children()} | :ignore
Specs
on_start_child() :: Supervisor.on_start_child() | {:error, start_error()}
Specs
option() :: {:max_restarts, non_neg_integer() | :infinity} | {:max_seconds, pos_integer()} | {:registry?, boolean()}
Specs
opts() :: [option()]
Specs
shutdown() :: non_neg_integer() | :infinity | :brutal_kill
Specs
shutdown_group() :: term()
Specs
start() :: (() -> Supervisor.on_start_child()) | {module(), atom(), [term()]}
Specs
start_error() :: :invalid_child_id | {:missing_deps, [child_ref()]} | {:non_uniform_shutdown_group, [shutdown_group()]}
Specs
start_spec() :: child_spec() | module() | {module(), term()}
Specs
Link to this section Functions
Specs
Returns true if the child process is still running, false otherwise.
Note that this function might return true even if the child has terminated. This can happen if
the corresponding :EXIT
message still hasn't been processed, and also if a non-ephemeral
child is not running.
Specs
Returns the id of a child process with the given pid.
Specs
child_meta(child_ref()) :: {:ok, child_meta()} | :error
Returns the meta associated with the given child id.
Specs
Returns the pid of a child process with the given id.
Specs
child_spec(start_spec(), Keyword.t() | child_spec()) :: child_spec()
Builds and overrides a child specification
This operation is similar to Supervisor.child_spec/1
Specs
children() :: [child()]
Returns the list of running child processes in the startup order.
Specs
handle_message(term()) :: handle_message_response() | nil
Should be invoked by the parent process for each incoming message.
If the given message is not handled, this function returns nil
. In such cases, the client code
should perform standard message handling. Otherwise, the message has been handled by the parent,
and the client code shouldn't treat this message as a standard message (e.g. by calling
handle_info
of the callback module).
If :ignore
is returned, the message has been processed, and the client code should ignore it.
Finally, if the return value is {:stopped_children, info}
, it indicates that some ephemeral
processes have stopped and have been removed from parent. A client may do some extra processing
in this case.
Note that you don't need to invoke this function in a Parent.GenServer
callback module.
Specs
initialize(opts()) :: :ok
Initializes the state of the parent process.
This function should be invoked once inside the parent process before other functions from this
module are used. If a parent behaviour, such as Parent.GenServer
, is used, this function must
not be invoked.
Specs
initialized?() :: boolean()
Returns true if the parent state is initialized.
Specs
num_children() :: non_neg_integer()
Returns the count of children.
Specs
parent_spec(Keyword.t() | child_spec()) :: child_spec()
Specs
restart_child(child_ref()) :: :ok | :error
Restarts the child and its siblings.
See "Restart flow" for details on restarting procedure.
Specs
return_children(stopped_children()) :: :ok
Starts new instances of stopped children.
This function can be invoked to return stopped children back to the parent. Essentially, this function works the same as automatic restarts.
The stopped_children
information is obtained via functions such as shutdown_child/1
or
shutdown_all/1
. In addition, Parent will provide this info via handle_message/1
if an
ephemeral child stops and is not restarted.
Specs
shutdown_all(term()) :: stopped_children()
Terminates all running child processes.
Children are terminated synchronously, in the reverse order from the order they have been started
in. All corresponding :EXIT
messages will be pulled from the mailbox.
Specs
shutdown_child(child_ref()) :: {:ok, stopped_children()} | :error
Shuts down the child and all siblings depending on it, and removes them from the parent state.
This function will also shut down all siblings directly and transitively bound to the given child.
The function will wait for the child to terminate, and pull the :EXIT
message from the mailbox.
All terminated children are removed from the parent state. The stopped_children
structure
describes all of these children, and can be used with return_children/1
to manually restart
these processes.
Specs
start_all_children!([start_spec()]) :: [pid() | :undefined]
Synchronously starts all children.
If some child fails to start, all of the children will be taken down and the parent process will exit.
Specs
start_child(start_spec(), Keyword.t()) :: on_start_child()
Starts the child described by the specification.
Specs
supervisor_count_children() :: [ specs: non_neg_integer(), active: non_neg_integer(), supervisors: non_neg_integer(), workers: non_neg_integer() ]
Should be invoked by the behaviour when handling :count_children
GenServer call.
See supervisor_which_children/0
for details.
Specs
supervisor_get_childspec(child_ref()) :: {:ok, child_spec()} | {:error, :not_found}
Should be invoked by the behaviour when handling :get_childspec
GenServer call.
See :supervisor.get_childspec/2
for details.
Specs
Should be invoked by the behaviour when handling :which_children
GenServer call.
You only need to invoke this function if you're implementing a parent process using a behaviour
which forwards GenServer
call messages to the handle_call
callback. In such cases you need
to respond to the client with the result of this function. Note that parent behaviours such as
Parent.GenServer
will do this automatically.
If no translation of GenServer
messages is taking place, i.e. if you're handling all messages
in their original shape, this function will be invoked through handle_message/1
.
Specs
update_child_meta(child_ref(), (child_meta() -> child_meta())) :: :ok | :error
Updates the meta of the given child process.