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.