|
92 | 92 | modules = [] :: [module()] | dynamic |
93 | 93 | }). |
94 | 94 | %% note: the list of children should always be kept in order, with first 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 -> |
@@ -319,7 +344,15 @@ handle_child_exit(Pid, Reason, State) -> |
319 | 344 | #child{} = Child -> |
320 | 345 | case should_restart(Reason, Child#child.restart) of |
321 | 346 | true -> |
322 | | - handle_restart_strategy(Child, State); |
| 347 | + case add_restart(State) of |
| 348 | + {ok, State1} -> |
| 349 | + handle_restart_strategy(Child, State1); |
| 350 | + {shutdown, State1} -> |
| 351 | + RemainingChildren = lists:keydelete( |
| 352 | + Pid, #child.pid, State1#state.children |
| 353 | + ), |
| 354 | + {shutdown, State1#state{children = RemainingChildren}} |
| 355 | + end; |
323 | 356 | false -> |
324 | 357 | Children = lists:keydelete(Pid, #child.pid, State#state.children), |
325 | 358 | {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]); |
@@ -498,6 +533,47 @@ verify_shutdown(#child{pid = Pid, shutdown = Timeout} = _Child) -> |
498 | 533 | end |
499 | 534 | end. |
500 | 535 |
|
| 536 | +add_restart( |
| 537 | + #state{ |
| 538 | + intensity = Intensity, period = Period, restart_count = RestartCount, restarts = Restarts |
| 539 | + } = State |
| 540 | +) -> |
| 541 | + Now = erlang:monotonic_time(millisecond), |
| 542 | + Threshold = Now - Period * 1000, |
| 543 | + case can_restart(Intensity, Threshold, Restarts, RestartCount) of |
| 544 | + {true, RestartCount1, Restarts1} -> |
| 545 | + {ok, State#state{ |
| 546 | + restarts = Restarts1 ++ [Now], restart_count = RestartCount1 + 1 |
| 547 | + }}; |
| 548 | + {false, _RestartCount1, _Restarts1} -> |
| 549 | + % TODO: log supervisor shutdown due to maximum intensity exceeded |
| 550 | + {shutdown, State} |
| 551 | + end. |
| 552 | + |
| 553 | +can_restart(0, _, _, _) -> |
| 554 | + {false, 0, []}; |
| 555 | +can_restart(_, _, _, 0) -> |
| 556 | + {true, 0, []}; |
| 557 | +can_restart(Intensity, Threshold, Restarts, RestartCount) when |
| 558 | + RestartCount >= ?STALE_RESTART_LIMIT |
| 559 | +-> |
| 560 | + {NewCount, Restarts1} = trim_expired_restarts(Threshold, lists:sort(Restarts)), |
| 561 | + can_restart(Intensity, Threshold, Restarts1, NewCount); |
| 562 | +can_restart(Intensity, Threshold, [Restart | _] = Restarts, RestartCount) when |
| 563 | + RestartCount >= Intensity andalso Restart < Threshold |
| 564 | +-> |
| 565 | + {NewCount, Restarts1} = trim_expired_restarts(Threshold, lists:sort(Restarts)), |
| 566 | + can_restart(Intensity, Threshold, Restarts1, NewCount); |
| 567 | +can_restart(Intensity, _, Restarts, RestartCount) when RestartCount >= Intensity -> |
| 568 | + {false, RestartCount, Restarts}; |
| 569 | +can_restart(Intensity, _, Restarts, RestartCount) when RestartCount < Intensity -> |
| 570 | + {true, RestartCount, Restarts}. |
| 571 | + |
| 572 | +trim_expired_restarts(Threshold, [Restart | Restarts]) when Restart < Threshold -> |
| 573 | + trim_expired_restarts(Threshold, Restarts); |
| 574 | +trim_expired_restarts(_Threshold, Restarts) -> |
| 575 | + {length(Restarts), Restarts}. |
| 576 | + |
501 | 577 | child_to_info(#child{id = Id, pid = Pid, type = Type, modules = Modules}) -> |
502 | 578 | Child = |
503 | 579 | case Pid of |
|
0 commit comments