summaryrefslogtreecommitdiff
path: root/src/gen_mod.erl
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2017-02-22 19:46:47 +0300
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2017-02-22 19:46:47 +0300
commit3c4057ff553928d4e4ab40e410dbc6a478dd8206 (patch)
treea0272f84a1221db928cfd4c9f44d9bed6e196bc8 /src/gen_mod.erl
parentMake sure all hooks are called with proper host (diff)
Reload modules when reloading configuration file
Diffstat (limited to 'src/gen_mod.erl')
-rw-r--r--src/gen_mod.erl111
1 files changed, 105 insertions, 6 deletions
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index 68a5aeb5..b74d1555 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -31,10 +31,10 @@
-author('alexey@process-one.net').
-export([init/1, start_link/0, start_child/3, start_child/4,
- stop_child/1, stop_child/2]).
+ stop_child/1, stop_child/2, config_reloaded/0]).
-export([start/0, start_module/2, start_module/3,
stop_module/2, stop_module_keep_config/2, get_opt/3,
- get_opt/4, get_opt_host/3, opt_type/1,
+ get_opt/4, get_opt_host/3, opt_type/1, is_equal_opt/5,
get_module_opt/4, get_module_opt/5, get_module_opt_host/3,
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2,
@@ -46,6 +46,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
-record(ejabberd_module,
{module_host = {undefined, <<"">>} :: {atom(), binary()},
@@ -56,9 +57,12 @@
-callback start(binary(), opts()) -> ok | {ok, pid()}.
-callback stop(binary()) -> any().
+-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()}.
-callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
-callback depends(binary(), opts()) -> [{module(), hard | soft}].
+-optional_callbacks([reload/3]).
+
-export_type([opts/0]).
-export_type([db_type/0]).
@@ -75,6 +79,7 @@ start_link() ->
supervisor:start_link({local, ejabberd_gen_mod_sup}, ?MODULE, []).
init([]) ->
+ ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
ets:new(ejabberd_modules,
[named_table, public,
{keypos, #ejabberd_module.module_host}]),
@@ -182,9 +187,7 @@ start_module(Host, Module) ->
start_module(Host, Module, Opts0) ->
?DEBUG("loading ~s at ~s", [Module, Host]),
Opts = validate_opts(Module, Opts0),
- ets:insert(ejabberd_modules,
- #ejabberd_module{module_host = {Module, Host},
- opts = Opts}),
+ store_options(Host, Module, Opts),
try case Module:start(Host, Opts) of
ok -> ok;
{ok, Pid} when is_pid(Pid) -> {ok, Pid};
@@ -202,6 +205,77 @@ start_module(Host, Module, Opts0) ->
erlang:raise(Class, Reason, erlang:get_stacktrace())
end.
+-spec reload_modules(binary()) -> ok.
+reload_modules(Host) ->
+ NewMods = ejabberd_config:get_option(
+ {modules, Host}, opt_type(modules), []),
+ OldMods = ets:select(
+ ejabberd_modules,
+ ets:fun2ms(
+ fun(#ejabberd_module{module_host = {M, H}, opts = O})
+ when H == Host -> {M, O}
+ end)),
+ lists:foreach(
+ fun({Mod, _Opts}) ->
+ case lists:keymember(Mod, 1, NewMods) of
+ false ->
+ stop_module(Host, Mod);
+ true ->
+ ok
+ end
+ end, OldMods),
+ lists:foreach(
+ fun({Mod, Opts}) ->
+ case lists:keymember(Mod, 1, OldMods) of
+ false ->
+ start_module(Host, Mod, Opts);
+ true ->
+ ok
+ end
+ end, NewMods),
+ lists:foreach(
+ fun({Mod, OldOpts}) ->
+ case lists:keyfind(Mod, 1, NewMods) of
+ {_, NewOpts} when NewOpts /= OldOpts ->
+ reload_module(Host, Mod, NewOpts, OldOpts);
+ _ ->
+ ok
+ end
+ end, OldMods).
+
+-spec reload_module(binary(), module(), opts(), opts()) -> ok | {ok, pid()}.
+reload_module(Host, Module, NewOpts0, OldOpts) ->
+ case erlang:function_exported(Module, reload, 3) of
+ true ->
+ ?DEBUG("reloading ~s at ~s", [Module, Host]),
+ NewOpts = validate_opts(Module, NewOpts0),
+ store_options(Host, Module, NewOpts),
+ try case Module:reload(Host, NewOpts, OldOpts) of
+ ok -> ok;
+ {ok, Pid} when is_pid(Pid) -> {ok, Pid};
+ Err -> erlang:error(Err)
+ end
+ catch Class:Reason ->
+ StackTrace = erlang:get_stacktrace(),
+ ?CRITICAL_MSG("failed to reload module ~s at ~s:~n"
+ "** Reason = ~p",
+ [Module, Host,
+ {Class, {Reason, StackTrace}}]),
+ erlang:raise(Class, Reason, StackTrace)
+ end;
+ false ->
+ ?WARNING_MSG("module ~s doesn't support reloading "
+ "and will be restarted", [Module]),
+ stop_module(Host, Module),
+ start_module(Host, Module, NewOpts0)
+ end.
+
+-spec store_options(binary(), module(), opts()) -> true.
+store_options(Host, Module, Opts) ->
+ ets:insert(ejabberd_modules,
+ #ejabberd_module{module_host = {Module, Host},
+ opts = Opts}).
+
maybe_halt_ejabberd(ErrorText) ->
case is_app_running(ejabberd) of
false ->
@@ -239,6 +313,7 @@ stop_modules(Host) ->
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
stop_module(Host, Module) ->
+ ?DEBUG("stopping ~s at ~s", [Module, Host]),
case stop_module_keep_config(Host, Module) of
error -> error;
ok -> ok
@@ -544,5 +619,29 @@ get_module_proc(Host, Base) ->
is_loaded(Host, Module) ->
ets:member(ejabberd_modules, {Module, Host}).
-opt_type(modules) -> fun (L) when is_list(L) -> L end;
+-spec config_reloaded() -> ok.
+config_reloaded() ->
+ lists:foreach(
+ fun(Host) ->
+ reload_modules(Host)
+ end, ?MYHOSTS).
+
+-spec is_equal_opt(atom(), opts(), opts(), check_fun(), any()) ->
+ true | {false, any(), any()}.
+is_equal_opt(Opt, NewOpts, OldOpts, VFun, Default) ->
+ NewVal = get_opt(Opt, NewOpts, VFun, Default),
+ OldVal = get_opt(Opt, OldOpts, VFun, Default),
+ if NewVal /= OldVal ->
+ {false, NewVal, OldVal};
+ true ->
+ true
+ end.
+
+opt_type(modules) ->
+ fun(Mods) ->
+ lists:map(
+ fun({M, A}) when is_atom(M), is_list(A) ->
+ {M, A}
+ end, Mods)
+ end;
opt_type(_) -> [modules].