|
-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, <ServerName>, <TimeOut>) ->_
|
|
%%
|
|
%% 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]).
|