-module(gen_apu). -compile(inline). -compile({inline_size, 128}). -include_lib("kernel/include/logger.hrl"). -include("genGbh.hrl"). -import(gen_call, [gcall/3, gcall/4, greply/2, try_greply/2]). -export([ %% API for gen_apu start/3, start/4, start_link/3, start_link/4 , start_monitor/3, start_monitor/4 , stop/1, stop/3 , call/2, call/3 , clfn/4, clfn/5, clfs/4, clfs/5, csfn/4, csfs/4 , send_request/2, send_request/4 , wait_response/2, receive_response/2, check_response/2 , wait_response/3, receive_response/3, check_response/3 , reqids_new/0, reqids_size/1 , reqids_add/3, reqids_to_list/1 , cast/2, send/2, reply/1, reply/2 , abcast/2, abcast/3 , multi_call/2, multi_call/3, multi_call/4 , enter_loop/3, enter_loop/4, enter_loop/5 %% 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/8 %% logger callback , format_log/1, format_log/2, print_event/3 ]). -define(STACKTRACE(), element(2, erlang:process_info(self(), current_stacktrace))). -type serverName() :: {'local', atom()} | {'global', GlobalName :: term()} | {'via', RegMod :: module(), Name :: term()}. -type serverRef() :: pid() | (LocalName :: atom()) | {Name :: atom(), Node :: atom()} | {'global', GlobalName :: term()} | {'via', RegMod :: module(), ViaName :: term()}. -type startOpt() :: daemon | {'timeout', Time :: timeout()} | {'spawn_opt', [proc_lib:spawn_option()]} | enterLoopOpt(). -type enterLoopOpt() :: {'debug', Debugs :: [sys:debug_option()]} | {'hibernate_after', HibernateAfterTimeout :: timeout()}. -type startRet() :: 'ignore' | {'ok', pid()} | {'ok', {pid(), reference()}} | {'error', term()}. -type action() :: timeout() | hibernate | {'doAfter', Args :: term()} | {'nTimeout', Name :: term(), Time :: timeouts(), TimeoutMsg :: term()} | {'nTimeout', Name :: term(), Time :: timeouts(), TimeoutMsg :: term(), Options :: ([timeoutOption()])} | {'uTimeout', Name :: term(), TimeoutMsg :: term()} | {'cTimeout', Name :: term()}. -type actions() :: action() | [action(), ...]. -type timeouts() :: Time :: timeout() | integer(). -type timeoutOption() :: {abs, Abs :: boolean()}. %% -type timer() :: #{TimeoutName :: atom() => {TimerRef :: reference(), TimeoutMsg :: term()}}. %% gcall 发送消息来源进程格式类型 -type from() :: {To :: pid(), Tag :: term()}. -type request_id() :: gen:request_id(). -type request_id_collection() :: gen:request_id_collection(). -type response_timeout() :: timeout() | {abs, integer()}. -type replyAction() :: {'reply', From :: from(), Reply :: term()}. -callback init(Args :: term()) -> {ok, State :: term()} | {ok, State :: term(), Actions :: actions()} | {stop, Reason :: term()} | ignore. %% call 消息的返回 -export_type([handleCallRet/0]). -type handleCallRet() :: kpS | {reply, Reply :: term()} | {reply, Reply :: term(), NewState :: term()} | {reply, Reply :: term(), NewState :: term(), Actions :: actions()} | {noreply, NewState :: term()} | {noreply, NewState :: term(), Actions :: actions()} | {mayReply, Reply :: term()} | {mayReply, Reply :: term(), NewState :: term()} | {mayReply, Reply :: term(), NewState :: term(), Actions :: actions()} | {stop, Reason :: term(), NewState :: term()} | {stopReply, Reason :: term(), Reply :: term(), NewState :: term()}. %% cast 消息的返回 -export_type([handleCastRet/0]). -type handleCastRet() :: kpS | {noreply, NewState :: term()} | {noreply, NewState :: term(), Actions :: actions()} | {stop, Reason :: term(), NewState :: term()}. -callback handleInfo(Info :: timeout | term(), State :: term()) -> kpS | {noreply, NewState :: term()} | {noreply, NewState :: term(), Actions :: actions()} | {stop, Reason :: term(), NewState :: term()}. -callback handleError(Error :: term(), State :: term()) -> kpS | {noreply, NewState :: term()} | {noreply, NewState :: term(), Actions :: actions()} | {stop, Reason :: term(), NewState :: term()}. -callback handleAfter(Info :: term(), State :: term()) -> kpS | {noreply, NewState :: term()} | {noreply, NewState :: term(), Actions :: actions()} | {stop, Reason :: term(), NewState :: term()}. -callback terminate(Reason :: (normal | shutdown | {shutdown, term()} |term()), State :: term()) -> term(). -callback code_change(OldVsn :: (term() | {down, term()}), State :: term(), Extra :: term()) -> {ok, NewState :: term()} | {error, Reason :: term()}. -callback formatStatus(Opt, StatusData) -> Status when Opt :: 'normal' | 'terminate', StatusData :: [PDict | State], PDict :: [{Key :: term(), Value :: term()}], State :: term(), Status :: term(). -optional_callbacks([ handleAfter/2 , handleInfo/2 , handleError/2 , terminate/2 , code_change/3 , formatStatus/2 ]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start stop API start %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -spec start(Module :: module(), Args :: term(), Opts :: [startOpt()]) -> startRet(). start(Module, Args, Options) -> gen:start(?MODULE, nolink, Module, Args, Options). -spec start(ServerName :: serverName(), Module :: module(), Args :: term(), Opts :: [startOpt()]) -> startRet(). start(ServerName, Module, Args, Options) -> gen:start(?MODULE, nolink, ServerName, Module, Args, Options). -spec start_link(Module :: module(), Args :: term(), Opts :: [startOpt()]) -> startRet(). start_link(Module, Args, Options) -> gen:start(?MODULE, link, Module, Args, Options). -spec start_link(ServerName :: serverName(), Module :: module(), Args :: term(), Opts :: [startOpt()]) -> startRet(). start_link(ServerName, Module, Args, Options) -> gen:start(?MODULE, link, ServerName, Module, Args, Options). -spec start_monitor(Module :: module(), Args :: term(), Opts :: [startOpt()]) -> startRet(). start_monitor(Module, Args, Options) -> gen:start(?MODULE, monitor, Module, Args, Options). -spec start_monitor(ServerName :: serverName(), Module :: module(), Args :: term(), Opts :: [startOpt()]) -> startRet(). start_monitor(ServerName, Module, Args, Options) -> gen:start(?MODULE, monitor, ServerName, Module, Args, Options). %%停止通用服务器并等待其终止。如果服务器位于另一个节点上,则将监视该节点。 -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). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start stop API end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% gen callbacks start %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% doModuleInit(Module, Args) -> try Module:init(Args) catch throw:Ret -> Ret; Class:Reason:Strace -> {'EXIT', Class, Reason, Strace} end. init_it(Starter, self, ServerRef, Module, Args, Options) -> init_it(Starter, self(), ServerRef, Module, Args, Options); init_it(Starter, Parent, ServerRef, Module, Args, Options) -> Name = gen:name(ServerRef), Debug = gen:debug_options(Name, Options), GbhOpts = #gbhOpts{daemon = lists:member(daemon, Options)}, HibernateAfterTimeout = gen:hibernate_after(Options), case doModuleInit(Module, Args) of {ok, State} -> proc_lib:init_ack(Starter, {ok, self()}), receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, #{}, State, false); {ok, State, Actions} -> proc_lib:init_ack(Starter, {ok, self()}), loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, #{}, State, listify(Actions)); {stop, Reason} -> % 为了保持一致性,我们必须确保在 % %%父进程收到有关失败的通知之前,必须先注销%%注册名称(如果有)。 % %%(否则,如果父进程立即%%再次尝试启动该进程,则其父进程可能会收到%already_started错误)。 gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); ignore -> gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, ignore), exit(normal); {'EXIT', Class, Reason, Stacktrace} -> gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error, terminate_reason(Class, Reason, Stacktrace)}), erlang:raise(Class, Reason, Stacktrace); _Ret -> gen:unregister_name(ServerRef), Error = {bad_return_value, _Ret}, proc_lib:init_ack(Starter, {error, Error}), exit(Error) end. %%----------------------------------------------------------------- %% enter_loop(Module, Options, State, , ) ->_ %% %% Description: Makes an existing process into a gen_apu. %% The calling process will enter the gen_apu receive %% loop and become a gen_apu process. %% The process *must* have been started using one of the %% start functions in proc_lib, see proc_lib(3). %% The user is responsible for any initialization of the %% process, including registering a name for it. %%----------------------------------------------------------------- -spec enter_loop(Module :: module(), State :: term(), Opts :: [enterLoopOpt()]) -> no_return(). enter_loop(Module, State, Opts) -> enter_loop(Module, State, Opts, self(), infinity). -spec enter_loop(Module :: module(), State :: term(), Opts :: [enterLoopOpt()], serverName() | pid()) -> no_return(). enter_loop(Module, State, Opts, ServerName) -> enter_loop(Module, State, Opts, ServerName, infinity). -spec enter_loop(Module :: module(), State :: term(), Opts :: [enterLoopOpt()], Server :: serverName() | pid(), Actions :: actions()) -> no_return(). enter_loop(Module, State, Opts, ServerName, Actions) -> Name = gen:get_proc_name(ServerName), Parent = gen:get_parent(), Debug = gen:debug_options(Name, Opts), GbhOpts = #gbhOpts{daemon = lists:member(daemon, Opts)}, HibernateAfterTimeout = gen:hibernate_after(Opts), loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, #{}, State, listify(Actions)). %%% Internal callbacks wakeupFromHib(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState) -> %% 这是一条新消息,唤醒了我们,因此我们必须立即收到它 receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, true). loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Actions) -> case doParseAL(Actions, Name, Debug, false, false, Timers) of {error, ErrorContent} -> innerError(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Actions, error, ErrorContent, ?STACKTRACE()); {Debug, IsHib, DoAfter, NewTimers} -> case DoAfter of {doAfter, Args} -> doAfterCall(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, NewTimers, CurState, listHib(IsHib), Args); _ -> case IsHib of true -> proc_lib:hibernate(?MODULE, wakeupFromHib, [Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, NewTimers, CurState]); _ -> receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, NewTimers, CurState, false) end end end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sys callbacks start %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%----------------------------------------------------------------- %% Callback functions for system messages handling. %%----------------------------------------------------------------- system_continue(Parent, Debug, {Name, Module, GbhOpts, HibernateAfterTimeout, Timers, CurState, IsHib}) -> case IsHib of true -> proc_lib:hibernate(?MODULE, wakeupFromHib, [Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState]); _ -> receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false) end. -spec system_terminate(_, _, _, [_]) -> no_return(). system_terminate(Reason, _Parent, Debug, {Name, Module, _GbhOpts, _HibernateAfterTimeout, Timers, CurState, _IsHib}) -> terminate(exit, Reason, ?STACKTRACE(), Name, Module, Debug, Timers, CurState, []). system_code_change({Name, Module, GbhOpts, HibernateAfterTimeout, Timers, CurState, IsHib}, _Module, OldVsn, Extra) -> case try Module:code_change(OldVsn, CurState, Extra) catch throw:Result -> Result; _C:_R:_S -> {_C, _R, _S} end of {ok, NewState} -> {ok, {Name, Module, GbhOpts, HibernateAfterTimeout, Timers, NewState, IsHib}}; Error -> Error end. system_get_state({_Name, _Module, _GbhOpts, _HibernateAfterTimeout, _Timers, CurState, _IsHib}) -> {ok, CurState}. system_replace_state(StateFun, {Name, Module, GbhOpts, HibernateAfterTimeout, Timers, CurState, IsHib}) -> NewState = StateFun(CurState), {ok, NewState, {Name, Module, GbhOpts, HibernateAfterTimeout, Timers, NewState, IsHib}}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sys callbacks end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% API helpers start %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% ----------------------------------------------------------------- %% Make a call to a generic server. %% If the server is located at another node, that node will %% be monitored. %% If the client is trapping exits and is linked server termination %% is handled here (? Shall we do that here (or rely on timeouts) ?). -spec call(ServerRef :: serverRef(), Request :: term()) -> Reply :: term(). call(ServerRef, Request) -> try gcall(ServerRef, '$gen_call', Request) of {ok, Reply} -> Reply catch Class:Reason -> erlang:raise(Class, {Reason, {?MODULE, call, [ServerRef, Request]}}, ?STACKTRACE()) end. -spec call(ServerRef :: serverRef(), Request :: term(), Timeout :: timeout()) -> Reply :: term(). call(ServerRef, Request, Timeout) -> try gcall(ServerRef, '$gen_call', Request, Timeout) of {ok, Reply} -> Reply catch Class:Reason -> erlang:raise(Class, {Reason, {?MODULE, call, [ServerRef, Request]}}, ?STACKTRACE()) end. -spec clfn(ServerRef :: serverRef(), M :: module(), F :: atom(), A :: list()) -> ok. clfn(ServerRef, M, F, A) -> try gcall(ServerRef, '$gen_clfn', {M, F, A}) of {ok, Reply} -> Reply catch Class:Reason -> erlang:raise(Class, {Reason, {?MODULE, clfn, [ServerRef, {M, F, A}]}}, ?STACKTRACE()) end. -spec clfn(ServerRef :: serverRef(), M :: module(), F :: atom(), A :: list(), Timeout :: timeout()) -> ok. clfn(ServerRef, M, F, A, Timeout) -> try gcall(ServerRef, '$gen_clfn', {M, F, A}, Timeout) of {ok, Reply} -> Reply catch Class:Reason -> erlang:raise(Class, {Reason, {?MODULE, clfn, [ServerRef, {M, F, A}]}}, ?STACKTRACE()) end. -spec clfs(ServerRef :: serverRef(), M :: module(), F :: atom(), A :: list()) -> ok. clfs(ServerRef, M, F, A) -> try gcall(ServerRef, '$gen_clfs', {M, F, A}) of {ok, Reply} -> Reply catch Class:Reason -> erlang:raise(Class, {Reason, {?MODULE, clfs, [ServerRef, {M, F, A}]}}, ?STACKTRACE()) end. -spec clfs(ServerRef :: serverRef(), M :: module(), F :: atom(), A :: list(), Timeout :: timeout()) -> ok. clfs(ServerRef, M, F, A, Timeout) -> try gcall(ServerRef, '$gen_clfs', {M, F, A}, Timeout) of {ok, Reply} -> Reply catch Class:Reason -> erlang:raise(Class, {Reason, {?MODULE, clfs, [ServerRef, {M, F, A}]}}, ?STACKTRACE()) end. -spec csfn(ServerRef :: serverRef(), M :: module(), F :: atom(), A :: list()) -> ok. csfn({global, Name}, M, F, A) -> try global:send(Name, {'$gen_csfn', {M, F, A}}), ok catch _:_ -> ok end; csfn({via, RegMod, Name}, M, F, A) -> try RegMod:send(Name, {'$gen_csfn', {M, F, A}}), ok catch _:_ -> ok end; csfn(Dest, M, F, A) -> try erlang:send(Dest, {'$gen_csfn', {M, F, A}}), ok catch _:_ -> ok end. -spec csfs(ServerRef :: serverRef(), M :: module(), F :: atom(), A :: list()) -> ok. csfs({global, Name}, M, F, A) -> try global:send(Name, {'$gen_csfs', {M, F, A}}), ok catch _:_ -> ok end; csfs({via, RegMod, Name}, M, F, A) -> try RegMod:send(Name, {'$gen_csfs', {M, F, A}}), ok catch _:_ -> ok end; csfs(Dest, M, F, A) -> try erlang:send(Dest, {'$gen_csfs', {M, F, A}}), ok catch _:_ -> ok end. %%% ----------------------------------------------------------------- %%% Make a call to servers at several nodes. %%% Returns: {[Replies],[BadNodes]} %%% A Timeout can be given %%% %%% A middleman process is used in case late answers arrives after %%% the timeout. If they would be allowed to glog the callers message %%% queue, it would probably become confused. Late answers will %%% now arrive to the terminated middleman and so be discarded. %%% ----------------------------------------------------------------- -spec multi_call( Name :: atom(), Request :: term() ) -> {Replies :: [{Node :: node(), Reply :: term()}], BadNodes :: [node()] }. %% multi_call(Name, Request) when is_atom(Name) -> multi_call([node() | nodes()], Name, Request, infinity). -spec multi_call( Nodes :: [node()], Name :: atom(), Request :: term() ) -> {Replies :: [{Node :: node(), Reply :: term()}], BadNodes :: [node()] }. %% multi_call(Nodes, Name, Request) when is_list(Nodes), is_atom(Name) -> multi_call(Nodes, Name, Request, infinity). -spec multi_call( Nodes :: [node()], Name :: atom(), Request :: term(), Timeout :: timeout() ) -> {Replies :: [{Node :: node(), Reply :: term()}], BadNodes :: [node()] }. -define(is_timeout(X), ( (X) =:= infinity orelse ( is_integer(X) andalso (X) >= 0 ) )). multi_call(Nodes, Name, Request, Timeout) when is_list(Nodes), is_atom(Name), ?is_timeout(Timeout) -> Alias = alias(), try Timer = if Timeout == infinity -> undefined; true -> erlang:start_timer(Timeout, self(), Alias) end, Reqs = mc_send(Nodes, Name, Alias, Request, Timer, []), mc_recv(Reqs, Alias, Timer, [], []) after _ = unalias(Alias) end. -dialyzer({no_improper_lists, mc_send/6}). mc_send([], _Name, _Alias, _Request, _Timer, Reqs) -> Reqs; mc_send([Node|Nodes], Name, Alias, Request, Timer, Reqs) when is_atom(Node) -> NN = {Name, Node}, Mon = try erlang:monitor(process, NN, [{tag, Alias}]) catch error:badarg -> %% Node not alive... M = make_ref(), Alias ! {Alias, M, process, NN, noconnection}, M end, try %% We use 'noconnect' since it is no point in bringing up a new %% connection if it was not brought up by the monitor signal... _ = erlang:send(NN, {'$gen_call', {self(), [[alias|Alias]|Mon]}, Request}, [noconnect]), ok catch _:_ -> ok end, mc_send(Nodes, Name, Alias, Request, Timer, [[Node|Mon]|Reqs]); mc_send(_BadNodes, _Name, Alias, _Request, Timer, Reqs) -> %% Cleanup then fail... unalias(Alias), mc_cancel_timer(Timer, Alias), _ = mc_recv_tmo(Reqs, Alias, [], []), error(badarg). mc_recv([], Alias, Timer, Replies, BadNodes) -> mc_cancel_timer(Timer, Alias), unalias(Alias), {Replies, BadNodes}; mc_recv([[Node|Mon] | RestReqs] = Reqs, Alias, Timer, Replies, BadNodes) -> receive {[[alias|Alias]|Mon], Reply} -> erlang:demonitor(Mon, [flush]), mc_recv(RestReqs, Alias, Timer, [{Node,Reply}|Replies], BadNodes); {Alias, Mon, process, _, _} -> mc_recv(RestReqs, Alias, Timer, Replies, [Node|BadNodes]); {timeout, Timer, Alias} -> unalias(Alias), mc_recv_tmo(Reqs, Alias, Replies, BadNodes) end. mc_recv_tmo([], _Alias, Replies, BadNodes) -> {Replies, BadNodes}; mc_recv_tmo([[Node|Mon] | RestReqs], Alias, Replies, BadNodes) -> erlang:demonitor(Mon), receive {[[alias|Alias]|Mon], Reply} -> mc_recv_tmo(RestReqs, Alias, [{Node,Reply}|Replies], BadNodes); {Alias, Mon, process, _, _} -> mc_recv_tmo(RestReqs, Alias, Replies, [Node|BadNodes]) after 0 -> mc_recv_tmo(RestReqs, Alias, Replies, [Node|BadNodes]) end. mc_cancel_timer(undefined, _Alias) -> ok; mc_cancel_timer(Timer, Alias) -> case erlang:cancel_timer(Timer) of false -> receive {timeout, Timer, Alias} -> ok end; _ -> ok end. -spec cast(ServerRef :: serverRef(), Msg :: term()) -> ok. cast({global, Name}, Msg) -> try global:send(Name, {'$gen_cast', Msg}), ok catch _:_ -> ok end; cast({via, RegMod, Name}, Msg) -> try RegMod:send(Name, {'$gen_cast', Msg}), ok catch _:_ -> ok end; cast(Dest, Msg) -> try erlang:send(Dest, {'$gen_cast', Msg}), ok catch _:_ -> ok end. -spec send(ServerRef :: serverRef(), Msg :: term()) -> ok. send({global, Name}, Msg) -> try global:send(Name, Msg), ok catch _:_ -> ok end; send({via, RegMod, Name}, Msg) -> try RegMod:send(Name, Msg), ok catch _:_ -> ok end; send(Dest, Msg) -> try erlang:send(Dest, Msg), ok catch _:_ -> ok end. %% 异步广播,不返回任何内容,只是发送“ n”祈祷 abcast(Name, Msg) when is_atom(Name) -> doAbcast([node() | nodes()], Name, Msg). abcast(Nodes, Name, Msg) when is_list(Nodes), is_atom(Name) -> doAbcast(Nodes, Name, Msg). doAbcast(Nodes, Name, Msg) -> [ begin try erlang:send({Name, Node}, {'$gen_cast', Msg}), ok catch _:_ -> ok end end || Node <- Nodes ], ok. %% Reply from a status machine callback to whom awaits in call/2 -spec reply([replyAction(), ...] | replyAction()) -> ok. reply({reply, {To, Tag}, Reply}) -> try To ! {Tag, Reply}, ok catch _:_ -> ok end; reply(Replies) when is_list(Replies) -> [greply(From, Reply) || {reply, From, Reply} <- Replies], ok. -compile({inline, [reply/2]}). -spec reply(From :: from(), Reply :: term()) -> ok. reply(From, Reply) -> greply(From, Reply). %% ----------------------------------------------------------------- %% Send a request to a generic server and return a Key which should be %% used with wait_response/2 or check_response/2 to fetch the %% result of the request. -spec send_request(ServerRef::serverRef(), Request::term()) -> ReqId::request_id(). send_request(ServerRef, Request) -> try gen:send_request(ServerRef, '$gen_call', Request) catch error:badarg -> error(badarg, [ServerRef, Request]) end. -spec send_request(ServerRef::serverRef(), Request::term(), Label::term(), ReqIdCollection::request_id_collection()) -> NewReqIdCollection::request_id_collection(). send_request(ServerRef, Request, Label, ReqIdCol) -> try gen:send_request(ServerRef, '$gen_call', Request, Label, ReqIdCol) catch error:badarg -> error(badarg, [ServerRef, Request, Label, ReqIdCol]) end. -spec wait_response(ReqId, WaitTime) -> Result when ReqId :: request_id(), WaitTime :: response_timeout(), Response :: {reply, Reply::term()} | {error, {Reason::term(), serverRef()}}, Result :: Response | 'timeout'. wait_response(ReqId, WaitTime) -> try gen:wait_response(ReqId, WaitTime) catch error:badarg -> error(badarg, [ReqId, WaitTime]) end. -spec wait_response(ReqIdCollection, WaitTime, Delete) -> Result when ReqIdCollection :: request_id_collection(), WaitTime :: response_timeout(), Delete :: boolean(), Response :: {reply, Reply::term()} | {error, {Reason::term(), serverRef()}}, Result :: {Response, Label::term(), NewReqIdCollection::request_id_collection()} | 'no_request' | 'timeout'. wait_response(ReqIdCol, WaitTime, Delete) -> try gen:wait_response(ReqIdCol, WaitTime, Delete) catch error:badarg -> error(badarg, [ReqIdCol, WaitTime, Delete]) end. -spec receive_response(ReqId, Timeout) -> Result when ReqId :: request_id(), Timeout :: response_timeout(), Response :: {reply, Reply::term()} | {error, {Reason::term(), serverRef()}}, Result :: Response | 'timeout'. receive_response(ReqId, Timeout) -> try gen:receive_response(ReqId, Timeout) catch error:badarg -> error(badarg, [ReqId, Timeout]) end. -spec receive_response(ReqIdCollection, Timeout, Delete) -> Result when ReqIdCollection :: request_id_collection(), Timeout :: response_timeout(), Delete :: boolean(), Response :: {reply, Reply::term()} | {error, {Reason::term(), serverRef()}}, Result :: {Response, Label::term(), NewReqIdCollection::request_id_collection()} | 'no_request' | 'timeout'. receive_response(ReqIdCol, Timeout, Delete) -> try gen:receive_response(ReqIdCol, Timeout, Delete) catch error:badarg -> error(badarg, [ReqIdCol, Timeout, Delete]) end. -spec check_response(Msg, ReqId) -> Result when Msg :: term(), ReqId :: request_id(), Response :: {reply, Reply::term()} | {error, {Reason::term(), serverRef()}}, Result :: Response | 'no_reply'. check_response(Msg, ReqId) -> try gen:check_response(Msg, ReqId) catch error:badarg -> error(badarg, [Msg, ReqId]) end. -spec check_response(Msg, ReqIdCollection, Delete) -> Result when Msg :: term(), ReqIdCollection :: request_id_collection(), Delete :: boolean(), Response :: {reply, Reply::term()} | {error, {Reason::term(), serverRef()}}, Result :: {Response, Label::term(), NewReqIdCollection::request_id_collection()} | 'no_request' | 'no_reply'. check_response(Msg, ReqIdCol, Delete) -> try gen:check_response(Msg, ReqIdCol, Delete) catch error:badarg -> error(badarg, [Msg, ReqIdCol, Delete]) end. -spec reqids_new() -> NewReqIdCollection::request_id_collection(). reqids_new() -> gen:reqids_new(). -spec reqids_size(ReqIdCollection::request_id_collection()) -> non_neg_integer(). reqids_size(ReqIdCollection) -> try gen:reqids_size(ReqIdCollection) catch error:badarg -> error(badarg, [ReqIdCollection]) end. -spec reqids_add(ReqId::request_id(), Label::term(), ReqIdCollection::request_id_collection()) -> NewReqIdCollection::request_id_collection(). reqids_add(ReqId, Label, ReqIdCollection) -> try gen:reqids_add(ReqId, Label, ReqIdCollection) catch error:badarg -> error(badarg, [ReqId, Label, ReqIdCollection]) end. -spec reqids_to_list(ReqIdCollection::request_id_collection()) -> [{ReqId::request_id(), Label::term()}]. reqids_to_list(ReqIdCollection) -> try gen:reqids_to_list(ReqIdCollection) catch error:badarg -> error(badarg, [ReqIdCollection]) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% API helpers end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, IsHib) -> receive Msg -> case Msg of {'$gen_call', From, Request} -> matchCallMsg(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, From, Request); {'$gen_cast', Cast} -> matchCastMsg(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Cast); {'$gen_clfn', From, MFA} -> matchMFA(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, From, MFA, false); {'$gen_clfs', From, MFA} -> matchMFA(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, From, MFA, true); {'$gen_csfn', MFA} -> matchMFA(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false, MFA, false); {'$gen_csfs', MFA} -> matchMFA(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false, MFA, true); {timeout, TimerRef, TimeoutName} -> case Timers of #{TimeoutName := {TimerRef, TimeoutMsg}} -> NewTimer = maps:remove(TimeoutName, Timers), matchInfoMsg(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, NewTimer, CurState, TimeoutMsg); _ -> matchInfoMsg(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Msg) end; {system, PidFrom, Request} -> %% 不返回但尾递归调用 system_continue/3 sys:handle_system_msg(Request, PidFrom, Parent, ?MODULE, Debug, {Name, Module, GbhOpts, HibernateAfterTimeout, Timers, CurState, IsHib}, IsHib); {'EXIT', Parent, Reason} -> terminate(exit, Reason, ?STACKTRACE(), Name, Module, Debug, Timers, CurState, Msg); _ -> matchInfoMsg(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Msg) end after HibernateAfterTimeout -> proc_lib:hibernate(?MODULE, wakeupFromHib, [Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState]) end. matchCallMsg(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, From, Request) -> ?SYS_DEBUG(Debug, Name, {in, {{call, From}, Request}}), try case is_tuple(Request) of true -> FunName = element(1, Request), Module:FunName(Request, CurState, From); _ -> Module:Request(Request, CurState, From) end of Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, From, false) catch throw:Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, From, false); error:undef -> try_greply(From, {error, undef}), receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false); Class:Reason:Strace -> try_greply(From, {error, {inner_error, {Class, Reason, Strace}}}), innerError(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, {{call, From}, Request}, Class, Reason, Strace) end. matchCastMsg(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Cast) -> ?SYS_DEBUG(Debug, Name, {in, {cast, Cast}}), try case is_tuple(Cast) of true -> FunName = element(1, Cast), Module:FunName(Cast, CurState); _ -> Module:Cast(Cast, CurState) end of Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, false, false) catch throw:Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, false, false); error:undef -> receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false); Class:Reason:Strace -> innerError(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, {cast, Cast}, Class, Reason, Strace) end. matchMFA(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, From, MFA, IsWithState) -> ?SYS_DEBUG(Debug, Name, {in, {mfa, MFA}}), try {M, F, A} = MFA, case IsWithState of true -> erlang:apply(M, F, A ++ [CurState]); _ -> erlang:apply(M, F, A) end of Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, From, true) catch throw:Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, From, true); error:undef -> try_greply(From, {error, undef}), receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false); Class:Reason:Strace -> try_greply(From, {error, {inner_error, {Class, Reason, Strace}}}), innerError(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, {mfa, MFA}, Class, Reason, Strace) end. matchInfoMsg(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Msg) -> ?SYS_DEBUG(Debug, Name, {in, {info, Msg}}), try Module:handleInfo(Msg, CurState) of Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, false, false) catch throw:Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, false, false); Class:Reason:Strace -> innerError(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, {info, Msg}, Class, Reason, Strace) end. doAfterCall(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, LeftAction, Args) -> ?SYS_DEBUG(Debug, Name, {in, {doAfter, Args}}), try Module:handleAfter(Args, CurState) of Result -> handleAR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, LeftAction, Result) catch throw:Result -> handleAR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, LeftAction, Result); Class:Reason:Strace -> innerError(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, {doAfter, Args}, Class, Reason, Strace) end. handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, From, IsAnyRet) -> case Result of kpS -> receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false); {reply, Reply} -> reply(From, Reply), ?SYS_DEBUG(Debug, Name, {out, Reply, From, CurState}), receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false); {mayReply, Reply} -> case From of false -> receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false); _ -> greply(From, Reply), ?SYS_DEBUG(Debug, Name, {out, Reply, From, CurState}), receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false) end; {noreply, NewState} -> receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, false); {reply, Reply, NewState} -> reply(From, Reply), ?SYS_DEBUG(Debug, Name, {out, Reply, From, NewState}), receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, false); {mayReply, Reply, NewState} -> case From of false -> receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, false); _ -> greply(From, Reply), ?SYS_DEBUG(Debug, Name, {out, Reply, From, NewState}), receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, false) end; {noreply, NewState, Actions} -> loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, listify(Actions)); {stop, Reason, NewState} -> terminate(exit, Reason, ?STACKTRACE(), Name, Module, Debug, Timers, NewState, {return, stop}); {reply, Reply, NewState, Actions} -> reply(From, Reply), ?SYS_DEBUG(Debug, Name, {out, Reply, From, NewState}), loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, listify(Actions)); {mayReply, Reply, NewState, Actions} -> case From of false -> loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, listify(Actions)); _ -> greply(From, Reply), ?SYS_DEBUG(Debug, Name, {out, Reply, From, NewState}), loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, listify(Actions)) end; {stopReply, Reason, Reply, NewState} -> ?SYS_DEBUG(Debug, Name, {out, Reply, From, NewState}), try terminate(exit, Reason, ?STACKTRACE(), Name, Module, Debug, Timers, NewState, {return, stop_reply}) after _ = reply(From, Reply) end; _AnyRet -> case IsAnyRet of true -> receiveIng(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, false); _ -> innerError(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, {return, _AnyRet}, error, bad_ret, ?STACKTRACE()) end end. handleAR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, LeftAction, Result) -> case Result of kpS -> loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, LeftAction); {noreply, NewState} -> loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, LeftAction); {noreply, NewState, Actions} -> loopEntry(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, NewState, listify(Actions) ++ LeftAction); {stop, Reason, NewState} -> terminate(exit, Reason, ?STACKTRACE(), Name, Module, Debug, Timers, NewState, {return, stop_reply}) end. %% loopParseActionsList doParseAL([], _Name, Debug, IsHib, DoAfter, Timers) -> {Debug, IsHib, DoAfter, Timers}; doParseAL([OneAction | LeftActions], Name, Debug, IsHib, DoAfter, Timers) -> case OneAction of hibernate -> doParseAL(LeftActions, Name, Debug, true, DoAfter, Timers); {'doAfter', _Args} -> doParseAL(LeftActions, Name, Debug, IsHib, OneAction, Timers); {'nTimeout', TimeoutName, Time, TimeoutMsg, Options} -> case Time of infinity -> NewTimers = doCancelTimer(TimeoutName, Timers), doParseAL(LeftActions, Name, Debug, IsHib, DoAfter, NewTimers); _ -> TimerRef = erlang:start_timer(Time, self(), TimeoutName, Options), ?SYS_DEBUG(Debug, Name, {start_timer, {TimeoutName, Time, TimeoutMsg, Options}}), NewTimers = doRegisterTimer(TimeoutName, TimerRef, TimeoutMsg, Timers), doParseAL(LeftActions, Name, Debug, IsHib, DoAfter, NewTimers) end; {'nTimeout', TimeoutName, Time, TimeoutMsg} -> case Time of infinity -> NewTimers = doCancelTimer(TimeoutName, Timers), doParseAL(LeftActions, Name, Debug, IsHib, DoAfter, NewTimers); _ -> TimerRef = erlang:start_timer(Time, self(), TimeoutName), ?SYS_DEBUG(Debug, Name, {start_timer, {TimeoutName, Time, TimeoutMsg, []}}), NewTimers = doRegisterTimer(TimeoutName, TimerRef, TimeoutMsg, Timers), doParseAL(LeftActions, Name, Debug, IsHib, DoAfter, NewTimers) end; {'uTimeout', TimeoutName, NewTimeoutMsg} -> NewTimers = doUpdateTimer(TimeoutName, NewTimeoutMsg, Timers), doParseAL(LeftActions, Name, Debug, IsHib, DoAfter, NewTimers); {'cTimeout', TimeoutName} -> NewTimers = doCancelTimer(TimeoutName, Timers), doParseAL(LeftActions, Name, Debug, IsHib, DoAfter, NewTimers); infinity -> doParseAL(LeftActions, Name, Debug, IsHib, DoAfter, Timers); Timeout when is_integer(Timeout) -> erlang:send_after(Timeout, self(), timeout), ?SYS_DEBUG(Debug, Name, {start_timer, {timeout, Timeout, timeout, []}}), doParseAL(LeftActions, Name, Debug, IsHib, DoAfter, Timers); _ -> {error, {bad_ActionType, OneAction}} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% timer deal start %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% doRegisterTimer(TimeoutType, NewTimerRef, TimeoutMsg, Timers) -> case Timers of #{TimeoutType := {OldTimerRef, _OldTimeMsg}} -> justCancelTimer(TimeoutType, OldTimerRef), Timers#{TimeoutType := {NewTimerRef, TimeoutMsg}}; _ -> Timers#{TimeoutType => {NewTimerRef, TimeoutMsg}} end. doCancelTimer(TimeoutType, Timers) -> case Timers of #{TimeoutType := {TimerRef, _TimeoutMsg}} -> cancelTimer(TimeoutType, TimerRef, Timers); _ -> Timers end. doUpdateTimer(TimeoutType, Timers, TimeoutMsg) -> case Timers of #{TimeoutType := {TimerRef, _OldTimeoutMsg}} -> Timers#{TimeoutType := {TimerRef, TimeoutMsg}}; _ -> Timers end. justCancelTimer(TimeoutType, TimerRef) -> case erlang:cancel_timer(TimerRef) of false -> %% 找不到计时器,我们还没有看到超时消息 receive {timeout, TimerRef, TimeoutType} -> %% 丢弃该超时消息 ok after 0 -> ok end; _ -> %% Timer 已经运行了 ok end. cancelTimer(TimeoutType, TimerRef, Timers) -> case erlang:cancel_timer(TimerRef) of false -> % 找不到计时器,我们还没有看到超时消息 receive {timeout, TimerRef, TimeoutType} -> ok % 丢弃该超时消息 after 0 -> ok end; _ -> ok % Timer 已经运行了 end, maps:remove(TimeoutType, Timers). listify(Item) when is_list(Item) -> Item; listify(Item) -> [Item]. listHib(false) -> []; listHib(_) -> [hibernate]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% timer deal end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% innerError(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, MsgEvent, Class, Reason, Stacktrace) -> case GbhOpts of #gbhOpts{daemon = true} -> error_msg({innerError, {Class, Reason, Stacktrace}}, Name, undefined, MsgEvent, Module, Debug, CurState), case erlang:function_exported(Module, handleError, 2) of true -> try Module:handleError({Class, Reason, Stacktrace}, CurState) of Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, false, false) catch throw:Result -> handleCR(Parent, Name, Module, GbhOpts, HibernateAfterTimeout, Debug, Timers, CurState, Result, false, false); IClass:IReason:IStrace -> error_msg({handleError, {IClass, IReason, IStrace}}, Name, undefined, {Class, Reason, Stacktrace}, Module, Debug, CurState), kpS end; false -> kpS end; _ -> terminate(Class, Reason, Stacktrace, Name, Module, Debug, Timers, CurState, MsgEvent) end. %%% --------------------------------------------------- %%% Terminate the server. %%% %%% terminate/8 is triggered by {stop, Reason} or bad %%% return values. The stacktrace is generated via the %%% ?STACKTRACE() macro and the ReportReason must not %%% be wrapped in tuples. %%% %%% terminate/9 is triggered in case of error/exit in %%% the user callback. In this case the report reason %%% always includes the user stacktrace. %%% %%% The reason received in the terminate/2 callbacks %%% always includes the stacktrace for errors and never %%% for exits. %%% --------------------------------------------------- terminate(Class, Reason, Stacktrace, Name, Module, Debug, _Timers, CurState, MsgEvent) -> Reply = try_terminate(Module, terminate_reason(Class, Reason, Stacktrace), CurState), case Reply of {'EXIT', C, R, S} -> error_info({R, S}, Name, undefined, MsgEvent, Module, Debug, CurState), erlang:raise(C, R, S); _ -> case {Class, Reason} of {exit, normal} -> ok; {exit, shutdown} -> ok; {exit, {shutdown, _}} -> ok; _ -> error_info(Reason, Name, undefined, MsgEvent, Module, Debug, CurState) end end, case Stacktrace of [] -> erlang:Class(Reason); _ -> erlang:raise(Class, Reason, Stacktrace) end. try_terminate(Mod, Reason, State) -> case erlang:function_exported(Mod, terminate, 2) of true -> try {ok, Mod:terminate(Reason, State)} catch throw:Ret -> {ok, Ret}; Class:Reason:Strace -> {'EXIT', Class, Reason, Strace} end; false -> {ok, ok} end. terminate_reason(error, Reason, Stacktrace) -> {Reason, Stacktrace}; terminate_reason(exit, Reason, _Stacktrace) -> Reason. error_info(_Reason, application_controller, _From, _Msg, _Mod, _State, _Debug) -> %% OTP-5811 Don't send an error report if it's the system process %% application_controller which is terminating - let init take care %% of it instead ok; error_info(Reason, Name, From, Msg, Module, Debug, State) -> Log = sys:get_log(Debug), ?LOG_ERROR(#{label => {gen_apu, terminate}, name => Name, last_message => Msg, state => format_status(terminate, Module, get(), State), log => format_log_state(Module, Log), reason => Reason, client_info => client_stacktrace(From)}, #{ domain => [otp], report_cb => fun gen_apu:format_log/2, error_logger => #{tag => error, report_cb => fun gen_apu:format_log/1} }), ok. error_msg(Reason, Name, From, Msg, Module, Debug, State) -> Log = sys:get_log(Debug), ?LOG_ERROR(#{label => {gen_apu, inner_error}, name => Name, last_message => Msg, state => format_status(inner_error, Module, get(), State), log => format_log_state(Module, Log), reason => Reason, client_info => client_stacktrace(From)}, #{ domain => [otp], report_cb => fun gen_apu:format_log/2, error_logger => #{tag => error, report_cb => fun gen_apu:format_log/1} }), ok. client_stacktrace(undefined) -> undefined; client_stacktrace({From, _Tag}) -> client_stacktrace(From); client_stacktrace(From) when is_pid(From), node(From) =:= node() -> case process_info(From, [current_stacktrace, registered_name]) of undefined -> {From, dead}; [{current_stacktrace, Stacktrace}, {registered_name, []}] -> {From, {From, Stacktrace}}; [{current_stacktrace, Stacktrace}, {registered_name, Name}] -> {From, {Name, Stacktrace}} end; client_stacktrace(From) when is_pid(From) -> {From, remote}. %% 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_apu, terminate}, last_message := Msg, state := State, log := Log, reason := Reason, client_info := Client} = Report, Depth) -> Report#{ last_message => io_lib:limit_term(Msg, Depth), state => io_lib:limit_term(State, Depth), log => [io_lib:limit_term(L, Depth) || L <- Log], reason => io_lib:limit_term(Reason, Depth), client_info => limit_client_report(Client, Depth) }; limit_report(#{label := {gen_apu, no_handle_info}, message := Msg} = Report, Depth) -> Report#{message => io_lib:limit_term(Msg, Depth)}. limit_client_report({From, {Name, Stacktrace}}, Depth) -> {From, {Name, io_lib:limit_term(Stacktrace, Depth)}}; limit_client_report(Client, _) -> Client. %% 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_apu, terminate}, name := Name, last_message := Msg, state := State, log := Log, reason := Reason, client_info := Client}, #{single_line := true, depth := Depth} = FormatOpts) -> P = p(FormatOpts), Format1 = lists:append(["Generic server ", P, " terminating. Reason: ", P, ". Last message: ", P, ". State: ", P, "."]), {ServerLogFormat, ServerLogArgs} = format_server_log_single(Log, FormatOpts), {ClientLogFormat, ClientLogArgs} = format_client_log_single(Client, FormatOpts), Args1 = case Depth of unlimited -> [Name, fix_reason(Reason), Msg, State]; _ -> [Name, Depth, fix_reason(Reason), Depth, Msg, Depth, State, Depth] end, {Format1 ++ ServerLogFormat ++ ClientLogFormat, Args1 ++ ServerLogArgs ++ ClientLogArgs}; format_log_single(#{label := {gen_apu, no_handle_info}, module := Module, 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 -> [Module, Msg]; _ -> [Module, Depth, Msg, Depth] end, {Format, Args}; format_log_single(Report, FormatOpts) -> format_log_multi(Report, FormatOpts). format_log_multi(#{label := {gen_apu, terminate}, name := Name, last_message := Msg, state := State, log := Log, reason := Reason, client_info := Client}, #{depth := Depth} = FormatOpts) -> Reason1 = fix_reason(Reason), {ClientFmt, ClientArgs} = format_client_log(Client, FormatOpts), P = p(FormatOpts), Format = lists:append( ["** Generic server ", P, " terminating \n" "** Last message in was ", P, "~n" "** When Server state == ", P, "~n" "** Reason for termination ==~n** ", P, "~n"] ++ case Log of [] -> []; _ -> ["** Log ==~n** [" | lists:join(",~n ", lists:duplicate(length(Log), P))] ++ ["]~n"] end) ++ ClientFmt, Args = case Depth of unlimited -> [Name, Msg, State, Reason1] ++ Log ++ ClientArgs; _ -> [Name, Depth, Msg, Depth, State, Depth, Reason1, Depth] ++ case Log of [] -> []; _ -> lists:flatmap(fun(L) -> [L, Depth] end, Log) end ++ ClientArgs end, {Format, Args}; format_log_multi(#{label := {gen_apu, no_handle_info}, module := Module, message := Msg}, #{depth := Depth} = FormatOpts) -> P = p(FormatOpts), Format = "** Undefined handle_info in ~p~n" "** Unhandled message: " ++ P ++ "~n", Args = case Depth of unlimited -> [Module, Msg]; _ -> [Module, Msg, Depth] end, {Format, Args}. fix_reason({undef, [{M, F, A, L} | MFAs]} = Reason) -> case code:is_loaded(M) of false -> {'module could not be loaded', [{M, F, A, L} | MFAs]}; _ -> case erlang:function_exported(M, F, length(A)) of true -> Reason; false -> {'function not exported', [{M, F, A, L} | MFAs]} end end; fix_reason(Reason) -> Reason. format_server_log_single([], _) -> {"", []}; format_server_log_single(Log, FormatOpts) -> Args = case maps:get(depth, FormatOpts) of unlimited -> [Log]; Depth -> [Log, Depth] end, {" Log: " ++ p(FormatOpts), Args}. format_client_log_single(undefined, _) -> {"", []}; format_client_log_single({From, dead}, _) -> {" Client ~0p is dead.", [From]}; format_client_log_single({From, remote}, _) -> {" Client ~0p is remote on node ~0p.", [From, node(From)]}; format_client_log_single({_From, {Name, Stacktrace0}}, FormatOpts) -> P = p(FormatOpts), %% Minimize the stacktrace a bit for single line reports. This is %% hopefully enough to point out the position. Stacktrace = lists:sublist(Stacktrace0, 4), Args = case maps:get(depth, FormatOpts) of unlimited -> [Name, Stacktrace]; Depth -> [Name, Depth, Stacktrace, Depth] end, {" Client " ++ P ++ " stacktrace: " ++ P ++ ".", Args}. format_client_log(undefined, _) -> {"", []}; format_client_log({From, dead}, _) -> {"** Client ~p is dead~n", [From]}; format_client_log({From, remote}, _) -> {"** Client ~p is remote on node ~p~n", [From, node(From)]}; format_client_log({_From, {Name, Stacktrace}}, FormatOpts) -> P = p(FormatOpts), Format = lists:append(["** Client ", P, " stacktrace~n", "** ", P, "~n"]), Args = case maps:get(depth, FormatOpts) of unlimited -> [Name, Stacktrace]; Depth -> [Name, Depth, Stacktrace, Depth] end, {Format, Args}. 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". %%----------------------------------------------------------------- %% Status information %%----------------------------------------------------------------- format_status(Opt, StatusData) -> [PDict, SysState, Parent, Debug, {Name, Module, _GbhOpts, _HibernateAfterTimeout, _Timers, CurState, _IsHib}] = StatusData, Header = gen:format_status_header("Status for generic server", Name), Log = sys:get_log(Debug), Specific = case format_status(Opt, Module, PDict, CurState) of S when is_list(S) -> S; S -> [S] end, [{header, Header}, {data, [{"Status", SysState}, {"Parent", Parent}, {"Logged events", format_log_state(Module, Log)}]} | Specific]. format_log_state(Module, Log) -> [case Event of {out, Msg, From, State} -> {out, Msg, From, format_status(terminate, Module, get(), State)}; {noreply, State} -> {noreply, format_status(terminate, Module, get(), State)}; _ -> Event end || Event <- Log]. format_status(Opt, Module, PDict, State) -> DefStatus = case Opt of terminate -> State; _ -> [{data, [{"State", State}]}] end, case erlang:function_exported(Module, format_status, 2) of true -> case catch Module:formatStatus(Opt, [PDict, State]) of {'EXIT', _} -> DefStatus; Else -> Else end; _ -> DefStatus 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, {in, Msg}, Name) -> case Msg of {{call, {From, _Tag}}, Call} -> io:format(Dev, "*DBG* ~tp got call ~tp from ~tw~n", [Name, Call, From]); {cast, Cast} -> io:format(Dev, "*DBG* ~tp got cast ~tp~n", [Name, Cast]); _ -> io:format(Dev, "*DBG* ~tp got ~tp~n", [Name, Msg]) end; print_event(Dev, {out, Msg, {To, _Tag}, State}, Name) -> io:format(Dev, "*DBG* ~tp sent ~tp to ~tw, new state ~tp~n", [Name, Msg, To, State]); print_event(Dev, {noreply, State}, Name) -> io:format(Dev, "*DBG* ~tp new state ~tp~n", [Name, State]); print_event(Dev, Event, Name) -> io:format(Dev, "*DBG* ~tp dbg ~tp~n", [Name, Event]).