Current section
Files
Jump to
Current section
Files
src/ides_march.erl
-module(ides_march).
-doc "Kill graph analysis and restart logic for ides.".
-export([kill_graph/1, should_restart/2, affected_siblings/1]).
-type exit_reason() :: normal | abnormal.
-export_type([exit_reason/0]).
-doc #{
f => kill_graph,
a => 1,
d =>
"Return all PIDs that could cause `TargetPid` to be killed.\n"
"\n"
"This is the set of ancestors unioned with siblings that trigger\n"
"cascade restarts under the parent supervisor's strategy:\n"
"- `one_for_one` / `simple_one_for_one`: only ancestors (no\n"
" sibling killers)\n"
"- `one_for_all`: all siblings are killers\n"
"- `rest_for_one`: siblings at positions before the target are\n"
" killers"
}.
-spec kill_graph(TargetPid :: pid()) -> {ok, [pid()]} | {error, term()}.
kill_graph(TargetPid) ->
case ides_family:get_ancestors(TargetPid) of
{ok, Ancestors} when Ancestors =/= [] ->
AncestorPids = lists:filtermap(
fun(P) ->
try ides_family:resolve_pid(P) of
Pid -> {true, Pid}
catch
_:_ -> false
end
end,
Ancestors
),
case ides_family:parent_info(TargetPid) of
{ok, Info} ->
KillerSiblings = killer_siblings(Info),
{ok,
ordsets:to_list(
ordsets:union(
ordsets:from_list(AncestorPids),
ordsets:from_list(KillerSiblings)
)
)};
{error, Reason} ->
{error, Reason}
end;
{ok, []} ->
{error, no_ancestors};
{error, Reason} ->
{error, Reason}
end.
-doc #{
f => should_restart,
a => 2,
d =>
"Return whether a terminated child `Pid` would be restarted by\n"
"its supervisor.\n"
"\n"
"Rules:\n"
"- `permanent`: always restarted\n"
"- `transient`: restarted only on `abnormal` exit\n"
"- `temporary`: never restarted"
}.
-spec should_restart(Pid :: pid(), Reason :: exit_reason()) -> boolean().
should_restart(Pid, ExitReason) ->
case ides_family:get_ancestors(Pid) of
{ok, Ancestors} when Ancestors =/= [] ->
[Parent | _] = Ancestors,
ParentPid = ides_family:resolve_pid(Parent),
ChildSpecs = supervisor:which_children(ParentPid),
{Id, _ChildPid, _Type, _Mods} = lists:keyfind(Pid, 2, ChildSpecs),
RestartType = ides_family:get_restart_type(ParentPid, Id),
should_restart_rule(RestartType, ExitReason);
_ ->
false
end.
-doc #{
f => affected_siblings,
a => 1,
d =>
"Return the PIDs of siblings that would be killed or restarted\n"
"if `TargetPid` dies.\n"
"\n"
"Depends on the parent supervisor's strategy:\n"
"- `one_for_one`: `TargetPid` only\n"
"- `one_for_all`: all siblings\n"
"- `rest_for_one`: `TargetPid` and all siblings after it\n"
"- `simple_one_for_one`: `TargetPid` only"
}.
-spec affected_siblings(TargetPid :: pid()) -> {ok, [pid()]} | {error, term()}.
affected_siblings(TargetPid) ->
case ides_family:parent_info(TargetPid) of
{ok, Info} ->
Affected = affected_siblings_rule(Info),
{ok, Affected};
{error, Reason} ->
{error, Reason}
end.
%% --- Internal ---
-spec killer_siblings(Info :: ides_family:parent_info()) -> [pid()].
killer_siblings(#{sup_strategy := one_for_all, child_pids := Children, target_position := _Pos}) ->
pids(Children);
killer_siblings(#{sup_strategy := rest_for_one, child_pids := Children, target_position := Pos}) ->
pids(child_prefix(Children, Pos - 1));
killer_siblings(#{sup_strategy := _, child_pids := _Children, target_position := _Pos}) ->
[].
-spec should_restart_rule(
RestartType :: ides_family:child_restart_type(), ExitReason :: exit_reason()
) -> boolean().
should_restart_rule(permanent, _) -> true;
should_restart_rule(transient, abnormal) -> true;
should_restart_rule(transient, normal) -> false;
should_restart_rule(temporary, _) -> false.
-spec affected_siblings_rule(Info :: ides_family:parent_info()) -> [pid()].
affected_siblings_rule(#{
sup_strategy := one_for_one, child_pids := Children, target_position := Pos
}) ->
pids(child_sublist(Children, Pos, 1));
affected_siblings_rule(#{
sup_strategy := one_for_all, child_pids := Children, target_position := _Pos
}) ->
pids(Children);
affected_siblings_rule(#{
sup_strategy := rest_for_one, child_pids := Children, target_position := Pos
}) ->
pids(child_suffix(Children, Pos - 1));
affected_siblings_rule(#{
sup_strategy := simple_one_for_one, child_pids := Children, target_position := Pos
}) ->
pids(child_sublist(Children, Pos, 1)).
-spec pids(Children :: [{term(), pid()}]) -> [pid()].
pids(Children) ->
[Pid || {_Id, Pid} <- Children].
-spec child_sublist(Children :: [{term(), pid()}], Start :: pos_integer(), Len :: pos_integer()) ->
[{term(), pid()}].
child_sublist(Children, Start, Len) ->
child_sublist_skip(Children, Start - 1, Len).
child_sublist_skip(Children, 0, Len) ->
child_prefix(Children, Len);
child_sublist_skip([], _, _) ->
[];
child_sublist_skip([_ | T], N, Len) ->
child_sublist_skip(T, N - 1, Len).
-spec child_prefix(Children :: [{term(), pid()}], Len :: non_neg_integer()) -> [{term(), pid()}].
child_prefix(_Children, 0) -> [];
child_prefix([], _) -> [];
child_prefix([H | T], N) -> [H | child_prefix(T, N - 1)].
-spec child_suffix(Children :: [{term(), pid()}], Skip :: non_neg_integer()) -> [{term(), pid()}].
child_suffix(Children, 0) -> Children;
child_suffix([], _) -> [];
child_suffix([_ | T], N) -> child_suffix(T, N - 1).