Skip to content

Commit eb4943a

Browse files
committed
Merge pull request #1372 from pguyot/w47/improve-supervisor
Extend support of `supervisor` module Add `supervisor:start_child/2` Add map syntax for supervisor init callback 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 94ced13 + 5e51e33 commit eb4943a

File tree

2 files changed

+253
-40
lines changed

2 files changed

+253
-40
lines changed

libs/estdlib/src/supervisor.erl

Lines changed: 142 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
-behavior(gen_server).
2424

2525
-export([
26-
start_link/2, start_link/3
26+
start_link/2,
27+
start_link/3,
28+
start_child/2
2729
]).
2830

2931
-export([
@@ -33,63 +35,138 @@
3335
handle_info/2
3436
]).
3537

36-
-record(child, {pid = undefined, id, mfargs, restart_type, shutdown, child_type, modules = []}).
37-
-record(state, {children = []}).
38+
-export_type([
39+
child_spec/0,
40+
strategy/0,
41+
sup_flags/0
42+
]).
43+
44+
-type restart() :: permanent | transient | temporary.
45+
-type shutdown() :: brutal_kill | timeout().
46+
-type child_type() :: worker | supervisor.
47+
48+
-type strategy() :: one_for_all | one_for_one.
49+
-type sup_flags() ::
50+
#{
51+
strategy => strategy(),
52+
intensity => non_neg_integer(),
53+
period => pos_integer()
54+
}
55+
| {RestartStrategy :: strategy(), Intensity :: non_neg_integer(), Period :: pos_integer()}.
56+
57+
-type child_spec() ::
58+
#{
59+
id := any(),
60+
start := {module(), atom(), [any()]},
61+
restart => restart(),
62+
shutdown => shutdown(),
63+
type => child_type(),
64+
modules => [module()] | dynamic
65+
}
66+
| {
67+
Id :: any(),
68+
StartFunc :: {module(), atom(), [any()]},
69+
Restart :: restart(),
70+
Shutdown :: shutdown(),
71+
Type :: child_type(),
72+
Modules :: [module()] | dynamic
73+
}.
74+
75+
-record(child, {
76+
pid = undefined,
77+
id :: any(),
78+
start :: {module(), atom(), [any()] | undefined},
79+
restart :: restart(),
80+
shutdown :: shutdown(),
81+
type :: child_type
82+
}).
83+
-record(state, {restart_strategy :: strategy(), children = [] :: [#child{}]}).
3884

3985
start_link(Module, Args) ->
4086
gen_server:start_link(?MODULE, {Module, Args}, []).
4187
start_link(SupName, Module, Args) ->
4288
gen_server:start_link(SupName, ?MODULE, {Module, Args}, []).
4389

90+
start_child(Supervisor, ChildSpec) ->
91+
gen_server:call(Supervisor, {start_child, ChildSpec}).
92+
4493
init({Mod, Args}) ->
4594
erlang:process_flag(trap_exit, true),
4695
case Mod:init(Args) of
47-
{ok, {{one_for_one, _Intensity, _Period}, StartSpec}} ->
48-
State = init_state(StartSpec, #state{}),
96+
{ok, {{Strategy, _Intensity, _Period}, StartSpec}} ->
97+
State = init_state(StartSpec, #state{restart_strategy = Strategy}),
98+
NewChildren = start_children(State#state.children, []),
99+
{ok, State#state{children = NewChildren}};
100+
{ok, {#{strategy := Strategy}, StartSpec}} ->
101+
State = init_state(StartSpec, #state{restart_strategy = Strategy}),
49102
NewChildren = start_children(State#state.children, []),
50103
{ok, State#state{children = NewChildren}};
51104
Error ->
52105
{stop, {bad_return, {mod, init, Error}}}
53106
end.
54107

55-
init_state([{ChildId, MFA, Restart, brutal_kill, Type, Modules} | T], State) ->
56-
Child = #child{
108+
-spec child_spec_to_record(child_spec()) -> #child{}.
109+
child_spec_to_record({ChildId, MFA, Restart, Shutdown, Type, _Modules}) ->
110+
#child{
111+
id = ChildId,
112+
start = MFA,
113+
restart = Restart,
114+
shutdown = Shutdown,
115+
type = Type
116+
};
117+
child_spec_to_record(#{id := ChildId, start := MFA} = ChildMap) ->
118+
Restart = maps:get(restart, ChildMap, permanent),
119+
Type = maps:get(type, ChildMap, worker),
120+
Shutdown = maps:get(
121+
shutdown,
122+
ChildMap,
123+
case Type of
124+
worker -> 5000;
125+
supervisor -> infinity
126+
end
127+
),
128+
#child{
57129
id = ChildId,
58-
mfargs = MFA,
59-
restart_type = Restart,
60-
shutdown = brutal_kill,
61-
child_type = Type,
62-
modules = Modules
63-
},
130+
start = MFA,
131+
restart = Restart,
132+
shutdown = Shutdown,
133+
type = Type
134+
}.
135+
136+
init_state([ChildSpec | T], State) ->
137+
Child = child_spec_to_record(ChildSpec),
64138
NewChildren = [Child | State#state.children],
65139
init_state(T, #state{children = NewChildren});
66140
init_state([], State) ->
67141
State#state{children = lists:reverse(State#state.children)}.
68142

69143
start_children([Child | T], StartedC) ->
70-
#child{mfargs = {M, F, Args}} = Child,
71-
case apply(M, F, Args) of
72-
{ok, Pid} when is_pid(Pid) ->
144+
case try_start(Child) of
145+
{ok, Pid, _Result} ->
73146
start_children(T, [Child#child{pid = Pid} | StartedC])
74147
end;
75148
start_children([], StartedC) ->
76149
StartedC.
77150

78151
restart_child(Pid, Reason, State) ->
79-
Child = lists:keyfind(Pid, #child.pid, State#state.children),
80-
81-
#child{mfargs = {M, F, Args}} = Child,
82-
case should_restart(Reason, Child#child.restart_type) of
83-
true ->
84-
case apply(M, F, Args) of
85-
{ok, NewPid} when is_pid(Pid) ->
86-
NewChild = Child#child{pid = NewPid},
87-
Children = lists:keyreplace(Pid, #child.pid, State#state.children, NewChild),
88-
{ok, State#state{children = Children}}
89-
end;
152+
case lists:keyfind(Pid, #child.pid, State#state.children) of
90153
false ->
91-
Children = lists:keydelete(Pid, #child.pid, State#state.children),
92-
{ok, State#state{children = Children}}
154+
{ok, State};
155+
#child{} = Child ->
156+
case should_restart(Reason, Child#child.restart) of
157+
true ->
158+
case try_start(Child) of
159+
{ok, NewPid, _Result} ->
160+
NewChild = Child#child{pid = NewPid},
161+
Children = lists:keyreplace(
162+
Pid, #child.pid, State#state.children, NewChild
163+
),
164+
{ok, State#state{children = Children}}
165+
end;
166+
false ->
167+
Children = lists:keydelete(Pid, #child.pid, State#state.children),
168+
{ok, State#state{children = Children}}
169+
end
93170
end.
94171

95172
should_restart(_Reason, permanent) ->
@@ -102,8 +179,23 @@ should_restart(Reason, transient) ->
102179
_any -> true
103180
end.
104181

105-
handle_call(_Msg, _from, State) ->
106-
{noreply, State}.
182+
handle_call({start_child, ChildSpec}, _From, #state{children = Children} = State) ->
183+
Child = child_spec_to_record(ChildSpec),
184+
#child{id = ID} = Child,
185+
case lists:keyfind(ID, #child.id, State#state.children) of
186+
#child{pid = undefined} ->
187+
{reply, {error, already_present}, State};
188+
#child{pid = Pid} ->
189+
{reply, {error, {already_started, Pid}}, State};
190+
false ->
191+
case try_start(Child) of
192+
{ok, Pid, Result} ->
193+
UpdatedChild = Child#child{pid = Pid},
194+
{reply, Result, State#state{children = [UpdatedChild | Children]}};
195+
{error, _Reason} = ErrorT ->
196+
{reply, ErrorT, State}
197+
end
198+
end.
107199

108200
handle_cast(_Msg, State) ->
109201
{noreply, State}.
@@ -118,3 +210,22 @@ handle_info({'EXIT', Pid, Reason}, State) ->
118210
handle_info(_Msg, State) ->
119211
%TODO: log unexpected message
120212
{noreply, State}.
213+
214+
try_start(#child{start = {M, F, Args}} = Record) ->
215+
try
216+
case apply(M, F, Args) of
217+
{ok, Pid} when is_pid(Pid) ->
218+
{ok, Pid, {ok, Pid}};
219+
{ok, Pid, Info} when is_pid(Pid) ->
220+
{ok, Pid, {ok, Pid, Info}};
221+
ignore ->
222+
{ok, undefined, {ok, undefined}};
223+
{error, Reason} ->
224+
{error, {Reason, Record}};
225+
Other ->
226+
{error, {Other, Record}}
227+
end
228+
catch
229+
error:Error ->
230+
{error, {{'EXIT', Error}, Record}}
231+
end.

0 commit comments

Comments
 (0)