Packages
rabbit_common
3.11.22
4.2.1
4.2.0
retired
4.2.0-rc.1
retired
4.1.6
4.1.5
retired
4.1.5-rc.2
retired
4.1.5-rc.1
4.0.3
4.0.3-rc.1
4.0.2
4.0.2-rc.2
4.0.2-rc.1
4.0.1
4.0.0
4.0.0-rc.2
4.0.0-rc.1
3.13.7
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.2-rc.1
3.13.1
3.13.0
3.13.0-rc.6
3.13.0-rc.5
3.13.0-rc.4
3.13.0-rc.3
3.13.0-rc.2
3.13.0-rc.1
3.12.14
3.12.13
3.12.12
3.12.11
3.12.10
3.12.9
3.12.8
3.12.7
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.12.0-rc.4
3.12.0-rc.3
3.12.0-rc.2
3.12.0-rc.1
3.11.28
3.11.27
3.11.26
3.11.25
3.11.24
3.11.23
3.11.22
3.11.21
3.11.20
3.11.19
3.11.18
3.11.17
3.11.16
3.11.15
3.11.14
3.11.13
3.11.12
3.11.11
3.11.10
3.11.9
3.11.8
3.11.7
3.11.6
3.11.5
3.11.4
3.11.3
3.11.2
3.11.1
3.11.0
3.11.0-rc.2
3.11.0-rc.1
3.11.0-1
3.10.25
3.10.24
3.10.23
3.10.22
3.10.21
3.10.20
3.10.19
3.10.18
3.10.17
3.10.16
3.10.15
3.10.14
3.10.13
3.10.12
3.10.11
3.10.10
3.10.9
3.10.8
3.10.7
3.10.6
3.10.5
3.10.4
3.10.3
3.10.2
3.10.1
3.10.0
3.10.0-rc.6
3.10.0-rc.5
3.9.29
3.9.28
3.9.27
3.9.26
3.9.25
3.9.24
3.9.23
3.9.22
3.9.21
3.9.20
3.9.19
3.9.18
3.9.17
3.9.16
3.9.15
3.9.11
3.9.10
3.9.9
3.9.8
3.9.7
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.8.35
3.8.34
3.8.33
3.8.32
3.8.31
3.8.30
3.8.26
3.8.25
3.8.24
3.8.23
3.8.22
3.8.21
3.8.20
3.8.19
3.8.14
3.8.12-rc.3
3.8.12-rc.2
3.8.12-rc.1
3.8.11
3.8.10
3.8.10-rc.6
3.8.10-rc.5
3.8.10-rc.1
3.8.9
3.8.8
3.8.7
3.8.6
3.8.6-rc.2
3.8.6-rc.1
3.8.5
3.8.5-rc.2
3.8.5-rc.1
3.8.4
3.8.4-rc.3
3.8.4-rc.1
3.8.3
3.8.3-rc.2
3.8.3-rc.1
3.8.2
3.8.2-rc.1
3.8.1
3.8.1-rc.1
3.8.0
3.8.0-rc.3
3.8.0-rc.2
3.8.0-rc.1
3.7.28
3.7.27
3.7.27-rc.2
3.7.27-rc.1
3.7.26
3.7.25
3.7.25-rc.1
3.7.24
3.7.24-rc.2
3.7.24-rc.1
3.7.23
3.7.23-rc.1
3.7.22
3.7.22-rc.2
3.7.22-rc.1
3.7.21
3.7.20
3.7.20-rc.2
3.7.20-rc.1
3.7.19
3.7.18
3.7.18-rc.1
3.7.17
3.7.17-rc.3
3.7.17-rc.2
3.7.17-rc.1
retired
3.7.16
retired
3.7.16-rc.4
retired
3.7.16-rc.3
retired
3.7.16-rc.2
retired
3.7.16-rc.1
retired
3.7.16-beta.1
3.7.15
3.7.14
3.7.14-rc.2
3.7.14-rc.1
3.7.13
3.7.13-rc.2
3.7.13-rc.1
3.7.12
3.7.12-rc.2
3.7.12-rc.1
3.7.11
3.7.11-rc.2
3.7.11-rc.1
3.7.10-rc.4
3.7.10-rc.3
3.7.10-rc.2
3.7.10-rc.1
3.7.9
3.7.9-rc.3
3.7.9-rc.2
3.7.8
3.7.8-rc.4
3.7.8-rc.3
3.7.8-rc.2
3.7.8-rc.1
3.7.7
3.7.7-rc.2
3.7.7-rc.1
3.7.6
3.7.6-rc.2
3.7.6-rc.1
3.7.5
3.7.5-rc.1
3.7.4
3.7.4-rc.4
3.7.4-rc.3
3.7.4-rc.2
3.7.4-rc.1
3.7.3
3.7.3-rc.2
3.7.3-rc.1
3.7.2
3.7.1
3.7.0-rc.2
3.7.0-alpha.544
3.7.0-alpha.542
3.6.16
3.6.16-rc.1
3.6.15
3.6.15-rc.1
3.6.14
3.6.13
3.6.12
3.6.11
3.6.10
3.6.9
3.6.8
3.6.7
3.6.7-pre.1
3.5.6
3.5.0
3.4.0
3.3.5
3.0.2
0.0.0-rc.1
Modules shared by rabbitmq-server and rabbitmq-erlang-client
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
src/delegate.erl
%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
%%
-module(delegate).
%% delegate is an alternative way of doing remote calls. Compared to
%% the rpc module, it reduces inter-node communication. For example,
%% if a message is routed to 1,000 queues on node A and needs to be
%% propagated to nodes B and C, it would be nice to avoid doing 2,000
%% remote casts to queue processes.
%%
%% An important issue here is preserving order - we need to make sure
%% that messages from a certain channel to a certain queue take a
%% consistent route, to prevent them being reordered. In fact all
%% AMQP-ish things (such as queue declaration results and basic.get)
%% must take the same route as well, to ensure that clients see causal
%% ordering correctly. Therefore we have a rather generic mechanism
%% here rather than just a message-reflector. That's also why we pick
%% the delegate process to use based on a hash of the source pid.
%%
%% When a function is invoked using delegate:invoke/2,
%% or delegate:invoke_no_result/2 on a group of pids, the pids are first split
%% into local and remote ones. Remote processes are then grouped by
%% node. The function is then invoked locally and on every node (using
%% gen_server2:multi/4) as many times as there are processes on that
%% node, sequentially.
%%
%% Errors returned when executing functions on remote nodes are re-raised
%% in the caller.
%%
%% RabbitMQ starts a pool of delegate processes on boot. The size of
%% the pool is configurable, the aim is to make sure we don't have too
%% few delegates and thus limit performance on many-CPU machines.
%% There are some optimisations applied.
%% If a message is sent to only one queue (a common scenario),
%% sending them over the delegate mechanism is redundant.
%% This optimization is applied to gen_server2 module calls when
%% delegate prefix matches the default, ?DEFAULT_NAME.
%%
%% Coonsider two examples:
%%
%% 1. "delegate:invoke(Pids, {erlang, process_info, [memory]})", "erlang:process_info/1"
%% should be called inside the target node.
%% 2. "{Results, Errors} = delegate:invoke(MemberPids, ?DELEGATE_PREFIX, FunOrMFA)",
%% Since this operation specifically specifies a delegate name rather than
%% relying on ?DEFAULT_NAME, it will be invoked using the delegate mechanism.
-behaviour(gen_server2).
-export([start_link/1, start_link/2, invoke_no_result/2,
invoke/2, invoke/3, monitor/2, monitor/3, demonitor/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {node, monitors, name}).
%%----------------------------------------------------------------------------
-export_type([monitor_ref/0]).
-type monitor_ref() :: reference() | {atom(), pid()}.
-type fun_or_mfa(A) :: fun ((pid()) -> A) | {atom(), atom(), [any()]}.
-spec start_link
(non_neg_integer()) -> {'ok', pid()} | ignore | {'error', any()}.
-spec invoke
( pid(), fun_or_mfa(A)) -> A;
([pid()], fun_or_mfa(A)) -> {[{pid(), A}], [{pid(), term()}]}.
-spec invoke_no_result(pid() | [pid()], fun_or_mfa(any())) -> 'ok'.
-spec monitor('process', pid()) -> monitor_ref().
-spec demonitor(monitor_ref()) -> 'true'.
%%----------------------------------------------------------------------------
-define(HIBERNATE_AFTER_MIN, 1000).
-define(DESIRED_HIBERNATE, 10000).
-define(DEFAULT_NAME, "delegate_").
%%----------------------------------------------------------------------------
start_link(Num) ->
start_link(?DEFAULT_NAME, Num).
start_link(Name, Num) ->
Name1 = delegate_name(Name, Num),
gen_server2:start_link({local, Name1}, ?MODULE, [Name1], []).
invoke(Pid, FunOrMFA = {gen_server2, _F, _A}) when is_pid(Pid) -> %% optimisation
case safe_invoke(Pid, FunOrMFA) of
{ok, _, Result} -> Result;
{error, _, {Class, Reason, StackTrace}} -> erlang:raise(Class, Reason, StackTrace)
end;
invoke(Pid, FunOrMFA) ->
invoke(Pid, ?DEFAULT_NAME, FunOrMFA).
invoke(Pid, _Name, FunOrMFA) when is_pid(Pid) andalso node(Pid) =:= node() ->
apply1(FunOrMFA, Pid);
invoke(Pid, ?DEFAULT_NAME, FunOrMFA = {gen_server2, _F, _A}) when is_pid(Pid) -> %% optimisation
case safe_invoke(Pid, FunOrMFA) of
{ok, _, Result} -> Result;
{error, _, {Class, Reason, StackTrace}} -> erlang:raise(Class, Reason, StackTrace)
end;
invoke(Pid, Name, FunOrMFA) when is_pid(Pid) ->
case invoke([Pid], Name, FunOrMFA) of
{[{Pid, Result}], []} ->
Result;
{[], [{Pid, {Class, Reason, StackTrace}}]} ->
erlang:raise(Class, Reason, StackTrace)
end;
invoke([], _Name, _FunOrMFA) -> %% optimisation
{[], []};
invoke([Pid], ?DEFAULT_NAME, FunOrMFA = {gen_server2, _F, _A}) when is_pid(Pid) -> %% optimisation
case safe_invoke(Pid, FunOrMFA) of
{ok, _, Result} -> {[{Pid, Result}], []};
{error, _, Error} -> {[], [{Pid, Error}]}
end;
invoke([Pid], _Name, FunOrMFA) when node(Pid) =:= node() -> %% optimisation
case safe_invoke(Pid, FunOrMFA) of
{ok, _, Result} -> {[{Pid, Result}], []};
{error, _, Error} -> {[], [{Pid, Error}]}
end;
invoke(Pids, Name = ?DEFAULT_NAME, FunOrMFA = {gen_server2, _F, _A}) when is_list(Pids) ->
{LocalCallPids, Grouped} = group_local_call_pids_by_node(Pids),
invoke(Pids, Name, FunOrMFA, LocalCallPids, Grouped);
invoke(Pids, Name, FunOrMFA) when is_list(Pids) ->
{LocalPids, Grouped} = group_pids_by_node(Pids),
invoke(Pids, Name, FunOrMFA, LocalPids, Grouped).
invoke(Pids, Name, FunOrMFA, LocalCallPids, Grouped) when is_list(Pids) ->
%% The use of multi_call is only safe because the timeout is
%% infinity, and thus there is no process spawned in order to do
%% the sending. Thus calls can't overtake preceding calls/casts.
{Replies, BadNodes} =
case maps:keys(Grouped) of
[] -> {[], []};
RemoteNodes -> gen_server2:multi_call(
RemoteNodes, delegate(self(), Name, RemoteNodes),
{invoke, FunOrMFA, Grouped}, infinity)
end,
BadPids = [{Pid, {exit, {nodedown, BadNode}, []}} ||
BadNode <- BadNodes,
Pid <- maps:get(BadNode, Grouped)],
ResultsNoNode = lists:append([safe_invoke(LocalCallPids, FunOrMFA) |
[Results || {_Node, Results} <- Replies]]),
lists:foldl(
fun ({ok, Pid, Result}, {Good, Bad}) -> {[{Pid, Result} | Good], Bad};
({error, Pid, Error}, {Good, Bad}) -> {Good, [{Pid, Error} | Bad]}
end, {[], BadPids}, ResultsNoNode).
monitor(process, Pid) ->
?MODULE:monitor(process, Pid, ?DEFAULT_NAME).
monitor(process, Pid, _Prefix) when node(Pid) =:= node() ->
erlang:monitor(process, Pid);
monitor(process, Pid, Prefix) ->
Name = delegate(Pid, Prefix, [node(Pid)]),
gen_server2:cast(Name, {monitor, self(), Pid}),
{Name, Pid}.
demonitor(Ref) when is_reference(Ref) ->
erlang:demonitor(Ref);
demonitor({Name, Pid}) ->
gen_server2:cast(Name, {demonitor, self(), Pid}).
invoke_no_result(Pid, FunOrMFA) when is_pid(Pid) andalso node(Pid) =:= node() ->
%% Optimization, avoids calling invoke_no_result/3.
%%
%% This may seem like a cosmetic change at first but it actually massively reduces the memory usage in mirrored
%% queues when ack/nack are sent to the node that hosts a mirror.
%% This way binary references are not kept around unnecessarily.
%%
%% See https://github.com/rabbitmq/rabbitmq-common/issues/208#issuecomment-311308583 for a before/after
%% comparison.
_ = safe_invoke(Pid, FunOrMFA), %% we don't care about any error
ok;
invoke_no_result(Pid, FunOrMFA) when is_pid(Pid) ->
%% Optimization, avoids calling invoke_no_result/3
RemoteNode = node(Pid),
gen_server2:abcast([RemoteNode], delegate(self(), ?DEFAULT_NAME, [RemoteNode]),
{invoke, FunOrMFA,
maps:from_list([{RemoteNode, [Pid]}])}),
ok;
invoke_no_result([], _FunOrMFA) -> %% optimisation
ok;
invoke_no_result([Pid], FunOrMFA) when node(Pid) =:= node() -> %% optimisation
_ = safe_invoke(Pid, FunOrMFA), %% must not die
ok;
invoke_no_result([Pid], FunOrMFA) ->
RemoteNode = node(Pid),
gen_server2:abcast([RemoteNode], delegate(self(), ?DEFAULT_NAME, [RemoteNode]),
{invoke, FunOrMFA,
maps:from_list([{RemoteNode, [Pid]}])}),
ok;
invoke_no_result(Pids, FunOrMFA) when is_list(Pids) ->
{LocalPids, Grouped} = group_pids_by_node(Pids),
invoke_no_result(Pids, FunOrMFA, LocalPids, Grouped).
invoke_no_result(Pids, FunOrMFA, LocalCallPids, Grouped) when is_list(Pids) ->
case maps:keys(Grouped) of
[] -> ok;
RemoteNodes -> gen_server2:abcast(
RemoteNodes, delegate(self(), ?DEFAULT_NAME, RemoteNodes),
{invoke, FunOrMFA, Grouped})
end,
_ = safe_invoke(LocalCallPids, FunOrMFA), %% must not die
ok.
%%----------------------------------------------------------------------------
group_pids_by_node(Pids) ->
LocalNode = node(),
lists:foldl(
fun (Pid, {Local, Remote}) when node(Pid) =:= LocalNode ->
{[Pid | Local], Remote};
(Pid, {Local, Remote}) ->
{Local,
maps:update_with(
node(Pid), fun (List) -> [Pid | List] end, [Pid], Remote)}
end, {[], maps:new()}, Pids).
group_local_call_pids_by_node(Pids) ->
{LocalPids0, Grouped0} = group_pids_by_node(Pids),
maps:fold(fun(K, V, {AccIn, MapsIn}) ->
case V of
%% just one Pid for the node
[SinglePid] -> {[SinglePid | AccIn], MapsIn};
%% If the value is a list of more than one pid,
%% the (K,V) will be put into the new map which will be called
%% through delegate to reduce inter-node communication.
_ -> {AccIn, maps:update_with(K, fun(V1) -> V1 end, V, MapsIn)}
end
end, {LocalPids0, maps:new()}, Grouped0).
delegate_name(Name, Hash) ->
list_to_atom(Name ++ integer_to_list(Hash)).
delegate(Pid, Prefix, RemoteNodes) ->
case get(delegate) of
undefined -> Name = delegate_name(Prefix,
erlang:phash2(Pid,
delegate_sup:count(RemoteNodes, Prefix))),
put(delegate, Name),
Name;
Name -> Name
end.
safe_invoke(Pids, FunOrMFA) when is_list(Pids) ->
[safe_invoke(Pid, FunOrMFA) || Pid <- Pids];
safe_invoke(Pid, FunOrMFA) when is_pid(Pid) ->
try
{ok, Pid, apply1(FunOrMFA, Pid)}
catch Class:Reason:Stacktrace ->
{error, Pid, {Class, Reason, Stacktrace}}
end.
apply1({M, F, A}, Arg) -> apply(M, F, [Arg | A]);
apply1(Fun, Arg) -> Fun(Arg).
%%----------------------------------------------------------------------------
init([Name]) ->
{ok, #state{node = node(), monitors = dict:new(), name = Name}, hibernate,
{backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}.
handle_call({invoke, FunOrMFA, Grouped}, _From, State = #state{node = Node}) ->
{reply, safe_invoke(maps:get(Node, Grouped), FunOrMFA), State,
hibernate}.
handle_cast({monitor, MonitoringPid, Pid},
State = #state{monitors = Monitors}) ->
Monitors1 = case dict:find(Pid, Monitors) of
{ok, {Ref, Pids}} ->
Pids1 = gb_sets:add_element(MonitoringPid, Pids),
dict:store(Pid, {Ref, Pids1}, Monitors);
error ->
Ref = erlang:monitor(process, Pid),
Pids = gb_sets:singleton(MonitoringPid),
dict:store(Pid, {Ref, Pids}, Monitors)
end,
{noreply, State#state{monitors = Monitors1}, hibernate};
handle_cast({demonitor, MonitoringPid, Pid},
State = #state{monitors = Monitors}) ->
Monitors1 = case dict:find(Pid, Monitors) of
{ok, {Ref, Pids}} ->
Pids1 = gb_sets:del_element(MonitoringPid, Pids),
case gb_sets:is_empty(Pids1) of
true -> erlang:demonitor(Ref),
dict:erase(Pid, Monitors);
false -> dict:store(Pid, {Ref, Pids1}, Monitors)
end;
error ->
Monitors
end,
{noreply, State#state{monitors = Monitors1}, hibernate};
handle_cast({invoke, FunOrMFA, Grouped}, State = #state{node = Node}) ->
_ = safe_invoke(maps:get(Node, Grouped), FunOrMFA),
{noreply, State, hibernate}.
handle_info({'DOWN', Ref, process, Pid, Info},
State = #state{monitors = Monitors, name = Name}) ->
{noreply,
case dict:find(Pid, Monitors) of
{ok, {Ref, Pids}} ->
Msg = {'DOWN', {Name, Pid}, process, Pid, Info},
gb_sets:fold(fun (MonitoringPid, _) -> MonitoringPid ! Msg end,
none, Pids),
State#state{monitors = dict:erase(Pid, Monitors)};
error ->
State
end, hibernate};
handle_info(_Info, State) ->
{noreply, State, hibernate}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.