Elixir/OTP : Basics of Supervisors
Supervisors in Elixir are OTP provided abstractions built on top of processes, designated to supervise and manage other processes. Supervisors provide fault-tolerance, resilience and self healing capabilities for an Elixir application by using lower level techniques such as process linking, process monitoring, spawning and killing processes etc. The main roles that supervisors take on are starting child processes, monitoring these child processes and restarting them on failures, and terminating child processes when certain predefined conditions are met. Processes based OTP abstractions such as GenServers, Agents and Tasks are easily compatible with supervisors and hence can be managed easily as child processes.
In every application, different processes that are related to one particular functionality will be grouped together under a supervisor process. Each of these smaller systems, will in turn be grouped under other high level supervisor processes and this grouping goes all the way up to a top level supervisor process. This depicts a tree-like hierarchical structure called a supervision tree. The application typically begins by starting the top level supervisor process which spawns the lower level supervisor processes, which will in turn spawn other worker processes under it and so on, branching down all the way to the last level of the supervision tree and ultimately getting the whole application to be up and running. When failure happens in any process in the application, the supervisor process, under which the failed process belongs to, will perform spawning and other necessary operations to heal the system and keep it running.
Child specification
For a supervisor process to start and manage child processes, it must first have information about the different child processes that it will be managing. This information is called a child specification, which is a map containing different options and information on how to start, manage, terminate and restart a child process. A supervisor process can be started using the Supervisor.start_link/2
function. A list of child specifications of all the children will be passed into the start_link
function as its first argument, based on which the supervisor process will spawn, terminate and restart each of its child processes. The supervisor also relies on some other common supervisor options passed into the start_link
function as its second argument, which will be discussed in a later section. The spawning of the child processes will happen in the same order of the input child specs list. The child specification map consists of 2 required keys such as :id
and :start
and 5 other optional keys such as :restart
, :shutdown
, :type
, :modules
and :significant
.
- The
:id
key takes in an atom or any other term as value. It is used to uniquely identify a child specification by a supervisor process. The supervisor process will fail to start if it contains more than one child specification with the same id. It typically takes in the name of the module that encapsulates the child process. Please note that child specs with the same id are not allowed only if the two child specs are direct children of a single supervisor. - The
:start
key takes in a 3-element tuple that contains the module name, function name atom and the arguments to be invoked to start the respective child process. Once the supervisor is started, it will start all the child processes by invoking the mfa provided in the:start
key. Please note that, for the supervisor process to monitor the termination of a child process, the invoked:start
key function must create a process link between the supervisor and the spawned child process. If the child process is an OTP abstraction such as GenServer or Agent, the abstraction module’s respectivestart_link
function can be used to start the child process as it internally enables process linking between the caller and the spawned process. The invoked:start
key’s function must return the tuples{:ok, child_pid}
or{:ok, child_pid, info_term}
. The functions can also return:ignore
if the child process need not be started now. If the:start
key’s function returns any other value, then the supervision tree will fail to start and all the child processes that are already started will be terminated.
defmodule ChildAgent do
def start do
IO.puts("starting agent")
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
---------------------------------------------------------------------------
child_spec = %{id: ChildAgent, start: {ChildAgent, :start, []}}
Supervisor.start_link([child_spec], [strategy: :one_for_one])
starting agent
{:ok, #PID<0.116.0>} # supervisor process has started
Process.whereis(ChildAgent)
#PID<0.117.0> # Supervisor has started ChildAgent process using the
# provided child spec's start key
# Note: Supervisor.start_link and its options such as strategy will be
# discussed in detail in a later section of this article
- The
:restart
key takes in one of the three values such as:permanent
,:temporary
and:transient
. These values determine if a child process should be restarted by the supervisor when it terminates. The:permanent
atom is the default value, and when it is used, the supervisor will restart the child process if it terminates. The:temporary
atom value, when used, the supervisor will not restart the child process if it terminates. When a:temporary
child process terminates, its child spec will be deleted from the supervisor. If the value:transient
is used, the child process is restarted only if it terminates abnormally with exit reasons other than:normal
,:shutdown
, and{:shutdown, term}
.
defmodule ChildAgent do
def start do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
---------------------------------------------------------------------------
# restart - :permanent
child_spec = %{id: ChildAgent, start: {ChildAgent, :start, []}}
Supervisor.start_link([child_spec], [strategy: :one_for_one])
Process.whereis(ChildAgent) # Child process started
#PID<0.116.0>
Agent.stop(ChildAgent, :normal) # Child process normal exit
:ok
Process.whereis(ChildAgent) # Child process restarted by supervisor
#PID<0.117.0>
Agent.stop(ChildAgent, :kill) # Child process abnormal exit
14:07:55.748 [error] GenServer ChildAgent terminating
** (stop) :kill
Last message: []
State: nil
:ok
Process.whereis(ChildAgent) # Child process restarted by supervisor
#PID<0.118.0>
---------------------------------------------------------------------------
# restart - :temporary
child_spec = %{id: ChildAgent, start: {ChildAgent, :start, []},
restart: :temporary}
{:ok, sup_pid} = Supervisor.start_link([child_spec], [strategy: :one_for_one])
Process.whereis(ChildAgent) # Child process started
#PID<0.116.0>
Agent.stop(ChildAgent, :normal) # Child process normal exit
:ok
Process.whereis(ChildAgent) # Child process not restarted by supervisor
nil
Supervisor.start_child(sup_pid, child_spec) # start child again manually
{:ok, #PID<0.117.0>}
Agent.stop(ChildAgent, :kill) # Child process abnormal exit
14:07:55.748 [error] GenServer ChildAgent terminating
** (stop) :kill
Last message: []
State: nil
:ok
Process.whereis(ChildAgent) # Child process not restarted by supervisor
nil
----------------------------------------------------------------------------
# restart - :transient
child_spec = %{id: ChildAgent, start: {ChildAgent, :start, []},
restart: :transient}
{:ok, sup_pid} = Supervisor.start_link([child_spec], [strategy: :one_for_one])
Process.whereis(ChildAgent) # Child process started
#PID<0.116.0>
Agent.stop(ChildAgent, :normal) # Child process normal exit
:ok
Process.whereis(ChildAgent) # Child process not restarted by supervisor
nil
Supervisor.restart_child(sup_pid, ChildAgent) # start child again manually
{:ok, #PID<0.117.0>}
Agent.stop(ChildAgent, :kill) # Child process abnormal exit
14:07:55.748 [error] GenServer ChildAgent terminating
** (stop) :kill
Last message: []
State: nil
:ok
Process.whereis(ChildAgent) # Child process restarted by supervisor
#PID<0.118.0>
- The
:type
key takes a value of either:worker
or:supervisor
. It is used to distinguish whether a child process is another supervisor or a normal worker process. The default value is:worker
. - The
:shutdown
key takes an integer value in milliseconds or the atoms:brutal_kill
or:infinity
. The default value is5000
milliseconds for a child process of type:worker
and the atom:infinity
for a child process of type:supervisor
. This value is used when a supervisor, on certain conditions, must manually terminate a child process. If a particular child spec has the value:brutal_kill
, then the supervisor will immediately kill the child using a:kill
exit signal,Process.exit(child, :kill)
. If the child spec has any integer value in milliseconds, then the supervisor will first send a:shutdown
exit signal to the child process usingProcess.exit(child, :shutdown)
and will start a timer for the provided milliseconds. The child process will be allowed to clean up and terminate within the provided time in milliseconds. Once the timer elapses and if the child process is still alive, then it will be abruptly killed usingProcess.exit(child, :kill)
. The value:infinity
is typically used for a supervisor child process to give it indefinite time to properly shut down all its child processes. Only a shutdown signal will be sent to the child process in this case using the function callProcess.exit(child, :shutdown)
, allowing the child process to take indefinite time to clean up and terminate. Please note that the child process must trap exits for it to stay alive and perform clean up during the time provided by the:shutdown
option. If it does not trap exits, it will terminate abruptly right after it receives the first:shutdown
exit signal. - The
:modules
key takes in a list of modules that the child process will depend on. The default value is a list containing the module provided in the:start
key. This list of modules is read and used during the hot code upgrade process. - The
:significant
key takes in a boolean as its value. Its default value isfalse
and only the child processes whose:restart
values are either:temporary
or:transient
can contain the valuetrue
for the:significant
key. This key is used with respect to the common option:auto_shutdown
which will be discussed in a later section.
child_spec/1
In the section above, we have seen how child specification maps of all the child processes are passed into the Supervisor.start_link/2
function. But instead of creating all the child specs in one place before passing them into the Supervisor.start_link/2
function, they can instead be defined within the individual modules associated with the child processes. A callback function called child_spec/1
can be created within the individual modules and the associated child specification map can be defined and returned from within the child_spec/1
function. For these modules that defined the child_spec/1
function, the module name can then be directly passed in to the Supervisor.start_link/2
function instead of the child specification map. The start_link
function will internally call the module.child_spec([])
function to retrieve and use the child specification for a particular child.
defmodule ChildAgent do
def child_spec(_arg) do
%{id: __MODULE__, start: {__MODULE__, :start, []}}
end
def start do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
---------------------------------------------------------------------------
Supervisor.start_link([ChildAgent], [strategy: :one_for_one]) # start_link
# function will call ChildAgent.child_spec([]) to retreive the child spec
Process.whereis(ChildAgent) # Child process started
#PID<0.116.0>
A module associated with a child process may be present under more than one supervisor and may require different child specification maps for different scenarios. In order to support this, the child_spec/1
function may take in a term as argument, based on which the required child specification map can be returned. Instead of just the individual module atom, a 2-element tuple with its first element as the individual module associated with the child process and its second element as the argument to be passed into the child_spec/1
function can be used. The start_link
function will internally pass in the provided argument into the module.child_spec
function call to retrieve the child specification for a child.
defmodule ChildAgent do
def child_spec(arg) do
restart_value = Keyword.get(arg, :restart, :permanent)
%{id: __MODULE__, start: {__MODULE__, :start, []}, restart: restart_value}
end
def start do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
---------------------------------------------------------------------------
children = [{ChildAgent, [restart: :temporary]}]
{:ok, sup_pid} = Supervisor.start_link(children, [strategy: :one_for_one])
# start_link will call ChildAgent.child_spec([restart: :temporary]) to
# retrieve the child spec
Process.whereis(ChildAgent) # Child process started
#PID<0.116.0>
:supervisor.get_childspec(sup_pid, ChildAgent)
{:ok,
%{
id: ChildAgent,
restart: :temporary,
shutdown: 5000,
start: {ChildAgent, :start, []},
type: :worker,
modules: [ChildAgent],
significant: false
}}
For OTP abstractions such as GenServers, Agents and Tasks, the use
macro can be used inside the individual modules to inject a default overridable implementation for the child_spec/1
function. The use
macro also supports options that can be used to customise the child specification inside the default implementation of the child_spec/1
function.
defmodule ChildGenserver do
use GenServer, restart: :temporary, shutdown: 1000
end
# injected child_spec/1 function by the use macro during compilation
defmodule ChildGenserver do
def child_spec(init_arg) do
default = %{ id: __MODULE__, start: {__MODULE__, :start_link, [init_arg]}}
Supervisor.child_spec(default, restart: temporary, shutdown: 1000)
end
end
----------------------------------------------------------------------------
defmodule ChildAgent do
use Agent
end
# injected child_spec/1 function by the use macro during compilation
defmodule ChildAgent do
def child_spec(arg) do
default = %{ id: __MODULE__, start: {__MODULE__, :start_link, [arg]}}
Supervisor.child_spec(default, [])
end
end
-----------------------------------------------------------------------------
defmodule ChildTask do
use Task, significant: true
end
# injected child_spec/1 function by the use macro during compilation
defmodule ChildTask do
def child_spec(arg) do
default = %{ id: __MODULE__,
start: {__MODULE__, :start_link, [arg]},
restart: :temporary
}
Supervisor.child_spec(default, significant: true)
end
end
The Supervisor.child_spec/2
function used above, takes in a child spec map and overrides it with the child spec options present in the keyword list that is passed in as the second argument. Please note that all of the injected default implementations have the function :start_link
and a single argument within the :start
key and hence a start_link/1
must be defined inside the modules for the default implementation to work.
Common supervisor options
So far we have seen about the child specification map and its options. The Supervisor.start_link/2
, other than the list of child specification maps passed in as the first argument, can also take in a keyword list of common supervisor options as its second argument. These common options may include :name
, :strategy
, :max_restarts
, :max_seconds
and :auto_shutdown
. Out of these five options, the :strategy
option is mandatory and the rest of them are optional.
- The
:strategy
option can take in one of these three atom values:one_for_one
,:rest_for_one
and:one_for_all
. These values determine what happens to the other child processes when one of the child processes terminates. When the value:one_for_one
is used, only the terminated child process will be restarted and the other child processes will not be disturbed. The value:one_for_all
terminates all the child processes under the supervisor and restarts all of them when one of the child processes terminates. The value:rest_for_one
restarts the terminated process and all the processes that were spawned after the terminated process.
defmodule ChildAgent1 do
use Agent
def start_link(_) do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
defmodule ChildAgent2 do
use Agent
def start_link(_) do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
defmodule ChildAgent3 do
use Agent
def start_link(_) do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
---------------------------------------------------------------------------
Supervisor.start_link([ChildAgent1, ChildAgent2], [strategy: :one_for_one])
Process.whereis(ChildAgent1)
#PID<0.115.0>
Process.whereis(ChildAgent2)
#PID<0.116.0>
Agent.stop(ChildAgent1)
:ok
Process.whereis(ChildAgent1) # restarted
#PID<0.117.0>
Process.whereis(ChildAgent2) # not restarted
#PID<0.116.0>
---------------------------------------------------------------------------
Supervisor.start_link([ChildAgent1, ChildAgent2], [strategy: :one_for_all])
Process.whereis(ChildAgent1)
#PID<0.115.0>
Process.whereis(ChildAgent2)
#PID<0.116.0>
Agent.stop(ChildAgent1)
:ok
Process.whereis(ChildAgent1) # restarted
#PID<0.117.0>
Process.whereis(ChildAgent2) # restarted
#PID<0.118.0>
---------------------------------------------------------------------------
opts = [strategy: :rest_for_one]
Supervisor.start_link([ChildAgent1, ChildAgent2, ChildAgent3], opts)
Process.whereis(ChildAgent1)
#PID<0.115.0>
Process.whereis(ChildAgent2)
#PID<0.116.0>
Process.whereis(ChildAgent3)
#PID<0.117.0>
Agent.stop(ChildAgent2)
:ok
Process.whereis(ChildAgent1) # not restarted
#PID<0.115.0>
Process.whereis(ChildAgent2) # restarted
#PID<0.118.0>
Process.whereis(ChildAgent3) # restarted since ChildAgent3 was spawned
#PID<0.119.0> # after the terminated ChildAgent2
Please note that the :restart
key that is specific to a child spec also plays a role in termination and restarting of the process along with the :strategy. Let us consider a supervisor with the strategy :one_for_all
that has four children of ids C1
, C2
, C3
and C4
, each having its own :restart
value. Let us now see different scenarios and how each process behaves in these scenarios.
1) C1 - permanent, C2 - permanent, C3 - temporary, C4 - transient
when C1 terminates,
C1 is restarted,
C2 and C4 are terminated and restarted,
C3 is terminated but not restarted.
2) C1 - temporary, C2 - permanent, C3 - temporary, C4 - transient
when C1 terminates,
C1 is not restarted,
C2, C3 and C4 are not disturbed
3) C1 - transient, C2 - permanent, C3 - temporary, C4 - transient
when C1 terminates normally,
C1 is not restarted,
C2, C3 and C4 are not disturbed
4) C1 - transient, C2 - permanent, C3 - temporary, C4 - transient
when C1 terminates abnormally,
C1 is restarted,
C2 and C4 are terminated and restarted,
C3 is terminated but not restarted.
- The
:name
option takes in an atom value that is registered to the pid of the supervisor process. The supervisor process can then be identified using the registered name.
defmodule ChildAgent do
use Agent
def start_link(_) do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
---------------------------------------------------------------------------
Supervisor.start_link([ChildAgent], name: SuperProc, strategy: :one_for_one)
Process.whereis(SuperProc)
#PID<0.118.0>
- The
:max_restarts
and:max_seconds
options take in an integer value and their default values are 3 and 5 respectively. They are used for limiting the maximum number of child process-restarts that can happen in a given time period. If the number of child process-restarts during a given time interval of:max_seconds
exceeds the provided:max_restarts
, then the associated supervisor will be terminated using a:shutdown
signal. The supervisor will in turn send out exit signals to all the child processes based on their:shutdown
child spec value, thus also terminating all of the child processes under the supervisor process. Please note that the restarts of the other child processes performed due to the strategies such as:one_for_all
and:rest_for_one
do not matter when:max_restarts
is calculated.
defmodule ChildAgent1 do
use Agent
def start_link(_) do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
defmodule ChildAgent2 do
use Agent
def start_link(_) do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
---------------------------------------------------------------------------
opts = [name: Sup, strategy: :one_for_all, max_restarts: 2, max_seconds: 10]
Supervisor.start_link([ChildAgent1, ChildAgent2], opts)
Time.utc_now
~T[16:28:42.384000]
Agent.stop(ChildAgent1) # restart 1
:ok
Agent.stop(ChildAgent2) # restart 2
:ok
Agent.stop(ChildAgent1) # restart 3 > max_restarts(2) within max_seconds(10)
:ok
Time.utc_now
~T[16:28:49.347000]
Process.whereis(Sup) # supervisor tree terminated
nil
Process.whereis(ChildAgent1)
nil
Process.whereis(ChildAgent2)
nil
- The
:auto_shutdown
option takes in one of the three values:never
,:any_significant
and:all_significant
. These values are used when any significant child processes are terminated. Significant child processes are the processes with their child spec’s:restart
values as either:temporary
or:transient
and their child spec’s:significant
value set astrue
. The default value for:auto_shutdown
is:never
and this value can be used only when none of the child processes are marked significant. The value:any_significant
will shutdown the supervisor process and the child processes using a:shutdown
signal, if any of the significant child processes is terminated. The value:all_significant
will shutdown the supervisor and the child processes if all of the significant child processes terminate. The termination is considered valid for a:transient
process only if it terminates normally and for a:temporary
process, both normal and abnormal terminations are considered. Again, the termination of child processes due to the:one_for_all
and:rest_for_one
strategies are not considered by the:auto_shutdown
option.
defmodule ChildAgent1 do
use Agent, restart: :temporary, significant: true
def start_link(_) do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
defmodule ChildAgent2 do
use Agent, restart: :transient, significant: true
def start_link(_) do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
end
---------------------------------------------------------------------------
opts = [name: Sup, strategy: :one_for_all]
Supervisor.start_link([ChildAgent1, ChildAgent2], opts)
** (EXIT from #PID<0.109.0>) shell process exited with
reason: bad child specification, got:
{:bad_combination, [auto_shutdown: :never, significant: true]}
---------------------------------------------------------------------------
opts = [name: Sup, strategy: :one_for_all, auto_shutdown: :any_significant]
Supervisor.start_link([ChildAgent1, ChildAgent2], opts)
Agent.stop(ChildAgent1)
:ok
Process.whereis(Sup) # supervisor shutdown since one of the significant
nil # process has terminated
---------------------------------------------------------------------------
opts = [name: Sup, strategy: :one_for_all, auto_shutdown: :all_significant]
Supervisor.start_link([ChildAgent1, ChildAgent2], opts)
Agent.stop(ChildAgent1)
:ok
Process.whereis(Sup)
#PID<0.110.0>
Agent.stop(ChildAgent2)
:ok
Process.whereis(Sup) # supervisor shutdown since all of the significant
nil # processes have terminated
Module based supervisors
So far we have used the Supervisor.start_link/2
to start a supervision tree, where the child specs were directly used. Using this function is compatible with a top level supervisor. But when a supervisor itself needs to be added as a child process to another higher level supervisor process, the child supervisor must also contain a child spec and its associated :start
key mfa in order to be added as a child. This is where module based supervisors come in, which can contain a child_spec/1
and a start_link
function similar to the worker child processes that we have seen before.
defmodule ChildAgent1 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule ChildAgent2 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule MySupervisor do
def child_spec(_) do
%{id: __MODULE__, start: {__MODULE__, :start_link, []}, type: :supervisor}
end
def start_link do
opts = [name: SupChild, strategy: :one_for_one]
Supervisor.start_link([ChildAgent1, ChildAgent2], opts)
end
end
----------------------------------------------------------------------------
Supervisor.start_link([MySupervisor], strategy: :one_for_one)
{:ok, #PID<0.133.0>}
Process.whereis(SupChild)
#PID<0.134.0>
Process.whereis(ChildAgent1)
#PID<0.135.0>
Process.whereis(ChildAgent2)
#PID<0.136.0>
Elixir also offers the Supervisor
behaviour that can be implemented by module based supervisors. It requires a callback function called init/1
to be implemented by the callback module along with the child_spec/1
and the :start
key’s mfa functions. Modules that use the Supervisor
behaviour can be started using the Supervisor.start_link/3
function which takes in the supervisor module name as the first argument, any term as the second argument that will be passed into the init/1
callback function and an optional :name
option as the third argument. The init/1
callback function must create the required child spec list of all its children and must call the Supervisor.init/2
function and return its value. The Supervisor.init/2
function takes in the child specs and the common supervisor options keyword list as its arguments and returns a supervisor specification map used internally by erlang.
defmodule ChildAgent1 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule ChildAgent2 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule MySupervisor do
@behaviour Supervisor
def child_spec(_) do
%{id: __MODULE__, start: {__MODULE__, :start_link, []}, type: :supervisor}
end
def start_link do
Supervisor.start_link(__MODULE__, nil, name: SupChild)
end
@impl true
def init(_arg) do
Supervisor.init([ChildAgent1, ChildAgent2], strategy: :one_for_one)
end
end
-----------------------------------------------------------------------------
Supervisor.start_link([MySupervisor], strategy: :one_for_one)
{:ok, #PID<0.133.0>}
Process.whereis(SupChild)
#PID<0.134.0>
Process.whereis(ChildAgent1)
#PID<0.135.0>
Process.whereis(ChildAgent2)
#PID<0.136.0>
Internally, both Supervisor.start_link/2
and Supervisor.start_link/3
call the erlang function :supervisor.start_link
to create a supervisor. The underlying erlang function requires a module associated with the supervisor that contains a init/1
callback function. The supervisor_module.init/1
callback function will be called internally by :supervisor.start_link
to obtain a supervisor specification required to start a supervisor in Erlang. The Supervisor.init/2
does exactly this by taking in the Elixir’s child specs and the supervisor options and converting them into the supervisor spec map required internally by Erlang.
defmodule ChildAgent1 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end)
end
defmodule ChildAgent2 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
--------------------------------------------------------------------------
Supervisor.init([ChildAgent1, ChildAgent2], strategy: :one_for_one)
{:ok,
{%{intensity: 3, period: 5, strategy: :one_for_one, auto_shutdown: :never},
[%{id: ChildAgent1, start: {ChildAgent1, :start_link, [[]]}},
%{id: ChildAgent2, start: {ChildAgent2, :start_link, [[]]}}]
}
}
When Supervisor.start_link/2
is called, it implicitly calls the Supervisor.init/2
function to obtain the supervisor spec and will use a dummy module called Supervisor.Default
to be passed in as the supervisor module to the internal Erlang function. This is why the Supervisor
behaviour enforces an init/1
callback and requires you to call the Supervisor.init/2
function to return a supervisor spec map from it.
defmodule Supervisor.Default do
def init(args), do: args
end
---------------------------------------------------------------------------
# a rough internal implementation of the Supervisor.start_link/2 function
def start_link(children, options) do
{sup_opts, start_opts} =
Keyword.split(options, [:strategy, :max_seconds, :max_restarts, :auto_shutdown])
sup_spec = Supervisor.init(children, sup_opts)
Supervisor.start_link(Supervisor.Default, sup_spec, start_opts)
end
# a rough internal implementation of the Supervisor.start_link/3 function
def start_link(module, init_arg, options \\ []) do
case Keyword.get(options, :name) do
nil -> :supervisor.start_link(module, init_arg)
atom when is_atom(atom) ->
:supervisor.start_link({:local, atom}, module, init_arg)
.
.
.
end
end
The Supervisor
module also offers the use
macro that will inject @behaviour Supervisor
and a default implementation of child_spec/1
function into the callback module.
defmodule MySupervisor do
use Supervisor, shutdown: 60000
def start_link(init_arg) do
Supervisor.start_link(__MODULE__, init_arg, name: SupChild)
end
@impl true
def init(_arg) do
Supervisor.init([ChildAgent1, ChildAgent2], strategy: :one_for_one)
end
end
---------------------------------------------------------------------------
# injected code during compilation
defmodule MySupervisor do
@behaviour Supervisor
def child_spec(init_arg) do
default = %{
id: __MODULE__,
start: {__MODULE__, :start_link, [init_arg]},
type: :supervisor
}
Supervisor.child_spec(default, [shutdown: 60000])
end
def start_link(init_arg) do
Supervisor.start_link(__MODULE__, init_arg, name: SupChild)
end
@impl true
def init(_arg) do
Supervisor.init([ChildAgent1, ChildAgent2], strategy: :one_for_one)
end
end
Supervisor module functions
- The
Supervisor.child_spec/2
function takes in a child spec map or a module atom or the tuple{module, child_spec_arg}
as its first argument and a keyword list of child spec option overrides as its second argument. It uses the child spec provided as the first argument as the base map and overrides or inserts the options provided in the keyword list into it.
base_spec = %{id: Test, start: {Test, :start_link, []}, restart: :temporary}
Supervisor.child_spec(base_spec, [restart: :transient, significant: true])
%{id: Test, restart: :transient, start: {Test, :start_link, []},
significant: true}
- The
Supervisor.count_children/1
function takes in a supervisor’s PID or its registered name as its argument and returns a map with different stats about the total number of its children. The:specs
key has the total number of children present under the supervisor,:workers
key has the total number of children of type:worker
present under the supervisor,:supervisors
key has the total number of children of type:supervisor
present under the supervisor and the keyactive
has the total number of children under the supervisor that are currently alive. The keys:specs
,:workers
and:supervisors
are counted based on the child specs passed in to the supervisor and a child is counted as long it has an associated child spec, even if a child process has not been started or if it has been terminated. Please note that only the children directly under the provided supervisor are counted and the children present in the lower levels are not counted in this function.
defmodule ChildAgent1 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule ChildAgent2 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule DummyAgent do
use Agent
def start_link(_), do: :ignore
end
defmodule MySupervisor do
use Supervisor
def start_link(init_arg) do
Supervisor.start_link(__MODULE__, init_arg, name: SupChild)
end
@impl true
def init(_arg) do
Supervisor.init([ChildAgent2], strategy: :one_for_one)
end
end
---------------------------------------------------------------------------
opts = [name: Sup, strategy: :one_for_one]
Supervisor.start_link([MySupervisor, ChildAgent1, DummyAgent], opts)
Supervisor.count_children(Sup)
%{active: 2, workers: 2, supervisors: 1, specs: 3}
# active - SupChild, ChildAgent1
# workers - ChildAgent1, DummyAgent
# supervisors - SupChild
# specs - SupChild, ChildAgent1, DummyAgent
Supervisor.count_children(SupChild)
%{active: 1, workers: 1, supervisors: 0, specs: 1}
# active - ChildAgent2
# workers - ChildAgent2
# supervisors - 0
# specs - ChildAgent2
- The
Supervisor.which_children/1
function, similar to the above function, takes in a supervisor’s PID or its registered name and returns information about its direct children. It returns a list of tuples with each tuple containing information about a child such as its id, pid, type and the child spec’s:modules
key value. If the child is dead, then the pid will have the value:undefined
and if the child is being restarted, then the pid’s value will be:restarting
.
defmodule ChildAgent1 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule ChildAgent2 do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule DummyAgent do
use Agent
def start_link(_), do: :ignore
end
defmodule MySupervisor do
use Supervisor
def start_link(init_arg) do
Supervisor.start_link(__MODULE__, init_arg, name: SupChild)
end
@impl true
def init(_arg) do
Supervisor.init([ChildAgent2], strategy: :one_for_one)
end
end
---------------------------------------------------------------------------
opts = [name: Sup, strategy: :one_for_one]
Supervisor.start_link([MySupervisor, ChildAgent1, DummyAgent], opts)
Supervisor.which_children(Sup)
[
{DummyAgent, :undefined, :worker, [DummyAgent]},
{ChildAgent1, #PID<0.186.0>, :worker, [ChildAgent1]},
{MySupervisor, #PID<0.184.0>, :supervisor, [MySupervisor]}
]
- The
Supervisor.terminate_child/2
takes in a supervisor’s PID or its registered name as its first argument and a child’s id as its second argument. If the provided child id is present in the supervisor’s child specs list, then the child process associated with it is terminated and:ok
is returned. If the child process is already dead, then:ok
is simply returned. If there is no child spec present with the provided child id, then the tuple{:error, :not_found}
is returned. Terminating a child process using this function has no effect on the:restart
,:strategy
and the:auto_shutdown
options. Hence, the terminated child process will not be restarted and the supervisor, other child processes will not be disturbed irrespective of the:restart
,:strategy
and:auto_shutdown
options. Please note that when a:temporary
process is terminated either normally or abnormally or by using this function, its respective child spec will be deleted from the supervisor.
defmodule ChildAgent do
use Agent, restart: :temporary
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule DummyAgent do
use Agent
def start_link(_), do: :ignore
end
---------------------------------------------------------------------------
Supervisor.start_link([ChildAgent, DummyAgent], name: Sup, strategy: :one_for_one)
Process.whereis(ChildAgent)
#PID<0.101.0>
Process.whereis(DummyAgent)
nil
Supervisor.terminate_child(Sup, DummyAgent)
:ok
Supervisor.terminate_child(Sup, ChildAgent)
:ok
Process.whereis(ChildAgent)
nil
Supervisor.terminate_child(Sup, ChildAgent) #child spec has already been
{:error, :not_found} #deleted
- The function
Supervisor.restart_child/2
takes in a supervisor’s PID or its registered name as its first argument and a child id as its second argument. If the child id is associated with an available child spec and if no child process associated with the child spec is alive, then the function will restart the child process by calling the child spec’s:start
mfa and return{:ok, child_pid}
. If the child spec’s:start
mfa returns:ignore
, then{:ok, :undefined}
is returned. If there is no child spec available with the provided child id or if there is already an associated child process running/being restarted or if the child spec’s:start
mfa fails, then an error tuple is returned in the form of{:error, error_reason}
. Restarting a child using this function has no effects on the:max_restarts
and:max_seconds
options.
defmodule ChildAgent do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule DummyAgent do
use Agent
def start_link(_), do: :ignore
end
---------------------------------------------------------------------------
Supervisor.start_link([ChildAgent, DummyAgent], name: Sup, strategy: :one_for_all)
Supervisor.restart_child(Sup, DummyAgent)
{:ok, :undefined}
Supervisor.restart_child(Sup, ChildAgent)
{:error, :running}
Supervisor.restart_child(Sup, InvalidID)
{:error, :not_found}
Supervisor.terminate_child(Sup, ChildAgent)
:ok
Supervisor.restart_child(Sup, ChildAgent)
{:ok, #PID<0.227.0>}
- The function
Supervisor.start_child/2
takes in a supervisor’s PID or its registered name as its first argument and a child spec map or a module atom or the{module, child_spec_arg}
tuple as its second argument. It adds the provided child spec to the supervisor and starts the child process by calling the:start
key mfa provided in its child spec. If the:start
key mfa returns{:ok, child_pid}
, then the same value is returned by thestart_child
function. If it returns:ignore
then{:ok, :undefined}
is returned from thestart_child
function. If there is already a child spec with the same id or if the:start
key mfa fails, then an error tuple in the format{:error, error_reason}
is returned.
defmodule ChildAgent do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
defmodule DummyAgent do
use Agent
def start_link(_), do: :ignore
end
---------------------------------------------------------------------------
Supervisor.start_link([], name: Sup, strategy: :one_for_all)
Supervisor.start_child(Sup, DummyAgent)
{:ok, :undefined}
Supervisor.start_child(Sup, ChildAgent)
{:ok, #PID<0.269.0>}
Supervisor.start_child(Sup, ChildAgent)
{:error, {:already_started, #PID<0.269.0>}}
Supervisor.terminate_child(Sup, ChildAgent)
:ok
Supervisor.start_child(Sup, ChildAgent)
{:error, :already_present}
- The
Supervisor.stop/3
function takes in a supervisor’s PID or its registered name as the first argument, an optional reason atom as its second argument and an optional timeout value in milliseconds as its third argument. It terminates the supervisor with the provided reason and waits for the provided time, after which the supervisor and all its child processes will be brutally killed if they are still alive. The default values for reason and timeout are:normal
and:infinity
respectively. This is a synchronous call and hence the caller will be blocked until the supervision tree is completely shutdown. When stopping the supervisor using this function, if the terminated supervisor is a child of another high level supervisor, then it will be restarted according to its child spec’s:restart
value.
defmodule ChildAgent do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end, name: __MODULE__)
end
---------------------------------------------------------------------------
Supervisor.start_link([ChildAgent], name: Sup, strategy: :one_for_one)
Supervisor.stop(Sup)
:ok
Process.whereis(Sup)
nil
Process.whereis(ChildAgent)
nil
- The
Supervisor.start_link/2
function takes in a list of child spec maps or modules or the{module, child_spec_arg}
tuples as its first argument and an optional keyword list of supervisor options as its second argument. It will start the supervisor process and will start the child processes in the same input order by calling the respective:start
key mfa. All of the:start
key mfas must return either{:ok, child_pid}
or{:ok, child_pid, info}
or:ignore
for the supervision tree to be successfully started. Once the supervision tree is successfully started, then{:ok, supervisor_pid}
is returned by thestart_link/2
function. When providing the:name
option for the supervisor, if the provided name is already used, then{:error, {:already_started, pid}}
will be returned. If any of the:start
key mfas fail or return a result in an invalid format, then the supervisor and the other already-started child processes will be shutdown with the exit reason:shutdown
and a tuple of the format{:error, {:shutdown, error_reason}}
will be returned. Calling this function will link the caller process and the supervisor process and hence abnormal exits in one of the processes will terminate the other, if exits are not trapped. Normal exit of the caller process will also shutdown the spawned supervisor process. - The
Supervisor.start_link/3
function takes in a module that implements theSupervisor
behaviour as its first argument, a term that will be passed on to theinit/1
callback as its second argument and an optional keyword list with the:name
option as its third argument. If theinit/1
callback present in the supervisor module returns a valid supervisor spec and after all the child processes have been started successfully, then thestart_link/3
function will return a tuple of the format{:ok, supervisor_pid}
. If theinit/1
callback returns:ignore
, then the supervisor process is shutdown with the reason:normal
and the same atom:ignore
is returned from the function. If theinit/1
callback returns an invalid response or if any of the child processes fail to start, then{:error, error_reason}
is returned. Similar tostart_link/2
the caller process will be linked with the spawned supervisor process when this function is used. - The
Supervisor.init/2
function takes in a list of child spec maps or module atoms or the tuple{module, child_spec_arg}
as its first argument and a keyword list of options that include:strategy
,:max_restarts
,:max_seconds
and:auto_shutdown
, as its second argument. It creates and returns a supervisor spec used for starting a supervisor.
Dynamic Supervisors
Dynamic supervisors are optimised versions of supervisors that are specifically used for creating and managing child processes dynamically. As opposed to creating a known list of children in a given order at the time of creation of the supervisor process, dynamic supervisors are started without any children. New children are added dynamically to the dynamic supervisor as and when they are required during run time. Dynamic supervisors were introduced to replace the now-deprecated :simple_one_for_one
strategy that was previously used for handling dynamic children in a supervisor. Most of the functionality and the functions of the DynamicSupervisor
module is similar to the Supervisor
module with some differences and additional options.
Dynamic supervisor options
A dynamic supervisor can be started using the DynamicSupervisor.start_link/1
function that takes in a keyword list of common supervisor options. Unlike the Supervisor.start_link/2
which requires a list of child specs, the dynamic supervisor starts without any children and hence it does not require a list of child specs while starting the supervisor. The children are started when required, using the DynamicSupervisor.start_child/2
function. The common options supported by dynamic supervisors are :name
, :strategy
, :max_restarts
and :max_seconds
which are the same as what normal supervisors support. In addition to that, the options :max_children
and :extra_arguments
are supported by dynamic supervisors. Unlike normal supervisors, the option :auto_shutdown
is not supported and the option :strategy
supports only :one_for_one
. Hence termination of any child process will not restart the other children.
- The
:max_children
option takes in a positive integer and its default value is the atom:infinity
. It determines the total number of children that can be alive at a given time under a particular dynamic supervisor. Whenstart_child/2
is called to start a child and if the:max_children
limit is already reached, then the tuple{:error, :max_children}
is returned and the child is not created.
defmodule ChildAgent do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end)
end
---------------------------------------------------------------------------
DynamicSupervisor.start_link(name: DynSup, max_children: 2)
{:ok, #PID<0.115.0>}
DynamicSupervisor.start_child(DynSup, ChildAgent) # child 1
{:ok, #PID<0.116.0>}
{:ok, c2_pid} = DynamicSupervisor.start_child(DynSup, ChildAgent) # child 2
{:ok, #PID<0.117.0>}
DynamicSupervisor.start_child(DynSup, ChildAgent)
{:error, :max_children}
DynamicSupervisor.terminate_child(DynSup, c2_pid)
:ok
DynamicSupervisor.start_child(DynSup, ChildAgent)
{:ok, #PID<0.118.0>}
- The
:extra_arguments
option takes in a list of arguments that will be prepended to the arguments passed into the:start
key mfa when a child is created using thestart_child/2
function. Hence, if this option is used when creating a dynamic supervisor, the value passed to it will be used as a common set of arguments for all the children created under the dynamic supervisor. Please note that the:start
key mfa function must support a total of:extra_arguments
plus the specific arguments present in the mfa of the individual child spec’s:start
key.
defmodule ChildAgent do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end)
end
defmodule ChildAgent1 do
use Agent
def start_link(arg1, arg2, arg3) do
IO.inspect([arg1, arg2, arg3], label: "Args")
Agent.start_link(fn -> nil end)
end
end
---------------------------------------------------------------------------
DynamicSupervisor.start_link(name: DynSup, extra_arguments: [:arg1, :arg2])
{:ok, #PID<0.115.0>}
DynamicSupervisor.start_child(DynSup, ChildAgent) # fails since start_link/1
{:error, # does not support the 2 additional extra arguments
{:undef,
[
{ChildAgent, :start_link, [:arg1, :arg2, []], []},
{DynamicSupervisor, :start_child, 3,
[file: ~c"lib/dynamic_supervisor.ex", line: 795]},
{DynamicSupervisor, :handle_start_child, 2,
[file: ~c"lib/dynamic_supervisor.ex", line: 781]},
{:gen_server, :try_handle_call, 4, [file: ~c"gen_server.erl", line: 1113]},
{:gen_server, :handle_msg, 6, [file: ~c"gen_server.erl", line: 1142]},
{:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 241]}
]}}
DynamicSupervisor.start_child(DynSup, ChildAgent1)
Args: [:arg1, :arg2, []]
{:ok, #PID<0.121.0>}
Dynamic supervisor - Child specification
The child spec map used for creating a child under a dynamic supervisor is mostly the same as that of a normal supervisor, supporting the options such as :start
, :restart
, :type
, :shutdown
and :modules
. Since the common option :auto_shutdown
is not supported by dynamic supervisors, the value for the child spec option :significant
is always false
for a child started under a dynamic supervisor. Another main difference is that the :id
option for a child started under a dynamic supervisor always has the value :undefined
. Please note that the :id
option is still required to be present in the child spec, but the value provided in it will be ignored. Hence the children started under a dynamic supervisor will not be having unique ids associated with them.
defmodule ChildAgent do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end)
end
---------------------------------------------------------------------------
DynamicSupervisor.start_link(name: DynSup)
child_spec = Supervisor.child_spec(ChildAgent, significant: true)
DynamicSupervisor.start_child(DynSup, child_spec)
{:error, {:invalid_significant, true}}
DynamicSupervisor.start_child(DynSup, ChildAgent)
{:ok, #PID<0.121.0>}
DynamicSupervisor.which_children(DynSup)
[{:undefined, #PID<0.121.0>, :worker, [ChildAgent]}] # id is :undefined
Starting a dynamic supervisor
A dynamic supervisor can be started as a top level supervisor using either the start_link/1
function that takes in the keyword list of common supervisor options or using the start_link/3
that takes in a module as its first argument, keyword list containing the common supervisor options as the second argument and a keyword list containing the :name
option as the last argument. Similar to the Supervisor
module, the DynamicSupervisor
also exposes a behaviour that requires the init/1
callback to be implemented. Since the DynamicSupervisor
module exposes a child_spec/1
and an init/1
functions, it can be directly used in the start_link/3
function as follows.
DynamicSupervisor.start_link(DynamicSupervisor, [max_children: 1000], name: DynSup1)
{:ok, #PID<0.123.0>}
User-defined module based dynamic supervisors can also be started using the start_link/3
function by implementing the DynamicSupervisor
behaviour. Dynamic supervisors are typically started under another higher level supervisor by passing the DynamicSupervisor
or a user-defined module in the list of child specs. The common supervisor options and the :name
option can be passed in by using the {supervisor_module, options_arg}
tuple into the child spec list. The module implementing the DynamicSupervisor
behaviour must call the DynamicSupervisor.init/1
function inside the init/1
callback to return a supervisor spec.
Supervisor.start_link([DynamicSupervisor], strategy: :one_for_one, name: Sup)
{:ok, #PID<0.130.0>}
Supervisor.which_children(Sup)
[{DynamicSupervisor, #PID<0.131.0>, :supervisor, [DynamicSupervisor]}]
-----------------------------------------------------------------------------
child_spec_tup = {DynamicSupervisor, [name: DynSup, max_children: 1000]}
Supervisor.start_link([child_spec_tup], strategy: :one_for_one, name: Sup1)
{:ok, #PID<0.132.0>}
Supervisor.which_children(Sup1)
[{DynamicSupervisor, #PID<0.141.0>, :supervisor, [DynamicSupervisor]}]
:supervisor.get_childspec(Sup1, DynamicSupervisor)
{:ok,
%{
id: DynamicSupervisor,
restart: :permanent,
shutdown: :infinity,
start: {DynamicSupervisor, :start_link, [[max_children: 1000]]},
type: :supervisor,
modules: [DynamicSupervisor],
significant: false
}}
-----------------------------------------------------------------------------
defmodule MyDynamicSup do # module based dynamic supervisor
use DynamicSupervisor
def start_link(options) do
DynamicSupervisor.start_link(__MODULE__, options, name: __MODULE__)
end
def init(options) do
DynamicSupervisor.init(options)
end
end
---------------------------------------------------------------------------
Supervisor.start_link([{MyDynamicSup, max_children: 1000}], strategy: :one_for_one)
{:ok, #PID<0.133.0>}
DynamicSupervisor module functions
- The
start_link/1
function takes in a keyword list of supervisor options such as:name
,:max_restarts
,:max_seconds
,:max_children
and:extra_arguments
and starts a dynamic supervisor. It returns{:ok, supervisor_pid}
on success and{:error, {:already_started, pid}}
if a:name
option is used and there is already a process using the provided name. Using this function creates a process link between the caller and the spawned dynamic supervisor and hence, abnormal exit in one of the processes will terminate the other, if exits are not trapped. Normal exit of the caller process will also terminate the spawned dynamic supervisor. - The
start_link/3
function takes in a module that implements theDynamicSupervisor
behaviour as its first argument, a term as the second argument which will be passed into theinit/1
callback function and finally the keyword list containing the:name
option as the third argument and the function starts a dynamic supervisor. Similar to thestart_link/1
function, it returns{:ok, supervisor_pid}
on success and{:error, {:already_started, pid}}
if the provided:name
is already being used. The caller and the spawned supervisor will be linked and hence will behave as provided in thestart_link/1
function. Theinit/1
callback must call theDynamicSupervisor.init/1
function to return a supervisor spec. If theinit/1
callback returns the atom:ignore
, then the spawned supervisor will be terminated and the same:ignore
will be returned from the function. In case of any errors, a tuple in the format of{:error, error_reason}
will be returned. - The
start_child/2
function takes in a dynamic supervisor’s PID or its associated name as its first argument and a child spec map or a module atom or the tuple{module, child_spec_arg}
as its second argument. It adds the provided child spec map to the dynamic supervisor and creates a child process under the supervisor by calling the:start
key mfa provided in the child spec. If the:start
key mfa returns either{:ok, child_pid}
or{:ok, child_pid, info_term}
then the function will successfully return the same. If the:start
key mfa does not spawn a child and returns:ignore
, then the same:ignore
is returned from the function. In case any errors in the:start
key mfa, then a tuple in the format{:error, error_reason}
will be returned from the function. If the option:max_children
is being used by the dynamic supervisor and when the total number of children under the supervisor has reached the provided limit, then the function will fail by returning{:error, :max_children}
. - The
child_spec/1
function takes in a keyword list of supervisor options supported by the dynamic supervisor and returns a child spec map. This function is internally called when theDynamicSupervisor
module is used as a child under another high level supervisor. The provided:name
option value is used as the value for:id
and the valueDynamicSupervisor
is used as the default:id
, if:name
option is not provided. Since children created directly under a normal supervisor must have unique ids, creating two dynamic supervisor children under a normal supervisor without explicit:name
options will fail due to duplicate ids.
Supervisor.start_link([DynamicSupervisor, DynamicSupervisor], strategy: :one_for_one)
** (EXIT from #PID<0.125.0>) shell process exited with reason:
bad child specification, more than one child specification has the id:
DynamicSupervisor.
The :start
key mfa will be set as {DynamicSupervisor, :start_link, [passed_in_options]}
and the option :type
will be set as :supervisor
.
DynamicSupervisor.child_spec([name: DynSup])
%{
id: DynSup,
start: {DynamicSupervisor, :start_link, [[name: DynSup]]},
type: :supervisor
}
- The
init/1
function takes in a keyword list containing the supervisor options except:name
and returns a supervisor spec used internally by Erlang to create a supervisor. Theinit/1
function is internally called when using theDynamicSupervisor
as a child under another supervisor, or when usingDynamicSupervisor
in thestart_link/3
function. It is explicitly used in theinit/1
callback contract function required for implementing theDynamicSupervisor
behaviour.
DynamicSupervisor.init([max_children: 1000, extra_arguments: [:arg1]])
{:ok,
%{
intensity: 3,
period: 5,
strategy: :one_for_one,
max_children: 1000,
extra_arguments: [:arg1]
}}
- The
terminate_child/2
function takes in a dynamic supervisor’s PID or its associated name as its first argument and a child’s PID as its second argument. It terminates the provided child if it is currently running under the provided dynamic supervisor. It returns:ok
if termination is successful and an error tuple in the format{:error, error_reason}
otherwise. It removes the child spec from the dynamic supervisor once a child has been terminated using this function.
defmodule ChildAgent do
use Agent
def start_link(_), do: Agent.start_link(fn -> nil end)
end
---------------------------------------------------------------------------
DynamicSupervisor.start_link(name: DynSup)
{:ok, child_pid} = DynamicSupervisor.start_child(DynSup, ChildAgent)
DynamicSupervisor.terminate_child(DynSup, child_pid)
:ok
DynamicSupervisor.terminate_child(DynSup, child_pid)
{:error, :not_found}
- The
stop/3
function takes in a dynamic supervisor’s PID or its associated name as its first argument, an optional reason atom as the second argument and an optional timeout value in milliseconds as the third argument. It behaves the same way asSupervisor.stop/3
and shuts down the given dynamic supervisor and its children. - The functions
which_children/1
andcount_children/1
behave the same way asSupervisor.which_children/1
andSupervisor.count_children/1
and provide information about the children of the given dynamic supervisor. Please note that dynamic supervisors contain only the child specs of the children that are currently running, while in normal supervisors, the child specs of terminated processes with:restart
values other than:temporary
can still be present under the supervisor.