Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 107 additions & 48 deletions tests/libs/estdlib/test_supervisor.erl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ test() ->
ok = test_one_for_all(),
ok = test_crash_limits(),
ok = try_again_restart(),
ok = try_again_restart_shutdown(),
ok = try_again_one_for_all(),
ok.

test_basic_supervisor() ->
Expand Down Expand Up @@ -222,7 +224,7 @@ child_start({trap_exit, Parent}) ->
end
end),
{ok, Pid};
child_start({get_permission, Arbitrator, Parent}) ->
child_start({get_permission, Arbitrator, Parent, Ref}) ->
Arbitrator ! {can_start, self()},
CanStart =
receive
Expand All @@ -234,11 +236,10 @@ child_start({get_permission, Arbitrator, Parent}) ->
true ->
Pid = spawn_link(fun() ->
receive
stop -> exit(normal)
stop -> ok
end
end),
register(crashy, Pid),
Parent ! Pid,
Parent ! {Ref, Pid},
{ok, Pid};
false ->
{error, start_denied};
Expand Down Expand Up @@ -380,19 +381,19 @@ init({test_crash_limits, Intensity, Period, Parent}) ->
}
],
{ok, {#{strategy => one_for_one, intensity => Intensity, period => Period}, ChildSpec}};
init({test_try_again, Arbitrator, Parent}) ->
init({test_try_again, Arbitrator, Parent, Ref}) ->
ChildSpec = [
#{
id => finicky_child,
start => {?MODULE, child_start, [{get_permission, Arbitrator, Parent}]},
start => {?MODULE, child_start, [{get_permission, Arbitrator, Parent, Ref}]},
restart => permanent,
shutdown => brutal_kill,
type => worker,
modules => [?MODULE]
}
],
{ok, {#{strategy => one_for_one, intensity => 5, period => 10}, ChildSpec}};
init({test_retry_one_for_all, Arbitrator, Parent}) ->
init({test_retry_one_for_all, Arbitrator, Parent, Ref}) ->
ChildSpec = [
#{
id => ping,
Expand All @@ -404,7 +405,7 @@ init({test_retry_one_for_all, Arbitrator, Parent}) ->
},
#{
id => crashy_child,
start => {?MODULE, child_start, [{get_permission, Arbitrator, Parent}]},
start => {?MODULE, child_start, [{get_permission, Arbitrator, Parent, Ref}]},
restart => permanent,
shutdown => brutal_kill,
type => worker,
Expand Down Expand Up @@ -603,72 +604,130 @@ try_again_restart() ->
process_flag(trap_exit, true),

%% Intensity is 5, use the arbitrator to prevent the child from restarting
%% 4 times. This should not exit the supervisor due to intensity.
Arbitrator1 = erlang:spawn(fun() -> arbitrator_start(4) end),
%% 3 times. With the other child, we'll have 4 restarts, and this should not
% exit the supervisor due to intensity.
Arbitrator1 = erlang:spawn(fun() -> arbitrator_start(3) end),
Ref1 = make_ref(),
{ok, SupPid1} = supervisor:start_link(
{local, try_again_test1}, ?MODULE, {test_try_again, Arbitrator1, self()}
{local, try_again_test1}, ?MODULE, {test_try_again, Arbitrator1, self(), Ref1}
),
ChildPid = wait_child_pid("ChildPid"),
ChildPid = wait_child_pid(Ref1, "ChildPid"),

ChildMonitor = monitor(process, ChildPid),
ChildPid ! stop,
ChildPid1 = wait_child_pid("ChildPid1"),
normal =
receive
{'DOWN', ChildMonitor, process, ChildPid, ChildReason} -> ChildReason
after 5000 -> timeout
end,

ChildPid1 = wait_child_pid(Ref1, "ChildPid1"),
ChildMonitor1 = monitor(process, ChildPid1),
ChildPid1 ! stop,
normal =
receive
{'DOWN', ChildMonitor1, process, ChildPid1, ChildReason1} -> ChildReason1
after 5000 -> timeout
end,

ArbitratorMonitor1 = monitor(process, Arbitrator1),
Arbitrator1 ! shutdown,
normal =
receive
{'DOWN', ArbitratorMonitor1, process, Arbitrator1, ArbitratorReason1} ->
ArbitratorReason1
after 5000 -> timeout
end,
exit(SupPid1, normal),
ok =
normal =
receive
{'EXIT', SupPid1, normal} ->
ok
after 2000 ->
{'EXIT', SupPid1, Reason} ->
Reason
after 5000 ->
error({supervisor_not_stopped, normal})
end,

%% Prevent 5 restart attempts allow on the 6th, this should cause the supervisor
%% to shutdown on the 6th attempt, which happens before period expires and we are
%% already at max restart intensity.
Arbitrator2 = erlang:spawn(fun() -> arbitrator_start(5) end),
{ok, SupPid2} = supervisor:start_link(
{local, test_try_again2}, ?MODULE, {test_try_again, Arbitrator2, self()}
process_flag(trap_exit, false),
ok.

try_again_restart_shutdown() ->
process_flag(trap_exit, true),

%% Intensity is 5, use the arbitrator to prevent the child from restarting
%% 4 times. With the other child, we'll have 4 restarts, so the supervisor
%% will shutdown due to intensity
Arbitrator1 = erlang:spawn(fun() -> arbitrator_start(4) end),
Ref = make_ref(),
{ok, SupPid1} = supervisor:start_link(
{local, try_again_test1}, ?MODULE, {test_try_again, Arbitrator1, self(), Ref}
),
ChildPid2 = wait_child_pid("ChildPid2"),
ChildPid = wait_child_pid(Ref, "ChildPid"),

ChildPid2 ! stop,
ok =
ChildMonitor = monitor(process, ChildPid),
ChildPid ! stop,
normal =
receive
{'EXIT', SupPid2, shutdown} ->
ok
after 2000 ->
error({supervisor_not_stopped, restart_try_again_exceeded})
{'DOWN', ChildMonitor, process, ChildPid, ChildReason} -> ChildReason
after 5000 -> timeout
end,

ChildPid1 = wait_child_pid(Ref, "ChildPid1"),
ChildMonitor1 = monitor(process, ChildPid1),
ChildPid1 ! stop,
normal =
receive
{'DOWN', ChildMonitor1, process, ChildPid1, ChildReason1} -> ChildReason1
after 5000 -> timeout
end,
Arbitrator2 ! shutdown,

ArbitratorMonitor1 = monitor(process, Arbitrator1),
Arbitrator1 ! shutdown,
normal =
receive
{'DOWN', ArbitratorMonitor1, process, Arbitrator1, ArbitratorReason1} ->
ArbitratorReason1
after 5000 -> timeout
end,
shutdown =
receive
{'EXIT', SupPid1, Reason} ->
Reason
after 5000 ->
error({supervisor_not_stopped, normal})
end,

process_flag(trap_exit, false),
ok.

try_again_one_for_all() ->
process_flag(trap_exit, true),

%% Test one_for_all
%% child 2 uses arbitrator to deny 4 restart attempts, succeeding on the 5th.
Arbitrator3 = erlang:spawn(fun() -> arbitrator_start(4) end),
Ref = make_ref(),
{ok, SupPid3} = supervisor:start_link(
{local, try_again_test3}, ?MODULE, {test_retry_one_for_all, Arbitrator3, self()}
{local, try_again_test3}, ?MODULE, {test_retry_one_for_all, Arbitrator3, self(), Ref}
),

{Ping1, _Crashy1} = wait_ping_server_and_child_pid(),
{Ping1, _Crashy1} = wait_ping_server_and_child_pid(Ref),

%% this will require 6 restarts (1 to restart ping + 4 denied attempts for
%% crashy and succeed on the 5th)
gen_server:call(Ping1, {stop, normal}),

%% Crashy will restart without error since the deny count was reached after
%% first time it was stopped
{Ping2, _Crashy2} = wait_ping_server_and_child_pid(),
{Ping2, _Crashy2} = wait_ping_server_and_child_pid(Ref),

%% this will surely exceed the limit
%crashy ! stop,
ok = gen_server:call(Ping2, {stop, normal}),

%% ping_pong_server has 2000ms timeout, we need to wait longer.
ok =
shutdown =
receive
{'EXIT', SupPid3, shutdown} ->
ok
{'EXIT', SupPid3, Reason} ->
Reason
after 5000 ->
error({supervisor_not_stopped, one_for_all_restarts_exceeded})
end,
Expand All @@ -677,39 +736,39 @@ try_again_restart() ->
process_flag(trap_exit, false),
ok.

wait_child_pid(Name) ->
wait_child_pid(Ref, Name) ->
receive
Pid when is_pid(Pid) ->
{Ref, Pid} when is_pid(Pid) ->
Pid;
{'EXIT', _, shutdown} ->
error({unexpected, supervisor_shutdown});
{'EXIT', _, _} ->
wait_child_pid(Name)
wait_child_pid(Ref, Name)
after 1000 ->
error({timeout, no_child_pid, Name})
end.

%% In the case where we have one_for_all, process all `ping_pong_server_ready`
%% messages until we get the crashy `pid()` message which means the crashy
%% process eventually started. Last `ping_pong_server_ready` will be received.
wait_ping_server_and_child_pid() ->
wait_ping_server_and_child_pid0(undefined, undefined).
wait_ping_server_and_child_pid(Ref) ->
wait_ping_server_and_child_pid0(Ref, undefined, undefined).

wait_ping_server_and_child_pid0(PingPongPid, ChildPid) ->
wait_ping_server_and_child_pid0(Ref, PingPongPid, ChildPid) ->
Timeout =
if
is_pid(PingPongPid) andalso is_pid(ChildPid) -> 0;
true -> 2000
end,
receive
{ping_pong_server_ready, NewPingPongPid} ->
wait_ping_server_and_child_pid0(NewPingPongPid, ChildPid);
NewChildPid when is_pid(NewChildPid) ->
wait_ping_server_and_child_pid0(PingPongPid, NewChildPid);
wait_ping_server_and_child_pid0(Ref, NewPingPongPid, ChildPid);
{Ref, NewChildPid} when is_pid(NewChildPid) ->
wait_ping_server_and_child_pid0(Ref, PingPongPid, NewChildPid);
{'EXIT', _, shutdown} ->
error({unexpected, supervisor_shutdown});
{'EXIT', _, _} ->
wait_ping_server_and_child_pid0(PingPongPid, ChildPid)
wait_ping_server_and_child_pid0(Ref, PingPongPid, ChildPid)
after Timeout ->
if
is_pid(PingPongPid) andalso is_pid(ChildPid) ->
Expand Down
Loading