Elixir/OTP : Basics of Supervisors

Arunmuthuram M
29 min readFeb 18, 2024

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 respective start_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 is 5000 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 using Process.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 using Process.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 call Process.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 is false and only the child processes whose :restart values are either :temporary or :transient can contain the value true 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 as true. 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 key active 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 the start_child function. If it returns :ignore then {:ok, :undefined} is returned from the start_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 the start_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 the Supervisor behaviour as its first argument, a term that will be passed on to the init/1 callback as its second argument and an optional keyword list with the :name option as its third argument. If the init/1 callback present in the supervisor module returns a valid supervisor spec and after all the child processes have been started successfully, then the start_link/3 function will return a tuple of the format {:ok, supervisor_pid}. If the init/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 the init/1 callback returns an invalid response or if any of the child processes fail to start, then {:error, error_reason} is returned. Similar to start_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. When start_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 the start_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 the DynamicSupervisor behaviour as its first argument, a term as the second argument which will be passed into the init/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 the start_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 the start_link/1 function. The init/1 callback must call the DynamicSupervisor.init/1 function to return a supervisor spec. If the init/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 the DynamicSupervisor 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 value DynamicSupervisor 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. The init/1 function is internally called when using the DynamicSupervisor as a child under another supervisor, or when using DynamicSupervisor in the start_link/3 function. It is explicitly used in the init/1 callback contract function required for implementing the DynamicSupervisor 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 as Supervisor.stop/3 and shuts down the given dynamic supervisor and its children.
  • The functions which_children/1 and count_children/1 behave the same way as Supervisor.which_children/1 and Supervisor.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.

--

--