Skip to content

Commit 57ea3aa

Browse files
committed
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 Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent 74258a1 commit 57ea3aa

File tree

6 files changed

+190
-25
lines changed

6 files changed

+190
-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: 115 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,8 @@
4145
sup_flags/0
4246
]).
4347

44-
-type restart() :: permanent | transient | temporary.
48+
-type restart() ::
49+
permanent | transient | temporary | {terminating, permanent | transient | temporary, gen_server:from()}.
4550
-type shutdown() :: brutal_kill | timeout().
4651
-type child_type() :: worker | supervisor.
4752

@@ -90,6 +95,15 @@ start_link(SupName, Module, Args) ->
9095
start_child(Supervisor, ChildSpec) ->
9196
gen_server:call(Supervisor, {start_child, ChildSpec}).
9297

98+
terminate_child(Supervisor, ChildId) ->
99+
gen_server:call(Supervisor, {terminate_child, ChildId}).
100+
101+
restart_child(Supervisor, ChildId) ->
102+
gen_server:call(Supervisor, {restart_child, ChildId}).
103+
104+
delete_child(Supervisor, ChildId) ->
105+
gen_server:call(Supervisor, {delete_child, ChildId}).
106+
93107
init({Mod, Args}) ->
94108
erlang:process_flag(trap_exit, true),
95109
case Mod:init(Args) of
@@ -152,6 +166,16 @@ restart_child(Pid, Reason, State) ->
152166
case lists:keyfind(Pid, #child.pid, State#state.children) of
153167
false ->
154168
{ok, State};
169+
#child{restart = {terminating, temporary, From}} ->
170+
gen_server:reply(From, ok),
171+
NewChildren = lists:keydelete(Pid, #child.pid, State#state.children),
172+
{ok, State#state{children = NewChildren}};
173+
#child{restart = {terminating, Restart, From}} = Child ->
174+
gen_server:reply(From, ok),
175+
NewChildren = lists:keyreplace(Pid, #child.pid, State#state.children, Child#child{
176+
pid = undefined, restart = Restart
177+
}),
178+
{ok, State#state{children = NewChildren}};
155179
#child{} = Child ->
156180
case should_restart(Reason, Child#child.restart) of
157181
true ->
@@ -195,6 +219,46 @@ handle_call({start_child, ChildSpec}, _From, #state{children = Children} = State
195219
{error, _Reason} = ErrorT ->
196220
{reply, ErrorT, State}
197221
end
222+
end;
223+
handle_call({terminate_child, ID}, From, #state{children = Children} = State) ->
224+
case lists:keyfind(ID, #child.id, Children) of
225+
#child{pid = undefined} ->
226+
{reply, ok, State};
227+
#child{restart = Restart} = Child ->
228+
do_terminate(Child),
229+
NewChild = Child#child{restart = {terminating, Restart, From}},
230+
NewChildren = lists:keyreplace(ID, #child.id, Children, NewChild),
231+
{noreply, State#state{children = NewChildren}};
232+
false ->
233+
{reply, {error, not_found}, State}
234+
end;
235+
handle_call({restart_child, ID}, _From, #state{children = Children} = State) ->
236+
case lists:keyfind(ID, #child.id, Children) of
237+
#child{pid = undefined} = Child ->
238+
case try_start(Child) of
239+
{ok, NewPid, Result} ->
240+
NewChild = Child#child{pid = NewPid},
241+
NewChildren = lists:keyreplace(
242+
ID, #child.id, Children, NewChild
243+
),
244+
{reply, Result, State#state{children = NewChildren}};
245+
{error, _Reason} = ErrorT ->
246+
{reply, ErrorT, State}
247+
end;
248+
#child{} ->
249+
{reply, {error, running}, State};
250+
false ->
251+
{reply, {error, not_found}, State}
252+
end;
253+
handle_call({delete_child, ID}, _From, #state{children = Children} = State) ->
254+
case lists:keyfind(ID, #child.id, Children) of
255+
#child{pid = undefined} ->
256+
NewChildren = lists:keydelete(ID, #child.id, Children),
257+
{reply, ok, State#state{children = NewChildren}};
258+
#child{} ->
259+
{reply, {error, running}, State};
260+
false ->
261+
{reply, {error, not_found}, State}
198262
end.
199263

200264
handle_cast(_Msg, State) ->
@@ -207,10 +271,50 @@ handle_info({'EXIT', Pid, Reason}, State) ->
207271
{shutdown, State1} ->
208272
{stop, shutdown, State1}
209273
end;
274+
handle_info({ensure_killed, Pid}, State) ->
275+
case lists:keyfind(Pid, #child.pid, State#state.children) of
276+
false ->
277+
{noreply, State};
278+
#child{} ->
279+
exit(Pid, kill),
280+
{noreply, State}
281+
end;
210282
handle_info(_Msg, State) ->
211283
%TODO: log unexpected message
212284
{noreply, State}.
213285

286+
%% @hidden
287+
terminate(_Reason, #state{children = Children} = State) ->
288+
RemainingChildren = loop_terminate(Children, []),
289+
loop_wait_termination(RemainingChildren),
290+
{ok, State}.
291+
292+
loop_terminate([#child{pid = undefined} | Tail], AccRemaining) ->
293+
loop_terminate(Tail, AccRemaining);
294+
loop_terminate([#child{pid = Pid} = Child | Tail], AccRemaining) when is_pid(Pid) ->
295+
do_terminate(Child),
296+
loop_terminate(Tail, [Pid | AccRemaining]);
297+
loop_terminate([], AccRemaining) ->
298+
AccRemaining.
299+
300+
loop_wait_termination([]) ->
301+
ok;
302+
loop_wait_termination(RemainingChildren0) ->
303+
receive
304+
{'EXIT', Pid, _Reason} ->
305+
RemainingChildren1 = lists:delete(Pid, RemainingChildren0),
306+
loop_wait_termination(RemainingChildren1);
307+
{ensure_killed, Pid} ->
308+
case lists:member(Pid, RemainingChildren0) of
309+
true ->
310+
exit(Pid, kill),
311+
RemainingChildren1 = lists:delete(Pid, RemainingChildren0),
312+
loop_wait_termination(RemainingChildren1);
313+
false ->
314+
loop_wait_termination(RemainingChildren0)
315+
end
316+
end.
317+
214318
try_start(#child{start = {M, F, Args}} = Record) ->
215319
try
216320
case apply(M, F, Args) of
@@ -229,3 +333,11 @@ try_start(#child{start = {M, F, Args}} = Record) ->
229333
error:Error ->
230334
{error, {{'EXIT', Error}, Record}}
231335
end.
336+
337+
do_terminate(#child{pid = Pid, shutdown = brutal_kill}) ->
338+
exit(Pid, kill);
339+
do_terminate(#child{pid = Pid, shutdown = infinity}) ->
340+
exit(Pid, shutdown);
341+
do_terminate(#child{pid = Pid, shutdown = Timeout}) when is_integer(Timeout) ->
342+
exit(Pid, shutdown),
343+
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: 49 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,41 @@ 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 = receive {Pid, {SupPid, shutdown}} -> ok after 1000 -> timeout end,
112+
{ok, Pid2} = supervisor:restart_child(SupPid, child_start),
113+
Pid2 ! ok,
114+
ok = supervisor:terminate_child(SupPid, child_start),
115+
ok = receive {Pid2, {SupPid, shutdown}} -> ok after 1000 -> timeout end,
116+
ok = supervisor:delete_child(SupPid, child_start),
117+
{ok, Pid3} = supervisor:start_child(SupPid, #{
118+
id => child_start, start => {?MODULE, child_start, [{trap_exit, Self}]}, shutdown => 500
119+
}),
120+
unlink(SupPid),
121+
exit(SupPid, shutdown),
122+
ok = receive {Pid3, {SupPid, shutdown}} -> ok after 1000 -> timeout end,
123+
ok.
124+
88125
child_start(ignore) ->
89126
ignore;
90127
child_start(start) ->
@@ -104,7 +141,18 @@ child_start(info) ->
104141
child_start(error) ->
105142
{error, child_error};
106143
child_start(fail) ->
107-
fail.
144+
fail;
145+
child_start({trap_exit, Parent}) ->
146+
Pid = spawn_link(fun() ->
147+
process_flag(trap_exit, true),
148+
receive
149+
{'EXIT', From, Reason} -> Parent ! {self(), {From, Reason}}
150+
end,
151+
receive
152+
ok -> ok
153+
end
154+
end),
155+
{ok, Pid}.
108156

109157
test_ping_pong(SupPid) ->
110158
Pid1 = get_and_test_server(),

0 commit comments

Comments
 (0)