-module(gen_emm).
|
|
|
|
-compile(inline).
|
|
-compile({inline_size, 128}).
|
|
|
|
-include_lib("kernel/include/logger.hrl").
|
|
|
|
-import(maps, [iterator/1, next/1]).
|
|
|
|
-export([
|
|
%% API for gen_emm
|
|
start/0, start/1, start/2, start_link/0, start_link/1, start_link/2
|
|
, start_monitor/0, start_monitor/1, start_monitor/2
|
|
, stop/1, stop/3
|
|
, call/3, call/4
|
|
, send/3
|
|
, send_request/3, wait_response/2, check_response/2
|
|
, info_notify/2, call_notify/2
|
|
, add_epm/3, add_sup_epm/3, del_epm/3
|
|
, swap_epm/3, swap_sup_epm/3
|
|
, which_epm/1
|
|
|
|
%% gen callbacks
|
|
, init_it/6
|
|
|
|
%% sys callbacks
|
|
, system_continue/3
|
|
, system_terminate/4
|
|
, system_code_change/4
|
|
, system_get_state/1
|
|
, system_replace_state/2
|
|
, format_status/2
|
|
|
|
%% Internal callbacks
|
|
, wakeupFromHib/5
|
|
|
|
%% logger callback
|
|
, format_log/1, format_log/2
|
|
]).
|
|
|
|
-define(STACKTRACE(), element(2, erlang:process_info(self(), current_stacktrace))).
|
|
|
|
%% debug 调试相关宏定义
|
|
-define(NOT_DEBUG, []).
|
|
-define(SYS_DEBUG(Debug, Name, Msg),
|
|
case Debug of
|
|
?NOT_DEBUG ->
|
|
Debug;
|
|
_ ->
|
|
sys:handle_debug(Debug, fun print_event/3, Name, Msg)
|
|
end).
|
|
|
|
-type epmHandler() ::
|
|
atom() |
|
|
{atom(), term()}.
|
|
|
|
-type terminateArgs() ::
|
|
term() |
|
|
stop |
|
|
removeEpm |
|
|
{error, term()} |
|
|
{stop, Reason :: term()} |
|
|
{error, {'EXIT', Reason :: term()}}.
|
|
|
|
-export_type([addEpmRet/0, delEpmRet/0]).
|
|
|
|
-type addEpmRet() ::
|
|
ok |
|
|
term() |
|
|
{'EXIT', term()}.
|
|
|
|
-type delEpmRet() ::
|
|
ok |
|
|
term() |
|
|
{'EXIT', term()}.
|
|
|
|
-type serverName() ::
|
|
{'local', atom()} |
|
|
{'global', term()} |
|
|
{'via', atom(), term()}.
|
|
|
|
-type serverRef() ::
|
|
pid() |
|
|
(LocalName :: atom())|
|
|
{Name :: atom(), Node :: atom()} |
|
|
{'global', term()} |
|
|
{'via', atom(), term()}.
|
|
|
|
-type debug_flag() ::
|
|
'trace' |
|
|
'log' |
|
|
'statistics' |
|
|
'debug'|
|
|
{'logfile', string()}.
|
|
|
|
-type startOpt() ::
|
|
{'timeout', timeout()} |
|
|
{'debug', [debug_flag()]} |
|
|
{'spawn_opt', [proc_lib:start_spawn_option()]} |
|
|
{'hibernate_after', timeout()}.
|
|
|
|
-type startRet() ::
|
|
{'ok', pid()} |
|
|
{'ok', {pid(), reference()}} |
|
|
{'error', term()}.
|
|
|
|
-type requestId() :: term().
|
|
|
|
-record(epmHer, {
|
|
epmId = undefined :: term(),
|
|
epmM :: atom(),
|
|
epmSup = undefined :: 'undefined' | pid(),
|
|
epmS :: term()
|
|
}).
|
|
|
|
-callback init(InitArgs :: term()) ->
|
|
{ok, State :: term()} |
|
|
{ok, State :: term(), hibernate} |
|
|
{error, Reason :: term()}.
|
|
|
|
-callback handleEvent(Event :: term(), State :: term()) ->
|
|
kpS |
|
|
removeEpm |
|
|
{ok, NewState :: term()} |
|
|
{ok, NewState :: term(), hibernate} |
|
|
{swapEpm, NewState :: term(), Args1 :: term(), NewHandler :: epmHandler(), Args2 :: term()}.
|
|
|
|
-callback handleCall(Request :: term(), State :: term()) ->
|
|
{removeEpm, Reply :: term()} |
|
|
{reply, Reply :: term()} |
|
|
{reply, Reply :: term(), NewState :: term()} |
|
|
{reply, Reply :: term(), NewState :: term(), hibernate} |
|
|
{swapEpm, Reply :: term(), NewState :: term(), Args1 :: term(), NewHandler :: epmHandler(), Args2 :: term()}.
|
|
|
|
-callback handleInfo(Info :: term(), State :: term()) ->
|
|
kpS |
|
|
removeEpm |
|
|
{ok, NewState :: term()} |
|
|
{ok, NewState :: term(), hibernate} |
|
|
{swapEpm, NewState :: term(), Args1 :: term(), NewHandler :: epmHandler(), Args2 :: term()}.
|
|
|
|
-callback terminate(Args :: terminateArgs(), State :: term()) ->
|
|
term().
|
|
|
|
-callback code_change(OldVsn :: (term() | {down, term()}), State :: term(), Extra :: term()) ->
|
|
{ok, NewState :: term()}.
|
|
|
|
-callback format_status(Opt, StatusData) -> Status when
|
|
Opt :: 'normal' | 'terminate',
|
|
StatusData :: [PDict | State],
|
|
PDict :: [{Key :: term(), Value :: term()}],
|
|
State :: term(),
|
|
Status :: term().
|
|
|
|
-optional_callbacks([
|
|
handleInfo/2
|
|
, terminate/2
|
|
, code_change/3
|
|
, format_status/2
|
|
]).
|
|
|
|
-spec start() -> startRet().
|
|
start() ->
|
|
gen:start(?MODULE, nolink, ?MODULE, [], []).
|
|
|
|
-spec start(ServerName :: serverName() | [startOpt()]) -> startRet().
|
|
start(ServerName) when is_tuple(ServerName) ->
|
|
gen:start(?MODULE, nolink, ServerName, ?MODULE, [], []);
|
|
start(Opts) when is_list(Opts) ->
|
|
gen:start(?MODULE, nolink, ?MODULE, [], Opts).
|
|
|
|
-spec start(ServerName :: serverName(), Opts :: [startOpt()]) -> startRet().
|
|
start(ServerName, Opts) ->
|
|
gen:start(?MODULE, nolink, ServerName, ?MODULE, [], Opts).
|
|
|
|
-spec start_link() -> startRet().
|
|
start_link() ->
|
|
gen:start(?MODULE, link, ?MODULE, [], []).
|
|
|
|
-spec start_link(ServerName :: serverName() | [startOpt()]) -> startRet().
|
|
start_link(ServerName) when is_tuple(ServerName) ->
|
|
gen:start(?MODULE, link, ServerName, ?MODULE, [], []);
|
|
start_link(Opts) when is_list(Opts) ->
|
|
gen:start(?MODULE, link, ?MODULE, [], Opts).
|
|
|
|
-spec start_link(ServerName :: serverName(), Opts :: [startOpt()]) -> startRet().
|
|
start_link(ServerName, Opts) ->
|
|
gen:start(?MODULE, link, ServerName, ?MODULE, [], Opts).
|
|
|
|
-spec start_monitor() -> startRet().
|
|
start_monitor() ->
|
|
gen:start(?MODULE, monitor, ?MODULE, [], []).
|
|
|
|
-spec start_monitor(ServerName :: serverName() | [startOpt()]) -> startRet().
|
|
start_monitor(ServerName) when is_tuple(ServerName) ->
|
|
gen:start(?MODULE, monitor, ServerName, ?MODULE, [], []);
|
|
start_monitor(Opts) when is_list(Opts) ->
|
|
gen:start(?MODULE, monitor, ?MODULE, [], Opts).
|
|
|
|
-spec start_monitor(ServerName :: serverName(), Opts :: [startOpt()]) -> startRet().
|
|
start_monitor(ServerName, Opts) ->
|
|
gen:start(?MODULE, monitor, ServerName, ?MODULE, [], Opts).
|
|
|
|
-spec stop(ServerRef :: serverRef()) -> 'ok'.
|
|
stop(ServerRef) ->
|
|
gen:stop(ServerRef).
|
|
|
|
-spec stop(ServerRef :: serverRef(), Reason :: term(), Timeout :: timeout()) -> ok.
|
|
stop(ServerRef, Reason, Timeout) ->
|
|
gen:stop(ServerRef, Reason, Timeout).
|
|
|
|
%% -spec init_it(pid(), 'self' | pid(), emgr_name(), module(), [term()], [_]) ->
|
|
init_it(Starter, self, ServerRef, Mod, Args, Options) ->
|
|
init_it(Starter, self(), ServerRef, Mod, Args, Options);
|
|
init_it(Starter, Parent, ServerRef, _, _, Options) ->
|
|
process_flag(trap_exit, true),
|
|
Name = gen:name(ServerRef),
|
|
Debug = gen:debug_options(Name, Options),
|
|
HibernateAfterTimeout = gen:hibernate_after(Options),
|
|
proc_lib:init_ack(Starter, {ok, self()}),
|
|
receiveIng(Parent, Name, HibernateAfterTimeout, #{}, Debug, false).
|
|
|
|
-spec add_epm(serverRef(), epmHandler(), term()) -> ok | {error, existed} | {error, term()}.
|
|
add_epm(EpmSrv, EpmHandler, Args) ->
|
|
epmRpc(EpmSrv, {'$addEpm', EpmHandler, Args}).
|
|
|
|
-spec add_sup_epm(serverRef(), epmHandler(), term()) -> ok | {error, existed} | {error, term()}.
|
|
add_sup_epm(EpmSrv, EpmHandler, Args) ->
|
|
epmRpc(EpmSrv, {'$addSupEpm', EpmHandler, Args, self()}).
|
|
|
|
-spec del_epm(serverRef(), epmHandler(), term()) -> ok | {error, module_not_found}.
|
|
del_epm(EpmSrv, EpmHandler, Args) ->
|
|
epmRpc(EpmSrv, {'$delEpm', EpmHandler, Args}).
|
|
|
|
-spec swap_epm(serverRef(), {epmHandler(), term()}, {epmHandler(), term()}) -> ok | {error, existed} | {error, term()}.
|
|
swap_epm(EpmSrv, {H1, A1}, {H2, A2}) ->
|
|
epmRpc(EpmSrv, {'$swapEpm', H1, A1, H2, A2}).
|
|
|
|
-spec swap_sup_epm(serverRef(), {epmHandler(), term()}, {epmHandler(), term()}) -> ok | {error, existed} | {error, term()}.
|
|
swap_sup_epm(EpmSrv, {H1, A1}, {H2, A2}) ->
|
|
epmRpc(EpmSrv, {'$swapSupEpm', H1, A1, H2, A2, self()}).
|
|
|
|
-spec which_epm(serverRef()) -> [epmHandler()].
|
|
which_epm(EpmSrv) ->
|
|
epmRpc(EpmSrv, '$which_handlers').
|
|
|
|
-spec info_notify(serverRef(), term()) -> 'ok'.
|
|
info_notify(EpmSrv, Event) ->
|
|
epmRequest(EpmSrv, {'$gen_info', '$infoNotify', Event}).
|
|
|
|
-spec call_notify(serverRef(), term()) -> 'ok'.
|
|
call_notify(EpmSrv, Event) ->
|
|
epmRpc(EpmSrv, {'$syncNotify', Event}).
|
|
|
|
-spec call(serverRef(), epmHandler(), term()) -> term().
|
|
call(EpmSrv, EpmHandler, Query) ->
|
|
epmRpc(EpmSrv, {'$epmCall', EpmHandler, Query}).
|
|
|
|
-spec call(serverRef(), epmHandler(), term(), timeout()) -> term().
|
|
call(EpmSrv, EpmHandler, Query, Timeout) ->
|
|
epmRpc(EpmSrv, {'$epmCall', EpmHandler, Query}, Timeout).
|
|
|
|
send(EpmSrv, EpmHandler, Msg) ->
|
|
epmRequest(EpmSrv, {'$gen_info', EpmHandler, Msg}).
|
|
|
|
-spec send_request(serverRef(), epmHandler(), term()) -> requestId().
|
|
send_request(EpmSrv, EpmHandler, Query) ->
|
|
gen:send_request(EpmSrv, '$gen_call', {'$epmCall', EpmHandler, Query}).
|
|
|
|
-spec wait_response(RequestId :: requestId(), timeout()) ->
|
|
{reply, Reply :: term()} | 'timeout' | {error, {Reason :: term(), serverRef()}}.
|
|
wait_response(RequestId, Timeout) ->
|
|
case gen:wait_response(RequestId, Timeout) of
|
|
{reply, {error, _} = Err} -> Err;
|
|
Return -> Return
|
|
end.
|
|
|
|
-spec check_response(Msg :: term(), RequestId :: requestId()) ->
|
|
{reply, Reply :: term()} | 'no_reply' | {error, {Reason :: term(), serverRef()}}.
|
|
check_response(Msg, RequestId) ->
|
|
case gen:check_response(Msg, RequestId) of
|
|
{reply, {error, _} = Err} -> Err;
|
|
Return -> Return
|
|
end.
|
|
|
|
epmRpc(EpmSrv, Cmd) ->
|
|
try gen:call(EpmSrv, '$gen_call', Cmd, infinity) of
|
|
{ok, Reply} ->
|
|
Reply
|
|
catch Class:Reason ->
|
|
erlang:raise(Class, {Reason, {?MODULE, call, [EpmSrv, Cmd, infinity]}}, ?STACKTRACE())
|
|
end.
|
|
|
|
epmRpc(EpmSrv, Cmd, Timeout) ->
|
|
try gen:call(EpmSrv, '$gen_call', Cmd, Timeout) of
|
|
{ok, Reply} ->
|
|
Reply
|
|
catch Class:Reason ->
|
|
erlang:raise(Class, {Reason, {?MODULE, call, [EpmSrv, Cmd, Timeout]}}, ?STACKTRACE())
|
|
end.
|
|
|
|
epmRequest({global, Name}, Msg) ->
|
|
try global:send(Name, Msg),
|
|
ok
|
|
catch _:_ -> ok
|
|
end;
|
|
epmRequest({via, RegMod, Name}, Msg) ->
|
|
try RegMod:send(Name, Msg),
|
|
ok
|
|
catch _:_ -> ok
|
|
end;
|
|
epmRequest(EpmSrv, Cmd) ->
|
|
EpmSrv ! Cmd,
|
|
ok.
|
|
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, true) ->
|
|
proc_lib:hibernate(?MODULE, wakeupFromHib, [Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug]);
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, _) ->
|
|
receiveIng(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, false).
|
|
|
|
wakeupFromHib(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug) ->
|
|
receiveIng(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, true).
|
|
|
|
receiveIng(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, IsHib) ->
|
|
receive
|
|
{system, From, Req} ->
|
|
sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, {ServerName, HibernateAfterTimeout, EpmHers, IsHib}, IsHib);
|
|
{'EXIT', Parent, Reason} ->
|
|
terminate_server(Reason, Parent, ServerName, EpmHers);
|
|
{'$gen_call', From, Request} ->
|
|
epmCallMsg(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, From, Request);
|
|
{'$gen_info', CmdOrEmpHandler, Event} ->
|
|
epmInfoMsg(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, CmdOrEmpHandler, Event);
|
|
Msg ->
|
|
handleMsg(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, Msg)
|
|
after HibernateAfterTimeout ->
|
|
proc_lib:hibernate(?MODULE, wakeupFromHib, [Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug])
|
|
end.
|
|
|
|
epmCallMsg(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, From, Request) ->
|
|
NewDebug = ?SYS_DEBUG(Debug, ServerName, {call, From, Request}),
|
|
case Request of
|
|
'$which_handlers' ->
|
|
reply(From, maps:keys(EpmHers)),
|
|
receiveIng(Parent, ServerName, HibernateAfterTimeout, EpmHers, NewDebug, false);
|
|
{'$addEpm', EpmHandler, Args} ->
|
|
{Reply, NewEpmHers, IsHib} = doAddEpm(EpmHers, EpmHandler, Args, undefined),
|
|
reply(From, Reply),
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib);
|
|
{'$addSupEpm', EpmHandler, Args, EpmSup} ->
|
|
{Reply, NewEpmHers, IsHib} = doAddSupEpm(EpmHers, EpmHandler, Args, EpmSup),
|
|
reply(From, Reply),
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib);
|
|
{'$delEpm', EpmHandler, Args} ->
|
|
{Reply, NewEpmHers} = doDelEpm(EpmHers, EpmHandler, Args),
|
|
reply(From, Reply),
|
|
receiveIng(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, false);
|
|
{'$swapEpm', EpmId1, Args1, EpmId2, Args2} ->
|
|
{Reply, NewEpmHers, IsHib} = doSwapEpm(EpmHers, EpmId1, Args1, EpmId2, Args2),
|
|
reply(From, Reply),
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib);
|
|
{'$swapSupEpm', EpmId1, Args1, EpmId2, Args2, SupPid} ->
|
|
{Reply, NewEpmHers, IsHib} = doSwapSupEpm(EpmHers, EpmId1, Args1, EpmId2, Args2, SupPid),
|
|
reply(From, Reply),
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib);
|
|
{'$syncNotify', Event} ->
|
|
{NewEpmHers, IsHib} = doNotify(EpmHers, handleEvent, Event, false),
|
|
reply(From, ok),
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib);
|
|
{'$epmCall', EpmHandler, Query} ->
|
|
case doEpmHandle(EpmHers, EpmHandler, handleCall, Query, From) of
|
|
{NewEpmHers, IsHib} ->
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib);
|
|
NewEpmHers ->
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, false)
|
|
end
|
|
end.
|
|
|
|
epmInfoMsg(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, CmdOrEmpHandler, Event) ->
|
|
NewDebug = ?SYS_DEBUG(Debug, ServerName, {info, CmdOrEmpHandler, Event}),
|
|
case CmdOrEmpHandler of
|
|
'$infoNotify' ->
|
|
{NewEpmHers, IsHib} = doNotify(EpmHers, handleEvent, Event, false),
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib);
|
|
EpmHandler ->
|
|
case doEpmHandle(EpmHers, EpmHandler, handleInfo, Event, false) of
|
|
{NewEpmHers, IsHib} ->
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib);
|
|
NewEpmHers ->
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, false)
|
|
end
|
|
end.
|
|
|
|
handleMsg(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, Msg) ->
|
|
NewDebug = ?SYS_DEBUG(Debug, ServerName, {in, Msg}),
|
|
case Msg of
|
|
{'EXIT', From, Reason} ->
|
|
NewEpmHers = epmStopOne(EpmHers, From, Reason),
|
|
receiveIng(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, false);
|
|
{_From, Tag, stop} ->
|
|
try terminate_server(normal, Parent, EpmHers, ServerName)
|
|
after
|
|
reply(Tag, ok)
|
|
end;
|
|
{_From, Tag, get_modules} ->
|
|
reply(Tag, get_modules(EpmHers)),
|
|
receiveIng(Parent, ServerName, HibernateAfterTimeout, EpmHers, NewDebug, false);
|
|
_ ->
|
|
{NewEpmHers, IsHib} = doNotify(EpmHers, handleInfo, EpmHers, false),
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, NewEpmHers, NewDebug, IsHib)
|
|
end.
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
%% EPM inner fun
|
|
addNewEpm(InitRet, EpmHers, Module, EpmId, EpmSup) ->
|
|
case InitRet of
|
|
{ok, State} ->
|
|
EpmHer = #epmHer{epmId = EpmId, epmM = Module, epmS = State, epmSup = EpmSup},
|
|
{ok, EpmHers#{EpmId => EpmHer}, false};
|
|
{ok, State, hibernate} ->
|
|
EpmHer = #epmHer{epmId = EpmId, epmM = Module, epmS = State, epmSup = EpmSup},
|
|
{ok, EpmHers#{EpmId => EpmHer}, true};
|
|
Other ->
|
|
{Other, EpmHers, false}
|
|
end.
|
|
|
|
doAddEpm(EpmHers, {Module, _SubId} = EpmId, Args, EpmSup) ->
|
|
case EpmHers of
|
|
#{EpmId := _EpmHer} ->
|
|
{{error, existed}, EpmHers, false};
|
|
_ ->
|
|
try Module:init(Args) of
|
|
Result ->
|
|
addNewEpm(Result, EpmHers, Module, EpmId, EpmSup)
|
|
catch
|
|
throw:Ret ->
|
|
addNewEpm(Ret, EpmHers, Module, EpmId, EpmSup);
|
|
C:R ->
|
|
{{error, {C, R, ?STACKTRACE()}}, EpmHers, false}
|
|
end
|
|
end;
|
|
doAddEpm(EpmHers, Module, Args, EpmSup) ->
|
|
case EpmHers of
|
|
#{Module := _EpmHer} ->
|
|
{{error, existed}, EpmHers, false};
|
|
_ ->
|
|
try Module:init(Args) of
|
|
Result ->
|
|
addNewEpm(Result, EpmHers, Module, Module, EpmSup)
|
|
catch
|
|
throw:Ret ->
|
|
addNewEpm(Ret, EpmHers, Module, Module, EpmSup);
|
|
C:R ->
|
|
{{error, {C, R, ?STACKTRACE()}}, EpmHers, false}
|
|
end
|
|
end.
|
|
|
|
doAddSupEpm(EpmHers, EpmHandler, Args, EpmSup) ->
|
|
case doAddEpm(EpmHers, EpmHandler, Args, EpmSup) of
|
|
{ok, _, _} = Result ->
|
|
link(EpmSup),
|
|
Result;
|
|
Ret ->
|
|
Ret
|
|
end.
|
|
|
|
doSwapEpm(EpmHers, EpmId1, Args1, EpmMId, Args2) ->
|
|
case EpmHers of
|
|
#{EpmId1 := #epmHer{epmSup = EpmSup} = EpmHer} ->
|
|
State2 = epmTerminate(EpmHer, Args1, swapped, {swapped, EpmMId, EpmSup}),
|
|
NewEpmHers = maps:remove(EpmId1, EpmHers),
|
|
case EpmSup of
|
|
false ->
|
|
doAddEpm(NewEpmHers, EpmMId, {Args2, State2}, undefined);
|
|
_ ->
|
|
doAddSupEpm(NewEpmHers, EpmMId, {Args2, State2}, EpmSup)
|
|
end;
|
|
_ ->
|
|
doAddEpm(EpmHers, EpmMId, {Args2, undefined}, undefined)
|
|
end.
|
|
|
|
doSwapSupEpm(EpmHers, EpmId1, Args1, EpmMId, Args2, EpmSup) ->
|
|
case EpmHers of
|
|
#{EpmId1 := #epmHer{epmSup = OldEpmSup} = EpmHer} ->
|
|
State2 = epmTerminate(EpmHer, Args1, swapped, {swapped, EpmMId, OldEpmSup}),
|
|
NewEpmHers = maps:remove(EpmId1, EpmHers),
|
|
doAddSupEpm(NewEpmHers, EpmMId, {Args2, State2}, EpmSup);
|
|
_ ->
|
|
doAddSupEpm(EpmHers, EpmMId, {Args2, undefined}, EpmSup)
|
|
end.
|
|
|
|
doNotify(EpmHers, Func, Event, _Form) ->
|
|
allNotify(maps:iterator(EpmHers), Func, Event, false, EpmHers, false).
|
|
|
|
allNotify(Iterator, Func, Event, From, TemEpmHers, IsHib) ->
|
|
case maps:next(Iterator) of
|
|
{K, _V, NextIterator} ->
|
|
case doEpmHandle(TemEpmHers, K, Func, Event, From) of
|
|
{NewEpmHers, NewIsHib} ->
|
|
allNotify(NextIterator, Func, Event, From, NewEpmHers, IsHib orelse NewIsHib);
|
|
NewEpmHers ->
|
|
allNotify(NextIterator, Func, Event, From, NewEpmHers, IsHib)
|
|
end;
|
|
_ ->
|
|
{TemEpmHers, IsHib}
|
|
end.
|
|
|
|
doEpmHandle(EpmHers, EpmId, Func, Event, From) ->
|
|
case EpmHers of
|
|
#{EpmId := #epmHer{epmM = EpmM, epmS = EpmS} = EpmHer} ->
|
|
try EpmM:Func(Event, EpmS) of
|
|
Result ->
|
|
handleEpmCR(Result, EpmHers, EpmId, EpmHer, Event, From)
|
|
catch
|
|
throw:Ret ->
|
|
handleEpmCR(Ret, EpmHers, EpmId, EpmHer, Event, From);
|
|
C:R ->
|
|
epmTerminate(EpmHer, {error, {C, R, ?STACKTRACE()}}, Event, crash),
|
|
maps:remove(EpmId, EpmHer)
|
|
end;
|
|
_ ->
|
|
try_reply(From, {error, bad_module}),
|
|
EpmHers
|
|
end.
|
|
|
|
doDelEpm(EpmHers, EpmHandler, Args) ->
|
|
case EpmHers of
|
|
#{EpmHandler := EpmHer} ->
|
|
epmTerminate(EpmHer, Args, delete, normal),
|
|
{ok, maps:remove(EpmHandler, EpmHers)};
|
|
_ ->
|
|
{{error, module_not_found}, EpmHers}
|
|
end.
|
|
|
|
%% handleEpmCallbackRet
|
|
handleEpmCR(Result, EpmHers, EpmId, EpmHer, Event, From) ->
|
|
case Result of
|
|
kpS ->
|
|
EpmHers;
|
|
{ok, NewEpmS} ->
|
|
MewEpmHer = setelement(#epmHer.epmS, EpmHer, NewEpmS),
|
|
EpmHers#{EpmId := MewEpmHer};
|
|
{ok, NewEpmS, hibernate} ->
|
|
MewEpmHer = setelement(#epmHer.epmS, EpmHer, NewEpmS),
|
|
{EpmHers#{EpmId := MewEpmHer}, true};
|
|
{swapEpm, NewEpmS, Args1, EpmMId, Args2} ->
|
|
#epmHer{epmSup = EpmSup} = MewEpmHer = setelement(#epmHer.epmS, EpmHer, NewEpmS),
|
|
State = epmTerminate(MewEpmHer, Args1, swapped, {swapped, EpmMId, EpmSup}),
|
|
TemEpmHers = maps:remove(EpmId, EpmHers),
|
|
{_, NewEpmHers, IsHib} =
|
|
case EpmSup of
|
|
undefined ->
|
|
doAddEpm(TemEpmHers, EpmMId, {Args2, State}, undefined);
|
|
_ ->
|
|
doAddSupEpm(TemEpmHers, EpmMId, {Args2, State}, EpmSup)
|
|
end,
|
|
{NewEpmHers, IsHib};
|
|
{swapEpm, Reply, NewEpmS, Args1, EpmMId, Args2} ->
|
|
reply(From, Reply),
|
|
#epmHer{epmSup = EpmSup} = MewEpmHer = setelement(#epmHer.epmS, EpmHer, NewEpmS),
|
|
State = epmTerminate(MewEpmHer, Args1, swapped, {swapped, EpmMId, EpmSup}),
|
|
TemEpmHers = maps:remove(EpmId, EpmHers),
|
|
{_, NewEpmHers, IsHib} =
|
|
case EpmSup of
|
|
undefined ->
|
|
doAddEpm(TemEpmHers, EpmMId, {Args2, State}, undefined);
|
|
_ ->
|
|
doAddSupEpm(TemEpmHers, EpmMId, {Args2, State}, EpmSup)
|
|
end,
|
|
{NewEpmHers, IsHib};
|
|
removeEpm ->
|
|
epmTerminate(EpmHer, removeEpm, remove, normal),
|
|
maps:remove(EpmId, EpmHers);
|
|
{removeEpm, Reply} ->
|
|
reply(From, Reply),
|
|
epmTerminate(EpmHer, removeEpm, remove, normal),
|
|
maps:remove(EpmId, EpmHers);
|
|
{reply, Reply} ->
|
|
reply(From, Reply),
|
|
EpmHers;
|
|
{reply, Reply, NewEpmS} ->
|
|
reply(From, Reply),
|
|
MewEpmHer = setelement(#epmHer.epmS, EpmHer, NewEpmS),
|
|
EpmHers#{EpmId := MewEpmHer};
|
|
{reply, Reply, NewEpmS, hibernate} ->
|
|
reply(From, Reply),
|
|
MewEpmHer = setelement(#epmHer.epmS, EpmHer, NewEpmS),
|
|
{EpmHers#{EpmId := MewEpmHer}, true};
|
|
Other ->
|
|
epmTerminate(EpmHer, {error, Other}, Event, crash),
|
|
maps:remove(EpmId, EpmHers)
|
|
end.
|
|
|
|
reportTerminate(EpmHer, crash, {error, Why}, LastIn, _) ->
|
|
reportTerminate2(EpmHer, Why, LastIn);
|
|
%% How == normal | shutdown | {swapped, NewHandler, NewSupervisor}
|
|
reportTerminate(EpmHer, How, _, LastIn, _) ->
|
|
reportTerminate2(EpmHer, How, LastIn).
|
|
|
|
reportTerminate2(#epmHer{epmSup = EpmSup, epmId = EpmId, epmS = State} = EpmHer, Reason, LastIn) ->
|
|
report_error(EpmHer, Reason, State, LastIn),
|
|
case EpmSup of
|
|
undefined ->
|
|
ok;
|
|
_ ->
|
|
EpmSup ! {gen_event_EXIT, EpmId, Reason},
|
|
ok
|
|
end.
|
|
|
|
report_error(_EpmHer, normal, _, _) -> ok;
|
|
report_error(_EpmHer, shutdown, _, _) -> ok;
|
|
report_error(_EpmHer, {swapped, _, _}, _, _) -> ok;
|
|
report_error(#epmHer{epmId = EpmId, epmM = EpmM}, Reason, State, LastIn) ->
|
|
?LOG_ERROR(
|
|
#{
|
|
label => {gen_emm, epm_terminate},
|
|
handler => {EpmId, EpmM},
|
|
name => undefined,
|
|
last_message => LastIn,
|
|
state => format_status(terminate, EpmM, get(), State),
|
|
reason => Reason
|
|
},
|
|
#{
|
|
domain => [otp],
|
|
report_cb => fun gen_emm:format_log/2,
|
|
error_logger => #{tag => error, report_cb => fun gen_event:format_log/1}
|
|
}).
|
|
|
|
epmStopAll(EpmHers) ->
|
|
forStopAll(maps:iterator(EpmHers)).
|
|
|
|
forStopAll(Iterator) ->
|
|
case maps:next(Iterator) of
|
|
{_K, V, NextIterator} ->
|
|
epmTerminate(V, stop, stop, shutdown),
|
|
case element(#epmHer.epmSup, V) of
|
|
undefined ->
|
|
ignore;
|
|
EpmSup ->
|
|
unlink(EpmSup)
|
|
end,
|
|
forStopAll(NextIterator);
|
|
_ ->
|
|
ok
|
|
end.
|
|
|
|
epmStopOne(ExitEmpSup, EpmHers, Reason) ->
|
|
forStopOne(maps:iterator(EpmHers), ExitEmpSup, Reason, EpmHers).
|
|
|
|
forStopOne(Iterator, ExitEmpSup, Reason, TemEpmHers) ->
|
|
case maps:next(Iterator) of
|
|
{K, V, NextIterator} ->
|
|
case element(#epmHer.epmSup, V) =:= ExitEmpSup of
|
|
true ->
|
|
epmTerminate(V, {stop, Reason}, {parent_terminated, {ExitEmpSup, Reason}}, shutdown),
|
|
forStopOne(NextIterator, ExitEmpSup, Reason, maps:remove(K, TemEpmHers));
|
|
_ ->
|
|
forStopOne(NextIterator, ExitEmpSup, Reason, TemEpmHers)
|
|
end;
|
|
_ ->
|
|
TemEpmHers
|
|
end.
|
|
|
|
epmTerminate(#epmHer{epmM = EpmM, epmS = State} = EpmHer, Args, LastIn, Reason) ->
|
|
case erlang:function_exported(EpmM, terminate, 2) of
|
|
true ->
|
|
Res = (catch EpmM:terminate(Args, State)),
|
|
reportTerminate(EpmHer, Reason, Args, LastIn, Res),
|
|
Res;
|
|
_ ->
|
|
reportTerminate(EpmHer, Reason, Args, LastIn, ok),
|
|
ok
|
|
end.
|
|
|
|
reply({To, Ref}, Msg) ->
|
|
try To ! {Ref, Msg},
|
|
ok
|
|
catch _:_ ->
|
|
ok
|
|
end.
|
|
|
|
try_reply(false, _Msg) ->
|
|
ignore;
|
|
try_reply({To, Ref}, Msg) ->
|
|
try To ! {Ref, Msg},
|
|
ok
|
|
catch _:_ ->
|
|
ok
|
|
end.
|
|
|
|
terminate_server(Reason, _Parent, _ServerName, EpmHers) ->
|
|
epmStopAll(EpmHers),
|
|
exit(Reason).
|
|
|
|
%%-----------------------------------------------------------------
|
|
%% Callback functions for system messages handling.
|
|
%%-----------------------------------------------------------------
|
|
system_continue(Parent, Debug, {ServerName, HibernateAfterTimeout, EpmHers, IsHib}) ->
|
|
loopEntry(Parent, ServerName, HibernateAfterTimeout, EpmHers, Debug, IsHib).
|
|
|
|
-spec system_terminate(_, _, _, _) -> no_return().
|
|
system_terminate(Reason, Parent, _Debug, {ServerName, _HibernateAfterTimeout, EpmHers, _IsHib}) ->
|
|
terminate_server(Reason, Parent, ServerName, EpmHers).
|
|
|
|
%%-----------------------------------------------------------------
|
|
%% Module here is sent in the system msg change_code. It specifies
|
|
%% which module should be changed.
|
|
%%-----------------------------------------------------------------
|
|
system_code_change({ServerName, HibernateAfterTimeout, EpmHers, IsHib}, Module, OldVsn, Extra) ->
|
|
NewEpmHers = forCodeChange(maps:iterator(EpmHers), Module, OldVsn, Extra, EpmHers),
|
|
{ok, {ServerName, HibernateAfterTimeout, NewEpmHers, IsHib}}.
|
|
|
|
forCodeChange(Iterator, CModule, OldVsn, Extra, TemEpmHers) ->
|
|
case maps:next(Iterator) of
|
|
{K, #epmHer{epmM = Module, epmS = EpmS} = V, NextIterator} when Module =:= CModule ->
|
|
{ok, NewEpmS} = Module:code_change(OldVsn, EpmS, Extra),
|
|
forCodeChange(NextIterator, CModule, OldVsn, Extra, TemEpmHers#{K := V#epmHer{epmS = NewEpmS}});
|
|
{_, _, NextIterator} ->
|
|
forCodeChange(NextIterator, CModule, OldVsn, Extra, TemEpmHers);
|
|
_ ->
|
|
TemEpmHers
|
|
end.
|
|
|
|
system_get_state({_ServerName, _HibernateAfterTimeout, EpmHers, _Hib}) ->
|
|
{ok, forGetState(maps:iterator(EpmHers), [])}.
|
|
|
|
forGetState(Iterator, Acc) ->
|
|
case maps:next(Iterator) of
|
|
{_K, #epmHer{epmId = EpmId, epmM = Module, epmS = EpmS}, NextIterator} ->
|
|
forGetState(NextIterator, [{Module, EpmId, EpmS} | Acc]);
|
|
_ ->
|
|
Acc
|
|
end.
|
|
|
|
system_replace_state(StateFun, {ServerName, HibernateAfterTimeout, EpmHers, IsHib}) ->
|
|
{NewEpmHers, NStates} = forReplaceState(maps:iterator(EpmHers), StateFun, EpmHers, []),
|
|
{ok, NStates, {ServerName, HibernateAfterTimeout, NewEpmHers, IsHib}}.
|
|
|
|
forReplaceState(Iterator, StateFun, TemEpmHers, NStates) ->
|
|
case maps:next(Iterator) of
|
|
{K, #epmHer{epmId = EpmId, epmM = Module, epmS = EpmS} = V, NextIterator} ->
|
|
NState = {_, _, NewEpmS} = StateFun({Module, EpmId, EpmS}),
|
|
forReplaceState(NextIterator, StateFun, TemEpmHers#{K := V#epmHer{epmS = NewEpmS}}, [NState | NStates]);
|
|
_ ->
|
|
{TemEpmHers, NStates}
|
|
end.
|
|
|
|
%%-----------------------------------------------------------------
|
|
%% Format debug messages. Print them as the call-back module sees
|
|
%% them, not as the real erlang messages. Use trace for that.
|
|
%%-----------------------------------------------------------------
|
|
print_event(Dev, Msg, Name) ->
|
|
case Msg of
|
|
{call, From, Request} ->
|
|
io:format(Dev, "*DBG* ~tp got call ~tp from ~tp ~n", [Name, Request, From]);
|
|
{info, CmdOrEmpHandler, Event} ->
|
|
io:format(Dev, "*DBG* ~tp got info ~tp~n", [CmdOrEmpHandler, Event]);
|
|
{in, Msg} ->
|
|
io:format(Dev, "*DBG* ~tp got in ~tp~n", [Name, Msg]);
|
|
_ ->
|
|
io:format(Dev, "*DBG* ~tp : ~tp~n", [Name, Msg])
|
|
end.
|
|
|
|
%% format_log/1 is the report callback used by Logger handler
|
|
%% error_logger only. It is kept for backwards compatibility with
|
|
%% legacy error_logger event handlers. This function must always
|
|
%% return {Format,Args} compatible with the arguments in this module's
|
|
%% calls to error_logger prior to OTP-21.0.
|
|
format_log(Report) ->
|
|
Depth = error_logger:get_format_depth(),
|
|
FormatOpts = #{
|
|
chars_limit => unlimited,
|
|
depth => Depth,
|
|
single_line => false,
|
|
encoding => utf8
|
|
},
|
|
format_log_multi(limit_report(Report, Depth), FormatOpts).
|
|
|
|
limit_report(Report, unlimited) ->
|
|
Report;
|
|
limit_report(#{label := {gen_event, terminate},
|
|
last_message := LastIn,
|
|
state := State,
|
|
reason := Reason} = Report,
|
|
Depth) ->
|
|
Report#{
|
|
last_message => io_lib:limit_term(LastIn, Depth),
|
|
state => io_lib:limit_term(State, Depth),
|
|
reason => io_lib:limit_term(Reason, Depth)
|
|
};
|
|
limit_report(#{label := {gen_event, no_handle_info}, message := Msg} = Report, Depth) ->
|
|
Report#{message => io_lib:limit_term(Msg, Depth)}.
|
|
|
|
%% format_log/2 is the report callback for any Logger handler, except
|
|
%% error_logger.
|
|
format_log(Report, FormatOpts0) ->
|
|
Default = #{
|
|
chars_limit => unlimited,
|
|
depth => unlimited,
|
|
single_line => false,
|
|
encoding => utf8
|
|
},
|
|
FormatOpts = maps:merge(Default, FormatOpts0),
|
|
IoOpts =
|
|
case FormatOpts of
|
|
#{chars_limit := unlimited} ->
|
|
[];
|
|
#{chars_limit := Limit} ->
|
|
[{chars_limit, Limit}]
|
|
end,
|
|
{Format, Args} = format_log_single(Report, FormatOpts),
|
|
io_lib:format(Format, Args, IoOpts).
|
|
|
|
format_log_single(#{label := {gen_event, terminate},
|
|
handler := Handler,
|
|
name := SName,
|
|
last_message := LastIn,
|
|
state := State,
|
|
reason := Reason},
|
|
#{single_line := true, depth := Depth} = FormatOpts) ->
|
|
P = p(FormatOpts),
|
|
Reason1 = fix_reason(Reason),
|
|
Format1 = lists:append(["Generic event handler ", P, " crashed. "
|
|
"Installed: ", P, ". Last event: ", P,
|
|
". State: ", P, ". Reason: ", P, "."]),
|
|
Args1 =
|
|
case Depth of
|
|
unlimited ->
|
|
[Handler, SName, Reason1, LastIn, State];
|
|
_ ->
|
|
[Handler, Depth, SName, Depth, Reason1, Depth,
|
|
LastIn, Depth, State, Depth]
|
|
end,
|
|
{Format1, Args1};
|
|
format_log_single(#{label := {gen_event, no_handle_info},
|
|
module := Mod,
|
|
message := Msg},
|
|
#{single_line := true, depth := Depth} = FormatOpts) ->
|
|
P = p(FormatOpts),
|
|
Format = lists:append(["Undefined handle_info in ", P, ". Unhandled message: ", P, "."]),
|
|
Args =
|
|
case Depth of
|
|
unlimited ->
|
|
[Mod, Msg];
|
|
_ ->
|
|
[Mod, Depth, Msg, Depth]
|
|
end,
|
|
{Format, Args};
|
|
format_log_single(Report, FormatOpts) ->
|
|
format_log_multi(Report, FormatOpts).
|
|
|
|
format_log_multi(#{label := {gen_event, terminate},
|
|
handler := Handler,
|
|
name := SName,
|
|
last_message := LastIn,
|
|
state := State,
|
|
reason := Reason},
|
|
#{depth := Depth} = FormatOpts) ->
|
|
Reason1 = fix_reason(Reason),
|
|
P = p(FormatOpts),
|
|
Format =
|
|
lists:append(["** gen_event handler ", P, " crashed.\n",
|
|
"** Was installed in ", P, "\n",
|
|
"** Last event was: ", P, "\n",
|
|
"** When handler state == ", P, "\n",
|
|
"** Reason == ", P, "\n"]),
|
|
Args =
|
|
case Depth of
|
|
unlimited ->
|
|
[Handler, SName, LastIn, State, Reason1];
|
|
_ ->
|
|
[Handler, Depth, SName, Depth, LastIn, Depth, State, Depth, Reason1, Depth]
|
|
end,
|
|
{Format, Args};
|
|
format_log_multi(#{label := {gen_event, no_handle_info},
|
|
module := Mod,
|
|
message := Msg},
|
|
#{depth := Depth} = FormatOpts) ->
|
|
P = p(FormatOpts),
|
|
Format =
|
|
"** Undefined handle_info in ~p\n"
|
|
"** Unhandled message: " ++ P ++ "\n",
|
|
Args =
|
|
case Depth of
|
|
unlimited ->
|
|
[Mod, Msg];
|
|
_ ->
|
|
[Mod, Msg, Depth]
|
|
end,
|
|
{Format, Args}.
|
|
|
|
fix_reason({'EXIT', {undef, [{M, F, A, _L} | _] = MFAs} = Reason}) ->
|
|
case code:is_loaded(M) of
|
|
false ->
|
|
{'module could not be loaded', MFAs};
|
|
_ ->
|
|
case erlang:function_exported(M, F, length(A)) of
|
|
true ->
|
|
Reason;
|
|
_ ->
|
|
{'function not exported', MFAs}
|
|
end
|
|
end;
|
|
fix_reason({'EXIT', Reason}) ->
|
|
Reason;
|
|
fix_reason(Reason) ->
|
|
Reason.
|
|
|
|
p(#{single_line := Single, depth := Depth, encoding := Enc}) ->
|
|
"~" ++ single(Single) ++ mod(Enc) ++ p(Depth);
|
|
p(unlimited) ->
|
|
"p";
|
|
p(_Depth) ->
|
|
"P".
|
|
|
|
single(true) -> "0";
|
|
single(false) -> "".
|
|
|
|
mod(latin1) -> "";
|
|
mod(_) -> "t".
|
|
|
|
%% Message from the release_handler.
|
|
%% The list of modules got to be a set, i.e. no duplicate elements!
|
|
get_modules(EpmHers) ->
|
|
allMods(maps:iterator(EpmHers), []).
|
|
|
|
allMods(Iterator, Acc) ->
|
|
case maps:next(Iterator) of
|
|
{_K, V, NextIterator} ->
|
|
allMods(NextIterator, [element(#epmHer.epmM, V) | Acc]);
|
|
_ ->
|
|
lists:usort(Acc)
|
|
end.
|
|
|
|
%%-----------------------------------------------------------------
|
|
%% Status information
|
|
%%-----------------------------------------------------------------
|
|
format_status(Opt, StatusData) ->
|
|
[PDict, SysState, Parent, _Debug, {ServerName, _HibernateAfterTimeout, EpmHers, _IsHib}] = StatusData,
|
|
Header = gen:format_status_header("Status for gen_emm handler", ServerName),
|
|
FmtMSL = allStateStatus(maps:iterator(EpmHers), Opt, PDict, []),
|
|
[{header, Header}, {data, [{"Status", SysState}, {"Parent", Parent}]}, {items, {"Installed handlers", FmtMSL}}].
|
|
|
|
allStateStatus(Iterator, Opt, PDict, EpmHers) ->
|
|
case maps:next(Iterator) of
|
|
{_K, #epmHer{epmM = Module, epmS = EpmS} = V, NextIterator} ->
|
|
NewEpmS = format_status(Opt, Module, PDict, EpmS),
|
|
allStateStatus(NextIterator, Opt, PDict, [V#epmHer{epmS = NewEpmS} | EpmHers]);
|
|
_ ->
|
|
EpmHers
|
|
end.
|
|
|
|
format_status(Opt, Mod, PDict, State) ->
|
|
case erlang:function_exported(Mod, format_status, 2) of
|
|
true ->
|
|
Args = [PDict, State],
|
|
case catch Mod:format_status(Opt, Args) of
|
|
{'EXIT', _} -> State;
|
|
Else -> Else
|
|
end;
|
|
_ ->
|
|
State
|
|
end.
|
|
|