Packages
brod
4.2.0
4.5.7
4.5.6
4.5.5
4.5.4
4.5.3
4.5.2
4.5.1
4.5.0
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.3
4.3.2
4.3.1
4.3.0
4.2.0
4.1.1
4.1.0
4.0.0
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.5
3.16.4
3.16.3
3.16.2
3.16.1
3.16.0
3.15.6
3.15.5
3.15.4
3.15.3
3.15.1
3.15.0
3.14.0
3.13.0
3.12.0
3.11.0
3.10.0
3.9.5
3.9.3
3.9.2
3.9.1
3.9.0
3.8.1
3.8.0
3.7.11
3.7.10
3.7.9
3.7.8
3.7.7
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.2
3.5.1
3.5.0
3.4.0
3.3.5
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.0
3.0.0
2.5.0
2.4.1
2.4.0
2.3.7
2.3.6
2.3.5
2.3.4
2.3.3
2.3.1
2.2.16
2.2.15
2.2.14
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.12
2.1.11
2.1.10
2.1.8
2.1.7
2.1.4
2.1.2
2.0.0
Apache Kafka Erlang client library
Current section
Files
Jump to
Current section
Files
src/brod_supervisor3.erl
%% Copied from https://github.com/klarna/supervisor3
%% This file is a copy of supervisor.erl from the R16B Erlang/OTP
%% distribution, with the following modifications:
%%
%% 1) the module name is supervisor2
%%
%% 2) a find_child/2 utility function has been added
%%
%% 3) Added an 'intrinsic' restart type. Like the transient type, this
%% type means the child should only be restarted if the child exits
%% abnormally. Unlike the transient type, if the child exits
%% normally, the supervisor itself also exits normally. If the
%% child is a supervisor and it exits normally (i.e. with reason of
%% 'shutdown') then the child's parent also exits normally.
%%
%% 4) child specifications can contain, as the restart type, a tuple
%% {permanent, Delay} | {transient, Delay} | {intrinsic, Delay}
%% where Delay >= 0 (see point (4) below for intrinsic). The delay,
%% in seconds, indicates what should happen if a child, upon being
%% restarted, exceeds the MaxT and MaxR parameters. Thus, if a
%% child exits, it is restarted as normal. If it exits sufficiently
%% quickly and often to exceed the boundaries set by the MaxT and
%% MaxR parameters, and a Delay is specified, then rather than
%% stopping the supervisor, the supervisor instead continues and
%% tries to start up the child again, Delay seconds later.
%%
%% Note that if a child is delay-restarted this will reset the
%% count of restarts towrds MaxR and MaxT. This matters if MaxT >
%% Delay, since otherwise we would fail to restart after the delay.
%%
%% Sometimes, you may wish for a transient or intrinsic child to
%% exit abnormally so that it gets restarted, but still log
%% nothing. gen_server will log any exit reason other than
%% 'normal', 'shutdown' or {'shutdown', _}. Thus the exit reason of
%% {'shutdown', 'restart'} is interpreted to mean you wish the
%% child to be restarted according to the delay parameters, but
%% gen_server will not log the error. Thus from gen_server's
%% perspective it's a normal exit, whilst from supervisor's
%% perspective, it's an abnormal exit.
%%
%% 5) normal, and {shutdown, _} exit reasons are all treated the same
%% (i.e. are regarded as normal exits)
%%
%% 6) rename the module to supervisor3
%%
%% 7) introduce post_init callback
%%
%% 8) call os:timestamp/0 and timer:now_diff/2 for timestamps
%%
%% 9) ignore delayed retry in MaxR accumulation
%%
%% Modifications 1-5 are (C) 2010-2013 GoPivotal, Inc.
%% Modifications 6-9 are (C) 2015 Klarna AB
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(brod_supervisor3).
-behaviour(gen_server).
%% External exports
-export([start_link/2, start_link/3,
start_child/2, restart_child/2,
delete_child/2, terminate_child/2,
which_children/1, count_children/1,
find_child/2, check_childspecs/1]).
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([try_again_restart/3]).
%%--------------------------------------------------------------------------
-export_type([child_spec/0, startchild_ret/0, strategy/0, sup_name/0]).
%%--------------------------------------------------------------------------
-type child() :: 'undefined' | pid().
-type child_id() :: term().
-type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}.
-type modules() :: [module()] | 'dynamic'.
-type delay() :: non_neg_integer().
-type restart() :: 'permanent'
| 'transient'
| 'temporary'
| 'intrinsic'
| {'permanent', delay()}
| {'transient', delay()}
| {'intrinsic', delay()}.
-type shutdown() :: 'brutal_kill' | timeout().
-type worker() :: 'worker' | 'supervisor'.
-type sup_name() :: {'local', Name :: atom()} | {'global', Name :: atom()}.
-type sup_ref() :: (Name :: atom())
| {Name :: atom(), Node :: node()}
| {'global', Name :: atom()}
| pid().
-type child_spec() :: {Id :: child_id(),
StartFunc :: mfargs(),
Restart :: restart(),
Shutdown :: shutdown(),
Type :: worker(),
Modules :: modules()}.
-type strategy() :: 'one_for_all' | 'one_for_one'
| 'rest_for_one' | 'simple_one_for_one'.
%%--------------------------------------------------------------------------
-type tref() :: reference().
-record(child, {% pid is undefined when child is not running
pid = undefined :: child()
| {restarting, pid()}
| {delayed_restart, tref()}
| [pid()],
name :: child_id(),
mfargs :: mfargs(),
restart_type :: restart(),
shutdown :: shutdown(),
child_type :: worker(),
modules = [] :: modules()}).
-type child_rec() :: #child{}.
-type dynamics() :: dict:dict() | sets:set().
-record(state, {name,
strategy,
children = [],
dynamics,
intensity,
period,
restarts = [],
module,
args}).
-type state() :: #state{name :: atom(),
strategy :: strategy(),
children :: [child_rec()],
dynamics :: dynamics(),
intensity :: non_neg_integer(),
period :: pos_integer(),
restarts :: list(),
module :: module(),
args :: list()
}.
-define(is_simple(State), State#state.strategy =:= simple_one_for_one).
-define(is_permanent(R), ((R =:= permanent) orelse
(is_tuple(R) andalso
tuple_size(R) == 2 andalso
element(1, R) =:= permanent))).
-define(is_explicit_restart(R),
R == {shutdown, restart}).
-callback init(Args :: term()) ->
{ok, {{RestartStrategy :: strategy(),
MaxR :: non_neg_integer(),
MaxT :: non_neg_integer()},
[ChildSpec :: child_spec()]}}
| ignore
| post_init.
-callback post_init(Args :: term()) ->
{ok, {{RestartStrategy :: strategy(),
MaxR :: non_neg_integer(),
MaxT :: non_neg_integer()},
[ChildSpec :: child_spec()]}}
| ignore.
-optional_callbacks([post_init/1]).
-define(restarting(_Pid_), {restarting, _Pid_}).
-define(delayed_restart(_TRef_), {delayed_restart, _TRef_}).
%%% ---------------------------------------------------
%%% This is a general process supervisor built upon gen_server.erl.
%%% Servers/processes should/could also be built using gen_server.erl.
%%% SupName = {local, atom()} | {global, atom()}.
%%% ---------------------------------------------------
-type startlink_err() :: {'already_started', pid()}
| {'shutdown', term()}
| term().
-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}.
-spec start_link(Module, Args) -> startlink_ret() when
Module :: module(),
Args :: term().
start_link(Mod, Args) ->
gen_server:start_link(?MODULE, {self, Mod, Args}, []).
-spec start_link(SupName, Module, Args) -> startlink_ret() when
SupName :: sup_name(),
Module :: module(),
Args :: term().
start_link(SupName, Mod, Args) ->
gen_server:start_link(SupName, ?MODULE, {SupName, Mod, Args}, []).
%%% ---------------------------------------------------
%%% Interface functions.
%%% ---------------------------------------------------
-type startchild_err() :: 'already_present'
| {'already_started', Child :: child()} | term().
-type startchild_ret() :: {'ok', Child :: child()}
| {'ok', Child :: child(), Info :: term()}
| {'error', startchild_err()}.
-spec start_child(SupRef, ChildSpec) -> startchild_ret() when
SupRef :: sup_ref(),
ChildSpec :: child_spec() | (List :: [term()]).
start_child(Supervisor, ChildSpec) ->
call(Supervisor, {start_child, ChildSpec}).
-spec restart_child(SupRef, Id) -> Result when
SupRef :: sup_ref(),
Id :: child_id(),
Result :: {'ok', Child :: child()}
| {'ok', Child :: child(), Info :: term()}
| {'error', Error},
Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one' |
term().
restart_child(Supervisor, Name) ->
call(Supervisor, {restart_child, Name}).
-spec delete_child(SupRef, Id) -> Result when
SupRef :: sup_ref(),
Id :: child_id(),
Result :: 'ok' | {'error', Error},
Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one'.
delete_child(Supervisor, Name) ->
call(Supervisor, {delete_child, Name}).
%%-----------------------------------------------------------------
%% Func: terminate_child/2
%% Returns: ok | {error, Reason}
%% Note that the child is *always* terminated in some
%% way (maybe killed).
%%-----------------------------------------------------------------
-spec terminate_child(SupRef, Id) -> Result when
SupRef :: sup_ref(),
Id :: pid() | child_id(),
Result :: 'ok' | {'error', Error},
Error :: 'not_found' | 'simple_one_for_one'.
terminate_child(Supervisor, Name) ->
call(Supervisor, {terminate_child, Name}).
-spec which_children(SupRef) -> [{Id, Child, Type, Modules}] when
SupRef :: sup_ref(),
Id :: child_id() | undefined,
Child :: child() | 'restarting',
Type :: worker(),
Modules :: modules().
which_children(Supervisor) ->
call(Supervisor, which_children).
-spec count_children(SupRef) -> PropListOfCounts when
SupRef :: sup_ref(),
PropListOfCounts :: [Count],
Count :: {specs, ChildSpecCount :: non_neg_integer()}
| {active, ActiveProcessCount :: non_neg_integer()}
| {supervisors, ChildSupervisorCount :: non_neg_integer()}
| {workers, ChildWorkerCount :: non_neg_integer()}.
count_children(Supervisor) ->
call(Supervisor, count_children).
-spec find_child(Supervisor, Name) -> [pid()] when
Supervisor :: sup_ref(),
Name :: child_id().
find_child(Supervisor, Name) ->
[Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor),
Name1 =:= Name].
call(Supervisor, Req) ->
gen_server:call(Supervisor, Req, infinity).
-spec check_childspecs(ChildSpecs) -> Result when
ChildSpecs :: [child_spec()],
Result :: 'ok' | {'error', Error :: term()}.
check_childspecs(ChildSpecs) when is_list(ChildSpecs) ->
case check_startspec(ChildSpecs) of
{ok, _} -> ok;
Error -> {error, Error}
end;
check_childspecs(X) -> {error, {badarg, X}}.
%%%-----------------------------------------------------------------
%%% Called by timer:apply_after from restart/2
-spec try_again_restart(SupRef, Child, Reason) -> ok when
SupRef :: sup_ref(),
Child :: child_id() | pid(),
Reason :: term().
try_again_restart(Supervisor, Child, Reason) ->
cast(Supervisor, {try_again_restart, Child, Reason}).
cast(Supervisor, Req) ->
gen_server:cast(Supervisor, Req).
%%% ---------------------------------------------------
%%%
%%% Initialize the supervisor.
%%%
%%% ---------------------------------------------------
-type init_sup_name() :: sup_name() | 'self'.
-type stop_rsn() :: {'shutdown', term()}
| {'bad_return', {module(), 'init', term()}}
| {'bad_start_spec', term()}
| {'start_spec', term()}
| {'supervisor_data', term()}.
-spec init({init_sup_name(), module(), [term()]}) ->
{'ok', state()} | 'ignore' | {'stop', stop_rsn()}.
init({SupName, Mod, Args}) ->
process_flag(trap_exit, true),
case Mod:init(Args) of
{ok, {SupFlags, StartSpec}} ->
do_init(SupName, SupFlags, StartSpec, Mod, Args);
post_init ->
self() ! {post_init, SupName, Mod, Args},
{ok, #state{}};
ignore ->
ignore;
Error ->
{stop, {bad_return, {Mod, init, Error}}}
end.
init_children(State, StartSpec) ->
SupName = State#state.name,
case check_startspec(StartSpec) of
{ok, Children} ->
case start_children(Children, SupName) of
{ok, NChildren} ->
{ok, State#state{children = NChildren}};
{error, NChildren, Reason} ->
terminate_children(NChildren, SupName),
{stop, {shutdown, Reason}}
end;
Error ->
{stop, {start_spec, Error}}
end.
init_dynamic(State, [StartSpec]) ->
case check_startspec([StartSpec]) of
{ok, Children} ->
{ok, State#state{children = Children}};
Error ->
{stop, {start_spec, Error}}
end;
init_dynamic(_State, StartSpec) ->
{stop, {bad_start_spec, StartSpec}}.
%%-----------------------------------------------------------------
%% Func: start_children/2
%% Args: Children = [child_rec()] in start order
%% SupName = {local, atom()} | {global, atom()} | {pid(), Mod}
%% Purpose: Start all children. The new list contains #child's
%% with pids.
%% Returns: {ok, NChildren} | {error, NChildren, Reason}
%% NChildren = [child_rec()] in termination order (reversed
%% start order)
%%-----------------------------------------------------------------
start_children(Children, SupName) -> start_children(Children, [], SupName).
start_children([Child|Chs], NChildren, SupName) ->
case do_start_child(SupName, Child) of
{ok, undefined} when Child#child.restart_type =:= temporary ->
start_children(Chs, NChildren, SupName);
{ok, Pid} ->
start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName);
{ok, Pid, _Extra} ->
start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName);
{error, Reason} ->
report_error(start_error, Reason, Child, SupName),
{error, lists:reverse(Chs) ++ [Child | NChildren],
{failed_to_start_child, Child#child.name, Reason}}
end;
start_children([], NChildren, _SupName) ->
{ok, NChildren}.
do_start_child(SupName, Child) ->
#child{mfargs = {M, F, Args}} = Child,
case catch apply(M, F, Args) of
{ok, Pid} when is_pid(Pid) ->
NChild = Child#child{pid = Pid},
report_progress(NChild, SupName),
{ok, Pid};
{ok, Pid, Extra} when is_pid(Pid) ->
NChild = Child#child{pid = Pid},
report_progress(NChild, SupName),
{ok, Pid, Extra};
ignore ->
{ok, undefined};
{error, What} -> {error, What};
What -> {error, What}
end.
do_start_child_i(M, F, A) ->
case catch apply(M, F, A) of
{ok, Pid} when is_pid(Pid) ->
{ok, Pid};
{ok, Pid, Extra} when is_pid(Pid) ->
{ok, Pid, Extra};
ignore ->
{ok, undefined};
{error, Error} ->
{error, Error};
What ->
{error, What}
end.
%%% ---------------------------------------------------
%%%
%%% Callback functions.
%%%
%%% ---------------------------------------------------
-type call() :: 'which_children' | 'count_children' | {_, _}. % XXX: refine
-spec handle_call(call(), term(), state()) -> {'reply', term(), state()}.
handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) ->
Child = hd(State#state.children),
#child{mfargs = {M, F, A}} = Child,
Args = A ++ EArgs,
case do_start_child_i(M, F, Args) of
{ok, undefined} when Child#child.restart_type =:= temporary ->
{reply, {ok, undefined}, State};
{ok, Pid} ->
NState = save_dynamic_child(Child#child.restart_type, Pid, Args,
State),
{reply, {ok, Pid}, NState};
{ok, Pid, Extra} ->
NState = save_dynamic_child(Child#child.restart_type, Pid, Args,
State),
{reply, {ok, Pid, Extra}, NState};
What ->
{reply, What, State}
end;
%% terminate_child for simple_one_for_one can only be done with pid
handle_call({terminate_child, Name}, _From, State) when not is_pid(Name),
?is_simple(State) ->
{reply, {error, simple_one_for_one}, State};
handle_call({terminate_child, Name}, _From, State) ->
case get_child(Name, State, ?is_simple(State)) of
{value, Child} ->
case do_terminate(Child, State#state.name) of
#child{restart_type=RT} when RT=:=temporary;
?is_simple(State) ->
{reply, ok, state_del_child(Child, State)};
NChild ->
{reply, ok, replace_child(NChild, State)}
end;
false ->
{reply, {error, not_found}, State}
end;
%%% The requests delete_child and restart_child are invalid for
%%% simple_one_for_one supervisors.
handle_call({_Req, _Data}, _From, State) when ?is_simple(State) ->
{reply, {error, simple_one_for_one}, State};
handle_call({start_child, ChildSpec}, _From, State) ->
case check_childspec(ChildSpec) of
{ok, Child} ->
{Resp, NState} = handle_start_child(Child, State),
{reply, Resp, NState};
What ->
{reply, {error, What}, State}
end;
handle_call({restart_child, Name}, _From, State) ->
case get_child(Name, State) of
{value, Child} when Child#child.pid =:= undefined ->
case do_start_child(State#state.name, Child) of
{ok, Pid} ->
NState = replace_child(Child#child{pid = Pid}, State),
{reply, {ok, Pid}, NState};
{ok, Pid, Extra} ->
NState = replace_child(Child#child{pid = Pid}, State),
{reply, {ok, Pid, Extra}, NState};
Error ->
{reply, Error, State}
end;
{value, #child{pid=?restarting(_)}} ->
{reply, {error, restarting}, State};
{value, #child{pid=?delayed_restart(_)}} ->
{reply, {error, restarting}, State};
{value, _} ->
{reply, {error, running}, State};
_ ->
{reply, {error, not_found}, State}
end;
handle_call({delete_child, Name}, _From, State) ->
case get_child(Name, State) of
{value, Child} when Child#child.pid =:= undefined ->
NState = remove_child(Child, State),
{reply, ok, NState};
{value, #child{pid=?restarting(_)}} ->
{reply, {error, restarting}, State};
{value, #child{pid=?delayed_restart(_)}} ->
{reply, {error, restarting}, State};
{value, _} ->
{reply, {error, running}, State};
_ ->
{reply, {error, not_found}, State}
end;
handle_call(which_children, _From,
#state{children = [#child{restart_type = temporary,
child_type = CT,
modules = Mods}]} = State)
when ?is_simple(State) ->
Reply = lists:map(fun(Pid) -> {undefined, Pid, CT, Mods} end,
sets:to_list(dynamics_db(temporary,
State#state.dynamics))),
{reply, Reply, State};
handle_call(which_children, _From,
#state{children = [#child{restart_type = RType,
child_type = CT,
modules = Mods}]} = State)
when ?is_simple(State) ->
Reply = lists:map(fun({?restarting(_), _}) -> {undefined, restarting, CT, Mods};
({Pid, _}) -> {undefined, Pid, CT, Mods} end,
dict:to_list(dynamics_db(RType, State#state.dynamics))),
{reply, Reply, State};
handle_call(which_children, _From, State) ->
Resp =
lists:map(fun(#child{pid = ?restarting(_), name = Name,
child_type = ChildType, modules = Mods}) ->
{Name, restarting, ChildType, Mods};
(#child{pid = ?delayed_restart(_), name = Name,
child_type = ChildType, modules = Mods}) ->
{Name, restarting, ChildType, Mods};
(#child{pid = Pid, name = Name,
child_type = ChildType, modules = Mods}) ->
{Name, Pid, ChildType, Mods}
end,
State#state.children),
{reply, Resp, State};
handle_call(count_children, _From,
#state{children = [#child{restart_type = temporary,
child_type = CT}]} = State)
when ?is_simple(State) ->
{Active, Count} =
sets:fold(fun(Pid, {Alive, Tot}) ->
count_if_alive(Pid, Alive, Tot)
end, {0, 0}, dynamics_db(temporary, State#state.dynamics)),
Reply = child_type(CT, Active, Count),
{reply, Reply, State};
handle_call(count_children, _From,
#state{children = [#child{restart_type = RType,
child_type = CT}]} = State)
when ?is_simple(State) ->
{Active, Count} =
dict:fold(fun(Pid, _Val, {Alive, Tot}) ->
count_if_alive(Pid, Alive, Tot)
end, {0, 0}, dynamics_db(RType, State#state.dynamics)),
Reply = child_type(CT, Active, Count),
{reply, Reply, State};
handle_call(count_children, _From, State) ->
%% Specs and children are together on the children list...
{Specs, Active, Supers, Workers} =
lists:foldl(fun(Child, Counts) ->
count_child(Child, Counts)
end, {0, 0, 0, 0}, State#state.children),
%% Reformat counts to a property list.
Reply = [{specs, Specs}, {active, Active},
{supervisors, Supers}, {workers, Workers}],
{reply, Reply, State}.
count_if_alive(Pid, Alive, Total) ->
case is_pid(Pid) andalso is_process_alive(Pid) of
true -> {Alive + 1, Total + 1};
false -> {Alive, Total + 1}
end.
child_type(supervisor, Active, Count) ->
[{specs, 1}, {active, Active}, {supervisors, Count}, {workers, 0}];
child_type(worker, Active, Count) ->
[{specs, 1}, {active, Active}, {supervisors, 0}, {workers, Count}].
count_child(#child{pid = Pid, child_type = worker},
{Specs, Active, Supers, Workers}) ->
case is_pid(Pid) andalso is_process_alive(Pid) of
true -> {Specs + 1, Active + 1, Supers, Workers + 1};
false -> {Specs + 1, Active, Supers, Workers + 1}
end;
count_child(#child{pid = Pid, child_type = supervisor},
{Specs, Active, Supers, Workers}) ->
case is_pid(Pid) andalso is_process_alive(Pid) of
true -> {Specs + 1, Active + 1, Supers + 1, Workers};
false -> {Specs + 1, Active, Supers + 1, Workers}
end.
%%% If a restart attempt failed, this message is sent via
%%% timer:apply_after(0,...) in order to give gen_server the chance to
%%% check it's inbox before trying again.
-spec handle_cast({try_again_restart, child_id() | pid(), term()}, state()) ->
{'noreply', state()} | {stop, shutdown, state()}.
handle_cast({try_again_restart, Pid, Reason}, #state{children=[Child]}=State)
when ?is_simple(State) ->
RT = Child#child.restart_type,
RPid = restarting(Pid),
case dynamic_child_args(RPid, dynamics_db(RT, State#state.dynamics)) of
{ok, Args} ->
{M, F, _} = Child#child.mfargs,
NChild = Child#child{pid = RPid, mfargs = {M, F, Args}},
try_restart(Child#child.restart_type, Reason, NChild, State);
error ->
{noreply, State}
end;
handle_cast({try_again_restart, Name, Reason}, State) ->
%% we still support >= R12-B3 in which lists:keyfind/3 doesn't exist
case lists:keysearch(Name, #child.name, State#state.children) of
{value, Child = #child{pid=?restarting(_), restart_type=RestartType}} ->
try_restart(RestartType, Reason, Child, State);
{value, Child = #child{pid=?delayed_restart(_), restart_type=RestartType}} ->
try_restart(RestartType, Reason, Child, State);
_ ->
{noreply, State}
end.
%%
%% Take care of terminated children.
%%
-spec handle_info(term(), state()) ->
{'noreply', state()} | {'stop', term(), state()}.
handle_info({'EXIT', Pid, Reason}, State) ->
case restart_child(Pid, Reason, State) of
{ok, State1} ->
{noreply, State1};
{shutdown, State1} ->
{stop, shutdown, State1}
end;
handle_info({delayed_restart, {RestartType, _Reason, Child}}, State)
when ?is_simple(State) ->
Reason = {?MODULE, delayed_restart},
try_restart(RestartType, Reason, Child, State#state{restarts = []}); %% [1]
handle_info({delayed_restart, {RestartType, _Reason, Child}}, State) ->
Reason = {?MODULE, delayed_restart},
case get_child(Child#child.name, State) of
{value, Child1} ->
try_restart(RestartType, Reason, Child1,
State#state{restarts = []}); %% [1]
_What ->
{noreply, State}
end;
%% [1] When we receive a delayed_restart message we want to reset the
%% restarts field since otherwise the MaxT might not have elapsed and
%% we would just delay again and again. Since a common use of the
%% delayed restart feature is for MaxR = 1, MaxT = some huge number
%% (so that we don't end up bouncing around in non-delayed restarts)
%% this is important.
handle_info({post_init, SupName, Mod, Args}, State0) ->
Res = case Mod:post_init(Args) of
{ok, {SupFlags, StartSpec}} ->
do_init(SupName, SupFlags, StartSpec, Mod, Args);
Error ->
{stop, {bad_return, {Mod, post_init, Error}}}
end,
%% map init/1 result type to handle_* result type
case Res of
{ok, NewState} -> {noreply, NewState};
{stop, Reason} -> {stop, Reason, State0}
end;
handle_info(Msg, State) ->
error_logger:error_msg("Supervisor received unexpected message: ~p~n",
[Msg]),
{noreply, State}.
%%
%% Terminate this server.
%%
-spec terminate(term(), state()) -> 'ok'.
terminate(_Reason, #state{children=[Child]} = State) when ?is_simple(State) ->
terminate_dynamic_children(Child, dynamics_db(Child#child.restart_type,
State#state.dynamics),
State#state.name);
terminate(_Reason, State) ->
terminate_children(State#state.children, State#state.name).
%%
%% Change code for the supervisor.
%% Call the new call-back module and fetch the new start specification.
%% Combine the new spec. with the old. If the new start spec. is
%% not valid the code change will not succeed.
%% Use the old Args as argument to Module:init/1.
%% NOTE: This requires that the init function of the call-back module
%% does not have any side effects.
%%
-spec code_change(term(), state(), term()) ->
{'ok', state()} | {'error', term()}.
code_change(_, State, _) ->
case (State#state.module):init(State#state.args) of
{ok, {SupFlags, StartSpec}} ->
case catch check_flags(SupFlags) of
ok ->
{Strategy, MaxIntensity, Period} = SupFlags,
update_childspec(State#state{strategy = Strategy,
intensity = MaxIntensity,
period = Period},
StartSpec);
Error ->
{error, Error}
end;
ignore ->
{ok, State};
Error ->
Error
end.
check_flags({Strategy, MaxIntensity, Period}) ->
valid_strategy(Strategy),
valid_intensity(MaxIntensity),
valid_period(Period),
ok;
check_flags(What) ->
{bad_flags, What}.
update_childspec(State, StartSpec) when ?is_simple(State) ->
case check_startspec(StartSpec) of
{ok, [Child]} ->
{ok, State#state{children = [Child]}};
Error ->
{error, Error}
end;
update_childspec(State, StartSpec) ->
case check_startspec(StartSpec) of
{ok, Children} ->
OldC = State#state.children, % In reverse start order !
NewC = update_childspec1(OldC, Children, []),
{ok, State#state{children = NewC}};
Error ->
{error, Error}
end.
update_childspec1([Child|OldC], Children, KeepOld) ->
case update_chsp(Child, Children) of
{ok, NewChildren} ->
update_childspec1(OldC, NewChildren, KeepOld);
false ->
update_childspec1(OldC, Children, [Child|KeepOld])
end;
update_childspec1([], Children, KeepOld) ->
%% Return them in (kept) reverse start order.
lists:reverse(Children ++ KeepOld).
update_chsp(OldCh, Children) ->
case lists:map(fun(Ch) when OldCh#child.name =:= Ch#child.name ->
Ch#child{pid = OldCh#child.pid};
(Ch) ->
Ch
end,
Children) of
Children ->
false; % OldCh not found in new spec.
NewC ->
{ok, NewC}
end.
%%% ---------------------------------------------------
%%% Start a new child.
%%% ---------------------------------------------------
handle_start_child(Child, State) ->
case get_child(Child#child.name, State) of
false ->
case do_start_child(State#state.name, Child) of
{ok, undefined} when Child#child.restart_type =:= temporary ->
{{ok, undefined}, State};
{ok, Pid} ->
{{ok, Pid}, save_child(Child#child{pid = Pid}, State)};
{ok, Pid, Extra} ->
{{ok, Pid, Extra},
save_child(Child#child{pid = Pid}, State)};
{error, What} ->
{{error, {What, Child}}, State}
end;
{value, OldChild} when is_pid(OldChild#child.pid) ->
{{error, {already_started, OldChild#child.pid}}, State};
{value, _OldChild} ->
{{error, already_present}, State}
end.
%%% ---------------------------------------------------
%%% Restart. A process has terminated.
%%% Returns: {ok, state()} | {shutdown, state()}
%%% ---------------------------------------------------
restart_child(Pid, Reason, #state{children = [Child]} = State)
when ?is_simple(State) ->
RestartType = Child#child.restart_type,
case dynamic_child_args(Pid, dynamics_db(RestartType,
State#state.dynamics)) of
{ok, Args} ->
{M, F, _} = Child#child.mfargs,
NChild = Child#child{pid = Pid, mfargs = {M, F, Args}},
do_restart(RestartType, Reason, NChild, State);
error ->
{ok, State}
end;
restart_child(Pid, Reason, State) ->
Children = State#state.children,
%% we still support >= R12-B3 in which lists:keyfind/3 doesn't exist
case lists:keysearch(Pid, #child.pid, Children) of
{value, #child{restart_type = RestartType} = Child} ->
do_restart(RestartType, Reason, Child, State);
false ->
{ok, State}
end.
try_restart(RestartType, Reason, Child, State) ->
case handle_restart(RestartType, Reason, Child, State) of
{ok, NState} -> {noreply, NState};
{shutdown, State2} -> {stop, shutdown, State2}
end.
do_restart(RestartType, Reason, Child, State) ->
maybe_report_error(RestartType, Reason, Child, State),
handle_restart(RestartType, Reason, Child, State).
maybe_report_error(permanent, Reason, Child, State) ->
report_child_termination(Reason, Child, State);
maybe_report_error({permanent, _}, Reason, Child, State) ->
report_child_termination(Reason, Child, State);
maybe_report_error(_Type, Reason, Child, State) ->
case is_abnormal_termination(Reason) of
true -> report_child_termination(Reason, Child, State);
false -> ok
end.
report_child_termination(Reason, Child, State) ->
report_error(child_terminated, Reason, Child, State#state.name).
handle_restart(permanent, _Reason, Child, State) ->
restart(Child, State);
handle_restart(transient, Reason, Child, State) ->
restart_if_explicit_or_abnormal(fun restart/2,
fun delete_child_and_continue/2,
Reason, Child, State);
handle_restart(intrinsic, Reason, Child, State) ->
restart_if_explicit_or_abnormal(fun restart/2,
fun delete_child_and_stop/2,
Reason, Child, State);
handle_restart(temporary, _Reason, Child, State) ->
delete_child_and_continue(Child, State);
handle_restart({permanent, _Delay}=Restart, Reason, Child, State) ->
do_restart_delay(Restart, Reason, Child, State);
handle_restart({transient, _Delay}=Restart, Reason, Child, State) ->
restart_if_explicit_or_abnormal(defer_to_restart_delay(Restart, Reason),
fun delete_child_and_continue/2,
Reason, Child, State);
handle_restart({intrinsic, _Delay}=Restart, Reason, Child, State) ->
restart_if_explicit_or_abnormal(defer_to_restart_delay(Restart, Reason),
fun delete_child_and_stop/2,
Reason, Child, State).
restart_if_explicit_or_abnormal(RestartHow, Otherwise, Reason, Child, State) ->
case ?is_explicit_restart(Reason) orelse is_abnormal_termination(Reason) of
true -> RestartHow(Child, State);
false -> Otherwise(Child, State)
end.
defer_to_restart_delay(Restart, Reason) ->
fun(Child, State) -> do_restart_delay(Restart, Reason, Child, State) end.
delete_child_and_continue(Child, State) ->
{ok, state_del_child(Child, State)}.
delete_child_and_stop(Child, State) ->
{shutdown, state_del_child(Child, State)}.
is_abnormal_termination(normal) -> false;
is_abnormal_termination(shutdown) -> false;
is_abnormal_termination({shutdown, _}) -> false;
is_abnormal_termination(_Other) -> true.
do_restart_delay({RestartType, Delay}, Reason, Child, State) ->
%% Reason =:= {?MODULE, delayed_restart} indicates
%% the first restart after delay (a clean retry)
%% do not add it to MaxR accumulation
IsCleanRetry = Reason =:= {?MODULE, delayed_restart},
case add_restart(State, IsCleanRetry) of
{ok, NState} ->
maybe_restart(NState#state.strategy, Child, NState);
{terminate, _NState} ->
%% we've reached the max restart intensity, but the
%% add_restart will have added to the restarts
%% field. Given we don't want to die here, we need to go
%% back to the old restarts field otherwise we'll never
%% attempt to restart later, which is why we ignore
%% NState for this clause.
TRef = erlang:send_after(trunc(Delay * 1000), self(),
{delayed_restart,
{{RestartType, Delay}, Reason, Child}}),
NState =
case ?is_simple(State) of
true ->
%% delete simple child, added back when restart
state_del_child(Child, State);
false ->
%% Do not delete (set pid = undefined) for non-simple ones
%% the ?restarting(_) state of the child should be kept.
%%
%% Otherwise, in case the child failed to start at delayed retry
%% (e.g. the process crashed in Module:init/1 which leads to
%% a 'start_error' error in restart/3), an immediate
%% 'try_again_restart' loop back cast will be sent in
%% maybe_restart/3, then the 'handle_cast' callback
%% will fail to find the child in ?restarting state,
%% hence ignore the restart, which will in turn
%% cause it to enter a permanent zombie state.
replace_child(Child#child{pid = ?delayed_restart(TRef)}, State)
end,
{ok, NState}
end.
restart(Child, State) ->
case add_restart(State) of
{ok, NState} ->
maybe_restart(NState#state.strategy, Child, NState);
{terminate, NState} ->
report_error(shutdown, reached_max_restart_intensity,
Child, State#state.name),
{shutdown, remove_child(Child, NState)}
end.
maybe_restart(Strategy, Child, State) ->
case restart(Strategy, Child, State) of
{try_again, Reason, NState2} ->
%% Leaving control back to gen_server before
%% trying again. This way other incoming requsts
%% for the supervisor can be handled - e.g. a
%% shutdown request for the supervisor or the
%% child.
Id = if ?is_simple(State) -> Child#child.pid;
true -> Child#child.name
end,
timer:apply_after(0, ?MODULE, try_again_restart, [self(), Id, Reason]),
{ok, NState2};
Other ->
Other
end.
restart(simple_one_for_one, Child, State) ->
#child{pid = OldPid, mfargs = {M, F, A}} = Child,
Dynamics = dict:erase(OldPid, dynamics_db(Child#child.restart_type,
State#state.dynamics)),
case do_start_child_i(M, F, A) of
{ok, Pid} ->
NState = State#state{dynamics = dict:store(Pid, A, Dynamics)},
{ok, NState};
{ok, Pid, _Extra} ->
NState = State#state{dynamics = dict:store(Pid, A, Dynamics)},
{ok, NState};
{error, Error} ->
NState = State#state{dynamics = dict:store(restarting(OldPid), A,
Dynamics)},
report_error(start_error, Error, Child, State#state.name),
{try_again, Error, NState}
end;
restart(one_for_one, Child, State) ->
OldPid = Child#child.pid,
case do_start_child(State#state.name, Child) of
{ok, Pid} ->
NState = replace_child(Child#child{pid = Pid}, State),
{ok, NState};
{ok, Pid, _Extra} ->
NState = replace_child(Child#child{pid = Pid}, State),
{ok, NState};
{error, Reason} ->
NState = replace_child(Child#child{pid = restarting(OldPid)},
State),
report_error(start_error, Reason, Child, State#state.name),
{try_again, Reason, NState}
end;
restart(rest_for_one, Child, State) ->
{ChAfter, ChBefore} = split_child(Child#child.pid, State#state.children),
ChAfter2 = terminate_children(ChAfter, State#state.name),
case start_children(ChAfter2, State#state.name) of
{ok, ChAfter3} ->
{ok, State#state{children = ChAfter3 ++ ChBefore}};
{error, ChAfter3, Reason} ->
NChild = Child#child{pid=restarting(Child#child.pid)},
NState = State#state{children = ChAfter3 ++ ChBefore},
{try_again, Reason, replace_child(NChild, NState)}
end;
restart(one_for_all, Child, State) ->
Children1 = del_child(Child#child.pid, State#state.children),
Children2 = terminate_children(Children1, State#state.name),
case start_children(Children2, State#state.name) of
{ok, NChs} ->
{ok, State#state{children = NChs}};
{error, NChs, Reason} ->
NChild = Child#child{pid=restarting(Child#child.pid)},
NState = State#state{children = NChs},
{try_again, Reason, replace_child(NChild, NState)}
end.
restarting(Pid) when is_pid(Pid) -> ?restarting(Pid);
restarting(RPid) -> RPid.
%%-----------------------------------------------------------------
%% Func: terminate_children/2
%% Args: Children = [child_rec()] in termination order
%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod}
%% Returns: NChildren = [child_rec()] in
%% startup order (reversed termination order)
%%-----------------------------------------------------------------
terminate_children(Children, SupName) ->
terminate_children(Children, SupName, []).
%% Temporary children should not be restarted and thus should
%% be skipped when building the list of terminated children, although
%% we do want them to be shut down as many functions from this module
%% use this function to just clear everything.
terminate_children([Child = #child{restart_type=temporary} | Children],
SupName, Res) ->
do_terminate(Child, SupName),
terminate_children(Children, SupName, Res);
terminate_children([Child | Children], SupName, Res) ->
NChild = do_terminate(Child, SupName),
terminate_children(Children, SupName, [NChild | Res]);
terminate_children([], _SupName, Res) ->
Res.
do_terminate(Child, SupName) when is_pid(Child#child.pid) ->
case shutdown(Child#child.pid, Child#child.shutdown) of
ok ->
ok;
{error, normal} when not ?is_permanent(Child#child.restart_type) ->
ok;
{error, OtherReason} ->
report_error(shutdown_error, OtherReason, Child, SupName)
end,
Child#child{pid = undefined};
do_terminate(Child = #child{pid = ?delayed_restart(TRef)}, _SupName) ->
erlang:cancel_timer(TRef),
Child#child{pid = undefined};
do_terminate(Child, _SupName) ->
Child#child{pid = undefined}.
%%-----------------------------------------------------------------
%% Shutdowns a child. We must check the EXIT value
%% of the child, because it might have died with another reason than
%% the wanted. In that case we want to report the error. We put a
%% monitor on the child an check for the 'DOWN' message instead of
%% checking for the 'EXIT' message, because if we check the 'EXIT'
%% message a "naughty" child, who does unlink(Sup), could hang the
%% supervisor.
%% Returns: ok | {error, OtherReason} (this should be reported)
%%-----------------------------------------------------------------
shutdown(Pid, brutal_kill) ->
case monitor_child(Pid) of
ok ->
exit(Pid, kill),
receive
{'DOWN', _MRef, process, Pid, killed} ->
ok;
{'DOWN', _MRef, process, Pid, OtherReason} ->
{error, OtherReason}
end;
{error, Reason} ->
{error, Reason}
end;
shutdown(Pid, Time) ->
case monitor_child(Pid) of
ok ->
exit(Pid, shutdown), %% Try to shutdown gracefully
receive
{'DOWN', _MRef, process, Pid, shutdown} ->
ok;
{'DOWN', _MRef, process, Pid, OtherReason} ->
{error, OtherReason}
after Time ->
exit(Pid, kill), %% Force termination.
receive
{'DOWN', _MRef, process, Pid, OtherReason} ->
{error, OtherReason}
end
end;
{error, Reason} ->
{error, Reason}
end.
%% Help function to shutdown/2 switches from link to monitor approach
monitor_child(Pid) ->
%% Do the monitor operation first so that if the child dies
%% before the monitoring is done causing a 'DOWN'-message with
%% reason noproc, we will get the real reason in the 'EXIT'-message
%% unless a naughty child has already done unlink...
erlang:monitor(process, Pid),
unlink(Pid),
receive
%% If the child dies before the unlik we must empty
%% the mail-box of the 'EXIT'-message and the 'DOWN'-message.
{'EXIT', Pid, Reason} ->
receive
{'DOWN', _, process, Pid, _} ->
{error, Reason}
end
after 0 ->
%% If a naughty child did unlink and the child dies before
%% monitor the result will be that shutdown/2 receives a
%% 'DOWN'-message with reason noproc.
%% If the child should die after the unlink there
%% will be a 'DOWN'-message with a correct reason
%% that will be handled in shutdown/2.
ok
end.
%%-----------------------------------------------------------------
%% Func: terminate_dynamic_children/3
%% Args: Child = child_rec()
%% Dynamics = dict() | set()
%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod}
%% Returns: ok
%%
%%
%% Shutdown all dynamic children. This happens when the supervisor is
%% stopped. Because the supervisor can have millions of dynamic children, we
%% can have an significative overhead here.
%%-----------------------------------------------------------------
terminate_dynamic_children(Child, Dynamics, SupName) ->
{Pids, EStack0} = monitor_dynamic_children(Child, Dynamics),
Sz = sets:size(Pids),
EStack = case Child#child.shutdown of
brutal_kill ->
sets:fold(fun(P, _) -> exit(P, kill) end, ok, Pids),
wait_dynamic_children(Child, Pids, Sz, undefined, EStack0);
infinity ->
sets:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids),
wait_dynamic_children(Child, Pids, Sz, undefined, EStack0);
Time ->
sets:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids),
TRef = erlang:start_timer(Time, self(), kill),
wait_dynamic_children(Child, Pids, Sz, TRef, EStack0)
end,
%% Unroll stacked errors and report them
dict:fold(fun(Reason, Ls, _) ->
report_error(shutdown_error, Reason,
Child#child{pid=Ls}, SupName)
end, ok, EStack).
monitor_dynamic_children(#child{restart_type=temporary}, Dynamics) ->
sets:fold(fun(P, {Pids, EStack}) ->
case monitor_child(P) of
ok ->
{sets:add_element(P, Pids), EStack};
{error, normal} ->
{Pids, EStack};
{error, Reason} ->
{Pids, dict:append(Reason, P, EStack)}
end
end, {sets:new(), dict:new()}, Dynamics);
monitor_dynamic_children(#child{restart_type=RType}, Dynamics) ->
dict:fold(fun(P, _, {Pids, EStack}) when is_pid(P) ->
case monitor_child(P) of
ok ->
{sets:add_element(P, Pids), EStack};
{error, normal} when not ?is_permanent(RType) ->
{Pids, EStack};
{error, Reason} ->
{Pids, dict:append(Reason, P, EStack)}
end;
(?restarting(_), _, {Pids, EStack}) ->
{Pids, EStack}
end, {sets:new(), dict:new()}, Dynamics).
wait_dynamic_children(_Child, _Pids, 0, undefined, EStack) ->
EStack;
wait_dynamic_children(_Child, _Pids, 0, TRef, EStack) ->
%% If the timer has expired before its cancellation, we must empty the
%% mail-box of the 'timeout'-message.
erlang:cancel_timer(TRef),
receive
{timeout, TRef, kill} ->
EStack
after 0 ->
EStack
end;
wait_dynamic_children(#child{shutdown=brutal_kill} = Child, Pids, Sz,
TRef, EStack) ->
receive
{'DOWN', _MRef, process, Pid, killed} ->
wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
TRef, EStack);
{'DOWN', _MRef, process, Pid, Reason} ->
wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
TRef, dict:append(Reason, Pid, EStack))
end;
wait_dynamic_children(#child{restart_type=RType} = Child, Pids, Sz,
TRef, EStack) ->
receive
{'DOWN', _MRef, process, Pid, shutdown} ->
wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
TRef, EStack);
{'DOWN', _MRef, process, Pid, normal} when not ?is_permanent(RType) ->
wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
TRef, EStack);
{'DOWN', _MRef, process, Pid, Reason} ->
wait_dynamic_children(Child, sets:del_element(Pid, Pids), Sz-1,
TRef, dict:append(Reason, Pid, EStack));
{timeout, TRef, kill} ->
sets:fold(fun(P, _) -> exit(P, kill) end, ok, Pids),
wait_dynamic_children(Child, Pids, Sz-1, undefined, EStack)
end.
%%-----------------------------------------------------------------
%% Child/State manipulating functions.
%%-----------------------------------------------------------------
%% Note we do not want to save the parameter list for temporary processes as
%% they will not be restarted, and hence we do not need this information.
%% Especially for dynamic children to simple_one_for_one supervisors
%% it could become very costly as it is not uncommon to spawn
%% very many such processes.
save_child(#child{restart_type = temporary,
mfargs = {M, F, _}} = Child,
#state{children = Children} = State) ->
State#state{children = [Child#child{mfargs = {M, F, undefined}} |Children]};
save_child(Child, #state{children = Children} = State) ->
State#state{children = [Child |Children]}.
save_dynamic_child(temporary, Pid, _, #state{dynamics = Dynamics} = State) ->
State#state{dynamics = sets:add_element(Pid,
dynamics_db(temporary, Dynamics))};
save_dynamic_child(RestartType, Pid, Args,
#state{dynamics = Dynamics} = State) ->
State#state{dynamics = dict:store(Pid, Args,
dynamics_db(RestartType, Dynamics))}.
dynamics_db(temporary, undefined) ->
sets:new();
dynamics_db(_, undefined) ->
dict:new();
dynamics_db(_, Dynamics) ->
Dynamics.
dynamic_child_args(Pid, Dynamics) ->
case sets:is_set(Dynamics) of
true ->
{ok, undefined};
false ->
dict:find(Pid, Dynamics)
end.
state_del_child(#child{pid = Pid, restart_type = temporary}, State)
when ?is_simple(State) ->
NDynamics = sets:del_element(Pid, dynamics_db(temporary,
State#state.dynamics)),
State#state{dynamics = NDynamics};
state_del_child(#child{pid = Pid, restart_type = RType}, State)
when ?is_simple(State) ->
NDynamics = dict:erase(Pid, dynamics_db(RType, State#state.dynamics)),
State#state{dynamics = NDynamics};
state_del_child(Child, State) ->
NChildren = del_child(Child#child.name, State#state.children),
State#state{children = NChildren}.
del_child(Name, [Ch=#child{pid = ?restarting(_)}|_]=Chs)
when Ch#child.name =:= Name ->
Chs;
del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name,
Ch#child.restart_type =:= temporary ->
Chs;
del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name ->
[Ch#child{pid = undefined} | Chs];
del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid,
Ch#child.restart_type =:= temporary ->
Chs;
del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid ->
[Ch#child{pid = undefined} | Chs];
del_child(Name, [Ch|Chs]) ->
[Ch|del_child(Name, Chs)];
del_child(_, []) ->
[].
%% Chs = [S4, S3, Ch, S1, S0]
%% Ret: {[S4, S3, Ch], [S1, S0]}
split_child(Name, Chs) ->
split_child(Name, Chs, []).
split_child(Name, [Ch|Chs], After) when Ch#child.name =:= Name ->
{lists:reverse([Ch#child{pid = undefined} | After]), Chs};
split_child(Pid, [Ch|Chs], After) when Ch#child.pid =:= Pid ->
{lists:reverse([Ch#child{pid = undefined} | After]), Chs};
split_child(Name, [Ch|Chs], After) ->
split_child(Name, Chs, [Ch | After]);
split_child(_, [], After) ->
{lists:reverse(After), []}.
get_child(Name, State) ->
get_child(Name, State, false).
get_child(Pid, State, AllowPid) when AllowPid, is_pid(Pid) ->
get_dynamic_child(Pid, State);
get_child(Name, State, _) ->
lists:keysearch(Name, #child.name, State#state.children).
get_dynamic_child(Pid, #state{children=[Child], dynamics=Dynamics}) ->
DynamicsDb = dynamics_db(Child#child.restart_type, Dynamics),
case is_dynamic_pid(Pid, DynamicsDb) of
true ->
{value, Child#child{pid=Pid}};
false ->
RPid = restarting(Pid),
case {is_dynamic_pid(RPid, DynamicsDb), erlang:is_process_alive(Pid)} of
{true, _} ->
{value, Child#child{pid=RPid}};
{false, false} ->
{value, Child};
{false, true} ->
false
end
end.
is_dynamic_pid(Pid, Dynamics) ->
case sets:is_set(Dynamics) of
true ->
sets:is_element(Pid, Dynamics);
false ->
dict:is_key(Pid, Dynamics)
end.
replace_child(Child, State) ->
Chs = do_replace_child(Child, State#state.children),
State#state{children = Chs}.
do_replace_child(Child, [Ch|Chs]) when Ch#child.name =:= Child#child.name ->
[Child | Chs];
do_replace_child(Child, [Ch|Chs]) ->
[Ch|do_replace_child(Child, Chs)].
remove_child(Child, State) ->
Chs = lists:keydelete(Child#child.name, #child.name, State#state.children),
State#state{children = Chs}.
%%-----------------------------------------------------------------
%% Func: do_init/5
%% Args: SupName = {local, atom()} | {global, atom()} | self
%% Type = {Strategy, MaxIntensity, Period}
%% Strategy = one_for_one | one_for_all | simple_one_for_one |
%% rest_for_one
%% MaxIntensity = integer() >= 0
%% Period = integer() > 0
%% Mod :== atom()
%% Args :== term()
%% Purpose: Check that Type is of correct type (!)
%% Returns: {ok, state()} | Error
%%-----------------------------------------------------------------
do_init(SupName, Type, StartSpec, Mod, Args) ->
case catch init_state(SupName, Type, Mod, Args) of
{ok, State} when ?is_simple(State) ->
init_dynamic(State, StartSpec);
{ok, State} ->
init_children(State, StartSpec);
Error ->
{stop, {supervisor_data, Error}}
end.
init_state(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) ->
valid_strategy(Strategy),
valid_intensity(MaxIntensity),
valid_period(Period),
{ok, #state{name = supname(SupName, Mod),
strategy = Strategy,
intensity = MaxIntensity,
period = Period,
module = Mod,
args = Args}};
init_state(_SupName, Type, _, _) ->
{invalid_type, Type}.
valid_strategy(simple_one_for_one) -> true;
valid_strategy(one_for_one) -> true;
valid_strategy(one_for_all) -> true;
valid_strategy(rest_for_one) -> true;
valid_strategy(What) -> throw({invalid_strategy, What}).
valid_intensity(Max) when is_integer(Max),
Max >= 0 -> true;
valid_intensity(What) -> throw({invalid_intensity, What}).
valid_period(Period) when is_integer(Period),
Period > 0 -> true;
valid_period(What) -> throw({invalid_period, What}).
supname(self, Mod) -> {self(), Mod};
supname(N, _) -> N.
%%% ------------------------------------------------------
%%% Check that the children start specification is valid.
%%% Shall be a six (6) tuple
%%% {Name, Func, RestartType, Shutdown, ChildType, Modules}
%%% where Name is an atom
%%% Func is {Mod, Fun, Args} == {atom(), atom(), list()}
%%% RestartType is permanent | temporary | transient |
%%% intrinsic | {permanent, Delay} |
%%% {transient, Delay} | {intrinsic, Delay}
%% where Delay >= 0
%%% Shutdown = integer() > 0 | infinity | brutal_kill
%%% ChildType = supervisor | worker
%%% Modules = [atom()] | dynamic
%%% Returns: {ok, [child_rec()]} | Error
%%% ------------------------------------------------------
check_startspec(Children) -> check_startspec(Children, []).
check_startspec([ChildSpec|T], Res) ->
case check_childspec(ChildSpec) of
{ok, Child} ->
case lists:keymember(Child#child.name, #child.name, Res) of
true -> {duplicate_child_name, Child#child.name};
false -> check_startspec(T, [Child | Res])
end;
Error -> Error
end;
check_startspec([], Res) ->
{ok, lists:reverse(Res)}.
check_childspec({Name, Func, RestartType, Shutdown, ChildType, Mods}) ->
catch check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods);
check_childspec(X) -> {invalid_child_spec, X}.
check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods) ->
valid_name(Name),
valid_func(Func),
valid_restart_type(RestartType),
valid_child_type(ChildType),
valid_shutdown(Shutdown, ChildType),
valid_mods(Mods),
{ok, #child{name = Name, mfargs = Func, restart_type = RestartType,
shutdown = Shutdown, child_type = ChildType, modules = Mods}}.
valid_child_type(supervisor) -> true;
valid_child_type(worker) -> true;
valid_child_type(What) -> throw({invalid_child_type, What}).
valid_name(_Name) -> true.
valid_func({M, F, A}) when is_atom(M),
is_atom(F),
is_list(A) -> true;
valid_func(Func) -> throw({invalid_mfa, Func}).
valid_restart_type(permanent) -> true;
valid_restart_type(temporary) -> true;
valid_restart_type(transient) -> true;
valid_restart_type(intrinsic) -> true;
valid_restart_type({permanent, Delay}) -> valid_delay(Delay);
valid_restart_type({intrinsic, Delay}) -> valid_delay(Delay);
valid_restart_type({transient, Delay}) -> valid_delay(Delay);
valid_restart_type(RestartType) -> throw({invalid_restart_type,
RestartType}).
valid_delay(Delay) when is_number(Delay),
Delay >= 0 -> true;
valid_delay(What) -> throw({invalid_delay, What}).
valid_shutdown(Shutdown, _)
when is_integer(Shutdown), Shutdown > 0 -> true;
valid_shutdown(infinity, _) -> true;
valid_shutdown(brutal_kill, _) -> true;
valid_shutdown(Shutdown, _) -> throw({invalid_shutdown, Shutdown}).
valid_mods(dynamic) -> true;
valid_mods(Mods) when is_list(Mods) ->
lists:foreach(fun(Mod) ->
if
is_atom(Mod) -> ok;
true -> throw({invalid_module, Mod})
end
end,
Mods);
valid_mods(Mods) -> throw({invalid_modules, Mods}).
%%% ------------------------------------------------------
%%% Add a new restart and calculate if the max restart
%%% intensity has been reached (in that case the supervisor
%%% shall terminate).
%%% All restarts accured inside the period amount of seconds
%%% are kept in the #state.restarts list.
%%% Returns: {ok, State'} | {terminate, State'}
%%% ------------------------------------------------------
add_restart(State) ->
add_restart(State, _IsCleanRetry = false).
add_restart(State, IsCleanRetry) ->
MaxR = State#state.intensity,
P = State#state.period,
R = State#state.restarts,
Now = os:timestamp(),
%% when it is a clean retry do not add it to maxR accumulation.
%%
%% NOTE: it may still return {terminate, ...} when for example
%% delayed retry is too quick, i.e. DelayedSeconds < MaxT
R1 = case IsCleanRetry of
true -> delete_old_restarts(R, Now, P);
false -> delete_old_restarts([Now|R], Now, P)
end,
State1 = State#state{restarts = R1},
case length(R1) of
Count when Count =< MaxR ->
{ok, State1};
_ ->
{terminate, State1}
end.
delete_old_restarts([], _, _) -> [];
delete_old_restarts([R|Restarts], Now, Period) ->
case is_in_period(R, Now, Period) of
true -> [R | delete_old_restarts(Restarts, Now, Period)];
_ -> []
end.
is_in_period(Time, Now, Period) ->
(timer:now_diff(Now, Time) div 1000000) =< Period.
%%% ------------------------------------------------------
%%% Error and progress reporting.
%%% ------------------------------------------------------
report_error(Error, Reason, Child, SupName) ->
ErrorMsg = [{supervisor, SupName},
{errorContext, Error},
{reason, Reason},
{offender, extract_child(Child)}],
error_logger:error_report(supervisor_report, ErrorMsg).
extract_child(Child) when is_list(Child#child.pid) ->
[{nb_children, length(Child#child.pid)} | extract_child_common(Child)];
extract_child(Child) ->
[{pid, Child#child.pid} | extract_child_common(Child)].
extract_child_common(Child) ->
[{id, Child#child.name},
{mfargs, Child#child.mfargs},
{restart_type, Child#child.restart_type},
{shutdown, Child#child.shutdown},
{child_type, Child#child.child_type}].
report_progress(Child, SupName) ->
Progress = [{supervisor, SupName},
{started, extract_child(Child)}],
logger:log(debug, Progress).