diff options
Diffstat (limited to 'lib/stdlib/src/gen_server.erl')
-rw-r--r-- | lib/stdlib/src/gen_server.erl | 612 |
1 files changed, 292 insertions, 320 deletions
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index af5e04f78a..c574f2aeb2 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2022. All Rights Reserved. +%% Copyright Ericsson AB 1996-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -147,13 +147,29 @@ ( (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()})}). %%%========================================================================= %%% API %%%========================================================================= -callback init(Args :: term()) -> {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate | {continue, term()}} | - {stop, Reason :: term()} | ignore. + {stop, Reason :: term()} | ignore | {error, Reason :: term()}. -callback handle_call(Request :: term(), From :: from(), State :: term()) -> {reply, Reply :: term(), NewState :: term()} | @@ -649,7 +665,7 @@ do_abcast([], _,_) -> abcast. %% multi_call(Name, Request) when is_atom(Name) -> - do_multi_call([node() | nodes()], Name, Request, infinity). + multi_call([node() | nodes()], Name, Request, infinity). -spec multi_call( Nodes :: [node()], @@ -663,7 +679,7 @@ multi_call(Name, Request) %% multi_call(Nodes, Name, Request) when is_list(Nodes), is_atom(Name) -> - do_multi_call(Nodes, Name, Request, infinity). + multi_call(Nodes, Name, Request, infinity). -spec multi_call( Nodes :: [node()], @@ -678,8 +694,93 @@ multi_call(Nodes, Name, Request) %% multi_call(Nodes, Name, Request, Timeout) when is_list(Nodes), is_atom(Name), ?is_timeout(Timeout) -> - do_multi_call(Nodes, Name, Request, 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. %%----------------------------------------------------------------- %% enter_loop(Mod, Options, State, <ServerName>, <TimeOut>) ->_ @@ -783,7 +884,8 @@ enter_loop(Mod, Options, State, ServerName, TimeoutOrHibernate) Parent = gen:get_parent(), Debug = gen:debug_options(Name, Options), HibernateAfterTimeout = gen:hibernate_after(Options), - loop(Parent, Name, State, Mod, TimeoutOrHibernate, HibernateAfterTimeout, Debug); + 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) -> @@ -791,7 +893,8 @@ enter_loop(Mod, Options, State, ServerName, {continue, _}=Continue) Parent = gen:get_parent(), Debug = gen:debug_options(Name, Options), HibernateAfterTimeout = gen:hibernate_after(Options), - loop(Parent, Name, State, Mod, Continue, HibernateAfterTimeout, Debug). + CbCache = create_callback_cache(Mod), + loop(Parent, Name, State, CbCache, Continue, HibernateAfterTimeout, Debug). %%%======================================================================== %%% Gen-callback functions @@ -810,19 +913,25 @@ 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, Mod, infinity, HibernateAfterTimeout, Debug); - {ok, {ok, State, TimeoutOrHibernate}} + 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, Mod, TimeoutOrHibernate, HibernateAfterTimeout, Debug); + 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, Mod, Continue, HibernateAfterTimeout, Debug); + 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 @@ -831,29 +940,32 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> %% an 'already_started' error if it immediately %% tried starting the process again.) gen:unregister_name(Name0), - proc_lib:init_ack(Starter, {error, Reason}), - exit(Reason); + 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_ack(Starter, ignore), - exit(normal); + proc_lib:init_fail(Starter, ignore, {exit, normal}); {ok, Else} -> - Error = {bad_return_value, Else}, - proc_lib:init_ack(Starter, {error, Error}), - exit(Error); + gen:unregister_name(Name0), + exit({bad_return_value, Else}); {'EXIT', Class, Reason, Stacktrace} -> gen:unregister_name(Name0), - proc_lib:init_ack(Starter, {error, terminate_reason(Class, Reason, Stacktrace)}), - erlang:raise(Class, Reason, Stacktrace) + erlang:raise(Class, Reason, Stacktrace) end. init_it(Mod, Args) -> try - {ok, Mod:init(Args)} + {ok, Mod:init(Args)} catch - throw:R -> {ok, R}; - Class:R:S -> {'EXIT', Class, R, S} + throw:R -> {ok, R}; + Class:R:S -> {'EXIT', Class, R, S} end. + %%%======================================================================== %%% Internal functions %%%======================================================================== @@ -861,58 +973,68 @@ init_it(Mod, Args) -> %%% The MAIN loop. %%% --------------------------------------------------- -loop(Parent, Name, State, Mod, {continue, Continue} = Msg, HibernateAfterTimeout, Debug) -> - Reply = try_dispatch(Mod, handle_continue, Continue, State), +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, Mod, - HibernateAfterTimeout, State); - _ -> - Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, Msg), - handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod, - HibernateAfterTimeout, State, Debug1) + [] -> + 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, Mod, hibernate, HibernateAfterTimeout, Debug) -> +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, Mod, infinity, HibernateAfterTimeout, Debug) -> - receive - Msg -> - decode_msg(Msg, Parent, Name, State, Mod, infinity, HibernateAfterTimeout, Debug, false) - after HibernateAfterTimeout -> - loop(Parent, Name, State, Mod, hibernate, HibernateAfterTimeout, Debug) - end; +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, Mod, Time, HibernateAfterTimeout, Debug) -> +loop(Parent, Name, State, CbCache, Time, HibernateAfterTimeout, Debug) -> Msg = receive - Input -> - Input - after Time -> - timeout - end, - decode_msg(Msg, Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug, false). + 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}. wake_hib(Parent, Name, State, Mod, HibernateAfterTimeout, Debug) -> Msg = receive - Input -> - Input - end, - decode_msg(Msg, Parent, Name, State, Mod, hibernate, HibernateAfterTimeout, Debug, true). + 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, Mod, Time, HibernateAfterTimeout, Debug, Hib) -> +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, Mod, Time, HibernateAfterTimeout], Hib); - {'EXIT', Parent, Reason} -> - terminate(Reason, ?STACKTRACE(), Name, undefined, Msg, Mod, State, Debug); - _Msg when Debug =:= [] -> - handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout); - _Msg -> - Debug1 = sys:handle_debug(Debug, fun print_event/3, - Name, {in, Msg}), - handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout, Debug1) + {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. %%% --------------------------------------------------- @@ -925,184 +1047,6 @@ do_send(Dest, Msg) -> end, ok. -do_multi_call([Node], Name, Req, infinity) when Node =:= node() -> - % Special case when multi_call is used with local node only. - % In that case we can leverage the benefit of recv_mark optimisation - % existing in simple gen:call. - try gen:call(Name, '$gen_call', Req, infinity) of - {ok, Res} -> {[{Node, Res}],[]} - catch exit:_ -> - {[], [Node]} - end; -do_multi_call(Nodes, Name, Req, infinity) -> - Tag = make_ref(), - Monitors = send_nodes(Nodes, Name, Tag, Req), - rec_nodes(Tag, Monitors, Name, undefined); -do_multi_call(Nodes, Name, Req, Timeout) -> - Tag = make_ref(), - Caller = self(), - Receiver = - spawn( - fun() -> - %% Middleman process. Should be unsensitive to regular - %% exit signals. The sychronization is needed in case - %% the receiver would exit before the caller started - %% the monitor. - process_flag(trap_exit, true), - Mref = erlang:monitor(process, Caller), - receive - {Caller,Tag} -> - Monitors = send_nodes(Nodes, Name, Tag, Req), - TimerId = erlang:start_timer(Timeout, self(), ok), - Result = rec_nodes(Tag, Monitors, Name, TimerId), - exit({self(),Tag,Result}); - {'DOWN',Mref,_,_,_} -> - %% Caller died before sending us the go-ahead. - %% Give up silently. - exit(normal) - end - end), - Mref = erlang:monitor(process, Receiver), - Receiver ! {self(),Tag}, - receive - {'DOWN',Mref,_,_,{Receiver,Tag,Result}} -> - Result; - {'DOWN',Mref,_,_,Reason} -> - %% The middleman code failed. Or someone did - %% exit(_, kill) on the middleman process => Reason==killed - exit(Reason) - end. - -send_nodes(Nodes, Name, Tag, Req) -> - send_nodes(Nodes, Name, Tag, Req, []). - -send_nodes([Node|Tail], Name, Tag, Req, Monitors) - when is_atom(Node) -> - Monitor = start_monitor(Node, Name), - %% Handle non-existing names in rec_nodes. - catch {Name, Node} ! {'$gen_call', {self(), {Tag, Node}}, Req}, - send_nodes(Tail, Name, Tag, Req, [Monitor | Monitors]); -send_nodes([_Node|Tail], Name, Tag, Req, Monitors) -> - %% Skip non-atom Node - send_nodes(Tail, Name, Tag, Req, Monitors); -send_nodes([], _Name, _Tag, _Req, Monitors) -> - Monitors. - -%% Against old nodes: -%% If no reply has been delivered within 2 secs. (per node) check that -%% the server really exists and wait for ever for the answer. -%% -%% Against contemporary nodes: -%% Wait for reply, server 'DOWN', or timeout from TimerId. - -rec_nodes(Tag, Nodes, Name, TimerId) -> - rec_nodes(Tag, Nodes, Name, [], [], 2000, TimerId). - -rec_nodes(Tag, [{N,R}|Tail], Name, Badnodes, Replies, Time, TimerId ) -> - receive - {'DOWN', R, _, _, _} -> - rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, Time, TimerId); - {{Tag, N}, Reply} -> %% Tag is bound !!! - erlang:demonitor(R, [flush]), - rec_nodes(Tag, Tail, Name, Badnodes, - [{N,Reply}|Replies], Time, TimerId); - {timeout, TimerId, _} -> - erlang:demonitor(R, [flush]), - %% Collect all replies that already have arrived - rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies) - end; -rec_nodes(Tag, [N|Tail], Name, Badnodes, Replies, Time, TimerId) -> - %% R6 node - receive - {nodedown, N} -> - monitor_node(N, false), - rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, 2000, TimerId); - {{Tag, N}, Reply} -> %% Tag is bound !!! - receive {nodedown, N} -> ok after 0 -> ok end, - monitor_node(N, false), - rec_nodes(Tag, Tail, Name, Badnodes, - [{N,Reply}|Replies], 2000, TimerId); - {timeout, TimerId, _} -> - receive {nodedown, N} -> ok after 0 -> ok end, - monitor_node(N, false), - %% Collect all replies that already have arrived - rec_nodes_rest(Tag, Tail, Name, [N | Badnodes], Replies) - after Time -> - case rpc:call(N, erlang, whereis, [Name]) of - Pid when is_pid(Pid) -> % It exists try again. - rec_nodes(Tag, [N|Tail], Name, Badnodes, - Replies, infinity, TimerId); - _ -> % badnode - receive {nodedown, N} -> ok after 0 -> ok end, - monitor_node(N, false), - rec_nodes(Tag, Tail, Name, [N|Badnodes], - Replies, 2000, TimerId) - end - end; -rec_nodes(_, [], _, Badnodes, Replies, _, TimerId) -> - case catch erlang:cancel_timer(TimerId) of - false -> % It has already sent it's message - receive - {timeout, TimerId, _} -> ok - after 0 -> - ok - end; - _ -> % Timer was cancelled, or TimerId was 'undefined' - ok - end, - {Replies, Badnodes}. - -%% Collect all replies that already have arrived -rec_nodes_rest(Tag, [{N,R}|Tail], Name, Badnodes, Replies) -> - receive - {'DOWN', R, _, _, _} -> - rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies); - {{Tag, N}, Reply} -> %% Tag is bound !!! - erlang:demonitor(R, [flush]), - rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies]) - after 0 -> - erlang:demonitor(R, [flush]), - rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies) - end; -rec_nodes_rest(Tag, [N|Tail], Name, Badnodes, Replies) -> - %% R6 node - receive - {nodedown, N} -> - monitor_node(N, false), - rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies); - {{Tag, N}, Reply} -> %% Tag is bound !!! - receive {nodedown, N} -> ok after 0 -> ok end, - monitor_node(N, false), - rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies]) - after 0 -> - receive {nodedown, N} -> ok after 0 -> ok end, - monitor_node(N, false), - rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies) - end; -rec_nodes_rest(_Tag, [], _Name, Badnodes, Replies) -> - {Replies, Badnodes}. - - -%%% --------------------------------------------------- -%%% Monitor functions -%%% --------------------------------------------------- - -start_monitor(Node, Name) when is_atom(Node), is_atom(Name) -> - if node() =:= nonode@nohost, Node =/= nonode@nohost -> - Ref = make_ref(), - self() ! {'DOWN', Ref, process, {Name, Node}, noconnection}, - {Node, Ref}; - true -> - case catch erlang:monitor(process, {Name, Node}) of - {'EXIT', _} -> - %% Remote node is R6 - monitor_node(Node, true), - Node; - Ref when is_reference(Ref) -> - {Node, Ref} - end - end. - %% --------------------------------------------------- %% Helper functions for try-catch of callbacks. %% Returns the return value of the callback, or @@ -1113,60 +1057,80 @@ start_monitor(Node, Name) when is_atom(Node), is_atom(Name) -> %% stacktraces. %% --------------------------------------------------- -try_dispatch({'$gen_cast', Msg}, Mod, State) -> - try_dispatch(Mod, handle_cast, Msg, State); -try_dispatch(Info, Mod, State) -> - try_dispatch(Mod, handle_info, Info, State). +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_dispatch(Mod, Func, Msg, State) -> +try_handle_info(#callback_cache{module = Mod, handle_info = HandleInfo}, Msg, State) -> try - {ok, Mod:Func(Msg, State)} + {ok, HandleInfo(Msg, State)} catch - throw:R -> - {ok, R}; - error:undef = R:Stacktrace when Func == handle_info -> + 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}}), + #{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} + Class:R:Stacktrace -> + {'EXIT', Class, R, Stacktrace} end. -try_handle_call(Mod, Msg, From, State) -> +try_handle_cast(#callback_cache{handle_cast = HandleCast}, Msg, State) -> try - {ok, Mod:handle_call(Msg, From, State)} + {ok, HandleCast(Msg, State)} catch - throw:R -> - {ok, R}; - Class:R:Stacktrace -> - {'EXIT', Class, R, Stacktrace} + 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} + true -> + try + {ok, Mod:terminate(Reason, State)} + catch + throw:R -> + {ok, R}; + Class:R:Stacktrace -> + {'EXIT', Class, R, Stacktrace} + end; + false -> + {ok, ok} end. @@ -1174,69 +1138,72 @@ try_terminate(Mod, Reason, State) -> %%% Message handling functions %%% --------------------------------------------------- -handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, HibernateAfterTimeout) -> - Result = try_handle_call(Mod, Msg, From, State), +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, Mod, infinity, HibernateAfterTimeout, []); + 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, Mod, TimeoutOrHibernate, HibernateAfterTimeout, []); + loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, []); {ok, {reply, Reply, NState, {continue, _}=Continue}} -> reply(From, Reply), - loop(Parent, Name, NState, Mod, Continue, HibernateAfterTimeout, []); + 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, Mod, HibernateAfterTimeout, State) + Other -> handle_common_reply(Other, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State) end; -handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout) -> - Reply = try_dispatch(Msg, Mod, State), - handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod, HibernateAfterTimeout, State). +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, Mod, HibernateAfterTimeout, Debug) -> - Result = try_handle_call(Mod, Msg, From, 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, Mod, infinity, HibernateAfterTimeout, Debug1); + 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, Mod, TimeoutOrHibernate, HibernateAfterTimeout, Debug1); + 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, Mod, Continue, HibernateAfterTimeout, Debug1); + 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, Mod, HibernateAfterTimeout, State, Debug) + handle_common_reply(Other, Parent, Name, From, Msg, CbCache, HibernateAfterTimeout, State, Debug) end; -handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout, Debug) -> - Reply = try_dispatch(Msg, Mod, State), - handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod, HibernateAfterTimeout, State, Debug). +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, Mod, HibernateAfterTimeout, State) -> +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, Mod, infinity, HibernateAfterTimeout, []); + loop(Parent, Name, NState, CbCache, infinity, HibernateAfterTimeout, []); {ok, {noreply, NState, TimeoutOrHibernate}} when ?is_timeout(TimeoutOrHibernate); TimeoutOrHibernate =:= hibernate -> - loop(Parent, Name, NState, Mod, TimeoutOrHibernate, HibernateAfterTimeout, []); + loop(Parent, Name, NState, CbCache, TimeoutOrHibernate, HibernateAfterTimeout, []); {ok, {noreply, NState, {continue, _}=Continue}} -> - loop(Parent, Name, NState, Mod, Continue, HibernateAfterTimeout, []); + loop(Parent, Name, NState, CbCache, Continue, HibernateAfterTimeout, []); {ok, {stop, Reason, NState}} -> terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, []); {'EXIT', Class, Reason, Stacktrace} -> @@ -1245,20 +1212,21 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, []) end. -handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, State, Debug) -> +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, Mod, infinity, HibernateAfterTimeout, Debug1); + 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, Mod, TimeoutOrHibernate, HibernateAfterTimeout, Debug1); + 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, Mod, Continue, HibernateAfterTimeout, Debug1); + 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} -> @@ -1270,32 +1238,34 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, reply(Name, From, Reply, State, Debug) -> reply(From, Reply), sys:handle_debug(Debug, fun print_event/3, Name, - {out, Reply, From, State} ). + {out, Reply, From, State} ). %%----------------------------------------------------------------- %% Callback functions for system messages handling. %%----------------------------------------------------------------- -system_continue(Parent, Debug, [Name, State, Mod, Time, HibernateAfterTimeout]) -> - loop(Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug). +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, Mod, _Time, _HibernateAfterTimeout]) -> +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, Mod, Time, HibernateAfterTimeout], _Module, OldVsn, Extra) -> +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, Mod, Time, HibernateAfterTimeout]}; - Else -> Else + {ok, NewState} -> {ok, [Name, NewState, CbCache, Time, HibernateAfterTimeout]}; + Else -> Else end. system_get_state([_Name, State, _Mod, _Time, _HibernateAfterTimeout]) -> {ok, State}. -system_replace_state(StateFun, [Name, State, Mod, Time, HibernateAfterTimeout]) -> +system_replace_state(StateFun, [Name, State, CbCache, Time, HibernateAfterTimeout]) -> NState = StateFun(State), - {ok, NState, [Name, NState, Mod, Time, HibernateAfterTimeout]}. + {ok, NState, [Name, NState, CbCache, Time, HibernateAfterTimeout]}. %%----------------------------------------------------------------- %% Format debug messages. Print them as the call-back module sees @@ -1348,7 +1318,7 @@ terminate(Class, Reason, Stacktrace, 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, terminate_reason(Class, Reason, Stacktrace), State), + 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), @@ -1371,8 +1341,9 @@ terminate(Class, Reason, Stacktrace, ReportStacktrace, Name, From, Msg, Mod, Sta erlang:raise(Class, Reason, Stacktrace) end. -terminate_reason(error, Reason, Stacktrace) -> {Reason, Stacktrace}; -terminate_reason(exit, Reason, _Stacktrace) -> Reason. +%% What an old style `catch` would return +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 Don't send an error report if it's the system process @@ -1659,7 +1630,8 @@ mod(_) -> "t". %% Status information %%----------------------------------------------------------------- format_status(Opt, StatusData) -> - [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, _HibernateAfterTimeout]] = 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) }, |