23
23
-behavior (gen_server ).
24
24
25
25
-export ([
26
- start_link /2 , start_link /3
26
+ start_link /2 ,
27
+ start_link /3 ,
28
+ start_child /2
27
29
]).
28
30
29
31
-export ([
33
35
handle_info /2
34
36
]).
35
37
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 {}]}).
38
84
39
85
start_link (Module , Args ) ->
40
86
gen_server :start_link (? MODULE , {Module , Args }, []).
41
87
start_link (SupName , Module , Args ) ->
42
88
gen_server :start_link (SupName , ? MODULE , {Module , Args }, []).
43
89
90
+ start_child (Supervisor , ChildSpec ) ->
91
+ gen_server :call (Supervisor , {start_child , ChildSpec }).
92
+
44
93
init ({Mod , Args }) ->
45
94
erlang :process_flag (trap_exit , true ),
46
95
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 }),
49
102
NewChildren = start_children (State # state .children , []),
50
103
{ok , State # state {children = NewChildren }};
51
104
Error ->
52
105
{stop , {bad_return , {mod , init , Error }}}
53
106
end .
54
107
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 {
57
129
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 ),
64
138
NewChildren = [Child | State # state .children ],
65
139
init_state (T , # state {children = NewChildren });
66
140
init_state ([], State ) ->
67
141
State # state {children = lists :reverse (State # state .children )}.
68
142
69
143
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 } ->
73
146
start_children (T , [Child # child {pid = Pid } | StartedC ])
74
147
end ;
75
148
start_children ([], StartedC ) ->
76
149
StartedC .
77
150
78
151
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
90
153
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
93
170
end .
94
171
95
172
should_restart (_Reason , permanent ) ->
@@ -102,8 +179,23 @@ should_restart(Reason, transient) ->
102
179
_any -> true
103
180
end .
104
181
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 .
107
199
108
200
handle_cast (_Msg , State ) ->
109
201
{noreply , State }.
@@ -118,3 +210,22 @@ handle_info({'EXIT', Pid, Reason}, State) ->
118
210
handle_info (_Msg , State ) ->
119
211
% TODO: log unexpected message
120
212
{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