各种有用的erlang行为
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

964 lines
33 KiB

-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.