|
92 | 92 | modules = [] :: [module()] | dynamic |
93 | 93 | }). |
94 | 94 | %% note: the list of children should always be kept in order, with last to start at the head. |
95 | | --record(state, {restart_strategy = one_for_one :: strategy(), children = [] :: [#child{}]}). |
| 95 | +-record(state, { |
| 96 | + restart_strategy = one_for_one :: strategy(), |
| 97 | + intensity = 1 :: non_neg_integer(), |
| 98 | + period = 5 :: pos_integer(), |
| 99 | + restart_count = 0 :: non_neg_integer(), |
| 100 | + restarts = [] :: [integer()], |
| 101 | + children = [] :: [#child{}] |
| 102 | +}). |
| 103 | + |
| 104 | +%% Used to trim stale restarts when the 'intensity' value is large. |
| 105 | +%% The number of restarts before triggering a purge of restarts older |
| 106 | +%% than 'period', so stale restarts do not continue to consume ram for |
| 107 | +%% the sake of MCUs with limited memory. In the future a function |
| 108 | +%% could be used to set a sane default for the platform (OTP uses 1000). |
| 109 | +-define(STALE_RESTART_LIMIT, 100). |
96 | 110 |
|
97 | 111 | start_link(Module, Args) -> |
98 | 112 | gen_server:start_link(?MODULE, {Module, Args}, []). |
@@ -121,12 +135,23 @@ count_children(Supervisor) -> |
121 | 135 | init({Mod, Args}) -> |
122 | 136 | erlang:process_flag(trap_exit, true), |
123 | 137 | case Mod:init(Args) of |
124 | | - {ok, {{Strategy, _Intensity, _Period}, StartSpec}} -> |
125 | | - State = init_state(StartSpec, #state{restart_strategy = Strategy}), |
| 138 | + {ok, {{Strategy, Intensity, Period}, StartSpec}} -> |
| 139 | + State = init_state(StartSpec, #state{ |
| 140 | + restart_strategy = Strategy, |
| 141 | + intensity = Intensity, |
| 142 | + period = Period |
| 143 | + }), |
126 | 144 | NewChildren = start_children(State#state.children, []), |
127 | 145 | {ok, State#state{children = NewChildren}}; |
128 | | - {ok, {#{strategy := Strategy}, StartSpec}} -> |
129 | | - State = init_state(StartSpec, #state{restart_strategy = Strategy}), |
| 146 | + {ok, {#{} = SupSpec, StartSpec}} -> |
| 147 | + Strategy = maps:get(strategy, SupSpec, one_for_one), |
| 148 | + Intensity = maps:get(intensity, SupSpec, 3), |
| 149 | + Period = maps:get(period, SupSpec, 5), |
| 150 | + State = init_state(StartSpec, #state{ |
| 151 | + restart_strategy = Strategy, |
| 152 | + intensity = Intensity, |
| 153 | + period = Period |
| 154 | + }), |
130 | 155 | NewChildren = start_children(State#state.children, []), |
131 | 156 | {ok, State#state{children = NewChildren}}; |
132 | 157 | Error -> |
@@ -322,7 +347,15 @@ handle_child_exit(Pid, Reason, State) -> |
322 | 347 | #child{} = Child -> |
323 | 348 | case should_restart(Reason, Child#child.restart) of |
324 | 349 | true -> |
325 | | - handle_restart_strategy(Child, State); |
| 350 | + case add_restart(State) of |
| 351 | + {ok, State1} -> |
| 352 | + handle_restart_strategy(Child, State1); |
| 353 | + {shutdown, State1} -> |
| 354 | + RemainingChildren = lists:keydelete( |
| 355 | + Pid, #child.pid, State1#state.children |
| 356 | + ), |
| 357 | + {shutdown, State1#state{children = RemainingChildren}} |
| 358 | + end; |
326 | 359 | false -> |
327 | 360 | Children = lists:keydelete(Pid, #child.pid, State#state.children), |
328 | 361 | {noreply, State#state{children = Children}} |
@@ -370,6 +403,8 @@ should_restart(Reason, transient) -> |
370 | 403 |
|
371 | 404 | loop_terminate([#child{pid = undefined} | Tail], AccRemaining) -> |
372 | 405 | loop_terminate(Tail, AccRemaining); |
| 406 | +loop_terminate([#child{pid = {restarting, _}} | Tail], AccRemaining) -> |
| 407 | + loop_terminate(Tail, AccRemaining); |
373 | 408 | loop_terminate([#child{pid = Pid} = Child | Tail], AccRemaining) when is_pid(Pid) -> |
374 | 409 | do_terminate(Child), |
375 | 410 | loop_terminate(Tail, [Pid | AccRemaining]); |
@@ -454,6 +489,47 @@ do_terminate_one_for_all([Child | Children], StopPids) -> |
454 | 489 | do_terminate_one_for_all(Children, StopPids) |
455 | 490 | end. |
456 | 491 |
|
| 492 | +add_restart( |
| 493 | + #state{ |
| 494 | + intensity = Intensity, period = Period, restart_count = RestartCount, restarts = Restarts |
| 495 | + } = State |
| 496 | +) -> |
| 497 | + Now = erlang:monotonic_time(millisecond), |
| 498 | + Threshold = Now - Period * 1000, |
| 499 | + case can_restart(Intensity, Threshold, Restarts, RestartCount) of |
| 500 | + {true, RestartCount1, Restarts1} -> |
| 501 | + {ok, State#state{ |
| 502 | + restarts = Restarts1 ++ [Now], restart_count = RestartCount1 + 1 |
| 503 | + }}; |
| 504 | + {false, _RestartCount1, _Restarts1} -> |
| 505 | + % TODO: log supervisor shutdown due to maximum intensity exceeded |
| 506 | + {shutdown, State} |
| 507 | + end. |
| 508 | + |
| 509 | +can_restart(0, _, _, _) -> |
| 510 | + {false, 0, []}; |
| 511 | +can_restart(_, _, _, 0) -> |
| 512 | + {true, 0, []}; |
| 513 | +can_restart(Intensity, Threshold, Restarts, RestartCount) when |
| 514 | + RestartCount >= ?STALE_RESTART_LIMIT |
| 515 | +-> |
| 516 | + {NewCount, Restarts1} = trim_expired_restarts(Threshold, lists:sort(Restarts)), |
| 517 | + can_restart(Intensity, Threshold, Restarts1, NewCount); |
| 518 | +can_restart(Intensity, Threshold, [Restart | _] = Restarts, RestartCount) when |
| 519 | + RestartCount >= Intensity andalso Restart < Threshold |
| 520 | +-> |
| 521 | + {NewCount, Restarts1} = trim_expired_restarts(Threshold, lists:sort(Restarts)), |
| 522 | + can_restart(Intensity, Threshold, Restarts1, NewCount); |
| 523 | +can_restart(Intensity, _, Restarts, RestartCount) when RestartCount >= Intensity -> |
| 524 | + {false, RestartCount, Restarts}; |
| 525 | +can_restart(Intensity, _, Restarts, RestartCount) when RestartCount < Intensity -> |
| 526 | + {true, RestartCount, Restarts}. |
| 527 | + |
| 528 | +trim_expired_restarts(Threshold, [Restart | Restarts]) when Restart < Threshold -> |
| 529 | + trim_expired_restarts(Threshold, Restarts); |
| 530 | +trim_expired_restarts(_Threshold, Restarts) -> |
| 531 | + {length(Restarts), Restarts}. |
| 532 | + |
457 | 533 | child_to_info(#child{id = Id, pid = Pid, type = Type, modules = Modules}) -> |
458 | 534 | Child = |
459 | 535 | case Pid of |
|
0 commit comments