summaryrefslogtreecommitdiff
path: root/lib/stdlib/src/gen_server.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/src/gen_server.erl')
-rw-r--r--lib/stdlib/src/gen_server.erl612
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) },