4
votes

I have root supervisor that create other supervisor:

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    RestartStrategy = {one_for_one, 5, 600},
    ListenerSup =
            {popd_listener_sup,
            {popd_listener_sup, start_link, []},
             permanent, 2000, supervisor, [popd_listener]},

    Children = [ListenerSup],

    {ok, {RestartStrategy, Children}}.

And i have gen_server - listener. How can i run this gen_server with popd_listener_sup supervisor, when supervisor created?

Thank you.

1

1 Answers

14
votes

Root supervisor

-module(root_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1, shutdown/0]).

start_link() ->
     supervisor:start_link({local,?MODULE}, ?MODULE, []).

init(_Args) ->
     RestartStrategy = {one_for_one, 10, 60},
     ListenerSup = {popd_listener_sup,
          {popd_listener_sup, start_link, []},
          permanent, infinity, supervisor, [popd_listener_sup]},
     Children = [ListenerSup],
     {ok, {RestartStrategy, Children}}.    

% supervisor can be shutdown by calling exit(SupPid,shutdown)
% or, if it's linked to its parent, by parent calling exit/1.
shutdown() ->
     exit(whereis(?MODULE), shutdown).
     % or
     % exit(normal).

If the child process is another supervisor, Shutdown in child specification should be set to infinity to give the subtree ample time to shutdown, and Type should be set to supervisor, and that's what we did.

Child supervisor

-module(popd_listener_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).

start_link() ->
    supervisor:start_link({local,?MODULE}, ?MODULE, []).

init(_Args) ->
    RestartStrategy = {one_for_one, 10, 60},
    Listener = {ch1, {ch1, start_link, []},
            permanent, 2000, worker, [ch1]},
    Children = [Listener],
    {ok, {RestartStrategy, Children}}.

Here, in a child specification, we set value of Shutdown to 2000. An integer timeout value means that the supervisor will tell the child process to terminate by calling exit(Child,shutdown) and then wait for an exit signal with reason shutdown back from the child process.

Listener

-module(ch1).
-behaviour(gen_server).

% Callback functions which should be exported
-export([init/1]).
-export([handle_cast/2, terminate/2]).

% user-defined interface functions
-export([start_link/0]).

start_link() ->
     gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init(_Args) ->
     erlang:process_flag(trap_exit, true),
     io:format("ch1 has started (~w)~n", [self()]),
     % If the initialization is successful, the function
     % should return {ok,State}, {ok,State,Timeout} ..
     {ok, []}.

handle_cast(calc, State) ->
     io:format("result 2+2=4~n"),
     {noreply, State};
handle_cast(calcbad, State) ->
     io:format("result 1/0~n"),
     1 / 0,
     {noreply, State}.

terminate(_Reason, _State) ->
     io:format("ch1: terminating.~n"),
     ok.

From Erlang/OTP documentation:

If the gen_server is part of a supervision tree and is ordered by its supervisor to terminate, the function Module:terminate(Reason, State) will be called with Reason=shutdown if the following conditions apply:

  • the gen_server has been set to trap exit signals, and
  • the shutdown strategy as defined in the supervisor's child specification
    is an integer timeout value, not
    brutal_kill.

That's why we called erlang:process_flag(trap_exit, true) in Module:init(Args).

Sample run

Starting the root supervisor:

1> root_sup:start_link().
ch1 has started (<0.35.0>)
{ok,<0.33.0>}

Root supervisor is run and automatically starts its child processes, child supervisor in our case. Child supervisor in turn starts its child processes; we have only one child in our case, ch1.

Let's make ch1 evaluate normal code:

2> gen_server:cast(ch1, calc).
result 2+2=4
ok

Now some bad code:

3> gen_server:cast(ch1, calcbad).
result 1/0
ok
ch1: terminating.

=ERROR REPORT==== 31-Jan-2011::01:38:44 ===
** Generic server ch1 terminating 
** Last message in was {'$gen_cast',calcbad}
** When Server state == []
** Reason for termination == 
** {badarith,[{ch1,handle_cast,2},
              {gen_server,handle_msg,5},
              {proc_lib,init_p_do_apply,3}]}
ch1 has started (<0.39.0>)
4> exit(normal).
ch1: terminating.
** exception exit: normal

As you may see child process ch1 was restarted by the child supervisor popd_listener_sup (notice ch1 has started (<0.39.0>)).

Since our shell and root supervisor are bidirectionally linked (call supervisor:start_link, not supervisor:start in the root supervisor function start_link/0), exit(normal) resulted in the root supervisor shutdown, but its child processes had some time to clean up.