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