Several bug fixes found when running tests

Validate delay value
This commit is contained in:
Luke Bakken 2020-09-21 17:22:07 -07:00
parent db7dfdf0cf
commit 111124bc05
1 changed files with 68 additions and 65 deletions

View File

@ -3,7 +3,7 @@
%% %%
%% 1) the module name is supervisor2 %% 1) the module name is supervisor2
%% %%
%% 2) a internal_find_child/2 utility function has been added %% 2) a find_child/2 utility function has been added
%% %%
%% 3) Added an 'intrinsic' restart type. Like the transient type, this %% 3) Added an 'intrinsic' restart type. Like the transient type, this
%% type means the child should only be restarted if the child exits %% type means the child should only be restarted if the child exits
@ -69,8 +69,8 @@
start_child/2, restart_child/2, start_child/2, restart_child/2,
delete_child/2, terminate_child/2, delete_child/2, terminate_child/2,
which_children/1, count_children/1, which_children/1, count_children/1,
find_child/2, check_childspecs/1, get_childspec/2,
check_childspecs/1, get_childspec/2]). find_child/2]).
%% Internal exports %% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@ -179,9 +179,10 @@
-define(is_simple(State), State#state.strategy=:=simple_one_for_one). -define(is_simple(State), State#state.strategy=:=simple_one_for_one).
-define(is_temporary(_Child_), _Child_#child.restart_type=:=temporary). -define(is_temporary(_Child_), _Child_#child.restart_type=:=temporary).
-define(is_transient(_Child_), _Child_#child.restart_type=:=transient). -define(is_permanent(_Child_), ((_Child_#child.restart_type=:=permanent) orelse
-define(is_permanent(_Child_), _Child_#child.restart_type=:=permanent). (is_tuple(_Child_#child.restart_type) andalso
-define(is_intrinsic(_Child_), _Child_#child.restart_type=:=intrinsic). tuple_size(_Child_#child.restart_type) =:= 2 andalso
element(1, _Child_#child.restart_type) =:= permanent))).
-define(is_explicit_restart(R), -define(is_explicit_restart(R),
R == {shutdown, restart}). R == {shutdown, restart}).
@ -637,8 +638,9 @@ handle_info({'EXIT', Pid, Reason}, State) ->
handle_info({delayed_restart, {Reason, Child}}, State) when ?is_simple(State) -> handle_info({delayed_restart, {Reason, Child}}, State) when ?is_simple(State) ->
try_restart(Reason, Child, State#state{restarts = []}); %% [1] try_restart(Reason, Child, State#state{restarts = []}); %% [1]
handle_info({delayed_restart, {Reason, Child}}, State) -> handle_info({delayed_restart, {Reason, Child}}, State) ->
case internal_find_child(Child#child.id, State) of ChildId = Child#child.id,
{value, Child1} -> case internal_find_child(ChildId, State) of
{ok, Child1} ->
try_restart(Reason, Child1, State#state{restarts = []}); %% [1] try_restart(Reason, Child1, State#state{restarts = []}); %% [1]
_What -> _What ->
{noreply, State} {noreply, State}
@ -775,10 +777,10 @@ try_restart(Reason, Child, State) ->
{shutdown, State2} -> {stop, shutdown, State2} {shutdown, State2} -> {stop, shutdown, State2}
end. end.
do_restart(Reason, Child, State) when ?is_permanent(Child) -> do_restart(Reason, Child=#child{restart_type=permanent}, State) -> % is_permanent
?report_error(child_terminated, Reason, Child, State#state.name), ?report_error(child_terminated, Reason, Child, State#state.name),
restart(Child, State); restart(Child, State);
do_restart(Reason, Child=#child{restart_type={transient,_Delay}}, State) -> % is_permanent_delay do_restart(Reason, Child=#child{restart_type={permanent,_Delay}}, State) -> % is_permanent_delay
?report_error(child_terminated, Reason, Child, State#state.name), ?report_error(child_terminated, Reason, Child, State#state.name),
do_restart_delay(Reason, Child, State); do_restart_delay(Reason, Child, State);
do_restart(normal, Child, State) -> do_restart(normal, Child, State) ->
@ -790,7 +792,7 @@ do_restart(shutdown, Child, State) ->
do_restart({shutdown, _Term}, Child, State) -> do_restart({shutdown, _Term}, Child, State) ->
NState = del_child(Child, State), NState = del_child(Child, State),
{ok, NState}; {ok, NState};
do_restart(Reason, Child, State) when ?is_transient(Child) -> do_restart(Reason, Child=#child{restart_type=transient}, State) -> % is_transient
?report_error(child_terminated, Reason, Child, State#state.name), ?report_error(child_terminated, Reason, Child, State#state.name),
restart(Child, State); restart(Child, State);
do_restart(Reason, Child=#child{restart_type={transient,_Delay}}, State) -> % is_transient_delay do_restart(Reason, Child=#child{restart_type={transient,_Delay}}, State) -> % is_transient_delay
@ -798,12 +800,12 @@ do_restart(Reason, Child=#child{restart_type={transient,_Delay}}, State) -> % is
restart_if_explicit_or_abnormal(defer_to_restart_delay(Reason), restart_if_explicit_or_abnormal(defer_to_restart_delay(Reason),
fun delete_child_and_continue/2, fun delete_child_and_continue/2,
Reason, Child, State); Reason, Child, State);
do_restart(Reason, Child, State) when ?is_intrinsic(Child) -> do_restart(Reason, Child=#child{restart_type=intrinsic}, State) -> % is_intrinsic
?report_error(child_terminated, Reason, Child, State#state.name), ?report_error(child_terminated, Reason, Child, State#state.name),
restart_if_explicit_or_abnormal(fun restart/2, restart_if_explicit_or_abnormal(fun restart/2,
fun delete_child_and_stop/2, fun delete_child_and_stop/2,
Reason, Child, State); Reason, Child, State);
do_restart(Reason, Child=#child{restart_type={transient,_Delay}}, State) -> % is_intrinsic_delay do_restart(Reason, Child=#child{restart_type={intrinsic,_Delay}}, State) -> % is_intrinsic_delay
?report_error(child_terminated, Reason, Child, State#state.name), ?report_error(child_terminated, Reason, Child, State#state.name),
restart_if_explicit_or_abnormal(defer_to_restart_delay(Reason), restart_if_explicit_or_abnormal(defer_to_restart_delay(Reason),
fun delete_child_and_stop/2, fun delete_child_and_stop/2,
@ -834,26 +836,29 @@ is_abnormal_termination(shutdown) -> false;
is_abnormal_termination({shutdown, _}) -> false; is_abnormal_termination({shutdown, _}) -> false;
is_abnormal_termination(_Other) -> true. is_abnormal_termination(_Other) -> true.
do_restart_delay(Reason, Child0=#child{restart_type={_RestartType,Delay}}, State0) -> do_restart_delay(Reason,
Child = #child{id = ChildId,
pid = ChildPid0,
restart_type = {_RestartType, Delay}},
State0) ->
case add_restart(State0) of case add_restart(State0) of
{ok, State1} -> {ok, State1} ->
Strategy = State1#state.strategy, Strategy = State1#state.strategy,
maybe_restart(Strategy, Child0, State1); maybe_restart(Strategy, Child, State1);
{terminate, _State1} -> {terminate, State1} ->
%% we've reached the max restart intensity, but the %% we've reached the max restart intensity, but the
%% add_restart will have added to the restarts %% add_restart will have added to the restarts
%% field. Given we don't want to die here, we need to go %% field. Given we don't want to die here, we need to go
%% back to the old restarts field otherwise we'll never %% back to the old restarts field otherwise we'll never
%% attempt to restart later, which is why we ignore %% attempt to restart later, which is why we ignore
%% NState for this clause. %% NState for this clause.
Msg = {delayed_restart, {Reason, Child0}}, Msg = {delayed_restart, {Reason, Child}},
_TRef = erlang:send_after(trunc(Delay*1000), self(), Msg), _TRef = erlang:send_after(trunc(Delay*1000), self(), Msg),
Pid0 = Child0#child.pid, ChildPid1 = restarting(ChildPid0),
Pid1 = restarting(Pid0),
Child1 = Child0#child{pid=Pid1},
% Note: State0 is intentionally used here % Note: State0 is intentionally used here
State1 = replace_child(Child1, State0), % TODO LRB
{ok, State1} State2 = set_pid(ChildPid1, ChildId, State1),
{ok, State2}
end. end.
maybe_restart(Strategy, Child, State) -> maybe_restart(Strategy, Child, State) ->
@ -1290,16 +1295,6 @@ set_pid(Pid, Id, {Ids, Db}) ->
NewDb = maps:update_with(Id, fun(Child) -> Child#child{pid=Pid} end, Db), NewDb = maps:update_with(Id, fun(Child) -> Child#child{pid=Pid} end, Db),
{Ids,NewDb}. {Ids,NewDb}.
-spec replace_child(child(), state()) -> state().
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.id =:= Child#child.id ->
[Child | Chs];
do_replace_child(Child, [Ch|Chs]) ->
[Ch|do_replace_child(Child, Chs)].
%% Remove the Id and the child record from the process state %% Remove the Id and the child record from the process state
-spec remove_child(child_id(), state()) -> state(). -spec remove_child(child_id(), state()) -> state().
remove_child(Id, #state{children={Ids,Db}} = State) -> remove_child(Id, #state{children={Ids,Db}} = State) ->
@ -1494,11 +1489,19 @@ validFunc({M, F, A}) when is_atom(M),
validFunc(Func) -> throw({invalid_mfa, Func}). validFunc(Func) -> throw({invalid_mfa, Func}).
validRestartType(permanent) -> true; validRestartType(permanent) -> true;
validRestartType({permanent, Delay}) -> validDelay(Delay);
validRestartType(temporary) -> true; validRestartType(temporary) -> true;
validRestartType(transient) -> true; validRestartType(transient) -> true;
validRestartType({transient, Delay}) -> validDelay(Delay);
validRestartType(intrinsic) -> true; validRestartType(intrinsic) -> true;
validRestartType({intrinsic, Delay}) -> validDelay(Delay);
validRestartType(RestartType) -> throw({invalid_restart_type, RestartType}). validRestartType(RestartType) -> throw({invalid_restart_type, RestartType}).
validDelay(Delay) when is_number(Delay), Delay >= 0 ->
true;
validDelay(What) ->
throw({invalid_delay, What}).
validShutdown(Shutdown) validShutdown(Shutdown)
when is_integer(Shutdown), Shutdown > 0 -> true; when is_integer(Shutdown), Shutdown > 0 -> true;
validShutdown(infinity) -> true; validShutdown(infinity) -> true;