./ct_report/coverage/mongoose_service.COVER.html

1 %%==============================================================================
2 %% Copyright 2018 Erlang Solutions Ltd.
3 %%
4 %% Licensed under the Apache License, Version 2.0 (the "License");
5 %% you may not use this file except in compliance with the License.
6 %% You may obtain a copy of the License at
7 %%
8 %% http://www.apache.org/licenses/LICENSE-2.0
9 %%
10 %% Unless required by applicable law or agreed to in writing, software
11 %% distributed under the License is distributed on an "AS IS" BASIS,
12 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 %% See the License for the specific language governing permissions and
14 %% limitations under the License.
15 %%==============================================================================
16
17 -module(mongoose_service).
18
19 -include("mongoose.hrl").
20
21 -export([start/0, stop/0,
22 start_service/2,
23 stop_service/1,
24 config_spec/1,
25 is_loaded/1,
26 assert_loaded/1,
27 ensure_loaded/1,
28 ensure_loaded/2,
29 purge_service/1,
30 get_service_opts/1,
31 loaded_services_with_opts/0
32 ]).
33
34 -export([check_deps/1]). % for testing
35
36 -ignore_xref([behaviour_info/1, check_deps/1, ensure_loaded/1, purge_service/1,
37 get_service_opts/1, start_service/2, stop/0]).
38
39 %%Question marks:
40 %%do we need the 'keep config' facility?
41 %%does anybody use the 'wait' option in stopping gen_mod?
42
43 -define(ETAB, mongoose_services).
44
45 -type service() :: atom().
46 -type options() :: [term()].
47
48 -export_type([service/0]).
49
50 -callback start(Opts :: list()) -> any().
51 -callback stop() -> any().
52 -callback config_spec() -> mongoose_config_spec:config_section().
53 %%optional:
54 %%-callback deps() -> [service()].
55
56 -spec start() -> ok.
57 start() ->
58 82 ets:new(?ETAB, [named_table, public, {read_concurrency, true}]),
59 82 ok.
60
61 -spec stop() -> ok.
62
:-(
stop() -> catch ets:delete(?ETAB), ok.
63
64 -spec start_service(service(), options() | undefined) -> ok | {error, already_started}.
65 start_service(Service, undefined) ->
66
:-(
error({service_not_configured, Service});
67 start_service(Service, Opts) when is_list(Opts) ->
68 188 case is_loaded(Service) of
69 4 true -> {error, already_started};
70 184 false -> run_start_service(Service, Opts)
71 end.
72
73 -spec stop_service(service()) -> ok | {error, not_running}.
74 stop_service(Service) ->
75 208 case is_loaded(Service) of
76 23 false -> {error, not_running};
77 185 true -> run_stop_service(Service)
78 end.
79
80 -spec config_spec(service()) -> mongoose_config_spec:config_section().
81 config_spec(Service) ->
82 246 Service:config_spec().
83
84 -spec ensure_loaded(service()) -> ok.
85 ensure_loaded(Service) ->
86
:-(
Options = mongoose_config:get_opt(services, []),
87
:-(
start_service(Service, proplists:get_value(Service, Options)),
88
:-(
ok.
89
90 -spec ensure_loaded(service(), options()) -> ok.
91 ensure_loaded(Service, Opts) ->
92 164 start_service(Service, Opts),
93 164 ok.
94
95 -spec assert_loaded(service()) -> ok.
96 assert_loaded(Service) ->
97
:-(
case is_loaded(Service) of
98 true ->
99
:-(
ok;
100 false ->
101
:-(
error({service_not_loaded, Service})
102 end.
103
104 -spec is_loaded(service()) -> boolean().
105 is_loaded(Service) ->
106 417 ets:member(?ETAB, Service).
107
108 -spec get_service_opts(service()) -> options().
109 get_service_opts(Service) ->
110 3 case ets:lookup(?ETAB, Service) of
111 3 [] -> [];
112
:-(
[{Service, Opts}] -> Opts
113 end.
114
115 -spec loaded_services_with_opts() -> [{service(), options()}].
116 loaded_services_with_opts() ->
117 82 ets:tab2list(?ETAB).
118
119 %% @doc to be used as an emergency feature if serviced crashed while stopping and is not
120 %% running but still lingers in the services tab
121 -spec purge_service(service()) -> ok.
122 purge_service(Service) ->
123
:-(
ets:delete(?ETAB, Service),
124
:-(
ok.
125
126 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
127
128 run_start_service(Service, Opts0) ->
129 184 start_deps(Service),
130 184 Opts = proplists:unfold(Opts0),
131 184 ets:insert(?ETAB, {Service, Opts}),
132 184 try
133 184 Res = Service:start(Opts),
134 184 ?LOG_INFO(#{what => service_startup_started, service => Service,
135 184 text => <<"Started MongooseIM service">>}),
136 184 case Res of
137 102 {ok, _} -> Res;
138 82 _ -> {ok, Res}
139 end
140 catch
141 Class:Reason:Stacktrace ->
142
:-(
ets:delete(?ETAB, Service),
143
:-(
?LOG_CRITICAL(#{what => service_startup_failed,
144 text => <<"Failed to start MongooseIM service">>,
145 service => Service, opts => Opts,
146
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
147
:-(
case is_app_running(mongooseim) of
148 true ->
149
:-(
erlang:raise(Class, Reason, Stacktrace);
150 false ->
151
:-(
?LOG_CRITICAL(#{what => stopping_mongooseim,
152 text => <<"mongooseim initialization was aborted "
153 "because a service start failed.">>,
154 service => Service, opts => Opts,
155
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
156
:-(
timer:sleep(3000),
157
:-(
ErrorText = io_lib:format("Stopping MongooseIM because of bad service~n"
158 "service=~p ~nreason=~0p ~nstactrace=~0p",
159 [Service, Reason, Stacktrace]),
160
:-(
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
161 end
162 end.
163
164 run_stop_service(Service) ->
165 185 try Service:stop() of
166 _ ->
167 185 ?LOG_INFO(#{what => service_stopped, service => Service,
168 185 text => <<"Stopped MongooseIM service">>}),
169 185 ets:delete(?ETAB, Service),
170 185 ok
171 catch Class:Reason:Stacktrace ->
172
:-(
ets:delete(?ETAB, Service),
173
:-(
?LOG_ERROR(#{what => service_stop_failed, service => Service,
174 text => <<"Failed to stop MongooseIM service">>,
175
:-(
class => Class, reason => Reason, stacktrace => Stacktrace})
176 end.
177
178 -spec is_app_running(_) -> boolean().
179 is_app_running(AppName) ->
180 %% Use a high timeout to prevent a false positive in a high load system
181
:-(
Timeout = 15000,
182
:-(
lists:keymember(AppName, 1, application:which_applications(Timeout)).
183
184 -spec start_deps(service()) -> ok.
185 start_deps(Service) ->
186 184 check_deps(Service), % make sure there are no circular deps
187 184 lists:map(fun ensure_loaded/1, get_deps(Service)),
188 184 ok.
189
190 check_deps(Service) ->
191 184 check_deps(Service, []).
192
193 check_deps(Service, Stack) ->
194 184 case lists:member(Service, Stack) of
195 true ->
196
:-(
error({circular_deps_detected, Service});
197 false ->
198 184 lists:foreach(fun(Serv) -> check_deps(Serv, [Service | Stack]) end,
199 get_deps(Service))
200 end.
201
202 -spec get_deps(service()) -> [service()].
203 get_deps(Service) ->
204 %% the module has to be loaded,
205 %% otherwise the erlang:function_exported/3 returns false
206 368 code:ensure_loaded(Service),
207 368 case erlang:function_exported(Service, deps, 0) of
208 true ->
209
:-(
Service:deps();
210 _ ->
211 368 []
212 end.
Line Hits Source