./ct_report/coverage/gen_mod.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : gen_mod.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose :
5 %%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(gen_mod).
27 -author('alexey@process-one.net').
28
29 -export_type([key_path/0, opt_key/0, opt_value/0, module_opts/0]).
30
31 -export([
32 % Modules start & stop, do NOT use in the tests, use mongoose_modules API instead
33 start_module/3,
34 stop_module/2,
35 does_module_support/2,
36 config_spec/1,
37 % Get/set opts by host or from a list
38 get_opt/2,
39 get_opt/3,
40 lookup_module_opt/3,
41 get_module_opt/3,
42 get_module_opt/4,
43 get_module_opts/2,
44 get_loaded_module_opts/2,
45
46 loaded_modules/0,
47 loaded_modules/1,
48 loaded_modules_with_opts/0,
49 loaded_modules_with_opts/1,
50 hosts_with_module/1,
51 hosts_and_opts_with_module/1,
52 get_module_proc/2,
53 is_loaded/2,
54 get_deps/3]).
55
56 -export([is_app_running/1]). % we have to mock it in some tests
57
58 -ignore_xref([loaded_modules_with_opts/0,
59 loaded_modules_with_opts/1, hosts_and_opts_with_module/1]).
60
61 -include("mongoose.hrl").
62
63 -type module_feature() :: atom().
64 -type host_type() :: mongooseim:host_type().
65 -type key_path() :: mongoose_config:key_path().
66 -type opt_key() :: atom().
67 -type opt_value() :: mongoose_config:value().
68 -type module_opts() :: #{opt_key() => opt_value()}.
69
70 -callback start(HostType :: host_type(), Opts :: module_opts()) -> any().
71 -callback stop(HostType :: host_type()) -> any().
72 -callback supported_features() -> [module_feature()].
73 -callback config_spec() -> mongoose_config_spec:config_section().
74
75 %% Optional callback specifying module dependencies.
76 %% The dependent module can specify parameters with which the dependee should be
77 %% started (the parameters will be merged with params given in user config and
78 %% by other modules).
79 %% The last element of the tuple specifies whether the ordering can be broken in
80 %% case of cycle (in that case soft dependency may be started after the
81 %% dependent module).
82 %%
83 %% TODO: think about getting rid of HostType param for deps/2 interface, currently
84 %% it's used only by global_distrib modules (see mod_global_distrib_utils:deps/4
85 %% function).
86 -callback deps(host_type(), module_opts()) -> gen_mod_deps:deps().
87
88 -optional_callbacks([config_spec/0, supported_features/0, deps/2]).
89
90 %% @doc This function should be called by mongoose_modules only.
91 %% To start a new module at runtime, use mongoose_modules:ensure_module/3 instead.
92 -spec start_module(host_type(), module(), module_opts()) -> {ok, term()}.
93 start_module(HostType, Module, Opts) ->
94 4618 assert_loaded(HostType, Module),
95 4618 start_module_for_host_type(HostType, Module, Opts).
96
97 start_module_for_host_type(HostType, Module, Opts) ->
98 4618 {links, LinksBefore} = erlang:process_info(self(), links),
99 4618 try
100 4618 lists:map(fun mongoose_service:assert_loaded/1,
101 get_required_services(HostType, Module, Opts)),
102 4618 check_dynamic_domains_support(HostType, Module),
103 4618 Res = Module:start(HostType, Opts),
104 4617 {links, LinksAfter} = erlang:process_info(self(), links),
105 4617 case lists:sort(LinksBefore) =:= lists:sort(LinksAfter) of
106 4617 true -> ok;
107 false ->
108 %% TODO: grepping for "fail_ci_build=true" is bad option
109 %% for ci testing, rework this.
110
:-(
CIInfo = "fail_ci_build=true ",
111 %% Note for programmers:
112 %% Never call start_link directly from your_module:start/2 function!
113 %% The process will be killed if we start modules remotely or in shell
114
:-(
?LOG_ERROR(#{what => unexpected_links, ci_info => CIInfo,
115
:-(
links_before => LinksBefore, links_after => LinksAfter})
116 end,
117 4617 ?LOG_DEBUG(#{what => module_started, module => Module, host_type => HostType}),
118 % normalise result
119 4617 case Res of
120 404 {ok, R} -> {ok, R};
121 4213 _ -> {ok, Res}
122 end
123 catch
124 Class:Reason:StackTrace ->
125 1 ErrorText = io_lib:format("Problem starting the module ~p for "
126 "host_type ~p~n options: ~p~n ~p: ~p~n~p",
127 [Module, HostType, Opts, Class, Reason,
128 StackTrace]),
129 1 ?LOG_CRITICAL(#{what => module_start_failed, module => Module,
130 host_type => HostType, opts => Opts, class => Class,
131
:-(
reason => Reason, stacktrace => StackTrace}),
132 1 case is_mim_or_ct_running() of
133 true ->
134 1 erlang:raise(Class, Reason, StackTrace);
135 false ->
136
:-(
?LOG_CRITICAL(#{what => mim_initialization_aborted,
137 text => <<"mongooseim initialization was aborted "
138 "because a module start failed.">>,
139 class => Class, reason => Reason,
140
:-(
stacktrace => StackTrace}),
141
:-(
timer:sleep(3000),
142
:-(
erlang:halt(string:substr(lists:flatten(ErrorText),
143 1, 199))
144 end
145 end.
146
147 check_dynamic_domains_support(HostType, Module) ->
148 4618 case lists:member(HostType, ?MYHOSTS) of
149 3665 true -> ok;
150 false ->
151 953 case gen_mod:does_module_support(Module, dynamic_domains) of
152 953 true -> ok;
153 false ->
154
:-(
error({Module, HostType, dynamic_domains_feature_is_not_supported})
155 end
156 end.
157
158 is_mim_or_ct_running() ->
159 1 ?MODULE:is_app_running(mongooseim)
160 %% Common tests would be very confused if we kill the whole node
161
:-(
orelse is_common_test_running().
162
163 is_common_test_running() ->
164
:-(
try
165
:-(
is_list(ct:get_status())
166 catch _:_ ->
167
:-(
false
168 end.
169
170 -spec is_app_running(_) -> boolean().
171 is_app_running(AppName) ->
172 %% Use a high timeout to prevent a false positive in a high load system
173 1 Timeout = 15000,
174 1 lists:keymember(AppName, 1, application:which_applications(Timeout)).
175
176 %% @doc This function should be called by mongoose_modules only.
177 %% To stop a module at runtime, use mongoose_modules:ensure_stopped/2 instead.
178 -spec stop_module(host_type(), module()) -> ok.
179 stop_module(HostType, Module) ->
180 4598 assert_loaded(HostType, Module),
181 4598 stop_module_for_host_type(HostType, Module).
182
183 -spec stop_module_for_host_type(host_type(), module()) -> ok.
184 stop_module_for_host_type(HostType, Module) ->
185 4598 try Module:stop(HostType) of
186 {wait, ProcList} when is_list(ProcList) ->
187
:-(
lists:foreach(fun wait_for_process/1, ProcList);
188 {wait, Process} ->
189
:-(
wait_for_process(Process);
190 _ ->
191 4598 ok
192 catch Class:Reason:Stacktrace ->
193
:-(
?LOG_ERROR(#{what => module_stopping_failed,
194 host_type => HostType, stop_module => Module,
195
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
196
:-(
erlang:raise(Class, Reason, Stacktrace)
197 end.
198
199 -spec does_module_support(module(), module_feature()) -> boolean().
200 does_module_support(Module, Feature) ->
201 1905 lists:member(Feature, get_supported_features(Module)).
202
203 -spec get_supported_features(module()) -> [module_feature()].
204 get_supported_features(Module) ->
205 %% if module is not loaded, erlang:function_exported/3 returns false
206 1905 case erlang:function_exported(Module, supported_features, 0) of
207 1905 true -> apply(Module, supported_features, []);
208
:-(
false -> []
209 end.
210
211 -spec config_spec(module()) -> mongoose_config_spec:config_section().
212 config_spec(Module) ->
213 6308 Module:config_spec().
214
215 -spec wait_for_process(atom() | pid() | {atom(), atom()}) -> 'ok'.
216 wait_for_process(Process) ->
217
:-(
MonitorReference = erlang:monitor(process, Process),
218
:-(
case wait_for_stop(MonitorReference) of
219
:-(
ok -> ok;
220 timeout ->
221
:-(
catch exit(whereis(Process), kill),
222
:-(
wait_for_stop(MonitorReference),
223
:-(
ok
224 end.
225
226 -spec wait_for_stop(reference()) -> 'ok' | timeout.
227 wait_for_stop(MonitorReference) ->
228
:-(
receive
229 {'DOWN', MonitorReference, _Type, _Object, _Info} ->
230
:-(
ok
231 after 5000 ->
232
:-(
timeout
233 end.
234
235 -spec get_opt(opt_key() | key_path(), module_opts()) -> opt_value().
236 get_opt(Path, Opts) when is_list(Path), is_map(Opts) ->
237
:-(
lists:foldl(fun maps:get/2, Opts, Path);
238 get_opt(Opt, Opts) when is_map(Opts) ->
239 1985 maps:get(Opt, Opts).
240
241 -spec get_opt(opt_key() | key_path(), module_opts(), opt_value()) -> opt_value().
242 get_opt(Path, Opts, Default) ->
243 134 try
244 134 get_opt(Path, Opts)
245 catch
246 133 error:{badkey, _} -> Default
247 end.
248
249 -spec lookup_module_opt(mongooseim:host_type(), module(), opt_key() | key_path()) ->
250 {ok, opt_value()} | {error, not_found}.
251 lookup_module_opt(HostType, Module, Key) ->
252 121 mongoose_config:lookup_opt(config_path(HostType, Module, Key)).
253
254 -spec get_module_opt(mongooseim:host_type(), module(), opt_key() | key_path(), opt_value()) ->
255 opt_value().
256 get_module_opt(HostType, Module, Key, Default) ->
257 %% Fail in dev builds.
258 %% It protects against passing something weird as a Module argument
259 %% or against wrong argument order.
260 11697 ?ASSERT_MODULE(Module),
261 11697 mongoose_config:get_opt(config_path(HostType, Module, Key), Default).
262
263 -spec get_module_opt(mongooseim:host_type(), module(), opt_key() | key_path()) -> opt_value().
264 get_module_opt(HostType, Module, Key) ->
265 20699 mongoose_config:get_opt(config_path(HostType, Module, Key)).
266
267 -spec config_path(mongooseim:host_type(), module(), opt_key() | key_path()) -> key_path().
268 config_path(HostType, Module, Path) when is_list(Path) ->
269 2884 [{modules, HostType}, Module] ++ Path;
270 config_path(HostType, Module, Key) when is_atom(Key) ->
271 29633 [{modules, HostType}, Module, Key].
272
273 -spec get_module_opts(mongooseim:host_type(), module()) -> module_opts().
274 get_module_opts(HostType, Module) ->
275 796 ?ASSERT_MODULE(Module),
276 796 mongoose_config:get_opt([{modules, HostType}, Module], #{}).
277
278 -spec get_loaded_module_opts(mongooseim:host_type(), module()) -> module_opts().
279 get_loaded_module_opts(HostType, Module) ->
280 1200 mongoose_config:get_opt([{modules, HostType}, Module]).
281
282 -spec loaded_modules() -> [module()].
283 loaded_modules() ->
284 3 lists:usort(lists:flatmap(fun loaded_modules/1, ?ALL_HOST_TYPES)).
285
286 -spec loaded_modules(host_type()) -> [module()].
287 loaded_modules(HostType) ->
288 447 maps:keys(mongoose_config:get_opt({modules, HostType})).
289
290 -spec loaded_modules_with_opts(host_type()) -> #{module() => module_opts()}.
291 loaded_modules_with_opts(HostType) ->
292 21878 mongoose_config:get_opt({modules, HostType}).
293
294 -spec loaded_modules_with_opts() -> #{host_type() => #{module() => module_opts()}}.
295 loaded_modules_with_opts() ->
296
:-(
maps:from_list([{HostType, loaded_modules_with_opts(HostType)} || HostType <- ?ALL_HOST_TYPES]).
297
298 -spec hosts_with_module(module()) -> [host_type()].
299 hosts_with_module(Module) ->
300 20 [HostType || HostType <- ?ALL_HOST_TYPES, is_loaded(HostType, Module)].
301
302 -spec hosts_and_opts_with_module(module()) -> #{host_type() => module_opts()}.
303 hosts_and_opts_with_module(Module) ->
304
:-(
maps:from_list(
305 lists:flatmap(fun(HostType) ->
306
:-(
case mongoose_config:lookup_opt([{modules, HostType}, Module]) of
307
:-(
{error, not_found} -> [];
308
:-(
{ok, Opts} -> [{HostType, Opts}]
309 end
310 end, ?ALL_HOST_TYPES)).
311
312 -spec get_module_proc(binary() | string(), module()) -> atom().
313 %% TODO:
314 %% split this interface into 2:
315 %% * create_module_proc_name/2 - which can create new atoms by calling list_to_atom/1
316 %% * get_module_proc_name/2 - which should use safe list_to_existing_atom/1 function
317 get_module_proc(Host, Base) when is_binary(Host) ->
318 21489 get_module_proc(binary_to_list(Host), Base);
319 get_module_proc(Host, Base) ->
320 21489 list_to_atom(atom_to_list(Base) ++ "_" ++ Host).
321
322 -spec assert_loaded(mongooseim:host_type(), module()) -> ok.
323 assert_loaded(HostType, Module) ->
324 9216 case is_loaded(HostType, Module) of
325 true ->
326 9216 ok;
327 false ->
328
:-(
error(#{what => module_not_loaded,
329 text => <<"Module missing from mongoose_config">>,
330 host_type => HostType,
331 module => Module})
332 end.
333
334 -spec is_loaded(HostType :: binary(), Module :: atom()) -> boolean().
335 is_loaded(HostType, Module) ->
336 21593 maps:is_key(Module, loaded_modules_with_opts(HostType)).
337
338 -spec get_deps(host_type(), module(), module_opts()) -> gen_mod_deps:module_deps().
339 get_deps(HostType, Module, Opts) ->
340 28317 case backend_module:is_exported(Module, deps, 2) of
341 true ->
342 1158 Deps = Module:deps(HostType, Opts),
343 1158 lists:filter(fun(D) -> element(1, D) =/= service end, Deps);
344 _ ->
345 27159 []
346 end.
347
348 -spec get_required_services(host_type(), module(), module_opts()) -> [mongoose_service:service()].
349 get_required_services(HostType, Module, Options) ->
350 4618 case backend_module:is_exported(Module, deps, 2) of
351 true ->
352 235 [Service || {service, Service} <- Module:deps(HostType, Options)];
353 _ ->
354 4383 []
355 end.
Line Hits Source