dynamic_supervisor behaviour (ex_stdlib v0.2.0)

View Source

A supervisor optimized to only start children dynamically.

The standard supervisor module was designed to handle mostly static children that are started in the given order when the supervisor starts. A dynamic_supervisor starts with no children. Instead, children are started on demand via start_child/2 and there is no ordering between children. This allows the dynamic_supervisor to hold millions of children by using efficient data structures and to execute certain operations, such as shutting down, concurrently.

Examples

A dynamic supervisor is started with no children and often a name:

   {ok, Pid} = dynamic_supervisor:start_link({local, my_dynamic_supervisor}, [
       {strategy, one_for_one}
   ]).

Once the dynamic supervisor is running, we can use it to start children on demand. Given this sample gen_server:

   -module(counter).
   -behaviour(gen_server).
  
   -export([start_link/1, inc/1]).
   -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  
   start_link(Initial) ->
       gen_server:start_link(?MODULE, Initial, []).
  
   inc(Pid) ->
       gen_server:call(Pid, inc).
  
   init(Initial) ->
       {ok, Initial}.
  
   handle_call(inc, _From, Count) ->
       {reply, Count, Count + 1}.

We can use start_child/2 with a child specification to start a counter server:

   {ok, Counter1} = dynamic_supervisor:start_child(my_dynamic_supervisor,
       #{id => counter1,
         start => {counter, start_link, [0]},
         restart => permanent,
         shutdown => 5000,
         type => worker,
         modules => [counter]}).
   counter:inc(Counter1).
   %% 0
  
   dynamic_supervisor:count_children(my_dynamic_supervisor).
   %% #{active => 1, specs => 1, supervisors => 0, workers => 1}

Summary

Functions

Returns a map containing count values for the supervisor.

Dynamically adds a child specification to Supervisor and starts that child.

Starts a dynamic supervisor with the given options.

Starts a named dynamic supervisor with the given options.

Starts an unnamed module-based supervisor process.

Starts a named module-based supervisor process.

Synchronously stops the given supervisor with the given Reason.

Terminates the given child identified by Pid.

Returns a list with information about all children.

Types

child_spec/0

-type child_spec() ::
          #{id := term(),
            start := {module(), atom(), [term()]},
            restart => restart(),
            shutdown => shutdown(),
            type => child_type(),
            modules => modules(),
            significant => boolean()}.

child_type/0

-type child_type() :: worker | supervisor.

init_option/0

-type init_option() ::
          {strategy, strategy()} |
          {max_restarts, non_neg_integer()} |
          {max_seconds, pos_integer()} |
          {max_children, non_neg_integer() | infinity} |
          {extra_arguments, [term()]}.

modules/0

-type modules() :: [module()] | dynamic.

restart/0

-type restart() :: permanent | transient | temporary.

shutdown/0

-type shutdown() :: brutal_kill | infinity | non_neg_integer().

start_option/0

-type start_option() :: {timeout, timeout()} | {debug, [sys:debug_option()]} | {spawn_opt, [term()]}.

strategy/0

-type strategy() :: one_for_one.

Callbacks

init/1

-callback init(Args :: term()) -> {ok, sup_flags()} | ignore.

Functions

count_children(Supervisor)

-spec count_children(Supervisor ::
                         pid() | atom() | {atom(), node()} | {global, atom()} | {via, module(), term()}) ->
                        #{specs := non_neg_integer(),
                          active := non_neg_integer(),
                          supervisors := non_neg_integer(),
                          workers := non_neg_integer()}.

Returns a map containing count values for the supervisor.

The map contains the following keys:

  • specs - the number of children processes
  • active - the count of all actively running child processes managed by this supervisor
  • supervisors - the count of all supervisors whether or not the child process is still alive
  • workers - the count of all workers, whether or not the child process is still alive

init(Options)

start_child(Supervisor, ChildSpec)

-spec start_child(Supervisor ::
                      pid() | atom() | {atom(), node()} | {global, atom()} | {via, module(), term()},
                  ChildSpec :: child_spec() | {module(), term()} | module()) ->
                     {ok, pid()} | {ok, pid(), term()} | ignore | {error, term()}.

Dynamically adds a child specification to Supervisor and starts that child.

ChildSpec should be a valid child specification. The child process will be started as defined in the child specification.

If the child process start function returns {ok, Child} or {ok, Child, Info}, then child specification and PID are added to the supervisor and this function returns the same value.

If the child process start function returns ignore, then no child is added to the supervision tree and this function returns ignore too.

If the child process start function returns an error tuple or an erroneous value, or if it fails, the child specification is discarded and this function returns {error, Error} where Error is the error or erroneous value returned from child process start function, or failure reason if it fails.

If the supervisor already has N children in a way that N exceeds the amount of max_children set on the supervisor initialization, then this function returns {error, max_children}.

start_link(Options)

-spec start_link([init_option()]) -> {ok, pid()} | ignore | {error, term()}.

Starts a dynamic supervisor with the given options.

Options:

  • {strategy, Strategy} - the restart strategy option. The only supported value is one_for_one which means that no other child is terminated if a child process terminates. Defaults to one_for_one.
  • {max_restarts, MaxRestarts} - the maximum number of restarts allowed in a time frame. Defaults to 3.
  • {max_seconds, MaxSeconds} - the time frame in which max_restarts applies. Defaults to 5.
  • {max_children, MaxChildren} - the maximum amount of children to be running under this supervisor at the same time. When max_children is exceeded, start_child/2 returns {error, max_children}. Defaults to infinity.
  • {extra_arguments, ExtraArgs} - arguments that are prepended to the arguments specified in the child spec given to start_child/2. Defaults to an empty list.

start_link(Name, InitArg)

-spec start_link(Name, [init_option()]) -> {ok, pid()} | ignore | {error, term()}
                    when Name :: {local, atom()} | {global, atom()} | {via, module(), term()}.

Starts a named dynamic supervisor with the given options.

This is the most common way to start a dynamic supervisor in Erlang style. The Name parameter follows standard Erlang naming conventions.

Examples:

  %% Local registration
  {ok, Pid} = dynamic_supervisor:start_link({local, my_supervisor}, []).
 
  %% Global registration
  {ok, Pid} = dynamic_supervisor:start_link({global, my_supervisor}, []).
 
  %% Via registration
  {ok, Pid} = dynamic_supervisor:start_link({via, Registry, {MyReg, key}}, []).

start_link(Module, InitArg, Opts)

-spec start_link(module(), term(), [start_option()]) -> {ok, pid()} | ignore | {error, term()}.

Starts an unnamed module-based supervisor process.

start_link(Name, Module, InitArg, Opts)

-spec start_link(Name, module(), term(), [start_option()]) -> {ok, pid()} | ignore | {error, term()}
                    when Name :: {local, atom()} | {global, atom()} | {via, module(), term()}.

Starts a named module-based supervisor process.

stop(Supervisor)

-spec stop(Supervisor :: pid() | atom() | {atom(), node()} | {global, atom()} | {via, module(), term()}) ->
              ok.

Synchronously stops the given supervisor with the given Reason.

It returns ok if the supervisor terminates with the given reason. If it terminates with another reason, the call exits.

This function keeps OTP semantics regarding error reporting. If the reason is any other than normal, shutdown or {shutdown, _}, an error report is logged.

stop(Supervisor, Reason)

-spec stop(Supervisor :: pid() | atom() | {atom(), node()} | {global, atom()} | {via, module(), term()},
           Reason :: term()) ->
              ok.

stop(Supervisor, Reason, Timeout)

-spec stop(Supervisor :: pid() | atom() | {atom(), node()} | {global, atom()} | {via, module(), term()},
           Reason :: term(),
           Timeout :: timeout()) ->
              ok.

terminate_child(Supervisor, Pid)

-spec terminate_child(Supervisor ::
                          pid() | atom() | {atom(), node()} | {global, atom()} | {via, module(), term()},
                      Pid :: pid()) ->
                         ok | {error, not_found}.

Terminates the given child identified by Pid.

If successful, this function returns ok. If there is no process with the given PID, this function returns {error, not_found}.

which_children(Supervisor)

-spec which_children(Supervisor ::
                         pid() | atom() | {atom(), node()} | {global, atom()} | {via, module(), term()}) ->
                        [{undefined, pid() | restarting, worker | supervisor, [module()] | dynamic}].

Returns a list with information about all children.

Note that calling this function when supervising a large number of children under low memory conditions can cause an out of memory exception.

This function returns a list of tuples containing:

  • Id - it is always undefined for dynamic supervisors
  • Child - the PID of the corresponding child process or the atom restarting if the process is about to be restarted
  • Type - worker or supervisor as defined in the child specification
  • Modules - as defined in the child specification