Skip to content

Commit 5e51e33

Browse files
committed
Extend support of supervisor module
Add `supervisor:start_child/2` Add map syntax for supervisor init callback Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent 94ced13 commit 5e51e33

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)