From 0dde228e6b3b1942917b1ad25cdeacd2108d79be Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Mon, 18 May 2026 21:23:16 +0900 Subject: [PATCH] Fork gen_server --- src/gd_con.erl | 104 ++- src/gd_server.erl | 1529 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1578 insertions(+), 55 deletions(-) create mode 100644 src/gd_server.erl diff --git a/src/gd_con.erl b/src/gd_con.erl index e1872a2..98a40bd 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -8,7 +8,7 @@ -copyright("QPQ AG "). -license("GPL-3.0-or-later"). --behavior(gen_server). +% -behavior(gd_server). -export([show_ui/1, open_wallet/2, close_wallet/0, new_wallet/4, import_wallet/3, drop_wallet/2, selected/1, network/0, @@ -25,6 +25,7 @@ -export([save/2]). -export([start_link/0, stop/0]). -export([init/1, terminate/2, code_change/3, + system_get_state/1, format_status/1, handle_call/3, handle_cast/2, handle_info/2]). -include("$zx_include/zx_logger.hrl"). -include("gd.hrl"). @@ -66,7 +67,7 @@ when Name :: ui_name(). show_ui(Name) -> - gen_server:call(?MODULE, {show_ui, Name}). + gd_server:call(?MODULE, {show_ui, Name}). -spec open_wallet(Path, Phrase) -> Result @@ -75,13 +76,13 @@ show_ui(Name) -> Result :: ok | {error, Reason :: term()}. open_wallet(Path, Phrase) -> - gen_server:call(?MODULE, {open_wallet, Path, Phrase}, infinity). + gd_server:call(?MODULE, {open_wallet, Path, Phrase}, infinity). -spec close_wallet() -> ok. close_wallet() -> - gen_server:cast(?MODULE, close_wallet). + gd_server:cast(?MODULE, close_wallet). -spec new_wallet(Net, Name, Path, Password) -> ok @@ -91,7 +92,7 @@ close_wallet() -> Password :: string(). new_wallet(Net, Name, Path, Password) -> - gen_server:cast(?MODULE, {new_wallet, Net, Name, Path, Password}). + gd_server:cast(?MODULE, {new_wallet, Net, Name, Path, Password}). -spec import_wallet(Name, Path, Password) -> ok @@ -100,7 +101,7 @@ new_wallet(Net, Name, Path, Password) -> Password :: string(). import_wallet(Name, Path, Password) -> - gen_server:cast(?MODULE, {import_wallet, Name, Path, Password}). + gd_server:cast(?MODULE, {import_wallet, Name, Path, Password}). -spec drop_wallet(Path, Delete) -> ok @@ -108,21 +109,21 @@ import_wallet(Name, Path, Password) -> Delete :: boolean(). drop_wallet(Path, Delete) -> - gen_server:cast(?MODULE, {drop_wallet, Path, Delete}). + gd_server:cast(?MODULE, {drop_wallet, Path, Delete}). -spec selected(Index) -> ok when Index :: pos_integer() | none. selected(Index) -> - gen_server:cast(?MODULE, {selected, Index}). + gd_server:cast(?MODULE, {selected, Index}). -spec network() -> {ok, NetworkID} | none when NetworkID :: binary(). network() -> - gen_server:call(?MODULE, network). + gd_server:call(?MODULE, network). -spec password(Old, New) -> ok @@ -130,13 +131,13 @@ network() -> New :: none | string(). password(Old, New) -> - gen_server:cast(?MODULE, {password, Old, New}). + gd_server:cast(?MODULE, {password, Old, New}). -spec refresh() -> ok. refresh() -> - gen_server:cast(?MODULE, refresh). + gd_server:cast(?MODULE, refresh). -spec nonce(ID) -> {ok, Nonce} | {error, Reason} @@ -145,48 +146,48 @@ refresh() -> Reason :: term(). % FIXME nonce(ID) -> - gen_server:call(?MODULE, {nonce, ID}). + gd_server:call(?MODULE, {nonce, ID}). -spec spend(TX) -> ok when TX :: #spend_tx{}. spend(TX) -> - gen_server:cast(?MODULE, {spend, TX}). + gd_server:cast(?MODULE, {spend, TX}). -spec chain_id() -> {ok, ID} when ID :: binary(). chain_id() -> - gen_server:call(?MODULE, chain_id). + gd_server:call(?MODULE, chain_id). -spec grids(string()) -> ok. grids(String) -> - gen_server:cast(?MODULE, {grids, String}). + gd_server:cast(?MODULE, {grids, String}). -spec sign_mess(Request) -> ok when Request :: map(). sign_mess(Request) -> - gen_server:cast(?MODULE, {sign_mess, Request}). + gd_server:cast(?MODULE, {sign_mess, Request}). -spec sign_binary(Request) -> ok when Request :: map(). sign_binary(Request) -> - gen_server:cast(?MODULE, {sign_binary, Request}). + gd_server:cast(?MODULE, {sign_binary, Request}). -spec sign_tx(Request) -> ok when Request :: map(). sign_tx(Request) -> - gen_server:cast(?MODULE, {sign_tx, Request}). + gd_server:cast(?MODULE, {sign_tx, Request}). -spec sign_call(ChainID, PubKey, TX) -> Result @@ -197,14 +198,14 @@ sign_tx(Request) -> | {error, Reason :: term()}. sign_call(ChainID, PubKey, TX) -> - gen_server:call(?MODULE, {sign_call, ChainID, PubKey, TX}). + gd_server:call(?MODULE, {sign_call, ChainID, PubKey, TX}). -spec deploy(Build) -> ok when Build :: map(). deploy(Build) -> - gen_server:cast(?MODULE, {deploy, Build}). + gd_server:cast(?MODULE, {deploy, Build}). -spec prompt_call(FunDef, ConID, Build) -> ok @@ -215,7 +216,7 @@ deploy(Build) -> Build :: map(). % Fixme prompt_call(FunDef, ConID, Build) -> - gen_server:cast(?MODULE, {prompt_call, FunDef, ConID, Build}). + gd_server:cast(?MODULE, {prompt_call, FunDef, ConID, Build}). -spec list_calls() -> Calls @@ -227,7 +228,7 @@ prompt_call(FunDef, ConID, Build) -> %% List any active contract call tasks. list_calls() -> - gen_server:call(?MODULE, list_calls). + gd_server:call(?MODULE, list_calls). -spec open_contract(ConID) -> ok @@ -247,7 +248,7 @@ open_contract(ConID) -> %% The controller will start the doomweaver if it isn't already on. open_contract(ConID, DevmanToFront) when is_binary(ConID) -> - gen_server:cast(?MODULE, {open_contract, ConID, DevmanToFront}); + gd_server:cast(?MODULE, {open_contract, ConID, DevmanToFront}); open_contract(ConID, DevmanToFront) when is_list(ConID) -> open_contract(list_to_binary(ConID), DevmanToFront). @@ -272,7 +273,7 @@ show_call(ConID, Info) -> %% Starts the doomweaver if it isn't already running. show_call(ConID, Info, DevmanToFront) when is_binary(ConID) -> - gen_server:cast(?MODULE, {show_call, ConID, Info, DevmanToFront}); + gd_server:cast(?MODULE, {show_call, ConID, Info, DevmanToFront}); show_call(ConID, Info, DevmanToFront) when is_list(ConID) -> show_call(list_to_binary(ConID), Info, DevmanToFront). @@ -293,14 +294,14 @@ show_call(ConID, Info, DevmanToFront) when is_list(ConID) -> %% system this will change quite a lot. make_key({eddsa, ed25519}, 256, Name, Seed, Encoding, Transform) -> - gen_server:cast(?MODULE, {make_key, Name, Seed, Encoding, Transform}). + gd_server:cast(?MODULE, {make_key, Name, Seed, Encoding, Transform}). -spec recover_key(Mnemonic) -> ok when Mnemonic :: string(). recover_key(Mnemonic) -> - gen_server:cast(?MODULE, {recover_key, Mnemonic}). + gd_server:cast(?MODULE, {recover_key, Mnemonic}). -spec mnemonic(ID) -> {ok, Mnemonic} | error @@ -308,7 +309,7 @@ recover_key(Mnemonic) -> Mnemonic :: string(). mnemonic(ID) -> - gen_server:call(?MODULE, {mnemonic, ID}). + gd_server:call(?MODULE, {mnemonic, ID}). -spec rename_key(ID, NewName) -> ok @@ -316,14 +317,14 @@ mnemonic(ID) -> NewName :: string(). rename_key(ID, NewName) -> - gen_server:cast(?MODULE, {rename_key, ID, NewName}). + gd_server:cast(?MODULE, {rename_key, ID, NewName}). -spec drop_key(ID) -> ok when ID :: gajudesk:id(). drop_key(ID) -> - gen_server:cast(?MODULE, {drop_key, ID}). + gd_server:cast(?MODULE, {drop_key, ID}). -spec list_keys() -> Result @@ -331,7 +332,7 @@ drop_key(ID) -> | error. list_keys() -> - gen_server:call(?MODULE, list_keys). + gd_server:call(?MODULE, list_keys). %%% Network functions @@ -340,25 +341,25 @@ list_keys() -> when New :: #node{}. add_node(New) -> - gen_server:cast(?MODULE, {add_node, New}). + gd_server:cast(?MODULE, {add_node, New}). -spec set_sole_node(TheOneTrueNode) -> ok when TheOneTrueNode :: #node{}. set_sole_node(TheOneTrueNode) -> - gen_server:cast(?MODULE, {set_sole_node, TheOneTrueNode}). + gd_server:cast(?MODULE, {set_sole_node, TheOneTrueNode}). -spec tic(Interval) -> ok when Interval :: pos_integer() | stop. tic(stop) -> - gen_server:cast(?MODULE, {tic, stop}); + gd_server:cast(?MODULE, {tic, stop}); tic(0) -> - gen_server:cast(?MODULE, {tic, stop}); + gd_server:cast(?MODULE, {tic, stop}); tic(Interval) when Interval -> - gen_server:cast(?MODULE, {tic, Interval}). + gd_server:cast(?MODULE, {tic, Interval}). -spec update_balance(AccountID, Pucks) -> ok @@ -366,14 +367,14 @@ tic(Interval) when Interval -> Pucks :: non_neg_integer(). update_balance(AccountID, Pucks) -> - gen_server:cast(?MODULE, {update_balance, AccountID, Pucks}). + gd_server:cast(?MODULE, {update_balance, AccountID, Pucks}). %%% Lifecycle functions -spec stop() -> ok. stop() -> - gen_server:cast(?MODULE, stop). + gd_server:cast(?MODULE, stop). -spec save(Module, Prefs) -> ok | {error, Reason} @@ -382,7 +383,7 @@ stop() -> Reason :: file:posix(). save(Module, Prefs) -> - gen_server:call(?MODULE, {save, Module, Prefs}). + gd_server:call(?MODULE, {save, Module, Prefs}). @@ -399,14 +400,14 @@ save(Module, Prefs) -> %% Called by gd_sup. start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). + gd_server:start_link({local, ?MODULE}, ?MODULE, none, []). -spec init(none) -> {ok, state()}. init(none) -> ok = log(info, "Starting"), - process_flag(sensitive, true), + _ = process_flag(sensitive, true), {FirstRun, Prefs} = read_prefs(), GUI_Prefs = maps:get(gd_gui, Prefs, #{}), Window = gd_gui:start_link(GUI_Prefs), @@ -434,9 +435,6 @@ default_tic() -> -%%% gen_server Message Handling Callbacks - - -spec handle_call(Message, From, State) -> Result when Message :: term(), From :: {pid(), reference()}, @@ -446,9 +444,6 @@ default_tic() -> Response :: ok | {error, {listening, inet:port_number()}}, NewState :: state(). -%% @private -%% The gen_server:handle_call/3 callback. -%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3 handle_call(list_keys, _, State) -> Response = do_list_keys(State), @@ -486,9 +481,6 @@ handle_call(Unexpected, From, State) -> when Message :: term(), State :: state(), NewState :: state(). -%% @private -%% The gen_server:handle_cast/2 callback. -%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2 handle_cast(close_wallet, State) -> NextState = do_close_wallet(State), @@ -574,9 +566,6 @@ handle_cast(Unexpected, State) -> when Message :: term(), State :: state(), NewState :: state(). -%% @private -%% The gen_server:handle_info/2 callback. -%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2 handle_info(tic, State) -> NewState = handle_tic(State), @@ -603,10 +592,15 @@ handle_down(Mon, PID, Info, State = #s{tasks = Tasks}) -> end. +system_get_state(State) -> + {ok, sanitize(State)}. + +format_status(Status = #{state := State}) -> + Status#{state := sanitize(State)}. + +sanitize(State) -> + State#s{wallet = nope, pass = nope, wallets = []}. -%% @private -%% gen_server callback to handle state transformations necessary for hot -%% code updates. This template performs no transformation. code_change(_, State, _) -> {ok, State}. diff --git a/src/gd_server.erl b/src/gd_server.erl new file mode 100644 index 0000000..508b2e8 --- /dev/null +++ b/src/gd_server.erl @@ -0,0 +1,1529 @@ +%%% @private +%%% A fork of gen_server, used specifically to override `system_get_state/1' +%%% and `system_replace_state/2'. +%%% @end + +-module(gd_server). + +-export([start/3, start/4, + start_link/3, start_link/4, + start_monitor/3, start_monitor/4, + stop/1, stop/3, + call/2, call/3, + 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, reply/2, + abcast/2, abcast/3, + multi_call/2, multi_call/3, multi_call/4, + enter_loop/3, enter_loop/4, enter_loop/5, wake_hib/6]). + +%% System exports +-export([system_continue/3, + system_terminate/4, + system_code_change/4, + system_get_state/1, + system_replace_state/2, + format_status/2]). + +-behaviour(sys). + +%% logger callback +-export([format_log/1, format_log/2]). + +%% Internal exports +-export([init_it/6]). + +-include_lib("kernel/include/logger.hrl"). + +-export_type( + [from/0, + reply_tag/0, + request_id/0, + request_id_collection/0, + format_status/0]). + +-export_type( + [server_name/0, + server_ref/0, + start_opt/0, + enter_loop_opt/0, + start_ret/0, + start_mon_ret/0]). + +-define( + STACKTRACE(), + element(2, erlang:process_info(self(), current_stacktrace))). + +-define( + is_timeout(X), + ( (X) =:= infinity orelse ( is_integer(X) andalso (X) >= 0 ) ) +). + +-record(callback_cache,{module :: module(), + handle_call :: fun((Request :: term(), From :: from(), State :: term()) -> + {reply, Reply :: term(), NewState :: term()} | + {reply, Reply :: term(), NewState :: term(), timeout() | hibernate | {continue, term()}} | + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate | {continue, term()}} | + {stop, Reason :: term(), Reply :: term(), NewState :: term()} | + {stop, Reason :: term(), NewState :: term()}), + handle_cast :: fun((Request :: term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate | {continue, term()}} | + {stop, Reason :: term(), NewState :: term()}), + handle_info :: fun((Info :: timeout | term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate | {continue, term()}} | + {stop, Reason :: term(), NewState :: term()})}). + +-callback init(Args :: term()) -> + {ok, State :: term()} | + {ok, State :: term(), timeout() | hibernate | {continue, term()}} | + {stop, Reason :: term()} | + ignore | + {error, Reason :: term()}. + +-callback handle_call(Request :: term(), From :: from(), + State :: term()) -> + {reply, Reply :: term(), NewState :: term()} | + {reply, Reply :: term(), NewState :: term(), + timeout() | hibernate | {continue, term()}} | + {noreply, NewState :: term()} | + {noreply, NewState :: term(), + timeout() | hibernate | {continue, term()}} | + {stop, Reason :: term(), Reply :: term(), NewState :: term()} | + {stop, Reason :: term(), NewState :: term()}. + +-callback handle_cast(Request :: term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), + timeout() | hibernate | {continue, term()}} | + {stop, Reason :: term(), NewState :: term()}. + +-callback handle_info(Info :: timeout | term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate | {continue, term()}} | + {stop, Reason :: term(), NewState :: term()}. + +-callback handle_continue(Info :: term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), + timeout() | hibernate | {continue, term()}} | + {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 format_status(Opt, StatusData) -> Status when + Opt :: 'normal' | 'terminate', + StatusData :: [PDict | State], + PDict :: [{Key :: term(), Value :: term()}], + State :: term(), + Status :: term(). + +-type format_status() :: + #{ state => term(), + message => term(), + reason => term(), + log => [sys:system_event()] }. + +-callback format_status(Status) -> NewStatus when + Status :: format_status(), + NewStatus :: format_status(). + + +-callback system_get_state(State) -> Sanitized + when State :: term(), + Sanitized :: term(). + +-optional_callbacks( + [handle_info/2, handle_continue/2, terminate/2, code_change/3, + format_status/1, format_status/2, system_get_state/1]). + + + +-type from() :: {Client :: pid(), Tag :: reply_tag()}. +-opaque reply_tag() :: gen:reply_tag(). + +-opaque request_id() :: gen:request_id(). + +-opaque request_id_collection() :: gen:request_id_collection(). + +-type response_timeout() :: + timeout() | {abs, integer()}. + +-type server_name() :: % Duplicate of gen:emgr_name() + {'local', LocalName :: atom()} + | {'global', GlobalName :: term()} + | {'via', RegMod :: module(), ViaName :: term()}. + +-type server_ref() :: % What gen:call/3,4 and gen:stop/1,3 accepts + pid() + | (LocalName :: atom()) + | {Name :: atom(), Node :: atom()} + | {'global', GlobalName :: term()} + | {'via', RegMod :: module(), ViaName :: term()}. + +-type start_opt() :: % Duplicate of gen:option() + {'timeout', Timeout :: timeout()} + | {'spawn_opt', SpawnOptions :: [proc_lib:start_spawn_option()]} + | enter_loop_opt(). + +-type enter_loop_opt() :: % Some gen:option()s works for enter_loop/* + {'hibernate_after', HibernateAfterTimeout :: timeout()} + | {'debug', Dbgs :: [sys:debug_option()]}. + +-type start_ret() :: % gen:start_ret() without monitor return + {'ok', Pid :: pid()} + | 'ignore' + | {'error', Reason :: term()}. + +-type start_mon_ret() :: % gen:start_ret() with only monitor return + {'ok', {Pid :: pid(), MonRef :: reference()}} + | 'ignore' + | {'error', Reason :: term()}. + +-spec start( + Module :: module(), + Args :: term(), + Options :: [start_opt()] + ) -> + start_ret(). + +start(Module, Args, Options) + when is_atom(Module), is_list(Options) -> + gen:start(?MODULE, nolink, Module, Args, Options); +start(Module, Args, Options) -> + error(badarg, [Module, Args, Options]). + +-spec start( + ServerName :: server_name(), + Module :: module(), + Args :: term(), + Options :: [start_opt()] + ) -> + start_ret(). + +start(ServerName, Module, Args, Options) + when is_tuple(ServerName), is_atom(Module), is_list(Options) -> + gen:start(?MODULE, nolink, ServerName, Module, Args, Options); +start(ServerName, Module, Args, Options) -> + error(badarg, [ServerName, Module, Args, Options]). + +-spec start_link( + Module :: module(), + Args :: term(), + Options :: [start_opt()] + ) -> + start_ret(). +%% +start_link(Module, Args, Options) + when is_atom(Module), is_list(Options) -> + gen:start(?MODULE, link, Module, Args, Options); +start_link(Module, Args, Options) -> + error(badarg, [Module, Args, Options]). + +-spec start_link( + ServerName :: server_name(), + Module :: module(), + Args :: term(), + Options :: [start_opt()] + ) -> + start_ret(). +%% +start_link(ServerName, Module, Args, Options) + when is_tuple(ServerName), is_atom(Module), is_list(Options) -> + gen:start(?MODULE, link, ServerName, Module, Args, Options); +start_link(ServerName, Module, Args, Options) -> + error(badarg, [ServerName, Module, Args, Options]). + +-spec start_monitor( + Module :: module(), + Args :: term(), + Options :: [start_opt()] + ) -> + start_mon_ret(). +%% +start_monitor(Module, Args, Options) + when is_atom(Module), is_list(Options) -> + gen:start(?MODULE, monitor, Module, Args, Options); +start_monitor(Module, Args, Options) -> + error(badarg, [Module, Args, Options]). + +-spec start_monitor( + ServerName :: server_name(), + Module :: module(), + Args :: term(), + Options :: [start_opt()] + ) -> + start_mon_ret(). +%% +start_monitor(ServerName, Module, Args, Options) + when is_tuple(ServerName), is_atom(Module), is_list(Options) -> + gen:start(?MODULE, monitor, ServerName, Module, Args, Options); +start_monitor(ServerName, Module, Args, Options) -> + error(badarg, [ServerName, Module, Args, Options]). + + +-spec stop( + ServerRef :: server_ref() + ) -> ok. +%% +stop(ServerRef) -> + gen:stop(ServerRef). + +-spec stop( + ServerRef :: server_ref(), + Reason :: term(), + Timeout :: timeout() + ) -> ok. +%% +stop(ServerRef, Reason, Timeout) -> + gen:stop(ServerRef, Reason, Timeout). + +-spec call( + ServerRef :: server_ref(), + Request :: term() + ) -> + Reply :: term(). +%% +call(ServerRef, Request) -> + case catch gen:call(ServerRef, '$gen_call', Request) of + {ok,Res} -> + Res; + {'EXIT',Reason} -> + exit({Reason, {?MODULE, call, [ServerRef, Request]}}) + end. + +-spec call( + ServerRef :: server_ref(), + Request :: term(), + Timeout :: timeout() + ) -> + Reply :: term(). +%% +call(ServerRef, Request, Timeout) -> + case catch gen:call(ServerRef, '$gen_call', Request, Timeout) of + {ok,Res} -> + Res; + {'EXIT',Reason} -> + exit({Reason, {?MODULE, call, [ServerRef, Request, Timeout]}}) + end. + +-doc(#{since => <<"OTP 23.0">>}). +-spec send_request(ServerRef::server_ref(), 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. + +-doc(#{since => <<"OTP 25.0">>}). +-spec send_request(ServerRef::server_ref(), + 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. + +-doc(#{since => <<"OTP 23.0">>}). +-spec wait_response(ReqId, WaitTime) -> Result when + ReqId :: request_id(), + WaitTime :: response_timeout(), + Response :: {reply, Reply::term()} + | {error, {Reason::term(), server_ref()}}, + Result :: Response | 'timeout'. + +wait_response(ReqId, WaitTime) -> + try + gen:wait_response(ReqId, WaitTime) + catch + error:badarg -> + error(badarg, [ReqId, WaitTime]) + end. + +-doc(#{since => <<"OTP 25.0">>}). +-spec wait_response(ReqIdCollection, WaitTime, Delete) -> Result when + ReqIdCollection :: request_id_collection(), + WaitTime :: response_timeout(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + 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. + +-doc(#{since => <<"OTP 24.0">>}). +-spec receive_response(ReqId, Timeout) -> Result when + ReqId :: request_id(), + Timeout :: response_timeout(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: Response | 'timeout'. + +receive_response(ReqId, Timeout) -> + try + gen:receive_response(ReqId, Timeout) + catch + error:badarg -> + error(badarg, [ReqId, Timeout]) + end. + +-doc(#{since => <<"OTP 25.0">>}). +-spec receive_response(ReqIdCollection, Timeout, Delete) -> Result when + ReqIdCollection :: request_id_collection(), + Timeout :: response_timeout(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + 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. + +-doc(#{since => <<"OTP 23.0">>}). +-spec check_response(Msg, ReqId) -> Result when + Msg :: term(), + ReqId :: request_id(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + Result :: Response | 'no_reply'. + +check_response(Msg, ReqId) -> + try + gen:check_response(Msg, ReqId) + catch + error:badarg -> + error(badarg, [Msg, ReqId]) + end. + +-doc(#{since => <<"OTP 25.0">>}). +-spec check_response(Msg, ReqIdCollection, Delete) -> Result when + Msg :: term(), + ReqIdCollection :: request_id_collection(), + Delete :: boolean(), + Response :: {reply, Reply::term()} | + {error, {Reason::term(), server_ref()}}, + 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. + +-doc(#{since => <<"OTP 25.0">>}). +-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. + +-spec cast( + ServerRef :: server_ref(), + Request :: term()) -> + ok. +%% +cast({global,Name}, Request) -> + catch global:send(Name, cast_msg(Request)), + ok; +cast({via, Mod, Name}, Request) -> + catch Mod:send(Name, cast_msg(Request)), + ok; +cast({Name,Node}=Dest, Request) when is_atom(Name), is_atom(Node) -> + do_cast(Dest, Request); +cast(Dest, Request) when is_atom(Dest) -> + do_cast(Dest, Request); +cast(Dest, Request) when is_pid(Dest) -> + do_cast(Dest, Request). + +do_cast(Dest, Request) -> + do_send(Dest, cast_msg(Request)), + ok. + +cast_msg(Request) -> {'$gen_cast',Request}. + +-spec reply( + Client :: from(), + Reply :: term() + ) -> + ok. +%% +reply(Client, Reply) -> + gen:reply(Client, Reply). + +-spec abcast( + Name :: atom(), + Request :: term() + ) -> + abcast. +%% +abcast(Name, Request) when is_atom(Name) -> + do_abcast([node() | nodes()], Name, cast_msg(Request)). + +-spec abcast( + Nodes :: [node()], + Name :: atom(), + Request :: term() + ) -> + abcast. +%% +abcast(Nodes, Name, Request) when is_list(Nodes), is_atom(Name) -> + do_abcast(Nodes, Name, cast_msg(Request)). + +do_abcast([Node|Nodes], Name, Msg) when is_atom(Node) -> + do_send({Name,Node},Msg), + do_abcast(Nodes, Name, Msg); +do_abcast([], _,_) -> abcast. + +-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). + +-doc(#{equiv => multi_call(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()] + }. +%% +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 enter_loop( + Module :: module(), + Options :: [enter_loop_opt()], + State :: term() + ) -> + no_return(). +%% +enter_loop(Mod, Options, State) + when is_atom(Mod), is_list(Options) -> + enter_loop(Mod, Options, State, self(), infinity). + +-spec enter_loop( + Module :: module(), + Options :: [enter_loop_opt()], + State :: term(), + ServerName :: server_name() | pid() + ) -> + no_return(); + ( + Module :: module(), + Options :: [enter_loop_opt()], + State :: term(), + How :: timeout() | 'hibernate' | {'continue', term()} + ) -> + no_return(). +%% +enter_loop(Mod, Options, State, ServerName = {Scope, _}) + when is_atom(Mod), is_list(Options), Scope == local; + is_atom(Mod), is_list(Options), Scope == global -> + enter_loop(Mod, Options, State, ServerName, infinity); +%% +enter_loop(Mod, Options, State, ServerName = {via, _, _}) + when is_atom(Mod), is_list(Options) -> + enter_loop(Mod, Options, State, ServerName, infinity); +%% +enter_loop(Mod, Options, State, TimeoutOrHibernate) + when is_atom(Mod), is_list(Options), ?is_timeout(TimeoutOrHibernate); + is_atom(Mod), is_list(Options), TimeoutOrHibernate =:= hibernate -> + enter_loop(Mod, Options, State, self(), TimeoutOrHibernate); +%% +enter_loop(Mod, Options, State, {continue, _}=Continue) + when is_atom(Mod), is_list(Options) -> + enter_loop(Mod, Options, State, self(), Continue). + +-spec enter_loop( + Module :: module(), + Options :: [enter_loop_opt()], + State :: term(), + ServerName :: server_name() | pid(), + Timeout :: timeout() + ) -> + no_return(); + ( + Module :: module(), + Options :: [enter_loop_opt()], + State :: term(), + ServerName :: server_name() | pid(), + Hibernate :: 'hibernate' + ) -> + no_return(); + ( + Module :: module(), + Options :: [enter_loop_opt()], + State :: term(), + ServerName :: server_name() | pid(), + Cont :: {'continue', term()} + ) -> + no_return(). +%% +enter_loop(Mod, Options, State, ServerName, TimeoutOrHibernate) + when is_atom(Mod), is_list(Options), ?is_timeout(TimeoutOrHibernate); + is_atom(Mod), is_list(Options), TimeoutOrHibernate =:= hibernate -> + Name = gen:get_proc_name(ServerName), + Parent = gen:get_parent(), + Debug = gen:debug_options(Name, Options), + HibernateAfterTimeout = gen:hibernate_after(Options), + CbCache = create_callback_cache(Mod), + loop(Parent, Name, State, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, Debug); +%% +enter_loop(Mod, Options, State, ServerName, {continue, _}=Continue) + when is_atom(Mod), is_list(Options) -> + Name = gen:get_proc_name(ServerName), + Parent = gen:get_parent(), + Debug = gen:debug_options(Name, Options), + HibernateAfterTimeout = gen:hibernate_after(Options), + CbCache = create_callback_cache(Mod), + loop(Parent, Name, State, CbCache, Continue, HibernateAfterTimeout, Debug). + +init_it(Starter, self, Name, Mod, Args, Options) -> + init_it(Starter, self(), Name, Mod, Args, Options); +init_it(Starter, Parent, Name0, Mod, Args, Options) -> + Name = gen:name(Name0), + Debug = gen:debug_options(Name, Options), + HibernateAfterTimeout = gen:hibernate_after(Options), + CbCache = create_callback_cache(Mod), + case init_it(Mod, Args) of + {ok, {ok, State}} -> + proc_lib:init_ack(Starter, {ok, self()}), + loop( + Parent, Name, State, CbCache, infinity, + HibernateAfterTimeout, Debug); + {ok, {ok, State, TimeoutOrHibernate}} + when ?is_timeout(TimeoutOrHibernate); + TimeoutOrHibernate =:= hibernate -> + proc_lib:init_ack(Starter, {ok, self()}), + loop( + Parent, Name, State, CbCache, TimeoutOrHibernate, + HibernateAfterTimeout, Debug); + {ok, {ok, State, {continue, _}=Continue}} -> + proc_lib:init_ack(Starter, {ok, self()}), + loop( + Parent, Name, State, CbCache, Continue, + HibernateAfterTimeout, Debug); + {ok, {stop, Reason}} -> + %% For consistency, we must make sure that the + %% registered name (if any) is unregistered before + %% the parent process is notified about the failure. + %% (Otherwise, the parent process could get + %% an 'already_started' error if it immediately + %% tried starting the process again.) + gen:unregister_name(Name0), + exit(Reason); + {ok, {error, _Reason} = ERROR} -> + %% The point of this clause is that we shall have a silent/graceful + %% termination. The error reason will be returned to the + %% 'Starter' ({error, Reason}), but *no* crash report. + gen:unregister_name(Name0), + proc_lib:init_fail(Starter, ERROR, {exit, normal}); + {ok, ignore} -> + gen:unregister_name(Name0), + proc_lib:init_fail(Starter, ignore, {exit, normal}); + {ok, Else} -> + gen:unregister_name(Name0), + exit({bad_return_value, Else}); + {'EXIT', Class, Reason, Stacktrace} -> + gen:unregister_name(Name0), + erlang:raise(Class, Reason, Stacktrace) + end. +init_it(Mod, Args) -> + try + {ok, Mod:init(Args)} + catch + throw:R -> {ok, R}; + Class:R:S -> {'EXIT', Class, R, S} + end. + + +loop(Parent, Name, State, CbCache, {continue, Continue} = Msg, HibernateAfterTimeout, Debug) -> + Reply = try_handle_continue(CbCache, Continue, State), + case Debug of + [] -> + handle_common_reply(Reply, Parent, Name, undefined, Msg, CbCache, + HibernateAfterTimeout, State); + _ -> + Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, Msg), + handle_common_reply(Reply, Parent, Name, undefined, Msg, CbCache, + HibernateAfterTimeout, State, Debug1) + end; + +loop(Parent, Name, State, CbCache, hibernate, HibernateAfterTimeout, Debug) -> + Mod = CbCache#callback_cache.module, + proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, HibernateAfterTimeout, Debug]); + +loop(Parent, Name, State, CbCache, infinity, HibernateAfterTimeout, Debug) -> + receive + Msg -> + decode_msg(Msg, Parent, Name, State, CbCache, infinity, HibernateAfterTimeout, Debug, false) + after HibernateAfterTimeout -> + loop(Parent, Name, State, CbCache, hibernate, HibernateAfterTimeout, Debug) + end; + +loop(Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug) -> + Msg = receive + Input -> + Input + after Time -> + timeout + end, + decode_msg(Msg, Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug, false). + +-spec create_callback_cache(module()) -> #callback_cache{}. +create_callback_cache(Mod) -> + #callback_cache{module = Mod, + handle_call = fun Mod:handle_call/3, + handle_cast = fun Mod:handle_cast/2, + handle_info = fun Mod:handle_info/2}. + +-doc false. +wake_hib(Parent, Name, State, Mod, HibernateAfterTimeout, Debug) -> + Msg = receive + Input -> + Input + end, + CbCache = create_callback_cache(Mod), + decode_msg(Msg, Parent, Name, State, CbCache, hibernate, HibernateAfterTimeout, Debug, true). + +decode_msg(Msg, Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug, Hib) -> + case Msg of + {system, From, Req} -> + sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, + [Name, State, CbCache, Time, HibernateAfterTimeout], Hib); + {'EXIT', Parent, Reason} -> + #callback_cache{module = Mod} = CbCache, + terminate(Reason, ?STACKTRACE(), Name, undefined, Msg, Mod, State, Debug); + _Msg when Debug =:= [] -> + handle_msg(Msg, Parent, Name, State, CbCache, HibernateAfterTimeout); + _Msg -> + Debug1 = sys:handle_debug(Debug, fun print_event/3, + Name, {in, Msg}), + handle_msg(Msg, Parent, Name, State, CbCache, HibernateAfterTimeout, Debug1) + end. + +do_send(Dest, Msg) -> + try erlang:send(Dest, Msg) + catch + error:_ -> ok + end, + ok. + +try_dispatch({'$gen_cast', Msg}, CbCache, State) -> + try_handle_cast(CbCache, Msg, State); +try_dispatch(Info, CbCache, State) -> + try_handle_info(CbCache, Info, State). + +try_handle_continue(#callback_cache{module = Mod}, Msg, State) -> + try + {ok, Mod:handle_continue(Msg, State)} + catch + throw:R -> + {ok, R}; + Class:R:Stacktrace -> + {'EXIT', Class, R, Stacktrace} + end. + +try_handle_info(#callback_cache{module = Mod, handle_info = HandleInfo}, Msg, State) -> + try + {ok, HandleInfo(Msg, State)} + catch + throw:R -> + {ok, R}; + error:undef = R:Stacktrace -> + case erlang:function_exported(Mod, handle_info, 2) of + false -> + ?LOG_WARNING( + #{label=>{gen_server,no_handle_info}, + module=>Mod, + message=>Msg}, + #{domain=>[otp], + report_cb=>fun gen_server:format_log/2, + error_logger=> + #{tag=>warning_msg, + report_cb=>fun gen_server:format_log/1}}), + {ok, {noreply, State}}; + true -> + {'EXIT', error, R, Stacktrace} + end; + Class:R:Stacktrace -> + {'EXIT', Class, R, Stacktrace} + end. + +try_handle_cast(#callback_cache{handle_cast = HandleCast}, Msg, State) -> + try + {ok, HandleCast(Msg, State)} + catch + throw:R -> + {ok, R}; + Class:R:Stacktrace -> + {'EXIT', Class, R, Stacktrace} + end. + +try_handle_call(#callback_cache{handle_call = HandleCall}, Msg, From, State) -> + try + {ok, HandleCall(Msg, From, State)} + catch + throw:R -> + {ok, R}; + Class:R:Stacktrace -> + {'EXIT', Class, R, Stacktrace} + end. + +try_terminate(Mod, Reason, State) -> + case erlang:function_exported(Mod, terminate, 2) of + true -> + try + {ok, Mod:terminate(Reason, State)} + catch + throw:R -> + {ok, R}; + Class:R:Stacktrace -> + {'EXIT', Class, R, Stacktrace} + end; + false -> + {ok, ok} + end. + + +handle_msg({'$gen_call', From, Msg}, Parent, Name, State, CbCache, HibernateAfterTimeout) -> + Result = try_handle_call(CbCache, Msg, From, State), + case Result of + {ok, {reply, Reply, NState}} -> + reply(From, Reply), + loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, []); + {ok, {reply, Reply, NState, TimeoutOrHibernate}} + when ?is_timeout(TimeoutOrHibernate); + TimeoutOrHibernate =:= hibernate -> + reply(From, Reply), + loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, []); + {ok, {reply, Reply, NState, {continue, _}=Continue}} -> + reply(From, Reply), + loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, []); + {ok, {stop, Reason, Reply, NState}} -> + try + Mod = CbCache#callback_cache.module, + terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, []) + after + reply(From, Reply) + end; + Other -> handle_common_reply(Other, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State) + end; +handle_msg(Msg, Parent, Name, State, CbCache, HibernateAfterTimeout) -> + Reply = try_dispatch(Msg, CbCache, State), + handle_common_reply(Reply, Parent, Name, undefined, Msg, CbCache, HibernateAfterTimeout, State). + +handle_msg({'$gen_call', From, Msg}, Parent, Name, State, CbCache, HibernateAfterTimeout, Debug) -> + Result = try_handle_call(CbCache, Msg, From, State), + case Result of + {ok, {reply, Reply, NState}} -> + Debug1 = reply(Name, From, Reply, NState, Debug), + loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, Debug1); + {ok, {reply, Reply, NState, TimeoutOrHibernate}} + when ?is_timeout(TimeoutOrHibernate); + TimeoutOrHibernate =:= hibernate -> + Debug1 = reply(Name, From, Reply, NState, Debug), + loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, Debug1); + {ok, {reply, Reply, NState, {continue, _}=Continue}} -> + Debug1 = reply(Name, From, Reply, NState, Debug), + loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, Debug1); + {ok, {stop, Reason, Reply, NState}} -> + try + Mod = CbCache#callback_cache.module, + terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, Debug) + after + _ = reply(Name, From, Reply, NState, Debug) + end; + Other -> + handle_common_reply(Other, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State, Debug) + end; +handle_msg(Msg, Parent, Name, State, CbCache, HibernateAfterTimeout, Debug) -> + Reply = try_dispatch(Msg, CbCache, State), + handle_common_reply(Reply, Parent, Name, undefined, Msg, CbCache, HibernateAfterTimeout, State, Debug). + +handle_common_reply(Reply, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State) -> + Mod = CbCache#callback_cache.module, + case Reply of + {ok, {noreply, NState}} -> + loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, []); + {ok, {noreply, NState, TimeoutOrHibernate}} + when ?is_timeout(TimeoutOrHibernate); + TimeoutOrHibernate =:= hibernate -> + loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, []); + {ok, {noreply, NState, {continue, _}=Continue}} -> + loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, []); + {ok, {stop, Reason, NState}} -> + terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, []); + {'EXIT', Class, Reason, Stacktrace} -> + terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, []); + {ok, BadReply} -> + terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, []) + end. + +handle_common_reply(Reply, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State, Debug) -> + Mod = CbCache#callback_cache.module, + case Reply of + {ok, {noreply, NState}} -> + Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, + {noreply, NState}), + loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, Debug1); + {ok, {noreply, NState, TimeoutOrHibernate}} + when ?is_timeout(TimeoutOrHibernate); + TimeoutOrHibernate =:= hibernate -> + Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), + loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, Debug1); + {ok, {noreply, NState, {continue, _}=Continue}} -> + Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), + loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, Debug1); + {ok, {stop, Reason, NState}} -> + terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, Debug); + {'EXIT', Class, Reason, Stacktrace} -> + terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, Debug); + {ok, BadReply} -> + terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, Debug) + end. + +reply(Name, From, Reply, State, Debug) -> + reply(From, Reply), + sys:handle_debug(Debug, fun print_event/3, Name, + {out, Reply, From, State} ). + + +system_continue(Parent, Debug, [Name, State, CbCache, Time, HibernateAfterTimeout]) -> + loop(Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug). + +-spec system_terminate(_, _, _, [_]) -> no_return(). + +system_terminate(Reason, _Parent, Debug, [Name, State, CbCache, _Time, _HibernateAfterTimeout]) -> + Mod = CbCache#callback_cache.module, + terminate(Reason, ?STACKTRACE(), Name, undefined, [], Mod, State, Debug). + +system_code_change([Name, State, CbCache, Time, HibernateAfterTimeout], _Module, OldVsn, Extra) -> + Mod = CbCache#callback_cache.module, + case catch Mod:code_change(OldVsn, State, Extra) of + {ok, NewState} -> {ok, [Name, NewState, CbCache, Time, HibernateAfterTimeout]}; + Else -> Else + end. + +system_get_state([Mod, State, _Callbacks, _Time, _HibernateAfterTimeout]) -> + case erlang:function_exported(Mod, system_get_state, 1) of + true -> Mod:system_get_state(State); + false -> {ok, State} + end. + + +system_replace_state(_, _) -> + exit(nice_try). + + +print_event(Dev, {in, Msg}, Name) -> + case Msg of + {'$gen_call', {From, _Tag}, Call} -> + io:format(Dev, "*DBG* ~tp got call ~tp from ~tw~n", + [Name, Call, From]); + {'$gen_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]). + + +-spec terminate(_, _, _, _, _, _, _, _) -> no_return(). +terminate(Reason, Stacktrace, Name, From, Msg, Mod, State, Debug) -> + terminate(exit, Reason, Stacktrace, false, Name, From, Msg, Mod, State, Debug). + +-spec terminate(_, _, _, _, _, _, _, _, _) -> no_return(). +terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, Debug) -> + terminate(Class, Reason, Stacktrace, true, Name, From, Msg, Mod, State, Debug). + +-spec terminate(_, _, _, _, _, _, _, _, _, _) -> no_return(). +terminate(Class, Reason, Stacktrace, ReportStacktrace, Name, From, Msg, Mod, State, Debug) -> + Reply = try_terminate(Mod, catch_result(Class, Reason, Stacktrace), State), + case Reply of + {'EXIT', C, R, S} -> + error_info(R, S, Name, From, Msg, Mod, State, Debug), + erlang:raise(C, R, S); + _ -> + case {Class, Reason} of + {exit, normal} -> ok; + {exit, shutdown} -> ok; + {exit, {shutdown,_}} -> ok; + _ when ReportStacktrace -> + error_info(Reason, Stacktrace, Name, From, Msg, Mod, State, Debug); + _ -> + error_info(Reason, undefined, Name, From, Msg, Mod, State, Debug) + end + end, + case Stacktrace of + [] -> + erlang:Class(Reason); + _ -> + erlang:raise(Class, Reason, Stacktrace) + end. + +catch_result(error, Reason, Stacktrace) -> {Reason, Stacktrace}; +catch_result(exit, Reason, _Stacktrace) -> Reason. + +error_info(_Reason, _ST, application_controller, _From, _Msg, _Mod, _State, _Debug) -> + %% OTP-5811 Do not 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, ST, Name, From, Msg, Mod, State, Debug) -> + Log = sys:get_log(Debug), + Status = + gen:format_status(Mod, terminate, + #{ reason => Reason, + state => State, + message => Msg, + log => Log }, + [get(),State]), + ReportReason = + if ST == undefined -> + %% When ST is undefined, it should not be included in the + %% reported reason for the crash as it is then caused + %% by an invalid return from a callback and thus thus the + %% stacktrace is irrelevant. + maps:get(reason, Status); + true -> + {maps:get(reason, Status), ST} + end, + + ?LOG_ERROR(#{label=>{gen_server,terminate}, + name=>Name, + last_message=>maps:get(message,Status), + state=>maps:get('EXIT',Status,maps:get('$status',Status,maps:get(state,Status))), + log=>format_log_state(Mod,maps:get(log,Status)), + reason=>ReportReason, + client_info=>client_stacktrace(From), + process_label=>proc_lib:get_label(self())}, + #{domain=>[otp], + report_cb=>fun gen_server:format_log/2, + error_logger=>#{tag=>error, + report_cb=>fun gen_server: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. +-doc false. +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_server,terminate}, + last_message:=Msg, + state:=State, + log:=Log, + reason:=Reason, + client_info:=Client, + process_label:=ProcessLabel}=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), + process_label=>io_lib:limit_term(ProcessLabel,Depth)}; +limit_report(#{label:={gen_server,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. +-doc false. +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_server,terminate}, + name:=Name, + last_message:=Msg, + state:=State, + log:=Log, + reason:=Reason, + client_info:=Client, + process_label:=ProcessLabel}, + #{single_line:=true,depth:=Depth}=FormatOpts) -> + P = p(FormatOpts), + Format1 = lists:append(["Generic server ",P," terminating", + case ProcessLabel of + undefined -> ""; + _ -> ". Label: "++P + end, + ". 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] ++ + case ProcessLabel of + undefined -> []; + _ -> [ProcessLabel] + end ++ + [fix_reason(Reason),Msg,State]; + _ -> + [Name,Depth] ++ + case ProcessLabel of + undefined -> []; + _ -> [ProcessLabel,Depth] + end ++ + [fix_reason(Reason),Depth,Msg,Depth,State,Depth] + end, + {Format1++ServerLogFormat++ClientLogFormat, + Args1++ServerLogArgs++ClientLogArgs}; +format_log_single(#{label:={gen_server,no_handle_info}, + module:=Mod, + message:=Msg}, + #{single_line:=true,depth:=Depth}=FormatOpts) -> + P = p(FormatOpts), + Format = lists:append(["Undefined handle_info in ",P, + ". Unhandled message: ",P,"."]), + Args = + case Depth of + unlimited -> + [Mod,Msg]; + _ -> + [Mod,Depth,Msg,Depth] + end, + {Format,Args}; +format_log_single(Report,FormatOpts) -> + format_log_multi(Report,FormatOpts). + +format_log_multi(#{label:={gen_server,terminate}, + name:=Name, + last_message:=Msg, + state:=State, + log:=Log, + reason:=Reason, + client_info:=Client, + process_label:=ProcessLabel}, + #{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"] ++ + case ProcessLabel of + undefined -> []; + _ -> ["** Process label == ",P,"~n"] + end ++ + ["** 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] ++ + case ProcessLabel of + undefined -> []; + _ -> [ProcessLabel] + end ++ + [Msg, State, Reason1] ++ Log ++ ClientArgs; + _ -> + [Name, Depth] ++ + case ProcessLabel of + undefined -> []; + _ -> [ProcessLabel, Depth] + end ++ + [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_server,no_handle_info}, + module:=Mod, + message:=Msg}, + #{depth:=Depth}=FormatOpts) -> + P = p(FormatOpts), + Format = + "** Undefined handle_info in ~p~n" + "** Unhandled message: "++P++"~n", + Args = + case Depth of + unlimited -> + [Mod,Msg]; + _ -> + [Mod,Msg,Depth] + end, + {Format,Args}. + +fix_reason({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 +%%----------------------------------------------------------------- +-doc false. +format_status(Opt, StatusData) -> + [PDict, SysState, Parent, Debug, [Name, State, CbCache, _Time, _HibernateAfterTimeout]] = StatusData, + Mod = CbCache#callback_cache.module, + Header = gen:format_status_header("Status for generic server", Name), + Status = + case gen:format_status(Mod, Opt, #{ state => State, log => sys:get_log(Debug) }, + [PDict, State]) of + #{ 'EXIT' := R } = M -> + M#{ '$status' => [{data,[{"State",R}]}] }; + %% Status is set when the old format_status/2 is called, + %% so we do a little backwards compatibility dance here + #{ '$status' := S } = M when is_list(S) -> M; + #{ '$status' := S } = M -> M#{ '$status' := [S] }; + #{ state := S } = M -> + M#{ '$status' => [{data, [{"State",S}] }] } + end, + [{header, Header}, + {data, [{"Status", SysState}, + {"Parent", Parent}, + {"Logged events", format_log_state(Mod, maps:get(log,Status))}]} | + maps:get('$status',Status)]. + +format_log_state(Mod, Log) -> + %% If format_status/1 was exported, the log has already been handled by + %% that call, so we should not pass all log events into the callback again. + case erlang:function_exported(Mod, format_status, 1) of + false -> + [case Event of + {out,Msg,From,State} -> + Status = gen:format_status( + Mod, terminate, #{ state => State }, + [get(), State]), + {out, Msg, From, maps:get(state, Status) }; + {noreply,State} -> + Status = gen:format_status( + Mod, terminate, #{ state => State }, + [get(), State]), + {noreply, maps:get(state, Status)}; + _ -> Event + end || Event <- Log]; + true -> + Log + end.