Skip to content

Commit 7b3c462

Browse files
committed
Merge pull request #1458 from pguyot/w01/supervisor-improvements
Complete supervisor behavior - add missing `supervisor:terminate_child/2`, `supervisor:delete_child/2` and `supervisor:restart_child/2` - fix termination of children of supervisor - add more termination strategies These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 74258a1 + 50fca6d commit 7b3c462

File tree

6 files changed

+205
-25
lines changed

6 files changed

+205
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Added support for `registered_name` in `erlang:process_info/2` and `Process.info/2`
1515
- Added `net:gethostname/0` on platforms with gethostname(3).
1616
- Added `socket:getopt/2`
17+
- Added `supervisor:terminate_child/2`, `supervisor:restart_child/2` and `supervisor:delete_child/2`
1718

1819
### Fixed
1920
- ESP32: improved sntp sync speed from a cold boot.

libs/estdlib/src/gen_server.erl

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,8 @@ init_it(Starter, Module, Args, Options) ->
211211
end,
212212
case StateT of
213213
undefined -> ok;
214-
{State, {continue, Continue}} -> loop(State, {continue, Continue});
215-
{State, Timeout} -> loop(State, Timeout)
214+
{State, {continue, Continue}} -> loop(Starter, State, {continue, Continue});
215+
{State, Timeout} -> loop(Starter, State, Timeout)
216216
end.
217217

218218
init_ack(Parent, Return) ->
@@ -499,34 +499,34 @@ reply({Pid, Ref}, Reply) ->
499499
%%
500500

501501
%% @private
502-
loop(#state{mod = Mod, mod_state = ModState} = State, {continue, Continue}) ->
502+
loop(Parent, #state{mod = Mod, mod_state = ModState} = State, {continue, Continue}) ->
503503
case Mod:handle_continue(Continue, ModState) of
504504
{noreply, NewModState} ->
505-
loop(State#state{mod_state = NewModState}, infinity);
505+
loop(Parent, State#state{mod_state = NewModState}, infinity);
506506
{noreply, NewModState, {continue, NewContinue}} ->
507-
loop(State#state{mod_state = NewModState}, {continue, NewContinue});
507+
loop(Parent, State#state{mod_state = NewModState}, {continue, NewContinue});
508508
{stop, Reason, NewModState} ->
509509
do_terminate(State, Reason, NewModState)
510510
end;
511-
loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) ->
511+
loop(Parent, #state{mod = Mod, mod_state = ModState} = State, Timeout) ->
512512
receive
513513
{'$call', {_Pid, _Ref} = From, Request} ->
514514
case Mod:handle_call(Request, From, ModState) of
515515
{reply, Reply, NewModState} ->
516516
ok = reply(From, Reply),
517-
loop(State#state{mod_state = NewModState}, infinity);
517+
loop(Parent, State#state{mod_state = NewModState}, infinity);
518518
{reply, Reply, NewModState, {continue, Continue}} ->
519519
ok = reply(From, Reply),
520-
loop(State#state{mod_state = NewModState}, {continue, Continue});
520+
loop(Parent, State#state{mod_state = NewModState}, {continue, Continue});
521521
{reply, Reply, NewModState, NewTimeout} ->
522522
ok = reply(From, Reply),
523-
loop(State#state{mod_state = NewModState}, NewTimeout);
523+
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
524524
{noreply, NewModState} ->
525-
loop(State#state{mod_state = NewModState}, infinity);
525+
loop(Parent, State#state{mod_state = NewModState}, infinity);
526526
{noreply, NewModState, {continue, Continue}} ->
527-
loop(State#state{mod_state = NewModState}, {continue, Continue});
527+
loop(Parent, State#state{mod_state = NewModState}, {continue, Continue});
528528
{noreply, NewModState, NewTimeout} ->
529-
loop(State#state{mod_state = NewModState}, NewTimeout);
529+
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
530530
{stop, Reason, Reply, NewModState} ->
531531
ok = reply(From, Reply),
532532
do_terminate(State, Reason, NewModState);
@@ -538,24 +538,26 @@ loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) ->
538538
{'$cast', Request} ->
539539
case Mod:handle_cast(Request, ModState) of
540540
{noreply, NewModState} ->
541-
loop(State#state{mod_state = NewModState}, infinity);
541+
loop(Parent, State#state{mod_state = NewModState}, infinity);
542542
{noreply, NewModState, {continue, Continue}} ->
543-
loop(State#state{mod_state = NewModState}, {continue, Continue});
543+
loop(Parent, State#state{mod_state = NewModState}, {continue, Continue});
544544
{noreply, NewModState, NewTimeout} ->
545-
loop(State#state{mod_state = NewModState}, NewTimeout);
545+
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
546546
{stop, Reason, NewModState} ->
547547
do_terminate(State, Reason, NewModState);
548548
_ ->
549549
do_terminate(State, {error, unexpected_reply}, ModState)
550550
end;
551551
{'$stop', Reason} ->
552552
do_terminate(State, Reason, ModState);
553+
{'EXIT', Parent, Reason} ->
554+
do_terminate(State, Reason, ModState);
553555
Info ->
554556
case Mod:handle_info(Info, ModState) of
555557
{noreply, NewModState} ->
556-
loop(State#state{mod_state = NewModState}, infinity);
558+
loop(Parent, State#state{mod_state = NewModState}, infinity);
557559
{noreply, NewModState, NewTimeout} ->
558-
loop(State#state{mod_state = NewModState}, NewTimeout);
560+
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
559561
{stop, Reason, NewModState} ->
560562
do_terminate(State, Reason, NewModState);
561563
_ ->
@@ -564,9 +566,9 @@ loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) ->
564566
after Timeout ->
565567
case Mod:handle_info(timeout, ModState) of
566568
{noreply, NewModState} ->
567-
loop(State#state{mod_state = NewModState}, infinity);
569+
loop(Parent, State#state{mod_state = NewModState}, infinity);
568570
{noreply, NewModState, NewTimeout} ->
569-
loop(State#state{mod_state = NewModState}, NewTimeout);
571+
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
570572
{stop, Reason, NewModState} ->
571573
do_terminate(State, Reason, NewModState);
572574
_ ->

libs/estdlib/src/supervisor.erl

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@
2525
-export([
2626
start_link/2,
2727
start_link/3,
28-
start_child/2
28+
start_child/2,
29+
terminate_child/2,
30+
restart_child/2,
31+
delete_child/2
2932
]).
3033

3134
-export([
3235
init/1,
3336
handle_call/3,
3437
handle_cast/2,
35-
handle_info/2
38+
handle_info/2,
39+
terminate/2
3640
]).
3741

3842
-export_type([
@@ -41,7 +45,11 @@
4145
sup_flags/0
4246
]).
4347

44-
-type restart() :: permanent | transient | temporary.
48+
-type restart() ::
49+
permanent
50+
| transient
51+
| temporary
52+
| {terminating, permanent | transient | temporary, gen_server:from()}.
4553
-type shutdown() :: brutal_kill | timeout().
4654
-type child_type() :: worker | supervisor.
4755

@@ -90,6 +98,15 @@ start_link(SupName, Module, Args) ->
9098
start_child(Supervisor, ChildSpec) ->
9199
gen_server:call(Supervisor, {start_child, ChildSpec}).
92100

101+
terminate_child(Supervisor, ChildId) ->
102+
gen_server:call(Supervisor, {terminate_child, ChildId}).
103+
104+
restart_child(Supervisor, ChildId) ->
105+
gen_server:call(Supervisor, {restart_child, ChildId}).
106+
107+
delete_child(Supervisor, ChildId) ->
108+
gen_server:call(Supervisor, {delete_child, ChildId}).
109+
93110
init({Mod, Args}) ->
94111
erlang:process_flag(trap_exit, true),
95112
case Mod:init(Args) of
@@ -152,6 +169,16 @@ restart_child(Pid, Reason, State) ->
152169
case lists:keyfind(Pid, #child.pid, State#state.children) of
153170
false ->
154171
{ok, State};
172+
#child{restart = {terminating, temporary, From}} ->
173+
gen_server:reply(From, ok),
174+
NewChildren = lists:keydelete(Pid, #child.pid, State#state.children),
175+
{ok, State#state{children = NewChildren}};
176+
#child{restart = {terminating, Restart, From}} = Child ->
177+
gen_server:reply(From, ok),
178+
NewChildren = lists:keyreplace(Pid, #child.pid, State#state.children, Child#child{
179+
pid = undefined, restart = Restart
180+
}),
181+
{ok, State#state{children = NewChildren}};
155182
#child{} = Child ->
156183
case should_restart(Reason, Child#child.restart) of
157184
true ->
@@ -195,6 +222,46 @@ handle_call({start_child, ChildSpec}, _From, #state{children = Children} = State
195222
{error, _Reason} = ErrorT ->
196223
{reply, ErrorT, State}
197224
end
225+
end;
226+
handle_call({terminate_child, ID}, From, #state{children = Children} = State) ->
227+
case lists:keyfind(ID, #child.id, Children) of
228+
#child{pid = undefined} ->
229+
{reply, ok, State};
230+
#child{restart = Restart} = Child ->
231+
do_terminate(Child),
232+
NewChild = Child#child{restart = {terminating, Restart, From}},
233+
NewChildren = lists:keyreplace(ID, #child.id, Children, NewChild),
234+
{noreply, State#state{children = NewChildren}};
235+
false ->
236+
{reply, {error, not_found}, State}
237+
end;
238+
handle_call({restart_child, ID}, _From, #state{children = Children} = State) ->
239+
case lists:keyfind(ID, #child.id, Children) of
240+
#child{pid = undefined} = Child ->
241+
case try_start(Child) of
242+
{ok, NewPid, Result} ->
243+
NewChild = Child#child{pid = NewPid},
244+
NewChildren = lists:keyreplace(
245+
ID, #child.id, Children, NewChild
246+
),
247+
{reply, Result, State#state{children = NewChildren}};
248+
{error, _Reason} = ErrorT ->
249+
{reply, ErrorT, State}
250+
end;
251+
#child{} ->
252+
{reply, {error, running}, State};
253+
false ->
254+
{reply, {error, not_found}, State}
255+
end;
256+
handle_call({delete_child, ID}, _From, #state{children = Children} = State) ->
257+
case lists:keyfind(ID, #child.id, Children) of
258+
#child{pid = undefined} ->
259+
NewChildren = lists:keydelete(ID, #child.id, Children),
260+
{reply, ok, State#state{children = NewChildren}};
261+
#child{} ->
262+
{reply, {error, running}, State};
263+
false ->
264+
{reply, {error, not_found}, State}
198265
end.
199266

200267
handle_cast(_Msg, State) ->
@@ -207,10 +274,50 @@ handle_info({'EXIT', Pid, Reason}, State) ->
207274
{shutdown, State1} ->
208275
{stop, shutdown, State1}
209276
end;
277+
handle_info({ensure_killed, Pid}, State) ->
278+
case lists:keyfind(Pid, #child.pid, State#state.children) of
279+
false ->
280+
{noreply, State};
281+
#child{} ->
282+
exit(Pid, kill),
283+
{noreply, State}
284+
end;
210285
handle_info(_Msg, State) ->
211286
%TODO: log unexpected message
212287
{noreply, State}.
213288

289+
%% @hidden
290+
terminate(_Reason, #state{children = Children} = State) ->
291+
RemainingChildren = loop_terminate(Children, []),
292+
loop_wait_termination(RemainingChildren),
293+
{ok, State}.
294+
295+
loop_terminate([#child{pid = undefined} | Tail], AccRemaining) ->
296+
loop_terminate(Tail, AccRemaining);
297+
loop_terminate([#child{pid = Pid} = Child | Tail], AccRemaining) when is_pid(Pid) ->
298+
do_terminate(Child),
299+
loop_terminate(Tail, [Pid | AccRemaining]);
300+
loop_terminate([], AccRemaining) ->
301+
AccRemaining.
302+
303+
loop_wait_termination([]) ->
304+
ok;
305+
loop_wait_termination(RemainingChildren0) ->
306+
receive
307+
{'EXIT', Pid, _Reason} ->
308+
RemainingChildren1 = lists:delete(Pid, RemainingChildren0),
309+
loop_wait_termination(RemainingChildren1);
310+
{ensure_killed, Pid} ->
311+
case lists:member(Pid, RemainingChildren0) of
312+
true ->
313+
exit(Pid, kill),
314+
RemainingChildren1 = lists:delete(Pid, RemainingChildren0),
315+
loop_wait_termination(RemainingChildren1);
316+
false ->
317+
loop_wait_termination(RemainingChildren0)
318+
end
319+
end.
320+
214321
try_start(#child{start = {M, F, Args}} = Record) ->
215322
try
216323
case apply(M, F, Args) of
@@ -229,3 +336,11 @@ try_start(#child{start = {M, F, Args}} = Record) ->
229336
error:Error ->
230337
{error, {{'EXIT', Error}, Record}}
231338
end.
339+
340+
do_terminate(#child{pid = Pid, shutdown = brutal_kill}) ->
341+
exit(Pid, kill);
342+
do_terminate(#child{pid = Pid, shutdown = infinity}) ->
343+
exit(Pid, shutdown);
344+
do_terminate(#child{pid = Pid, shutdown = Timeout}) when is_integer(Timeout) ->
345+
exit(Pid, shutdown),
346+
erlang:send_after(Timeout, self(), {ensure_killed, Pid}).

src/libAtomVM/defaultatoms.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,5 @@ X(EXTERNAL_ATOM, "\x8", "external")
165165
X(LOCAL_ATOM, "\x5", "local")
166166

167167
X(REGISTERED_NAME_ATOM, "\xF", "registered_name")
168+
169+
X(SHUTDOWN_ATOM, "\x8", "shutdown")

src/libAtomVM/opcodesswitch.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7053,8 +7053,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
70537053
}
70547054
}
70557055

7056-
// Do not print crash dump if reason is normal.
7057-
if (x_regs[0] != LOWERCASE_EXIT_ATOM || x_regs[1] != NORMAL_ATOM) {
7056+
// Do not print crash dump if reason is normal or shutdown.
7057+
if (x_regs[0] != LOWERCASE_EXIT_ATOM || (x_regs[1] != NORMAL_ATOM && x_regs[1] != SHUTDOWN_ATOM)) {
70587058
dump(ctx);
70597059
}
70607060

tests/libs/estdlib/test_supervisor.erl

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ test() ->
3131
ok = test_start_child(),
3232
ok = test_start_child_ping_pong(),
3333
ok = test_supervisor_order(),
34+
ok = test_terminate_delete_child(),
35+
ok = test_terminate_timeout(),
3436
ok.
3537

3638
test_basic_supervisor() ->
@@ -85,6 +87,53 @@ test_start_child() ->
8587
exit(SupPid, shutdown),
8688
ok.
8789

90+
test_terminate_delete_child() ->
91+
{ok, SupPid} = supervisor:start_link(?MODULE, {test_no_child, self()}),
92+
{ok, Pid} = supervisor:start_child(SupPid, #{
93+
id => child_start, start => {?MODULE, child_start, [start]}
94+
}),
95+
{error, not_found} = supervisor:terminate_child(SupPid, Pid),
96+
{error, running} = supervisor:delete_child(SupPid, child_start),
97+
ok = supervisor:terminate_child(SupPid, child_start),
98+
ok = supervisor:delete_child(SupPid, child_start),
99+
{error, not_found} = supervisor:delete_child(SupPid, child_start),
100+
unlink(SupPid),
101+
exit(SupPid, shutdown),
102+
ok.
103+
104+
test_terminate_timeout() ->
105+
{ok, SupPid} = supervisor:start_link(?MODULE, {test_no_child, self()}),
106+
Self = self(),
107+
{ok, Pid} = supervisor:start_child(SupPid, #{
108+
id => child_start, start => {?MODULE, child_start, [{trap_exit, Self}]}, shutdown => 500
109+
}),
110+
ok = supervisor:terminate_child(SupPid, child_start),
111+
ok =
112+
receive
113+
{Pid, {SupPid, shutdown}} -> ok
114+
after 1000 -> timeout
115+
end,
116+
{ok, Pid2} = supervisor:restart_child(SupPid, child_start),
117+
Pid2 ! ok,
118+
ok = supervisor:terminate_child(SupPid, child_start),
119+
ok =
120+
receive
121+
{Pid2, {SupPid, shutdown}} -> ok
122+
after 1000 -> timeout
123+
end,
124+
ok = supervisor:delete_child(SupPid, child_start),
125+
{ok, Pid3} = supervisor:start_child(SupPid, #{
126+
id => child_start, start => {?MODULE, child_start, [{trap_exit, Self}]}, shutdown => 500
127+
}),
128+
unlink(SupPid),
129+
exit(SupPid, shutdown),
130+
ok =
131+
receive
132+
{Pid3, {SupPid, shutdown}} -> ok
133+
after 1000 -> timeout
134+
end,
135+
ok.
136+
88137
child_start(ignore) ->
89138
ignore;
90139
child_start(start) ->
@@ -104,7 +153,18 @@ child_start(info) ->
104153
child_start(error) ->
105154
{error, child_error};
106155
child_start(fail) ->
107-
fail.
156+
fail;
157+
child_start({trap_exit, Parent}) ->
158+
Pid = spawn_link(fun() ->
159+
process_flag(trap_exit, true),
160+
receive
161+
{'EXIT', From, Reason} -> Parent ! {self(), {From, Reason}}
162+
end,
163+
receive
164+
ok -> ok
165+
end
166+
end),
167+
{ok, Pid}.
108168

109169
test_ping_pong(SupPid) ->
110170
Pid1 = get_and_test_server(),

0 commit comments

Comments
 (0)