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).
-author('alexey@process-one.net').
-export([start/0, stop/0, start_app/1,
get_pid_file/0]).
-export([start/0, stop/0, start_app/1, start_app/2,
get_pid_file/0, check_app/1]).
-include("logger.hrl").
@ -51,27 +51,93 @@ get_pid_file() ->
Path
end.
start_app(App) when not is_list(App) ->
start_app([App]);
start_app([App|Apps]) ->
start_app(App) ->
start_app(App, temporary).
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
ok ->
start_app(Apps);
spawn(fun() -> check_app_modules(App, StartFlag) end),
start_app(Apps, Type, StartFlag);
{error, {already_started, _}} ->
start_app(Apps);
start_app(Apps, Type, StartFlag);
{error, {not_started, DepApp}} ->
case lists:member(DepApp, [App|Apps]) of
true ->
?CRITICAL_MSG("failed to start application '~p': "
"circular dependency on '~p' detected",
[App, DepApp]),
erlang:error(application_start_failed);
Reason = io_lib:format(
"failed to start application '~p': "
"circular dependency on '~p' detected",
[App, DepApp]),
exit_or_halt(Reason, StartFlag);
false ->
start_app([DepApp,App|Apps])
start_app([DepApp,App|Apps], Type, StartFlag)
end;
Err ->
?CRITICAL_MSG("failed to start application '~p': ~p", [App, Err]),
erlang:error(application_start_failed)
Reason = io_lib:format("failed to start application '~p': ~p",
[App, Err]),
exit_or_halt(Reason, StartFlag)
end;
start_app([]) ->
start_app([], _Type, _StartFlag) ->
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(),
write_pid_file(),
start_apps(),
ejabberd:check_app(ejabberd),
randoms:start(),
db_init(),
start(),
@ -52,7 +53,6 @@ start(normal, _Args) ->
ejabberd_admin:start(),
gen_mod:start(),
ejabberd_config:start(),
ejabberd_check:config(),
connect_nodes(),
Sup = ejabberd_sup:start_link(),
ejabberd_rdbms:start(),
@ -112,7 +112,7 @@ db_init() ->
_ ->
ok
end,
application:start(mnesia, permanent),
ejabberd:start_app(mnesia, permanent),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
%% 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").
start() ->
case lists:any(fun needs_odbc/1, ?MYHOSTS) of
case lists:any(fun(H) -> needs_odbc(H) /= false end,
?MYHOSTS) of
true ->
start_hosts();
false ->
@ -45,14 +46,15 @@ start() ->
start_hosts() ->
lists:foreach(fun (Host) ->
case needs_odbc(Host) of
true -> start_odbc(Host);
{true, App} -> start_odbc(Host, App);
false -> ok
end
end,
?MYHOSTS).
%% 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,
ejabberd_odbc_sup),
ChildSpec = {Supervisor_name,
@ -64,11 +66,21 @@ start_odbc(Host) ->
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
"..~n",
[Supervisor_name, _Error]),
start_odbc(Host)
start_odbc(Host, App)
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) ->
LHost = jlib:nameprep(Host),
ejabberd_config:get_local_option(
{odbc_server, LHost}, fun(_) -> true end, false).
case ejabberd_config:get_local_option(
{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.