25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

Improve the applications start-up

* Check if all modules present for every application loaded.
* Get rid of now obsoleted 'ejabberd_check' module.
This commit is contained in:
Evgeniy Khramtsov 2013-07-07 02:11:01 +10:00
parent c262c08513
commit 0aca3a4585
4 changed files with 102 additions and 135 deletions

View File

@ -27,8 +27,8 @@
-module(ejabberd). -module(ejabberd).
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-export([start/0, stop/0, start_app/1, -export([start/0, stop/0, start_app/1, start_app/2,
get_pid_file/0]). get_pid_file/0, check_app/1]).
-include("logger.hrl"). -include("logger.hrl").
@ -51,27 +51,93 @@ get_pid_file() ->
Path Path
end. end.
start_app(App) when not is_list(App) -> start_app(App) ->
start_app([App]); start_app(App, temporary).
start_app([App|Apps]) ->
start_app(App, Type) ->
StartFlag = not is_loaded(),
start_app(App, Type, StartFlag).
check_app(App) ->
StartFlag = not is_loaded(),
spawn(fun() -> check_app_modules(App, StartFlag) end),
ok.
is_loaded() ->
Apps = application:which_applications(),
lists:keymember(ejabberd, 1, Apps).
start_app(App, Type, StartFlag) when not is_list(App) ->
start_app([App], Type, StartFlag);
start_app([App|Apps], Type, StartFlag) ->
case application:start(App) of case application:start(App) of
ok -> ok ->
start_app(Apps); spawn(fun() -> check_app_modules(App, StartFlag) end),
start_app(Apps, Type, StartFlag);
{error, {already_started, _}} -> {error, {already_started, _}} ->
start_app(Apps); start_app(Apps, Type, StartFlag);
{error, {not_started, DepApp}} -> {error, {not_started, DepApp}} ->
case lists:member(DepApp, [App|Apps]) of case lists:member(DepApp, [App|Apps]) of
true -> true ->
?CRITICAL_MSG("failed to start application '~p': " Reason = io_lib:format(
"circular dependency on '~p' detected", "failed to start application '~p': "
[App, DepApp]), "circular dependency on '~p' detected",
erlang:error(application_start_failed); [App, DepApp]),
exit_or_halt(Reason, StartFlag);
false -> false ->
start_app([DepApp,App|Apps]) start_app([DepApp,App|Apps], Type, StartFlag)
end; end;
Err -> Err ->
?CRITICAL_MSG("failed to start application '~p': ~p", [App, Err]), Reason = io_lib:format("failed to start application '~p': ~p",
erlang:error(application_start_failed) [App, Err]),
exit_or_halt(Reason, StartFlag)
end; end;
start_app([]) -> start_app([], _Type, _StartFlag) ->
ok. ok.
check_app_modules(App, StartFlag) ->
{A, B, C} = now(),
random:seed(A, B, C),
sleep(5000),
case application:get_key(App, modules) of
{ok, Mods} ->
lists:foreach(
fun(Mod) ->
case code:which(Mod) of
non_existing ->
File = get_module_file(App, Mod),
Reason = io_lib:format(
"couldn't find module ~s "
"needed for application '~p'",
[File, App]),
exit_or_halt(Reason, StartFlag);
_ ->
sleep(10)
end
end, Mods);
_ ->
%% No modules? This is strange
ok
end.
exit_or_halt(Reason, StartFlag) ->
?CRITICAL_MSG(Reason, []),
if StartFlag ->
%% Wait for the critical message is written in the console/log
timer:sleep(1000),
halt(string:substr(lists:flatten(Reason), 1, 199));
true ->
erlang:error(application_start_failed)
end.
sleep(N) ->
timer:sleep(random:uniform(N)).
get_module_file(App, Mod) ->
BaseName = atom_to_list(Mod),
case code:lib_dir(App, ebin) of
{error, _} ->
BaseName;
Dir ->
filename:join([Dir, BaseName ++ ".beam"])
end.

View File

@ -42,6 +42,7 @@ start(normal, _Args) ->
ejabberd_logger:start(), ejabberd_logger:start(),
write_pid_file(), write_pid_file(),
start_apps(), start_apps(),
ejabberd:check_app(ejabberd),
randoms:start(), randoms:start(),
db_init(), db_init(),
start(), start(),
@ -52,7 +53,6 @@ start(normal, _Args) ->
ejabberd_admin:start(), ejabberd_admin:start(),
gen_mod:start(), gen_mod:start(),
ejabberd_config:start(), ejabberd_config:start(),
ejabberd_check:config(),
connect_nodes(), connect_nodes(),
Sup = ejabberd_sup:start_link(), Sup = ejabberd_sup:start_link(),
ejabberd_rdbms:start(), ejabberd_rdbms:start(),
@ -112,7 +112,7 @@ db_init() ->
_ -> _ ->
ok ok
end, end,
application:start(mnesia, permanent), ejabberd:start_app(mnesia, permanent),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity). mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
%% Start all the modules in all the hosts %% Start all the modules in all the hosts

View File

@ -1,111 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_check.erl
%%% Author : Mickael Remond <mremond@process-one.net>
%%% Purpose : Check ejabberd configuration and
%%% Created : 27 Feb 2008 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_check).
-export([libs/0, config/0]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_config.hrl").
%% TODO:
%% We want to implement library checking at launch time to issue
%% human readable user messages.
libs() ->
ok.
%% @doc Consistency check on ejabberd configuration
config() ->
check_database_modules().
check_database_modules() ->
[check_database_module(M)||M<-get_db_used()].
check_database_module(odbc) ->
check_modules(odbc, [odbc, odbc_app, odbc_sup, ejabberd_odbc, ejabberd_odbc_sup, odbc_queries]);
check_database_module(mysql) ->
check_modules(mysql, [mysql, mysql_auth, mysql_conn, mysql_recv]);
check_database_module(pgsql) ->
check_modules(pgsql, [pgsql, pgsql_proto, pgsql_tcp, pgsql_util]).
%% @doc Issue a critical error and throw an exit if needing module is
%% missing.
check_modules(DB, Modules) ->
case get_missing_modules(Modules) of
[] ->
ok;
MissingModules when is_list(MissingModules) ->
?CRITICAL_MSG("ejabberd is configured to use '~p', but the following Erlang modules are not installed: '~p'", [DB, MissingModules]),
exit(database_module_missing)
end.
%% @doc Return the list of undefined modules
get_missing_modules(Modules) ->
lists:filter(fun(Module) ->
case catch Module:module_info() of
{'EXIT', {undef, _}} ->
true;
_ -> false
end
end, Modules).
%% @doc Return the list of databases used
get_db_used() ->
%% Retrieve domains with a database configured:
Domains =
ets:match(local_config, #local_config{key={odbc_server, '$1'},
value='$2'}),
%% Check that odbc is the auth method used for those domains:
%% and return the database name
DBs = lists:foldr(
fun([Domain, DB], Acc) ->
case check_odbc_option(
ejabberd_config:get_local_option(
{auth_method, Domain}, fun(V) -> V end)) of
true -> [get_db_type(DB)|Acc];
_ -> Acc
end
end,
[], Domains),
lists:usort(DBs).
%% @doc Depending in the DB definition, return which type of DB this is.
%% Note that MSSQL is detected as ODBC.
%% @spec (DB) -> mysql | pgsql | odbc
get_db_type(DB) when is_tuple(DB) ->
element(1, DB);
get_db_type(DB) when is_list(DB) ->
odbc.
%% @doc Return true if odbc option is used
check_odbc_option(odbc) ->
true;
check_odbc_option(AuthMethods) when is_list(AuthMethods) ->
lists:member(odbc, AuthMethods);
check_odbc_option(_) ->
false.

View File

@ -34,7 +34,8 @@
-include("logger.hrl"). -include("logger.hrl").
start() -> start() ->
case lists:any(fun needs_odbc/1, ?MYHOSTS) of case lists:any(fun(H) -> needs_odbc(H) /= false end,
?MYHOSTS) of
true -> true ->
start_hosts(); start_hosts();
false -> false ->
@ -45,14 +46,15 @@ start() ->
start_hosts() -> start_hosts() ->
lists:foreach(fun (Host) -> lists:foreach(fun (Host) ->
case needs_odbc(Host) of case needs_odbc(Host) of
true -> start_odbc(Host); {true, App} -> start_odbc(Host, App);
false -> ok false -> ok
end end
end, end,
?MYHOSTS). ?MYHOSTS).
%% Start the ODBC module on the given host %% Start the ODBC module on the given host
start_odbc(Host) -> start_odbc(Host, App) ->
ejabberd:start_app(App),
Supervisor_name = gen_mod:get_module_proc(Host, Supervisor_name = gen_mod:get_module_proc(Host,
ejabberd_odbc_sup), ejabberd_odbc_sup),
ChildSpec = {Supervisor_name, ChildSpec = {Supervisor_name,
@ -64,11 +66,21 @@ start_odbc(Host) ->
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying." ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
"..~n", "..~n",
[Supervisor_name, _Error]), [Supervisor_name, _Error]),
start_odbc(Host) start_odbc(Host, App)
end. end.
%% Returns true if we have configured odbc_server for the given host %% Returns {true, App} if we have configured odbc_server for the given host
needs_odbc(Host) -> needs_odbc(Host) ->
LHost = jlib:nameprep(Host), LHost = jlib:nameprep(Host),
ejabberd_config:get_local_option( case ejabberd_config:get_local_option(
{odbc_server, LHost}, fun(_) -> true end, false). {odbc_server, LHost}, fun(Res) -> Res end) of
{mysql, _, _, _, _} -> {true, p1_mysql};
{pgsql, _, _, _, _} -> {true, p1_pgsql};
{mysql, _, _, _, _, _} -> {true, p1_mysql};
{pgsql, _, _, _, _, _} -> {true, p1_pgsql};
S ->
case catch iolist_to_binary(S) of
{'EXIT', _} -> false;
_ -> true
end
end.