./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 -callback start(Opts :: list()) -> any().
49 -callback stop() -> any().
50 -callback config_spec() -> mongoose_config_spec:config_section().
51 %%optional:
52 %%-callback deps() -> [service()].
53
54 -spec start() -> ok.
55 start() ->
56 80 ets:new(?ETAB, [named_table, public, {read_concurrency, true}]),
57 80 ok.
58
59 -spec stop() -> ok.
60
:-(
stop() -> catch ets:delete(?ETAB), ok.
61
62 -spec start_service(service(), options() | undefined) -> ok | {error, already_started}.
63 start_service(Service, undefined) ->
64
:-(
error({service_not_configured, Service});
65 start_service(Service, Opts) when is_list(Opts) ->
66 184 case is_loaded(Service) of
67 4 true -> {error, already_started};
68 180 false -> run_start_service(Service, Opts)
69 end.
70
71 -spec stop_service(service()) -> ok | {error, not_running}.
72 stop_service(Service) ->
73 204 case is_loaded(Service) of
74 23 false -> {error, not_running};
75 181 true -> run_stop_service(Service)
76 end.
77
78 -spec config_spec(service()) -> mongoose_config_spec:config_section().
79 config_spec(Service) ->
80 240 Service:config_spec().
81
82 -spec ensure_loaded(service()) -> ok.
83 ensure_loaded(Service) ->
84
:-(
Options = mongoose_config:get_opt(services, []),
85
:-(
start_service(Service, proplists:get_value(Service, Options)),
86
:-(
ok.
87
88 -spec ensure_loaded(service(), options()) -> ok.
89 ensure_loaded(Service, Opts) ->
90 160 start_service(Service, Opts),
91 160 ok.
92
93 -spec assert_loaded(service()) -> ok.
94 assert_loaded(Service) ->
95
:-(
case is_loaded(Service) of
96 true ->
97
:-(
ok;
98 false ->
99
:-(
error({service_not_loaded, Service})
100 end.
101
102 -spec is_loaded(service()) -> boolean().
103 is_loaded(Service) ->
104 409 ets:member(?ETAB, Service).
105
106 -spec get_service_opts(service()) -> options().
107 get_service_opts(Service) ->
108 3 case ets:lookup(?ETAB, Service) of
109 3 [] -> [];
110
:-(
[{Service, Opts}] -> Opts
111 end.
112
113 -spec loaded_services_with_opts() -> [{service(), options()}].
114 loaded_services_with_opts() ->
115 80 ets:tab2list(?ETAB).
116
117 %% @doc to be used as an emergency feature if serviced crashed while stopping and is not
118 %% running but still lingers in the services tab
119 -spec purge_service(service()) -> ok.
120 purge_service(Service) ->
121
:-(
ets:delete(?ETAB, Service),
122
:-(
ok.
123
124 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
125
126 run_start_service(Service, Opts0) ->
127 180 start_deps(Service),
128 180 Opts = proplists:unfold(Opts0),
129 180 ets:insert(?ETAB, {Service, Opts}),
130 180 try
131 180 Res = Service:start(Opts),
132 180 ?LOG_INFO(#{what => service_startup_started, service => Service,
133 180 text => <<"Started MongooseIM service">>}),
134 180 case Res of
135 100 {ok, _} -> Res;
136 80 _ -> {ok, Res}
137 end
138 catch
139 Class:Reason:Stacktrace ->
140
:-(
ets:delete(?ETAB, Service),
141
:-(
?LOG_CRITICAL(#{what => service_startup_failed,
142 text => <<"Failed to start MongooseIM service">>,
143 service => Service, opts => Opts,
144
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
145
:-(
case is_app_running(mongooseim) of
146 true ->
147
:-(
erlang:raise(Class, Reason, Stacktrace);
148 false ->
149
:-(
?LOG_CRITICAL(#{what => stopping_mongooseim,
150 text => <<"mongooseim initialization was aborted "
151 "because a service start failed.">>,
152 service => Service, opts => Opts,
153
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
154
:-(
timer:sleep(3000),
155
:-(
ErrorText = io_lib:format("Stopping MongooseIM because of bad service~n"
156 "service=~p ~nreason=~0p ~nstactrace=~0p",
157 [Service, Reason, Stacktrace]),
158
:-(
erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
159 end
160 end.
161
162 run_stop_service(Service) ->
163 181 try Service:stop() of
164 _ ->
165 181 ?LOG_INFO(#{what => service_stopped, service => Service,
166 181 text => <<"Stopped MongooseIM service">>}),
167 181 ets:delete(?ETAB, Service),
168 181 ok
169 catch Class:Reason:Stacktrace ->
170
:-(
ets:delete(?ETAB, Service),
171
:-(
?LOG_ERROR(#{what => service_stop_failed, service => Service,
172 text => <<"Failed to stop MongooseIM service">>,
173
:-(
class => Class, reason => Reason, stacktrace => Stacktrace})
174 end.
175
176 -spec is_app_running(_) -> boolean().
177 is_app_running(AppName) ->
178 %% Use a high timeout to prevent a false positive in a high load system
179
:-(
Timeout = 15000,
180
:-(
lists:keymember(AppName, 1, application:which_applications(Timeout)).
181
182 -spec start_deps(service()) -> ok.
183 start_deps(Service) ->
184 180 check_deps(Service), % make sure there are no circular deps
185 180 lists:map(fun ensure_loaded/1, get_deps(Service)),
186 180 ok.
187
188 check_deps(Service) ->
189 180 check_deps(Service, []).
190
191 check_deps(Service, Stack) ->
192 180 case lists:member(Service, Stack) of
193 true ->
194
:-(
error({circular_deps_detected, Service});
195 false ->
196 180 lists:foreach(fun(Serv) -> check_deps(Serv, [Service | Stack]) end,
197 get_deps(Service))
198 end.
199
200 -spec get_deps(service()) -> [service()].
201 get_deps(Service) ->
202 %% the module has to be loaded,
203 %% otherwise the erlang:function_exported/3 returns false
204 360 code:ensure_loaded(Service),
205 360 case erlang:function_exported(Service, deps, 0) of
206 true ->
207
:-(
Service:deps();
208 _ ->
209 360 []
210 end.
Line Hits Source