diff options
author | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2017-02-22 19:46:47 +0300 |
---|---|---|
committer | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2017-02-22 19:46:47 +0300 |
commit | 3c4057ff553928d4e4ab40e410dbc6a478dd8206 (patch) | |
tree | a0272f84a1221db928cfd4c9f44d9bed6e196bc8 /src/gen_mod.erl | |
parent | Make 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.erl | 111 |
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]. |