diff options
Diffstat (limited to 'src')
137 files changed, 0 insertions, 43145 deletions
diff --git a/src/app_utils.erl b/src/app_utils.erl deleted file mode 100644 index ad270518..00000000 --- a/src/app_utils.erl +++ /dev/null @@ -1,133 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% --module(app_utils). - --export([load_applications/1, start_applications/1, start_applications/2, - stop_applications/1, stop_applications/2, app_dependency_order/2, - app_dependencies/1]). - --ifdef(use_specs). - --type error_handler() :: fun((atom(), any()) -> 'ok'). - --spec load_applications([atom()]) -> 'ok'. --spec start_applications([atom()]) -> 'ok'. --spec stop_applications([atom()]) -> 'ok'. --spec start_applications([atom()], error_handler()) -> 'ok'. --spec stop_applications([atom()], error_handler()) -> 'ok'. --spec app_dependency_order([atom()], boolean()) -> [digraph:vertex()]. --spec app_dependencies(atom()) -> [atom()]. - --endif. - -%%--------------------------------------------------------------------------- -%% Public API - -load_applications(Apps) -> - load_applications(queue:from_list(Apps), sets:new()), - ok. - -start_applications(Apps) -> - start_applications( - Apps, fun (App, Reason) -> - throw({error, {cannot_start_application, App, Reason}}) - end). - -stop_applications(Apps) -> - stop_applications( - Apps, fun (App, Reason) -> - throw({error, {cannot_stop_application, App, Reason}}) - end). - -start_applications(Apps, ErrorHandler) -> - manage_applications(fun lists:foldl/3, - fun application:start/1, - fun application:stop/1, - already_started, - ErrorHandler, - Apps). - -stop_applications(Apps, ErrorHandler) -> - manage_applications(fun lists:foldr/3, - %% Mitigation for bug 26467. TODO remove when we fix it. - fun (mnesia) -> - timer:sleep(1000), - application:stop(mnesia); - (App) -> - application:stop(App) - end, - fun application:start/1, - not_started, - ErrorHandler, - Apps). - -app_dependency_order(RootApps, StripUnreachable) -> - {ok, G} = rabbit_misc:build_acyclic_graph( - fun ({App, _Deps}) -> [{App, App}] end, - fun ({App, Deps}) -> [{Dep, App} || Dep <- Deps] end, - [{App, app_dependencies(App)} || - {App, _Desc, _Vsn} <- application:loaded_applications()]), - try - case StripUnreachable of - true -> digraph:del_vertices(G, digraph:vertices(G) -- - digraph_utils:reachable(RootApps, G)); - false -> ok - end, - digraph_utils:topsort(G) - after - true = digraph:delete(G) - end. - -%%--------------------------------------------------------------------------- -%% Private API - -load_applications(Worklist, Loaded) -> - case queue:out(Worklist) of - {empty, _WorkList} -> - ok; - {{value, App}, Worklist1} -> - case sets:is_element(App, Loaded) of - true -> load_applications(Worklist1, Loaded); - false -> case application:load(App) of - ok -> ok; - {error, {already_loaded, App}} -> ok; - Error -> throw(Error) - end, - load_applications( - queue:join(Worklist1, - queue:from_list(app_dependencies(App))), - sets:add_element(App, Loaded)) - end - end. - -app_dependencies(App) -> - case application:get_key(App, applications) of - undefined -> []; - {ok, Lst} -> Lst - end. - -manage_applications(Iterate, Do, Undo, SkipError, ErrorHandler, Apps) -> - Iterate(fun (App, Acc) -> - case Do(App) of - ok -> [App | Acc]; - {error, {SkipError, _}} -> Acc; - {error, Reason} -> - lists:foreach(Undo, Acc), - ErrorHandler(App, Reason) - end - end, [], Apps), - ok. - diff --git a/src/background_gc.erl b/src/background_gc.erl deleted file mode 100644 index d30fa896..00000000 --- a/src/background_gc.erl +++ /dev/null @@ -1,81 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(background_gc). - --behaviour(gen_server2). - --export([start_link/0, run/0]). --export([gc/0]). %% For run_interval only - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --define(MAX_RATIO, 0.01). --define(IDEAL_INTERVAL, 60000). - --record(state, {last_interval}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> {'ok', pid()} | {'error', any()}). --spec(run/0 :: () -> 'ok'). --spec(gc/0 :: () -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> gen_server2:start_link({local, ?MODULE}, ?MODULE, [], - [{timeout, infinity}]). - -run() -> gen_server2:cast(?MODULE, run). - -%%---------------------------------------------------------------------------- - -init([]) -> {ok, interval_gc(#state{last_interval = ?IDEAL_INTERVAL})}. - -handle_call(Msg, _From, State) -> - {stop, {unexpected_call, Msg}, {unexpected_call, Msg}, State}. - -handle_cast(run, State) -> gc(), {noreply, State}; - -handle_cast(Msg, State) -> {stop, {unexpected_cast, Msg}, State}. - -handle_info(run, State) -> {noreply, interval_gc(State)}; - -handle_info(Msg, State) -> {stop, {unexpected_info, Msg}, State}. - -code_change(_OldVsn, State, _Extra) -> {ok, State}. - -terminate(_Reason, State) -> State. - -%%---------------------------------------------------------------------------- - -interval_gc(State = #state{last_interval = LastInterval}) -> - {ok, Interval} = rabbit_misc:interval_operation( - {?MODULE, gc, []}, - ?MAX_RATIO, ?IDEAL_INTERVAL, LastInterval), - erlang:send_after(Interval, self(), run), - State#state{last_interval = Interval}. - -gc() -> - [garbage_collect(P) || P <- processes(), - {status, waiting} == process_info(P, status)], - garbage_collect(), %% since we will never be waiting... - ok. diff --git a/src/credit_flow.erl b/src/credit_flow.erl deleted file mode 100644 index 89320621..00000000 --- a/src/credit_flow.erl +++ /dev/null @@ -1,160 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(credit_flow). - -%% Credit flow is controlled by a credit specification - a -%% {InitialCredit, MoreCreditAfter} tuple. For the message sender, -%% credit starts at InitialCredit and is decremented with every -%% message sent. The message receiver grants more credit to the sender -%% by sending it a {bump_credit, ...} control message after receiving -%% MoreCreditAfter messages. The sender should pass this message in to -%% handle_bump_msg/1. The sender should block when it goes below 0 -%% (check by invoking blocked/0). If a process is both a sender and a -%% receiver it will not grant any more credit to its senders when it -%% is itself blocked - thus the only processes that need to check -%% blocked/0 are ones that read from network sockets. - --define(DEFAULT_CREDIT, {200, 50}). - --export([send/1, send/2, ack/1, ack/2, handle_bump_msg/1, blocked/0, state/0]). --export([peer_down/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([bump_msg/0]). - --opaque(bump_msg() :: {pid(), non_neg_integer()}). --type(credit_spec() :: {non_neg_integer(), non_neg_integer()}). - --spec(send/1 :: (pid()) -> 'ok'). --spec(send/2 :: (pid(), credit_spec()) -> 'ok'). --spec(ack/1 :: (pid()) -> 'ok'). --spec(ack/2 :: (pid(), credit_spec()) -> 'ok'). --spec(handle_bump_msg/1 :: (bump_msg()) -> 'ok'). --spec(blocked/0 :: () -> boolean()). --spec(peer_down/1 :: (pid()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -%% process dict update macro - eliminates the performance-hurting -%% closure creation a HOF would introduce --define(UPDATE(Key, Default, Var, Expr), - begin - %% We deliberately allow Var to escape from the case here - %% to be used in Expr. Any temporary var we introduced - %% would also escape, and might conflict. - case get(Key) of - undefined -> Var = Default; - Var -> ok - end, - put(Key, Expr) - end). - -%%---------------------------------------------------------------------------- - -%% There are two "flows" here; of messages and of credit, going in -%% opposite directions. The variable names "From" and "To" refer to -%% the flow of credit, but the function names refer to the flow of -%% messages. This is the clearest I can make it (since the function -%% names form the API and want to make sense externally, while the -%% variable names are used in credit bookkeeping and want to make -%% sense internally). - -%% For any given pair of processes, ack/2 and send/2 must always be -%% called with the same credit_spec(). - -send(From) -> send(From, ?DEFAULT_CREDIT). - -send(From, {InitialCredit, _MoreCreditAfter}) -> - ?UPDATE({credit_from, From}, InitialCredit, C, - if C == 1 -> block(From), - 0; - true -> C - 1 - end). - -ack(To) -> ack(To, ?DEFAULT_CREDIT). - -ack(To, {_InitialCredit, MoreCreditAfter}) -> - ?UPDATE({credit_to, To}, MoreCreditAfter, C, - if C == 1 -> grant(To, MoreCreditAfter), - MoreCreditAfter; - true -> C - 1 - end). - -handle_bump_msg({From, MoreCredit}) -> - ?UPDATE({credit_from, From}, 0, C, - if C =< 0 andalso C + MoreCredit > 0 -> unblock(From), - C + MoreCredit; - true -> C + MoreCredit - end). - -blocked() -> case get(credit_blocked) of - undefined -> false; - [] -> false; - _ -> true - end. - -state() -> case blocked() of - true -> flow; - false -> case get(credit_blocked_at) of - undefined -> running; - B -> Diff = timer:now_diff(erlang:now(), B), - case Diff < 5000000 of - true -> flow; - false -> running - end - end - end. - -peer_down(Peer) -> - %% In theory we could also remove it from credit_deferred here, but it - %% doesn't really matter; at some point later we will drain - %% credit_deferred and thus send messages into the void... - unblock(Peer), - erase({credit_from, Peer}), - erase({credit_to, Peer}), - ok. - -%% -------------------------------------------------------------------------- - -grant(To, Quantity) -> - Msg = {bump_credit, {self(), Quantity}}, - case blocked() of - false -> To ! Msg; - true -> ?UPDATE(credit_deferred, [], Deferred, [{To, Msg} | Deferred]) - end. - -block(From) -> - case blocked() of - false -> put(credit_blocked_at, erlang:now()); - true -> ok - end, - ?UPDATE(credit_blocked, [], Blocks, [From | Blocks]). - -unblock(From) -> - ?UPDATE(credit_blocked, [], Blocks, Blocks -- [From]), - case blocked() of - false -> case erase(credit_deferred) of - undefined -> ok; - Credits -> [To ! Msg || {To, Msg} <- Credits] - end; - true -> ok - end. diff --git a/src/delegate.erl b/src/delegate.erl deleted file mode 100644 index 378759a6..00000000 --- a/src/delegate.erl +++ /dev/null @@ -1,244 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(delegate). - --behaviour(gen_server2). - --export([start_link/1, invoke_no_result/2, invoke/2, - monitor/2, demonitor/1, call/2, cast/2]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --record(state, {node, monitors, name}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --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/1 :: - (non_neg_integer()) -> {'ok', pid()} | ignore | {'error', any()}). --spec(invoke/2 :: ( pid(), fun_or_mfa(A)) -> A; - ([pid()], fun_or_mfa(A)) -> {[{pid(), A}], - [{pid(), term()}]}). --spec(invoke_no_result/2 :: (pid() | [pid()], fun_or_mfa(any())) -> 'ok'). --spec(monitor/2 :: ('process', pid()) -> monitor_ref()). --spec(demonitor/1 :: (monitor_ref()) -> 'true'). - --spec(call/2 :: - ( pid(), any()) -> any(); - ([pid()], any()) -> {[{pid(), any()}], [{pid(), term()}]}). --spec(cast/2 :: (pid() | [pid()], any()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - --define(HIBERNATE_AFTER_MIN, 1000). --define(DESIRED_HIBERNATE, 10000). - -%%---------------------------------------------------------------------------- - -start_link(Num) -> - Name = delegate_name(Num), - gen_server2:start_link({local, Name}, ?MODULE, [Name], []). - -invoke(Pid, FunOrMFA) when is_pid(Pid) andalso node(Pid) =:= node() -> - apply1(FunOrMFA, Pid); -invoke(Pid, FunOrMFA) when is_pid(Pid) -> - case invoke([Pid], FunOrMFA) of - {[{Pid, Result}], []} -> - Result; - {[], [{Pid, {Class, Reason, StackTrace}}]} -> - erlang:raise(Class, Reason, StackTrace) - end; - -invoke([], _FunOrMFA) -> %% optimisation - {[], []}; -invoke([Pid], FunOrMFA) when node(Pid) =:= node() -> %% optimisation - case safe_invoke(Pid, FunOrMFA) of - {ok, _, Result} -> {[{Pid, Result}], []}; - {error, _, Error} -> {[], [{Pid, Error}]} - end; -invoke(Pids, FunOrMFA) when is_list(Pids) -> - {LocalPids, Grouped} = group_pids_by_node(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 orddict:fetch_keys(Grouped) of - [] -> {[], []}; - RemoteNodes -> gen_server2:multi_call( - RemoteNodes, delegate(self(), RemoteNodes), - {invoke, FunOrMFA, Grouped}, infinity) - end, - BadPids = [{Pid, {exit, {nodedown, BadNode}, []}} || - BadNode <- BadNodes, - Pid <- orddict:fetch(BadNode, Grouped)], - ResultsNoNode = lists:append([safe_invoke(LocalPids, 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). - -invoke_no_result(Pid, FunOrMFA) when is_pid(Pid) andalso node(Pid) =:= node() -> - safe_invoke(Pid, FunOrMFA), %% we don't care about any error - ok; -invoke_no_result(Pid, FunOrMFA) when is_pid(Pid) -> - invoke_no_result([Pid], FunOrMFA); - -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(Pids, FunOrMFA) when is_list(Pids) -> - {LocalPids, Grouped} = group_pids_by_node(Pids), - case orddict:fetch_keys(Grouped) of - [] -> ok; - RemoteNodes -> gen_server2:abcast( - RemoteNodes, delegate(self(), RemoteNodes), - {invoke, FunOrMFA, Grouped}) - end, - safe_invoke(LocalPids, FunOrMFA), %% must not die - ok. - -monitor(process, Pid) when node(Pid) =:= node() -> - erlang:monitor(process, Pid); -monitor(process, Pid) -> - Name = delegate(Pid, [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}). - -call(PidOrPids, Msg) -> - invoke(PidOrPids, {gen_server2, call, [Msg, infinity]}). - -cast(PidOrPids, Msg) -> - invoke_no_result(PidOrPids, {gen_server2, cast, [Msg]}). - -%%---------------------------------------------------------------------------- - -group_pids_by_node(Pids) -> - LocalNode = node(), - lists:foldl( - fun (Pid, {Local, Remote}) when node(Pid) =:= LocalNode -> - {[Pid | Local], Remote}; - (Pid, {Local, Remote}) -> - {Local, - orddict:update( - node(Pid), fun (List) -> [Pid | List] end, [Pid], Remote)} - end, {[], orddict:new()}, Pids). - -delegate_name(Hash) -> - list_to_atom("delegate_" ++ integer_to_list(Hash)). - -delegate(Pid, RemoteNodes) -> - case get(delegate) of - undefined -> Name = delegate_name( - erlang:phash2(Pid, - delegate_sup:count(RemoteNodes))), - 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 -> - {error, Pid, {Class, Reason, erlang:get_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(orddict:fetch(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(orddict:fetch(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}. diff --git a/src/delegate_sup.erl b/src/delegate_sup.erl deleted file mode 100644 index cb5ef2b8..00000000 --- a/src/delegate_sup.erl +++ /dev/null @@ -1,59 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(delegate_sup). - --behaviour(supervisor). - --export([start_link/1, count/1]). - --export([init/1]). - --define(SERVER, ?MODULE). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/1 :: (integer()) -> rabbit_types:ok_pid_or_error()). --spec(count/1 :: ([node()]) -> integer()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Count) -> - supervisor:start_link({local, ?SERVER}, ?MODULE, [Count]). - -count([]) -> - 1; -count([Node | Nodes]) -> - try - length(supervisor:which_children({?SERVER, Node})) - catch exit:{{R, _}, _} when R =:= nodedown; R =:= shutdown -> - count(Nodes); - exit:{R, _} when R =:= noproc; R =:= normal; R =:= shutdown; - R =:= nodedown -> - count(Nodes) - end. - -%%---------------------------------------------------------------------------- - -init([Count]) -> - {ok, {{one_for_one, 10, 10}, - [{Num, {delegate, start_link, [Num]}, - transient, 16#ffffffff, worker, [delegate]} || - Num <- lists:seq(0, Count - 1)]}}. diff --git a/src/dtree.erl b/src/dtree.erl deleted file mode 100644 index 9013bd6d..00000000 --- a/src/dtree.erl +++ /dev/null @@ -1,172 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - -%% A dual-index tree. -%% -%% Entries have the following shape: -%% -%% +----+--------------------+---+ -%% | PK | SK1, SK2, ..., SKN | V | -%% +----+--------------------+---+ -%% -%% i.e. a primary key, set of secondary keys, and a value. -%% -%% There can be only one entry per primary key, but secondary keys may -%% appear in multiple entries. -%% -%% The set of secondary keys must be non-empty. Or, to put it another -%% way, entries only exist while their secondary key set is non-empty. - --module(dtree). - --export([empty/0, insert/4, take/3, take/2, take_all/2, drop/2, - is_defined/2, is_empty/1, smallest/1, size/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([?MODULE/0]). - --opaque(?MODULE() :: {gb_trees:tree(), gb_trees:tree()}). - --type(pk() :: any()). --type(sk() :: any()). --type(val() :: any()). --type(kv() :: {pk(), val()}). - --spec(empty/0 :: () -> ?MODULE()). --spec(insert/4 :: (pk(), [sk()], val(), ?MODULE()) -> ?MODULE()). --spec(take/3 :: ([pk()], sk(), ?MODULE()) -> {[kv()], ?MODULE()}). --spec(take/2 :: (sk(), ?MODULE()) -> {[kv()], ?MODULE()}). --spec(take_all/2 :: (sk(), ?MODULE()) -> {[kv()], ?MODULE()}). --spec(drop/2 :: (pk(), ?MODULE()) -> ?MODULE()). --spec(is_defined/2 :: (sk(), ?MODULE()) -> boolean()). --spec(is_empty/1 :: (?MODULE()) -> boolean()). --spec(smallest/1 :: (?MODULE()) -> kv()). --spec(size/1 :: (?MODULE()) -> non_neg_integer()). - --endif. - -%%---------------------------------------------------------------------------- - -empty() -> {gb_trees:empty(), gb_trees:empty()}. - -%% Insert an entry. Fails if there already is an entry with the given -%% primary key. -insert(PK, [], V, {P, S}) -> - %% dummy insert to force error if PK exists - gb_trees:insert(PK, {gb_sets:empty(), V}, P), - {P, S}; -insert(PK, SKs, V, {P, S}) -> - {gb_trees:insert(PK, {gb_sets:from_list(SKs), V}, P), - lists:foldl(fun (SK, S0) -> - case gb_trees:lookup(SK, S0) of - {value, PKS} -> PKS1 = gb_sets:insert(PK, PKS), - gb_trees:update(SK, PKS1, S0); - none -> PKS = gb_sets:singleton(PK), - gb_trees:insert(SK, PKS, S0) - end - end, S, SKs)}. - -%% Remove the given secondary key from the entries of the given -%% primary keys, returning the primary-key/value pairs of any entries -%% that were dropped as the result (i.e. due to their secondary key -%% set becoming empty). It is ok for the given primary keys and/or -%% secondary key to not exist. -take(PKs, SK, {P, S}) -> - case gb_trees:lookup(SK, S) of - none -> {[], {P, S}}; - {value, PKS} -> TakenPKS = gb_sets:from_list(PKs), - PKSInter = gb_sets:intersection(PKS, TakenPKS), - PKSDiff = gb_sets_difference (PKS, PKSInter), - {KVs, P1} = take2(PKSInter, SK, P), - {KVs, {P1, case gb_sets:is_empty(PKSDiff) of - true -> gb_trees:delete(SK, S); - false -> gb_trees:update(SK, PKSDiff, S) - end}} - end. - -%% Remove the given secondary key from all entries, returning the -%% primary-key/value pairs of any entries that were dropped as the -%% result (i.e. due to their secondary key set becoming empty). It is -%% ok for the given secondary key to not exist. -take(SK, {P, S}) -> - case gb_trees:lookup(SK, S) of - none -> {[], {P, S}}; - {value, PKS} -> {KVs, P1} = take2(PKS, SK, P), - {KVs, {P1, gb_trees:delete(SK, S)}} - end. - -%% Drop all entries which contain the given secondary key, returning -%% the primary-key/value pairs of these entries. It is ok for the -%% given secondary key to not exist. -take_all(SK, {P, S}) -> - case gb_trees:lookup(SK, S) of - none -> {[], {P, S}}; - {value, PKS} -> {KVs, SKS, P1} = take_all2(PKS, P), - {KVs, {P1, prune(SKS, PKS, S)}} - end. - -%% Drop all entries for the given primary key (which does not have to exist). -drop(PK, {P, S}) -> - case gb_trees:lookup(PK, P) of - none -> {P, S}; - {value, {SKS, _V}} -> {gb_trees:delete(PK, P), - prune(SKS, gb_sets:singleton(PK), S)} - end. - -is_defined(SK, {_P, S}) -> gb_trees:is_defined(SK, S). - -is_empty({P, _S}) -> gb_trees:is_empty(P). - -smallest({P, _S}) -> {K, {_SKS, V}} = gb_trees:smallest(P), - {K, V}. - -size({P, _S}) -> gb_trees:size(P). - -%%---------------------------------------------------------------------------- - -take2(PKS, SK, P) -> - gb_sets:fold(fun (PK, {KVs, P0}) -> - {SKS, V} = gb_trees:get(PK, P0), - SKS1 = gb_sets:delete(SK, SKS), - case gb_sets:is_empty(SKS1) of - true -> KVs1 = [{PK, V} | KVs], - {KVs1, gb_trees:delete(PK, P0)}; - false -> {KVs, gb_trees:update(PK, {SKS1, V}, P0)} - end - end, {[], P}, PKS). - -take_all2(PKS, P) -> - gb_sets:fold(fun (PK, {KVs, SKS0, P0}) -> - {SKS, V} = gb_trees:get(PK, P0), - {[{PK, V} | KVs], gb_sets:union(SKS, SKS0), - gb_trees:delete(PK, P0)} - end, {[], gb_sets:empty(), P}, PKS). - -prune(SKS, PKS, S) -> - gb_sets:fold(fun (SK0, S0) -> - PKS1 = gb_trees:get(SK0, S0), - PKS2 = gb_sets_difference(PKS1, PKS), - case gb_sets:is_empty(PKS2) of - true -> gb_trees:delete(SK0, S0); - false -> gb_trees:update(SK0, PKS2, S0) - end - end, S, SKS). - -gb_sets_difference(S1, S2) -> - gb_sets:fold(fun gb_sets:delete_any/2, S1, S2). diff --git a/src/file_handle_cache.erl b/src/file_handle_cache.erl deleted file mode 100644 index 2922e146..00000000 --- a/src/file_handle_cache.erl +++ /dev/null @@ -1,1367 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(file_handle_cache). - -%% A File Handle Cache -%% -%% This extends a subset of the functionality of the Erlang file -%% module. In the below, we use "file handle" to specifically refer to -%% file handles, and "file descriptor" to refer to descriptors which -%% are not file handles, e.g. sockets. -%% -%% Some constraints -%% 1) This supports one writer, multiple readers per file. Nothing -%% else. -%% 2) Do not open the same file from different processes. Bad things -%% may happen, especially for writes. -%% 3) Writes are all appends. You cannot write to the middle of a -%% file, although you can truncate and then append if you want. -%% 4) Although there is a write buffer, there is no read buffer. Feel -%% free to use the read_ahead mode, but beware of the interaction -%% between that buffer and the write buffer. -%% -%% Some benefits -%% 1) You do not have to remember to call sync before close -%% 2) Buffering is much more flexible than with the plain file module, -%% and you can control when the buffer gets flushed out. This means -%% that you can rely on reads-after-writes working, without having to -%% call the expensive sync. -%% 3) Unnecessary calls to position and sync get optimised out. -%% 4) You can find out what your 'real' offset is, and what your -%% 'virtual' offset is (i.e. where the hdl really is, and where it -%% would be after the write buffer is written out). -%% -%% There is also a server component which serves to limit the number -%% of open file descriptors. This is a hard limit: the server -%% component will ensure that clients do not have more file -%% descriptors open than it's configured to allow. -%% -%% On open, the client requests permission from the server to open the -%% required number of file handles. The server may ask the client to -%% close other file handles that it has open, or it may queue the -%% request and ask other clients to close file handles they have open -%% in order to satisfy the request. Requests are always satisfied in -%% the order they arrive, even if a latter request (for a small number -%% of file handles) can be satisfied before an earlier request (for a -%% larger number of file handles). On close, the client sends a -%% message to the server. These messages allow the server to keep -%% track of the number of open handles. The client also keeps a -%% gb_tree which is updated on every use of a file handle, mapping the -%% time at which the file handle was last used (timestamp) to the -%% handle. Thus the smallest key in this tree maps to the file handle -%% that has not been used for the longest amount of time. This -%% smallest key is included in the messages to the server. As such, -%% the server keeps track of when the least recently used file handle -%% was used *at the point of the most recent open or close* by each -%% client. -%% -%% Note that this data can go very out of date, by the client using -%% the least recently used handle. -%% -%% When the limit is exceeded (i.e. the number of open file handles is -%% at the limit and there are pending 'open' requests), the server -%% calculates the average age of the last reported least recently used -%% file handle of all the clients. It then tells all the clients to -%% close any handles not used for longer than this average, by -%% invoking the callback the client registered. The client should -%% receive this message and pass it into -%% set_maximum_since_use/1. However, it is highly possible this age -%% will be greater than the ages of all the handles the client knows -%% of because the client has used its file handles in the mean -%% time. Thus at this point the client reports to the server the -%% current timestamp at which its least recently used file handle was -%% last used. The server will check two seconds later that either it -%% is back under the limit, in which case all is well again, or if -%% not, it will calculate a new average age. Its data will be much -%% more recent now, and so it is very likely that when this is -%% communicated to the clients, the clients will close file handles. -%% (In extreme cases, where it's very likely that all clients have -%% used their open handles since they last sent in an update, which -%% would mean that the average will never cause any file handles to -%% be closed, the server can send out an average age of 0, resulting -%% in all available clients closing all their file handles.) -%% -%% Care is taken to ensure that (a) processes which are blocked -%% waiting for file descriptors to become available are not sent -%% requests to close file handles; and (b) given it is known how many -%% file handles a process has open, when the average age is forced to -%% 0, close messages are only sent to enough processes to release the -%% correct number of file handles and the list of processes is -%% randomly shuffled. This ensures we don't cause processes to -%% needlessly close file handles, and ensures that we don't always -%% make such requests of the same processes. -%% -%% The advantage of this scheme is that there is only communication -%% from the client to the server on open, close, and when in the -%% process of trying to reduce file handle usage. There is no -%% communication from the client to the server on normal file handle -%% operations. This scheme forms a feed-back loop - the server does -%% not care which file handles are closed, just that some are, and it -%% checks this repeatedly when over the limit. -%% -%% Handles which are closed as a result of the server are put into a -%% "soft-closed" state in which the handle is closed (data flushed out -%% and sync'd first) but the state is maintained. The handle will be -%% fully reopened again as soon as needed, thus users of this library -%% do not need to worry about their handles being closed by the server -%% - reopening them when necessary is handled transparently. -%% -%% The server also supports obtain, release and transfer. obtain/{0,1} -%% blocks until a file descriptor is available, at which point the -%% requesting process is considered to 'own' more descriptor(s). -%% release/{0,1} is the inverse operation and releases previously obtained -%% descriptor(s). transfer/{1,2} transfers ownership of file descriptor(s) -%% between processes. It is non-blocking. Obtain has a -%% lower limit, set by the ?OBTAIN_LIMIT/1 macro. File handles can use -%% the entire limit, but will be evicted by obtain calls up to the -%% point at which no more obtain calls can be satisfied by the obtains -%% limit. Thus there will always be some capacity available for file -%% handles. Processes that use obtain are never asked to return them, -%% and they are not managed in any way by the server. It is simply a -%% mechanism to ensure that processes that need file descriptors such -%% as sockets can do so in such a way that the overall number of open -%% file descriptors is managed. -%% -%% The callers of register_callback/3, obtain, and the argument of -%% transfer are monitored, reducing the count of handles in use -%% appropriately when the processes terminate. - --behaviour(gen_server2). - --export([register_callback/3]). --export([open/3, close/1, read/2, append/2, needs_sync/1, sync/1, position/2, - truncate/1, current_virtual_offset/1, current_raw_offset/1, flush/1, - copy/3, set_maximum_since_use/1, delete/1, clear/1]). --export([obtain/0, obtain/1, release/0, release/1, transfer/1, transfer/2, - set_limit/1, get_limit/0, info_keys/0, with_handle/1, with_handle/2, - info/0, info/1]). --export([ulimit/0]). - --export([start_link/0, start_link/2, init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3, prioritise_cast/3]). - --define(SERVER, ?MODULE). --define(RESERVED_FOR_OTHERS, 100). - --define(FILE_HANDLES_LIMIT_OTHER, 1024). --define(FILE_HANDLES_CHECK_INTERVAL, 2000). - --define(OBTAIN_LIMIT(LIMIT), trunc((LIMIT * 0.9) - 2)). --define(CLIENT_ETS_TABLE, file_handle_cache_client). --define(ELDERS_ETS_TABLE, file_handle_cache_elders). - -%%---------------------------------------------------------------------------- - --record(file, - { reader_count, - has_writer - }). - --record(handle, - { hdl, - offset, - is_dirty, - write_buffer_size, - write_buffer_size_limit, - write_buffer, - read_buffer, - read_buffer_size, - read_buffer_size_limit, - at_eof, - path, - mode, - options, - is_write, - is_read, - last_used_at - }). - --record(fhc_state, - { elders, - limit, - open_count, - open_pending, - obtain_limit, %%socket - obtain_count_socket, - obtain_count_file, - obtain_pending_socket, - obtain_pending_file, - clients, - timer_ref, - alarm_set, - alarm_clear - }). - --record(cstate, - { pid, - callback, - opened, - obtained_socket, - obtained_file, - blocked, - pending_closes - }). - --record(pending, - { kind, - pid, - requested, - from - }). - -%%---------------------------------------------------------------------------- -%% Specs -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(ref() :: any()). --type(ok_or_error() :: 'ok' | {'error', any()}). --type(val_or_error(T) :: {'ok', T} | {'error', any()}). --type(position() :: ('bof' | 'eof' | non_neg_integer() | - {('bof' |'eof'), non_neg_integer()} | - {'cur', integer()})). --type(offset() :: non_neg_integer()). - --spec(register_callback/3 :: (atom(), atom(), [any()]) -> 'ok'). --spec(open/3 :: - (file:filename(), [any()], - [{'write_buffer', (non_neg_integer() | 'infinity' | 'unbuffered')} | - {'read_buffer', (non_neg_integer() | 'unbuffered')}]) - -> val_or_error(ref())). --spec(close/1 :: (ref()) -> ok_or_error()). --spec(read/2 :: (ref(), non_neg_integer()) -> - val_or_error([char()] | binary()) | 'eof'). --spec(append/2 :: (ref(), iodata()) -> ok_or_error()). --spec(sync/1 :: (ref()) -> ok_or_error()). --spec(position/2 :: (ref(), position()) -> val_or_error(offset())). --spec(truncate/1 :: (ref()) -> ok_or_error()). --spec(current_virtual_offset/1 :: (ref()) -> val_or_error(offset())). --spec(current_raw_offset/1 :: (ref()) -> val_or_error(offset())). --spec(flush/1 :: (ref()) -> ok_or_error()). --spec(copy/3 :: (ref(), ref(), non_neg_integer()) -> - val_or_error(non_neg_integer())). --spec(delete/1 :: (ref()) -> ok_or_error()). --spec(clear/1 :: (ref()) -> ok_or_error()). --spec(set_maximum_since_use/1 :: (non_neg_integer()) -> 'ok'). --spec(obtain/0 :: () -> 'ok'). --spec(obtain/1 :: (non_neg_integer()) -> 'ok'). --spec(release/0 :: () -> 'ok'). --spec(release/1 :: (non_neg_integer()) -> 'ok'). --spec(transfer/1 :: (pid()) -> 'ok'). --spec(transfer/2 :: (pid(), non_neg_integer()) -> 'ok'). --spec(with_handle/1 :: (fun(() -> A)) -> A). --spec(with_handle/2 :: (non_neg_integer(), fun(() -> A)) -> A). --spec(set_limit/1 :: (non_neg_integer()) -> 'ok'). --spec(get_limit/0 :: () -> non_neg_integer()). --spec(info_keys/0 :: () -> rabbit_types:info_keys()). --spec(info/0 :: () -> rabbit_types:infos()). --spec(info/1 :: ([atom()]) -> rabbit_types:infos()). --spec(ulimit/0 :: () -> 'unknown' | non_neg_integer()). - --endif. - -%%---------------------------------------------------------------------------- --define(INFO_KEYS, [total_limit, total_used, sockets_limit, sockets_used]). - -%%---------------------------------------------------------------------------- -%% Public API -%%---------------------------------------------------------------------------- - -start_link() -> - start_link(fun alarm_handler:set_alarm/1, fun alarm_handler:clear_alarm/1). - -start_link(AlarmSet, AlarmClear) -> - gen_server2:start_link({local, ?SERVER}, ?MODULE, [AlarmSet, AlarmClear], - [{timeout, infinity}]). - -register_callback(M, F, A) - when is_atom(M) andalso is_atom(F) andalso is_list(A) -> - gen_server2:cast(?SERVER, {register_callback, self(), {M, F, A}}). - -open(Path, Mode, Options) -> - Path1 = filename:absname(Path), - File1 = #file { reader_count = RCount, has_writer = HasWriter } = - case get({Path1, fhc_file}) of - File = #file {} -> File; - undefined -> #file { reader_count = 0, - has_writer = false } - end, - Mode1 = append_to_write(Mode), - IsWriter = is_writer(Mode1), - case IsWriter andalso HasWriter of - true -> {error, writer_exists}; - false -> {ok, Ref} = new_closed_handle(Path1, Mode1, Options), - case get_or_reopen([{Ref, new}]) of - {ok, [_Handle1]} -> - RCount1 = case is_reader(Mode1) of - true -> RCount + 1; - false -> RCount - end, - HasWriter1 = HasWriter orelse IsWriter, - put({Path1, fhc_file}, - File1 #file { reader_count = RCount1, - has_writer = HasWriter1 }), - {ok, Ref}; - Error -> - erase({Ref, fhc_handle}), - Error - end - end. - -close(Ref) -> - case erase({Ref, fhc_handle}) of - undefined -> ok; - Handle -> case hard_close(Handle) of - ok -> ok; - {Error, Handle1} -> put_handle(Ref, Handle1), - Error - end - end. - -read(Ref, Count) -> - with_flushed_handles( - [Ref], keep, - fun ([#handle { is_read = false }]) -> - {error, not_open_for_reading}; - ([Handle = #handle{read_buffer = Buf, - read_buffer_size = BufSz, - offset = Offset}]) when BufSz >= Count -> - <<Hd:Count/binary, Tl/binary>> = Buf, - {{ok, Hd}, [Handle#handle{offset = Offset + Count, - read_buffer = Tl, - read_buffer_size = BufSz - Count}]}; - ([Handle = #handle{read_buffer = Buf, - read_buffer_size = BufSz, - read_buffer_size_limit = BufSzLimit, - hdl = Hdl, - offset = Offset}]) -> - WantedCount = Count - BufSz, - case prim_file_read(Hdl, lists:max([BufSzLimit, WantedCount])) of - {ok, Data} -> - ReadCount = size(Data), - case ReadCount < WantedCount of - true -> - OffSet1 = Offset + BufSz + ReadCount, - {{ok, <<Buf/binary, Data/binary>>}, - [reset_read_buffer( - Handle#handle{offset = OffSet1})]}; - false -> - <<Hd:WantedCount/binary, Tl/binary>> = Data, - OffSet1 = Offset + BufSz + WantedCount, - BufSz1 = ReadCount - WantedCount, - {{ok, <<Buf/binary, Hd/binary>>}, - [Handle#handle{offset = OffSet1, - read_buffer = Tl, - read_buffer_size = BufSz1}]} - end; - eof -> - {eof, [Handle #handle { at_eof = true }]}; - Error -> - {Error, [reset_read_buffer(Handle)]} - end - end). - -append(Ref, Data) -> - with_handles( - [Ref], - fun ([#handle { is_write = false }]) -> - {error, not_open_for_writing}; - ([Handle]) -> - case maybe_seek(eof, Handle) of - {{ok, _Offset}, #handle { hdl = Hdl, offset = Offset, - write_buffer_size_limit = 0, - at_eof = true } = Handle1} -> - Offset1 = Offset + iolist_size(Data), - {prim_file_write(Hdl, Data), - [Handle1 #handle { is_dirty = true, offset = Offset1 }]}; - {{ok, _Offset}, #handle { write_buffer = WriteBuffer, - write_buffer_size = Size, - write_buffer_size_limit = Limit, - at_eof = true } = Handle1} -> - WriteBuffer1 = [Data | WriteBuffer], - Size1 = Size + iolist_size(Data), - Handle2 = Handle1 #handle { write_buffer = WriteBuffer1, - write_buffer_size = Size1 }, - case Limit =/= infinity andalso Size1 > Limit of - true -> {Result, Handle3} = write_buffer(Handle2), - {Result, [Handle3]}; - false -> {ok, [Handle2]} - end; - {{error, _} = Error, Handle1} -> - {Error, [Handle1]} - end - end). - -sync(Ref) -> - with_flushed_handles( - [Ref], keep, - fun ([#handle { is_dirty = false, write_buffer = [] }]) -> - ok; - ([Handle = #handle { hdl = Hdl, - is_dirty = true, write_buffer = [] }]) -> - case prim_file_sync(Hdl) of - ok -> {ok, [Handle #handle { is_dirty = false }]}; - Error -> {Error, [Handle]} - end - end). - -needs_sync(Ref) -> - %% This must *not* use with_handles/2; see bug 25052 - case get({Ref, fhc_handle}) of - #handle { is_dirty = false, write_buffer = [] } -> false; - #handle {} -> true - end. - -position(Ref, NewOffset) -> - with_flushed_handles( - [Ref], keep, - fun ([Handle]) -> {Result, Handle1} = maybe_seek(NewOffset, Handle), - {Result, [Handle1]} - end). - -truncate(Ref) -> - with_flushed_handles( - [Ref], - fun ([Handle1 = #handle { hdl = Hdl }]) -> - case prim_file:truncate(Hdl) of - ok -> {ok, [Handle1 #handle { at_eof = true }]}; - Error -> {Error, [Handle1]} - end - end). - -current_virtual_offset(Ref) -> - with_handles([Ref], fun ([#handle { at_eof = true, is_write = true, - offset = Offset, - write_buffer_size = Size }]) -> - {ok, Offset + Size}; - ([#handle { offset = Offset }]) -> - {ok, Offset} - end). - -current_raw_offset(Ref) -> - with_handles([Ref], fun ([Handle]) -> {ok, Handle #handle.offset} end). - -flush(Ref) -> - with_flushed_handles([Ref], fun ([Handle]) -> {ok, [Handle]} end). - -copy(Src, Dest, Count) -> - with_flushed_handles( - [Src, Dest], - fun ([SHandle = #handle { is_read = true, hdl = SHdl, offset = SOffset }, - DHandle = #handle { is_write = true, hdl = DHdl, offset = DOffset }] - ) -> - case prim_file:copy(SHdl, DHdl, Count) of - {ok, Count1} = Result1 -> - {Result1, - [SHandle #handle { offset = SOffset + Count1 }, - DHandle #handle { offset = DOffset + Count1, - is_dirty = true }]}; - Error -> - {Error, [SHandle, DHandle]} - end; - (_Handles) -> - {error, incorrect_handle_modes} - end). - -delete(Ref) -> - case erase({Ref, fhc_handle}) of - undefined -> - ok; - Handle = #handle { path = Path } -> - case hard_close(Handle #handle { is_dirty = false, - write_buffer = [] }) of - ok -> prim_file:delete(Path); - {Error, Handle1} -> put_handle(Ref, Handle1), - Error - end - end. - -clear(Ref) -> - with_handles( - [Ref], - fun ([#handle { at_eof = true, write_buffer_size = 0, offset = 0 }]) -> - ok; - ([Handle]) -> - case maybe_seek(bof, Handle#handle{write_buffer = [], - write_buffer_size = 0}) of - {{ok, 0}, Handle1 = #handle { hdl = Hdl }} -> - case prim_file:truncate(Hdl) of - ok -> {ok, [Handle1 #handle { at_eof = true }]}; - Error -> {Error, [Handle1]} - end; - {{error, _} = Error, Handle1} -> - {Error, [Handle1]} - end - end). - -set_maximum_since_use(MaximumAge) -> - Now = now(), - case lists:foldl( - fun ({{Ref, fhc_handle}, - Handle = #handle { hdl = Hdl, last_used_at = Then }}, Rep) -> - case Hdl =/= closed andalso - timer:now_diff(Now, Then) >= MaximumAge of - true -> soft_close(Ref, Handle) orelse Rep; - false -> Rep - end; - (_KeyValuePair, Rep) -> - Rep - end, false, get()) of - false -> age_tree_change(), ok; - true -> ok - end. - -obtain() -> obtain(1). -release() -> release(1). -transfer(Pid) -> transfer(Pid, 1). - -obtain(Count) -> obtain(Count, socket). -release(Count) -> release(Count, socket). - -with_handle(Fun) -> - with_handle(1, Fun). - -with_handle(N, Fun) -> - ok = obtain(N, file), - try Fun() - after ok = release(N, file) - end. - -obtain(Count, Type) when Count > 0 -> - %% If the FHC isn't running, obtains succeed immediately. - case whereis(?SERVER) of - undefined -> ok; - _ -> gen_server2:call( - ?SERVER, {obtain, Count, Type, self()}, infinity) - end. - -release(Count, Type) when Count > 0 -> - gen_server2:cast(?SERVER, {release, Count, Type, self()}). - -transfer(Pid, Count) when Count > 0 -> - gen_server2:cast(?SERVER, {transfer, Count, self(), Pid}). - -set_limit(Limit) -> - gen_server2:call(?SERVER, {set_limit, Limit}, infinity). - -get_limit() -> - gen_server2:call(?SERVER, get_limit, infinity). - -info_keys() -> ?INFO_KEYS. - -info() -> info(?INFO_KEYS). -info(Items) -> gen_server2:call(?SERVER, {info, Items}, infinity). - -%%---------------------------------------------------------------------------- -%% Internal functions -%%---------------------------------------------------------------------------- - -prim_file_read(Hdl, Size) -> - file_handle_cache_stats:update( - read, Size, fun() -> prim_file:read(Hdl, Size) end). - -prim_file_write(Hdl, Bytes) -> - file_handle_cache_stats:update( - write, iolist_size(Bytes), fun() -> prim_file:write(Hdl, Bytes) end). - -prim_file_sync(Hdl) -> - file_handle_cache_stats:update(sync, fun() -> prim_file:sync(Hdl) end). - -prim_file_position(Hdl, NewOffset) -> - file_handle_cache_stats:update( - seek, fun() -> prim_file:position(Hdl, NewOffset) end). - -is_reader(Mode) -> lists:member(read, Mode). - -is_writer(Mode) -> lists:member(write, Mode). - -append_to_write(Mode) -> - case lists:member(append, Mode) of - true -> [write | Mode -- [append, write]]; - false -> Mode - end. - -with_handles(Refs, Fun) -> - with_handles(Refs, reset, Fun). - -with_handles(Refs, ReadBuffer, Fun) -> - case get_or_reopen([{Ref, reopen} || Ref <- Refs]) of - {ok, Handles0} -> - Handles = case ReadBuffer of - reset -> [reset_read_buffer(H) || H <- Handles0]; - keep -> Handles0 - end, - case Fun(Handles) of - {Result, Handles1} when is_list(Handles1) -> - lists:zipwith(fun put_handle/2, Refs, Handles1), - Result; - Result -> - Result - end; - Error -> - Error - end. - -with_flushed_handles(Refs, Fun) -> - with_flushed_handles(Refs, reset, Fun). - -with_flushed_handles(Refs, ReadBuffer, Fun) -> - with_handles( - Refs, ReadBuffer, - fun (Handles) -> - case lists:foldl( - fun (Handle, {ok, HandlesAcc}) -> - {Res, Handle1} = write_buffer(Handle), - {Res, [Handle1 | HandlesAcc]}; - (Handle, {Error, HandlesAcc}) -> - {Error, [Handle | HandlesAcc]} - end, {ok, []}, Handles) of - {ok, Handles1} -> - Fun(lists:reverse(Handles1)); - {Error, Handles1} -> - {Error, lists:reverse(Handles1)} - end - end). - -get_or_reopen(RefNewOrReopens) -> - case partition_handles(RefNewOrReopens) of - {OpenHdls, []} -> - {ok, [Handle || {_Ref, Handle} <- OpenHdls]}; - {OpenHdls, ClosedHdls} -> - Oldest = oldest(get_age_tree(), fun () -> now() end), - case gen_server2:call(?SERVER, {open, self(), length(ClosedHdls), - Oldest}, infinity) of - ok -> - case reopen(ClosedHdls) of - {ok, RefHdls} -> sort_handles(RefNewOrReopens, - OpenHdls, RefHdls, []); - Error -> Error - end; - close -> - [soft_close(Ref, Handle) || - {{Ref, fhc_handle}, Handle = #handle { hdl = Hdl }} <- - get(), - Hdl =/= closed], - get_or_reopen(RefNewOrReopens) - end - end. - -reopen(ClosedHdls) -> reopen(ClosedHdls, get_age_tree(), []). - -reopen([], Tree, RefHdls) -> - put_age_tree(Tree), - {ok, lists:reverse(RefHdls)}; -reopen([{Ref, NewOrReopen, Handle = #handle { hdl = closed, - path = Path, - mode = Mode0, - offset = Offset, - last_used_at = undefined }} | - RefNewOrReopenHdls] = ToOpen, Tree, RefHdls) -> - Mode = case NewOrReopen of - new -> Mode0; - reopen -> file_handle_cache_stats:update(reopen), - [read | Mode0] - end, - case prim_file:open(Path, Mode) of - {ok, Hdl} -> - Now = now(), - {{ok, _Offset}, Handle1} = - maybe_seek(Offset, reset_read_buffer( - Handle#handle{hdl = Hdl, - offset = 0, - last_used_at = Now})), - put({Ref, fhc_handle}, Handle1), - reopen(RefNewOrReopenHdls, gb_trees:insert(Now, Ref, Tree), - [{Ref, Handle1} | RefHdls]); - Error -> - %% NB: none of the handles in ToOpen are in the age tree - Oldest = oldest(Tree, fun () -> undefined end), - [gen_server2:cast(?SERVER, {close, self(), Oldest}) || _ <- ToOpen], - put_age_tree(Tree), - Error - end. - -partition_handles(RefNewOrReopens) -> - lists:foldr( - fun ({Ref, NewOrReopen}, {Open, Closed}) -> - case get({Ref, fhc_handle}) of - #handle { hdl = closed } = Handle -> - {Open, [{Ref, NewOrReopen, Handle} | Closed]}; - #handle {} = Handle -> - {[{Ref, Handle} | Open], Closed} - end - end, {[], []}, RefNewOrReopens). - -sort_handles([], [], [], Acc) -> - {ok, lists:reverse(Acc)}; -sort_handles([{Ref, _} | RefHdls], [{Ref, Handle} | RefHdlsA], RefHdlsB, Acc) -> - sort_handles(RefHdls, RefHdlsA, RefHdlsB, [Handle | Acc]); -sort_handles([{Ref, _} | RefHdls], RefHdlsA, [{Ref, Handle} | RefHdlsB], Acc) -> - sort_handles(RefHdls, RefHdlsA, RefHdlsB, [Handle | Acc]). - -put_handle(Ref, Handle = #handle { last_used_at = Then }) -> - Now = now(), - age_tree_update(Then, Now, Ref), - put({Ref, fhc_handle}, Handle #handle { last_used_at = Now }). - -with_age_tree(Fun) -> put_age_tree(Fun(get_age_tree())). - -get_age_tree() -> - case get(fhc_age_tree) of - undefined -> gb_trees:empty(); - AgeTree -> AgeTree - end. - -put_age_tree(Tree) -> put(fhc_age_tree, Tree). - -age_tree_update(Then, Now, Ref) -> - with_age_tree( - fun (Tree) -> - gb_trees:insert(Now, Ref, gb_trees:delete_any(Then, Tree)) - end). - -age_tree_delete(Then) -> - with_age_tree( - fun (Tree) -> - Tree1 = gb_trees:delete_any(Then, Tree), - Oldest = oldest(Tree1, fun () -> undefined end), - gen_server2:cast(?SERVER, {close, self(), Oldest}), - Tree1 - end). - -age_tree_change() -> - with_age_tree( - fun (Tree) -> - case gb_trees:is_empty(Tree) of - true -> Tree; - false -> {Oldest, _Ref} = gb_trees:smallest(Tree), - gen_server2:cast(?SERVER, {update, self(), Oldest}) - end, - Tree - end). - -oldest(Tree, DefaultFun) -> - case gb_trees:is_empty(Tree) of - true -> DefaultFun(); - false -> {Oldest, _Ref} = gb_trees:smallest(Tree), - Oldest - end. - -new_closed_handle(Path, Mode, Options) -> - WriteBufferSize = - case proplists:get_value(write_buffer, Options, unbuffered) of - unbuffered -> 0; - infinity -> infinity; - N when is_integer(N) -> N - end, - ReadBufferSize = - case proplists:get_value(read_buffer, Options, unbuffered) of - unbuffered -> 0; - N2 when is_integer(N2) -> N2 - end, - Ref = make_ref(), - put({Ref, fhc_handle}, #handle { hdl = closed, - offset = 0, - is_dirty = false, - write_buffer_size = 0, - write_buffer_size_limit = WriteBufferSize, - write_buffer = [], - read_buffer_size = 0, - read_buffer_size_limit = ReadBufferSize, - read_buffer = <<>>, - at_eof = false, - path = Path, - mode = Mode, - options = Options, - is_write = is_writer(Mode), - is_read = is_reader(Mode), - last_used_at = undefined }), - {ok, Ref}. - -soft_close(Ref, Handle) -> - {Res, Handle1} = soft_close(Handle), - case Res of - ok -> put({Ref, fhc_handle}, Handle1), - true; - _ -> put_handle(Ref, Handle1), - false - end. - -soft_close(Handle = #handle { hdl = closed }) -> - {ok, Handle}; -soft_close(Handle) -> - case write_buffer(Handle) of - {ok, #handle { hdl = Hdl, - is_dirty = IsDirty, - last_used_at = Then } = Handle1 } -> - ok = case IsDirty of - true -> prim_file_sync(Hdl); - false -> ok - end, - ok = prim_file:close(Hdl), - age_tree_delete(Then), - {ok, Handle1 #handle { hdl = closed, - is_dirty = false, - last_used_at = undefined }}; - {_Error, _Handle} = Result -> - Result - end. - -hard_close(Handle) -> - case soft_close(Handle) of - {ok, #handle { path = Path, - is_read = IsReader, is_write = IsWriter }} -> - #file { reader_count = RCount, has_writer = HasWriter } = File = - get({Path, fhc_file}), - RCount1 = case IsReader of - true -> RCount - 1; - false -> RCount - end, - HasWriter1 = HasWriter andalso not IsWriter, - case RCount1 =:= 0 andalso not HasWriter1 of - true -> erase({Path, fhc_file}); - false -> put({Path, fhc_file}, - File #file { reader_count = RCount1, - has_writer = HasWriter1 }) - end, - ok; - {_Error, _Handle} = Result -> - Result - end. - -maybe_seek(NewOffset, Handle = #handle{hdl = Hdl, - offset = Offset, - read_buffer = Buf, - read_buffer_size = BufSz, - at_eof = AtEoF}) -> - {AtEoF1, NeedsSeek} = needs_seek(AtEoF, Offset, NewOffset), - case NeedsSeek of - true when is_number(NewOffset) andalso - NewOffset >= Offset andalso NewOffset =< BufSz + Offset -> - Diff = NewOffset - Offset, - <<_:Diff/binary, Rest/binary>> = Buf, - {{ok, NewOffset}, Handle#handle{offset = NewOffset, - at_eof = AtEoF1, - read_buffer = Rest, - read_buffer_size = BufSz - Diff}}; - true -> - case prim_file_position(Hdl, NewOffset) of - {ok, Offset1} = Result -> - {Result, reset_read_buffer(Handle#handle{offset = Offset1, - at_eof = AtEoF1})}; - {error, _} = Error -> - {Error, Handle} - end; - false -> - {{ok, Offset}, Handle} - end. - -needs_seek( AtEoF, _CurOffset, cur ) -> {AtEoF, false}; -needs_seek( AtEoF, _CurOffset, {cur, 0}) -> {AtEoF, false}; -needs_seek( true, _CurOffset, eof ) -> {true , false}; -needs_seek( true, _CurOffset, {eof, 0}) -> {true , false}; -needs_seek( false, _CurOffset, eof ) -> {true , true }; -needs_seek( false, _CurOffset, {eof, 0}) -> {true , true }; -needs_seek( AtEoF, 0, bof ) -> {AtEoF, false}; -needs_seek( AtEoF, 0, {bof, 0}) -> {AtEoF, false}; -needs_seek( AtEoF, CurOffset, CurOffset) -> {AtEoF, false}; -needs_seek( true, CurOffset, {bof, DesiredOffset}) - when DesiredOffset >= CurOffset -> - {true, true}; -needs_seek( true, _CurOffset, {cur, DesiredOffset}) - when DesiredOffset > 0 -> - {true, true}; -needs_seek( true, CurOffset, DesiredOffset) %% same as {bof, DO} - when is_integer(DesiredOffset) andalso DesiredOffset >= CurOffset -> - {true, true}; -%% because we can't really track size, we could well end up at EoF and not know -needs_seek(_AtEoF, _CurOffset, _DesiredOffset) -> - {false, true}. - -write_buffer(Handle = #handle { write_buffer = [] }) -> - {ok, Handle}; -write_buffer(Handle = #handle { hdl = Hdl, offset = Offset, - write_buffer = WriteBuffer, - write_buffer_size = DataSize, - at_eof = true }) -> - case prim_file_write(Hdl, lists:reverse(WriteBuffer)) of - ok -> - Offset1 = Offset + DataSize, - {ok, Handle #handle { offset = Offset1, is_dirty = true, - write_buffer = [], write_buffer_size = 0 }}; - {error, _} = Error -> - {Error, Handle} - end. - -reset_read_buffer(Handle) -> - Handle#handle{read_buffer = <<>>, - read_buffer_size = 0}. - -infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. - -i(total_limit, #fhc_state{limit = Limit}) -> Limit; -i(total_used, State) -> used(State); -i(sockets_limit, #fhc_state{obtain_limit = Limit}) -> Limit; -i(sockets_used, #fhc_state{obtain_count_socket = Count}) -> Count; -i(Item, _) -> throw({bad_argument, Item}). - -used(#fhc_state{open_count = C1, - obtain_count_socket = C2, - obtain_count_file = C3}) -> C1 + C2 + C3. - -%%---------------------------------------------------------------------------- -%% gen_server2 callbacks -%%---------------------------------------------------------------------------- - -init([AlarmSet, AlarmClear]) -> - file_handle_cache_stats:init(), - Limit = case application:get_env(file_handles_high_watermark) of - {ok, Watermark} when (is_integer(Watermark) andalso - Watermark > 0) -> - Watermark; - _ -> - case ulimit() of - unknown -> ?FILE_HANDLES_LIMIT_OTHER; - Lim -> lists:max([2, Lim - ?RESERVED_FOR_OTHERS]) - end - end, - ObtainLimit = obtain_limit(Limit), - error_logger:info_msg("Limiting to approx ~p file handles (~p sockets)~n", - [Limit, ObtainLimit]), - Clients = ets:new(?CLIENT_ETS_TABLE, [set, private, {keypos, #cstate.pid}]), - Elders = ets:new(?ELDERS_ETS_TABLE, [set, private]), - {ok, #fhc_state { elders = Elders, - limit = Limit, - open_count = 0, - open_pending = pending_new(), - obtain_limit = ObtainLimit, - obtain_count_file = 0, - obtain_pending_file = pending_new(), - obtain_count_socket = 0, - obtain_pending_socket = pending_new(), - clients = Clients, - timer_ref = undefined, - alarm_set = AlarmSet, - alarm_clear = AlarmClear }}. - -prioritise_cast(Msg, _Len, _State) -> - case Msg of - {release, _, _, _} -> 5; - _ -> 0 - end. - -handle_call({open, Pid, Requested, EldestUnusedSince}, From, - State = #fhc_state { open_count = Count, - open_pending = Pending, - elders = Elders, - clients = Clients }) - when EldestUnusedSince =/= undefined -> - true = ets:insert(Elders, {Pid, EldestUnusedSince}), - Item = #pending { kind = open, - pid = Pid, - requested = Requested, - from = From }, - ok = track_client(Pid, Clients), - case needs_reduce(State #fhc_state { open_count = Count + Requested }) of - true -> case ets:lookup(Clients, Pid) of - [#cstate { opened = 0 }] -> - true = ets:update_element( - Clients, Pid, {#cstate.blocked, true}), - {noreply, - reduce(State #fhc_state { - open_pending = pending_in(Item, Pending) })}; - [#cstate { opened = Opened }] -> - true = ets:update_element( - Clients, Pid, - {#cstate.pending_closes, Opened}), - {reply, close, State} - end; - false -> {noreply, run_pending_item(Item, State)} - end; - -handle_call({obtain, N, Type, Pid}, From, - State = #fhc_state { clients = Clients }) -> - Count = obtain_state(Type, count, State), - Pending = obtain_state(Type, pending, State), - ok = track_client(Pid, Clients), - Item = #pending { kind = {obtain, Type}, pid = Pid, - requested = N, from = From }, - Enqueue = fun () -> - true = ets:update_element(Clients, Pid, - {#cstate.blocked, true}), - set_obtain_state(Type, pending, - pending_in(Item, Pending), State) - end, - {noreply, - case obtain_limit_reached(Type, State) of - true -> Enqueue(); - false -> case needs_reduce( - set_obtain_state(Type, count, Count + 1, State)) of - true -> reduce(Enqueue()); - false -> adjust_alarm( - State, run_pending_item(Item, State)) - end - end}; - -handle_call({set_limit, Limit}, _From, State) -> - {reply, ok, adjust_alarm( - State, maybe_reduce( - process_pending( - State #fhc_state { - limit = Limit, - obtain_limit = obtain_limit(Limit) })))}; - -handle_call(get_limit, _From, State = #fhc_state { limit = Limit }) -> - {reply, Limit, State}; - -handle_call({info, Items}, _From, State) -> - {reply, infos(Items, State), State}. - -handle_cast({register_callback, Pid, MFA}, - State = #fhc_state { clients = Clients }) -> - ok = track_client(Pid, Clients), - true = ets:update_element(Clients, Pid, {#cstate.callback, MFA}), - {noreply, State}; - -handle_cast({update, Pid, EldestUnusedSince}, - State = #fhc_state { elders = Elders }) - when EldestUnusedSince =/= undefined -> - true = ets:insert(Elders, {Pid, EldestUnusedSince}), - %% don't call maybe_reduce from here otherwise we can create a - %% storm of messages - {noreply, State}; - -handle_cast({release, N, Type, Pid}, State) -> - State1 = process_pending(update_counts({obtain, Type}, Pid, -N, State)), - {noreply, adjust_alarm(State, State1)}; - -handle_cast({close, Pid, EldestUnusedSince}, - State = #fhc_state { elders = Elders, clients = Clients }) -> - true = case EldestUnusedSince of - undefined -> ets:delete(Elders, Pid); - _ -> ets:insert(Elders, {Pid, EldestUnusedSince}) - end, - ets:update_counter(Clients, Pid, {#cstate.pending_closes, -1, 0, 0}), - {noreply, adjust_alarm(State, process_pending( - update_counts(open, Pid, -1, State)))}; - -handle_cast({transfer, N, FromPid, ToPid}, State) -> - ok = track_client(ToPid, State#fhc_state.clients), - {noreply, process_pending( - update_counts({obtain, socket}, ToPid, +N, - update_counts({obtain, socket}, FromPid, -N, - State)))}. - -handle_info(check_counts, State) -> - {noreply, maybe_reduce(State #fhc_state { timer_ref = undefined })}; - -handle_info({'DOWN', _MRef, process, Pid, _Reason}, - State = #fhc_state { elders = Elders, - open_count = OpenCount, - open_pending = OpenPending, - obtain_count_file = ObtainCountF, - obtain_count_socket = ObtainCountS, - obtain_pending_file = ObtainPendingF, - obtain_pending_socket = ObtainPendingS, - clients = Clients }) -> - [#cstate { opened = Opened, - obtained_file = ObtainedFile, - obtained_socket = ObtainedSocket}] = - ets:lookup(Clients, Pid), - true = ets:delete(Clients, Pid), - true = ets:delete(Elders, Pid), - Fun = fun (#pending { pid = Pid1 }) -> Pid1 =/= Pid end, - State1 = process_pending( - State #fhc_state { - open_count = OpenCount - Opened, - open_pending = filter_pending(Fun, OpenPending), - obtain_count_file = ObtainCountF - ObtainedFile, - obtain_count_socket = ObtainCountS - ObtainedSocket, - obtain_pending_file = filter_pending(Fun, ObtainPendingF), - obtain_pending_socket = filter_pending(Fun, ObtainPendingS) }), - {noreply, adjust_alarm(State, State1)}. - -terminate(_Reason, State = #fhc_state { clients = Clients, - elders = Elders }) -> - ets:delete(Clients), - ets:delete(Elders), - State. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%---------------------------------------------------------------------------- -%% pending queue abstraction helpers -%%---------------------------------------------------------------------------- - -queue_fold(Fun, Init, Q) -> - case queue:out(Q) of - {empty, _Q} -> Init; - {{value, V}, Q1} -> queue_fold(Fun, Fun(V, Init), Q1) - end. - -filter_pending(Fun, {Count, Queue}) -> - {Delta, Queue1} = - queue_fold( - fun (Item = #pending { requested = Requested }, {DeltaN, QueueN}) -> - case Fun(Item) of - true -> {DeltaN, queue:in(Item, QueueN)}; - false -> {DeltaN - Requested, QueueN} - end - end, {0, queue:new()}, Queue), - {Count + Delta, Queue1}. - -pending_new() -> - {0, queue:new()}. - -pending_in(Item = #pending { requested = Requested }, {Count, Queue}) -> - {Count + Requested, queue:in(Item, Queue)}. - -pending_out({0, _Queue} = Pending) -> - {empty, Pending}; -pending_out({N, Queue}) -> - {{value, #pending { requested = Requested }} = Result, Queue1} = - queue:out(Queue), - {Result, {N - Requested, Queue1}}. - -pending_count({Count, _Queue}) -> - Count. - -pending_is_empty({0, _Queue}) -> - true; -pending_is_empty({_N, _Queue}) -> - false. - -%%---------------------------------------------------------------------------- -%% server helpers -%%---------------------------------------------------------------------------- - -obtain_limit(infinity) -> infinity; -obtain_limit(Limit) -> case ?OBTAIN_LIMIT(Limit) of - OLimit when OLimit < 0 -> 0; - OLimit -> OLimit - end. - -obtain_limit_reached(socket, State) -> obtain_limit_reached(State); -obtain_limit_reached(file, State) -> needs_reduce(State). - -obtain_limit_reached(#fhc_state{obtain_limit = Limit, - obtain_count_socket = Count}) -> - Limit =/= infinity andalso Count >= Limit. - -obtain_state(file, count, #fhc_state{obtain_count_file = N}) -> N; -obtain_state(socket, count, #fhc_state{obtain_count_socket = N}) -> N; -obtain_state(file, pending, #fhc_state{obtain_pending_file = N}) -> N; -obtain_state(socket, pending, #fhc_state{obtain_pending_socket = N}) -> N. - -set_obtain_state(file, count, N, S) -> S#fhc_state{obtain_count_file = N}; -set_obtain_state(socket, count, N, S) -> S#fhc_state{obtain_count_socket = N}; -set_obtain_state(file, pending, N, S) -> S#fhc_state{obtain_pending_file = N}; -set_obtain_state(socket, pending, N, S) -> S#fhc_state{obtain_pending_socket = N}. - -adjust_alarm(OldState = #fhc_state { alarm_set = AlarmSet, - alarm_clear = AlarmClear }, NewState) -> - case {obtain_limit_reached(OldState), obtain_limit_reached(NewState)} of - {false, true} -> AlarmSet({file_descriptor_limit, []}); - {true, false} -> AlarmClear(file_descriptor_limit); - _ -> ok - end, - NewState. - -process_pending(State = #fhc_state { limit = infinity }) -> - State; -process_pending(State) -> - process_open(process_obtain(socket, process_obtain(file, State))). - -process_open(State = #fhc_state { limit = Limit, - open_pending = Pending}) -> - {Pending1, State1} = process_pending(Pending, Limit - used(State), State), - State1 #fhc_state { open_pending = Pending1 }. - -process_obtain(Type, State = #fhc_state { limit = Limit, - obtain_limit = ObtainLimit }) -> - ObtainCount = obtain_state(Type, count, State), - Pending = obtain_state(Type, pending, State), - Quota = case Type of - file -> Limit - (used(State)); - socket -> lists:min([ObtainLimit - ObtainCount, - Limit - (used(State))]) - end, - {Pending1, State1} = process_pending(Pending, Quota, State), - set_obtain_state(Type, pending, Pending1, State1). - -process_pending(Pending, Quota, State) when Quota =< 0 -> - {Pending, State}; -process_pending(Pending, Quota, State) -> - case pending_out(Pending) of - {empty, _Pending} -> - {Pending, State}; - {{value, #pending { requested = Requested }}, _Pending1} - when Requested > Quota -> - {Pending, State}; - {{value, #pending { requested = Requested } = Item}, Pending1} -> - process_pending(Pending1, Quota - Requested, - run_pending_item(Item, State)) - end. - -run_pending_item(#pending { kind = Kind, - pid = Pid, - requested = Requested, - from = From }, - State = #fhc_state { clients = Clients }) -> - gen_server2:reply(From, ok), - true = ets:update_element(Clients, Pid, {#cstate.blocked, false}), - update_counts(Kind, Pid, Requested, State). - -update_counts(Kind, Pid, Delta, - State = #fhc_state { open_count = OpenCount, - obtain_count_file = ObtainCountF, - obtain_count_socket = ObtainCountS, - clients = Clients }) -> - {OpenDelta, ObtainDeltaF, ObtainDeltaS} = - update_counts1(Kind, Pid, Delta, Clients), - State #fhc_state { open_count = OpenCount + OpenDelta, - obtain_count_file = ObtainCountF + ObtainDeltaF, - obtain_count_socket = ObtainCountS + ObtainDeltaS }. - -update_counts1(open, Pid, Delta, Clients) -> - ets:update_counter(Clients, Pid, {#cstate.opened, Delta}), - {Delta, 0, 0}; -update_counts1({obtain, file}, Pid, Delta, Clients) -> - ets:update_counter(Clients, Pid, {#cstate.obtained_file, Delta}), - {0, Delta, 0}; -update_counts1({obtain, socket}, Pid, Delta, Clients) -> - ets:update_counter(Clients, Pid, {#cstate.obtained_socket, Delta}), - {0, 0, Delta}. - -maybe_reduce(State) -> - case needs_reduce(State) of - true -> reduce(State); - false -> State - end. - -needs_reduce(State = #fhc_state { limit = Limit, - open_pending = OpenPending, - obtain_limit = ObtainLimit, - obtain_count_socket = ObtainCountS, - obtain_pending_file = ObtainPendingF, - obtain_pending_socket = ObtainPendingS }) -> - Limit =/= infinity - andalso ((used(State) > Limit) - orelse (not pending_is_empty(OpenPending)) - orelse (not pending_is_empty(ObtainPendingF)) - orelse (ObtainCountS < ObtainLimit - andalso not pending_is_empty(ObtainPendingS))). - -reduce(State = #fhc_state { open_pending = OpenPending, - obtain_pending_file = ObtainPendingFile, - obtain_pending_socket = ObtainPendingSocket, - elders = Elders, - clients = Clients, - timer_ref = TRef }) -> - Now = now(), - {CStates, Sum, ClientCount} = - ets:foldl(fun ({Pid, Eldest}, {CStatesAcc, SumAcc, CountAcc} = Accs) -> - [#cstate { pending_closes = PendingCloses, - opened = Opened, - blocked = Blocked } = CState] = - ets:lookup(Clients, Pid), - case Blocked orelse PendingCloses =:= Opened of - true -> Accs; - false -> {[CState | CStatesAcc], - SumAcc + timer:now_diff(Now, Eldest), - CountAcc + 1} - end - end, {[], 0, 0}, Elders), - case CStates of - [] -> ok; - _ -> case (Sum / ClientCount) - - (1000 * ?FILE_HANDLES_CHECK_INTERVAL) of - AverageAge when AverageAge > 0 -> - notify_age(CStates, AverageAge); - _ -> - notify_age0(Clients, CStates, - pending_count(OpenPending) + - pending_count(ObtainPendingFile) + - pending_count(ObtainPendingSocket)) - end - end, - case TRef of - undefined -> TRef1 = erlang:send_after( - ?FILE_HANDLES_CHECK_INTERVAL, ?SERVER, - check_counts), - State #fhc_state { timer_ref = TRef1 }; - _ -> State - end. - -notify_age(CStates, AverageAge) -> - lists:foreach( - fun (#cstate { callback = undefined }) -> ok; - (#cstate { callback = {M, F, A} }) -> apply(M, F, A ++ [AverageAge]) - end, CStates). - -notify_age0(Clients, CStates, Required) -> - case [CState || CState <- CStates, CState#cstate.callback =/= undefined] of - [] -> ok; - Notifications -> S = random:uniform(length(Notifications)), - {L1, L2} = lists:split(S, Notifications), - notify(Clients, Required, L2 ++ L1) - end. - -notify(_Clients, _Required, []) -> - ok; -notify(_Clients, Required, _Notifications) when Required =< 0 -> - ok; -notify(Clients, Required, [#cstate{ pid = Pid, - callback = {M, F, A}, - opened = Opened } | Notifications]) -> - apply(M, F, A ++ [0]), - ets:update_element(Clients, Pid, {#cstate.pending_closes, Opened}), - notify(Clients, Required - Opened, Notifications). - -track_client(Pid, Clients) -> - case ets:insert_new(Clients, #cstate { pid = Pid, - callback = undefined, - opened = 0, - obtained_file = 0, - obtained_socket = 0, - blocked = false, - pending_closes = 0 }) of - true -> _MRef = erlang:monitor(process, Pid), - ok; - false -> ok - end. - - -%% To increase the number of file descriptors: on Windows set ERL_MAX_PORTS -%% environment variable, on Linux set `ulimit -n`. -ulimit() -> - case proplists:get_value(max_fds, erlang:system_info(check_io)) of - MaxFds when is_integer(MaxFds) andalso MaxFds > 1 -> - case os:type() of - {win32, _OsName} -> - %% On Windows max_fds is twice the number of open files: - %% https://github.com/yrashk/erlang/blob/e1282325ed75e52a98d5/erts/emulator/sys/win32/sys.c#L2459-2466 - MaxFds div 2; - _Any -> - %% For other operating systems trust Erlang. - MaxFds - end; - _ -> - unknown - end. diff --git a/src/file_handle_cache_stats.erl b/src/file_handle_cache_stats.erl deleted file mode 100644 index 832f0b3d..00000000 --- a/src/file_handle_cache_stats.erl +++ /dev/null @@ -1,60 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(file_handle_cache_stats). - -%% stats about read / write operations that go through the fhc. - --export([init/0, update/3, update/2, update/1, get/0]). - --define(TABLE, ?MODULE). - -init() -> - ets:new(?TABLE, [public, named_table]), - [ets:insert(?TABLE, {{Op, Counter}, 0}) || Op <- [read, write], - Counter <- [count, bytes, time]], - [ets:insert(?TABLE, {{Op, Counter}, 0}) || Op <- [sync, seek], - Counter <- [count, time]], - [ets:insert(?TABLE, {{Op, Counter}, 0}) || Op <- [reopen], - Counter <- [count]]. - -update(Op, Bytes, Thunk) -> - {Time, Res} = timer_tc(Thunk), - ets:update_counter(?TABLE, {Op, count}, 1), - ets:update_counter(?TABLE, {Op, bytes}, Bytes), - ets:update_counter(?TABLE, {Op, time}, Time), - Res. - -update(Op, Thunk) -> - {Time, Res} = timer_tc(Thunk), - ets:update_counter(?TABLE, {Op, count}, 1), - ets:update_counter(?TABLE, {Op, time}, Time), - Res. - -update(Op) -> - ets:update_counter(?TABLE, {Op, count}, 1), - ok. - -get() -> - lists:sort(ets:tab2list(?TABLE)). - -%% TODO timer:tc/1 was introduced in R14B03; use that function once we -%% require that version. -timer_tc(Thunk) -> - T1 = os:timestamp(), - Res = Thunk(), - T2 = os:timestamp(), - {timer:now_diff(T2, T1), Res}. diff --git a/src/gatherer.erl b/src/gatherer.erl deleted file mode 100644 index 8bce1707..00000000 --- a/src/gatherer.erl +++ /dev/null @@ -1,145 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(gatherer). - --behaviour(gen_server2). - --export([start_link/0, stop/1, fork/1, finish/1, in/2, sync_in/2, out/1]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(stop/1 :: (pid()) -> 'ok'). --spec(fork/1 :: (pid()) -> 'ok'). --spec(finish/1 :: (pid()) -> 'ok'). --spec(in/2 :: (pid(), any()) -> 'ok'). --spec(sync_in/2 :: (pid(), any()) -> 'ok'). --spec(out/1 :: (pid()) -> {'value', any()} | 'empty'). - --endif. - -%%---------------------------------------------------------------------------- - --define(HIBERNATE_AFTER_MIN, 1000). --define(DESIRED_HIBERNATE, 10000). - -%%---------------------------------------------------------------------------- - --record(gstate, { forks, values, blocked }). - -%%---------------------------------------------------------------------------- - -start_link() -> - gen_server2:start_link(?MODULE, [], [{timeout, infinity}]). - -stop(Pid) -> - gen_server2:call(Pid, stop, infinity). - -fork(Pid) -> - gen_server2:call(Pid, fork, infinity). - -finish(Pid) -> - gen_server2:cast(Pid, finish). - -in(Pid, Value) -> - gen_server2:cast(Pid, {in, Value}). - -sync_in(Pid, Value) -> - gen_server2:call(Pid, {in, Value}, infinity). - -out(Pid) -> - gen_server2:call(Pid, out, infinity). - -%%---------------------------------------------------------------------------- - -init([]) -> - {ok, #gstate { forks = 0, values = queue:new(), blocked = queue:new() }, - hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. - -handle_call(stop, _From, State) -> - {stop, normal, ok, State}; - -handle_call(fork, _From, State = #gstate { forks = Forks }) -> - {reply, ok, State #gstate { forks = Forks + 1 }, hibernate}; - -handle_call({in, Value}, From, State) -> - {noreply, in(Value, From, State), hibernate}; - -handle_call(out, From, State = #gstate { forks = Forks, - values = Values, - blocked = Blocked }) -> - case queue:out(Values) of - {empty, _} when Forks == 0 -> - {reply, empty, State, hibernate}; - {empty, _} -> - {noreply, State #gstate { blocked = queue:in(From, Blocked) }, - hibernate}; - {{value, {PendingIn, Value}}, NewValues} -> - reply(PendingIn, ok), - {reply, {value, Value}, State #gstate { values = NewValues }, - hibernate} - end; - -handle_call(Msg, _From, State) -> - {stop, {unexpected_call, Msg}, State}. - -handle_cast(finish, State = #gstate { forks = Forks, blocked = Blocked }) -> - NewForks = Forks - 1, - NewBlocked = case NewForks of - 0 -> [gen_server2:reply(From, empty) || - From <- queue:to_list(Blocked)], - queue:new(); - _ -> Blocked - end, - {noreply, State #gstate { forks = NewForks, blocked = NewBlocked }, - hibernate}; - -handle_cast({in, Value}, State) -> - {noreply, in(Value, undefined, State), hibernate}; - -handle_cast(Msg, State) -> - {stop, {unexpected_cast, Msg}, State}. - -handle_info(Msg, State) -> - {stop, {unexpected_info, Msg}, State}. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -terminate(_Reason, State) -> - State. - -%%---------------------------------------------------------------------------- - -in(Value, From, State = #gstate { values = Values, blocked = Blocked }) -> - case queue:out(Blocked) of - {empty, _} -> - State #gstate { values = queue:in({From, Value}, Values) }; - {{value, PendingOut}, NewBlocked} -> - reply(From, ok), - gen_server2:reply(PendingOut, {value, Value}), - State #gstate { blocked = NewBlocked } - end. - -reply(undefined, _Reply) -> ok; -reply(From, Reply) -> gen_server2:reply(From, Reply). diff --git a/src/gen_server2.erl b/src/gen_server2.erl deleted file mode 100644 index d2f96b52..00000000 --- a/src/gen_server2.erl +++ /dev/null @@ -1,1349 +0,0 @@ -%% This file is a copy of gen_server.erl from the R13B-1 Erlang/OTP -%% distribution, with the following modifications: -%% -%% 1) the module name is gen_server2 -%% -%% 2) more efficient handling of selective receives in callbacks -%% gen_server2 processes drain their message queue into an internal -%% buffer before invoking any callback module functions. Messages are -%% dequeued from the buffer for processing. Thus the effective message -%% queue of a gen_server2 process is the concatenation of the internal -%% buffer and the real message queue. -%% As a result of the draining, any selective receive invoked inside a -%% callback is less likely to have to scan a large message queue. -%% -%% 3) gen_server2:cast is guaranteed to be order-preserving -%% The original code could reorder messages when communicating with a -%% process on a remote node that was not currently connected. -%% -%% 4) The callback module can optionally implement prioritise_call/4, -%% prioritise_cast/3 and prioritise_info/3. These functions take -%% Message, From, Length and State or just Message, Length and State -%% (where Length is the current number of messages waiting to be -%% processed) and return a single integer representing the priority -%% attached to the message, or 'drop' to ignore it (for -%% prioritise_cast/3 and prioritise_info/3 only). Messages with -%% higher priorities are processed before requests with lower -%% priorities. The default priority is 0. -%% -%% 5) The callback module can optionally implement -%% handle_pre_hibernate/1 and handle_post_hibernate/1. These will be -%% called immediately prior to and post hibernation, respectively. If -%% handle_pre_hibernate returns {hibernate, NewState} then the process -%% will hibernate. If the module does not implement -%% handle_pre_hibernate/1 then the default action is to hibernate. -%% -%% 6) init can return a 4th arg, {backoff, InitialTimeout, -%% MinimumTimeout, DesiredHibernatePeriod} (all in milliseconds, -%% 'infinity' does not make sense here). Then, on all callbacks which -%% can return a timeout (including init), timeout can be -%% 'hibernate'. When this is the case, the current timeout value will -%% be used (initially, the InitialTimeout supplied from init). After -%% this timeout has occurred, hibernation will occur as normal. Upon -%% awaking, a new current timeout value will be calculated. -%% -%% The purpose is that the gen_server2 takes care of adjusting the -%% current timeout value such that the process will increase the -%% timeout value repeatedly if it is unable to sleep for the -%% DesiredHibernatePeriod. If it is able to sleep for the -%% DesiredHibernatePeriod it will decrease the current timeout down to -%% the MinimumTimeout, so that the process is put to sleep sooner (and -%% hopefully stays asleep for longer). In short, should a process -%% using this receive a burst of messages, it should not hibernate -%% between those messages, but as the messages become less frequent, -%% the process will not only hibernate, it will do so sooner after -%% each message. -%% -%% When using this backoff mechanism, normal timeout values (i.e. not -%% 'hibernate') can still be used, and if they are used then the -%% handle_info(timeout, State) will be called as normal. In this case, -%% returning 'hibernate' from handle_info(timeout, State) will not -%% hibernate the process immediately, as it would if backoff wasn't -%% being used. Instead it'll wait for the current timeout as described -%% above. -%% -%% 7) The callback module can return from any of the handle_* -%% functions, a {become, Module, State} triple, or a {become, Module, -%% State, Timeout} quadruple. This allows the gen_server to -%% dynamically change the callback module. The State is the new state -%% which will be passed into any of the callback functions in the new -%% module. Note there is no form also encompassing a reply, thus if -%% you wish to reply in handle_call/3 and change the callback module, -%% you need to use gen_server2:reply/2 to issue the reply -%% manually. The init function can similarly return a 5th argument, -%% Module, in order to dynamically decide the callback module on init. -%% -%% 8) The callback module can optionally implement -%% format_message_queue/2 which is the equivalent of format_status/2 -%% but where the second argument is specifically the priority_queue -%% which contains the prioritised message_queue. -%% -%% 9) The function with_state/2 can be used to debug a process with -%% heavyweight state (without needing to copy the entire state out of -%% process as sys:get_status/1 would). Pass through a function which -%% can be invoked on the state, get back the result. The state is not -%% modified. -%% -%% 10) an mcall/1 function has been added for performing multiple -%% call/3 in parallel. Unlike multi_call, which sends the same request -%% to same-named processes residing on a supplied list of nodes, it -%% operates on name/request pairs, where name is anything accepted by -%% call/3, i.e. a pid, global name, local name, or local name on a -%% particular node. -%% - -%% All modifications are (C) 2009-2013 GoPivotal, Inc. - -%% ``The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved via the world wide web at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. -%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings -%% AB. All Rights Reserved.'' -%% -%% $Id$ -%% --module(gen_server2). - -%%% --------------------------------------------------- -%%% -%%% The idea behind THIS server is that the user module -%%% provides (different) functions to handle different -%%% kind of inputs. -%%% If the Parent process terminates the Module:terminate/2 -%%% function is called. -%%% -%%% The user module should export: -%%% -%%% init(Args) -%%% ==> {ok, State} -%%% {ok, State, Timeout} -%%% {ok, State, Timeout, Backoff} -%%% {ok, State, Timeout, Backoff, Module} -%%% ignore -%%% {stop, Reason} -%%% -%%% handle_call(Msg, {From, Tag}, State) -%%% -%%% ==> {reply, Reply, State} -%%% {reply, Reply, State, Timeout} -%%% {noreply, State} -%%% {noreply, State, Timeout} -%%% {stop, Reason, Reply, State} -%%% Reason = normal | shutdown | Term terminate(State) is called -%%% -%%% handle_cast(Msg, State) -%%% -%%% ==> {noreply, State} -%%% {noreply, State, Timeout} -%%% {stop, Reason, State} -%%% Reason = normal | shutdown | Term terminate(State) is called -%%% -%%% handle_info(Info, State) Info is e.g. {'EXIT', P, R}, {nodedown, N}, ... -%%% -%%% ==> {noreply, State} -%%% {noreply, State, Timeout} -%%% {stop, Reason, State} -%%% Reason = normal | shutdown | Term, terminate(State) is called -%%% -%%% terminate(Reason, State) Let the user module clean up -%%% Reason = normal | shutdown | {shutdown, Term} | Term -%%% always called when server terminates -%%% -%%% ==> ok | Term -%%% -%%% handle_pre_hibernate(State) -%%% -%%% ==> {hibernate, State} -%%% {stop, Reason, State} -%%% Reason = normal | shutdown | Term, terminate(State) is called -%%% -%%% handle_post_hibernate(State) -%%% -%%% ==> {noreply, State} -%%% {stop, Reason, State} -%%% Reason = normal | shutdown | Term, terminate(State) is called -%%% -%%% The work flow (of the server) can be described as follows: -%%% -%%% User module Generic -%%% ----------- ------- -%%% start -----> start -%%% init <----- . -%%% -%%% loop -%%% handle_call <----- . -%%% -----> reply -%%% -%%% handle_cast <----- . -%%% -%%% handle_info <----- . -%%% -%%% terminate <----- . -%%% -%%% -----> reply -%%% -%%% -%%% --------------------------------------------------- - -%% API --export([start/3, start/4, - start_link/3, start_link/4, - call/2, call/3, - cast/2, reply/2, - abcast/2, abcast/3, - multi_call/2, multi_call/3, multi_call/4, - mcall/1, - with_state/2, - enter_loop/3, enter_loop/4, enter_loop/5, enter_loop/6, wake_hib/1]). - -%% System exports --export([system_continue/3, - system_terminate/4, - system_code_change/4, - format_status/2]). - -%% Internal exports --export([init_it/6]). - --import(error_logger, [format/2]). - -%% State record --record(gs2_state, {parent, name, state, mod, time, - timeout_state, queue, debug, prioritisers}). - --ifdef(use_specs). - -%%%========================================================================= -%%% Specs. These exist only to shut up dialyzer's warnings -%%%========================================================================= - --type(gs2_state() :: #gs2_state{}). - --spec(handle_common_termination/3 :: - (any(), atom(), gs2_state()) -> no_return()). --spec(hibernate/1 :: (gs2_state()) -> no_return()). --spec(pre_hibernate/1 :: (gs2_state()) -> no_return()). --spec(system_terminate/4 :: (_, _, _, gs2_state()) -> no_return()). - --type(millis() :: non_neg_integer()). - -%%%========================================================================= -%%% API -%%%========================================================================= - --callback init(Args :: term()) -> - {ok, State :: term()} | - {ok, State :: term(), timeout() | hibernate} | - {ok, State :: term(), timeout() | hibernate, - {backoff, millis(), millis(), millis()}} | - {ok, State :: term(), timeout() | hibernate, - {backoff, millis(), millis(), millis()}, atom()} | - ignore | - {stop, Reason :: term()}. --callback handle_call(Request :: term(), From :: {pid(), Tag :: term()}, - State :: term()) -> - {reply, Reply :: term(), NewState :: term()} | - {reply, Reply :: term(), NewState :: term(), timeout() | hibernate} | - {noreply, NewState :: term()} | - {noreply, NewState :: term(), timeout() | hibernate} | - {stop, Reason :: term(), - Reply :: term(), NewState :: term()}. --callback handle_cast(Request :: term(), State :: term()) -> - {noreply, NewState :: term()} | - {noreply, NewState :: term(), timeout() | hibernate} | - {stop, Reason :: term(), NewState :: term()}. --callback handle_info(Info :: term(), State :: term()) -> - {noreply, NewState :: term()} | - {noreply, NewState :: term(), timeout() | hibernate} | - {stop, Reason :: term(), NewState :: term()}. --callback terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), - State :: term()) -> - ok | term(). --callback code_change(OldVsn :: (term() | {down, term()}), State :: term(), - Extra :: term()) -> - {ok, NewState :: term()} | {error, Reason :: term()}. - -%% It's not possible to define "optional" -callbacks, so putting specs -%% for handle_pre_hibernate/1 and handle_post_hibernate/1 will result -%% in warnings (the same applied for the behaviour_info before). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{init,1},{handle_call,3},{handle_cast,2},{handle_info,2}, - {terminate,2},{code_change,3}]; -behaviour_info(_Other) -> - undefined. - --endif. - -%%% ----------------------------------------------------------------- -%%% Starts a generic server. -%%% start(Mod, Args, Options) -%%% start(Name, Mod, Args, Options) -%%% start_link(Mod, Args, Options) -%%% start_link(Name, Mod, Args, Options) where: -%%% Name ::= {local, atom()} | {global, atom()} -%%% Mod ::= atom(), callback module implementing the 'real' server -%%% Args ::= term(), init arguments (to Mod:init/1) -%%% Options ::= [{timeout, Timeout} | {debug, [Flag]}] -%%% Flag ::= trace | log | {logfile, File} | statistics | debug -%%% (debug == log && statistics) -%%% Returns: {ok, Pid} | -%%% {error, {already_started, Pid}} | -%%% {error, Reason} -%%% ----------------------------------------------------------------- -start(Mod, Args, Options) -> - gen:start(?MODULE, nolink, Mod, Args, Options). - -start(Name, Mod, Args, Options) -> - gen:start(?MODULE, nolink, Name, Mod, Args, Options). - -start_link(Mod, Args, Options) -> - gen:start(?MODULE, link, Mod, Args, Options). - -start_link(Name, Mod, Args, Options) -> - gen:start(?MODULE, link, Name, Mod, Args, Options). - - -%% ----------------------------------------------------------------- -%% Make a call to a generic server. -%% If the server is located at another node, that node will -%% be monitored. -%% If the client is trapping exits and is linked server termination -%% is handled here (? Shall we do that here (or rely on timeouts) ?). -%% ----------------------------------------------------------------- -call(Name, Request) -> - case catch gen:call(Name, '$gen_call', Request) of - {ok,Res} -> - Res; - {'EXIT',Reason} -> - exit({Reason, {?MODULE, call, [Name, Request]}}) - end. - -call(Name, Request, Timeout) -> - case catch gen:call(Name, '$gen_call', Request, Timeout) of - {ok,Res} -> - Res; - {'EXIT',Reason} -> - exit({Reason, {?MODULE, call, [Name, Request, Timeout]}}) - end. - -%% ----------------------------------------------------------------- -%% Make a cast to a generic server. -%% ----------------------------------------------------------------- -cast({global,Name}, Request) -> - catch global: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}. - -%% ----------------------------------------------------------------- -%% Send a reply to the client. -%% ----------------------------------------------------------------- -reply({To, Tag}, Reply) -> - catch To ! {Tag, Reply}. - -%% ----------------------------------------------------------------- -%% Asyncronous broadcast, returns nothing, it's just send'n pray -%% ----------------------------------------------------------------- -abcast(Name, Request) when is_atom(Name) -> - do_abcast([node() | nodes()], Name, cast_msg(Request)). - -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. - -%%% ----------------------------------------------------------------- -%%% Make a call to servers at several nodes. -%%% Returns: {[Replies],[BadNodes]} -%%% A Timeout can be given -%%% -%%% A middleman process is used in case late answers arrives after -%%% the timeout. If they would be allowed to glog the callers message -%%% queue, it would probably become confused. Late answers will -%%% now arrive to the terminated middleman and so be discarded. -%%% ----------------------------------------------------------------- -multi_call(Name, Req) - when is_atom(Name) -> - do_multi_call([node() | nodes()], Name, Req, infinity). - -multi_call(Nodes, Name, Req) - when is_list(Nodes), is_atom(Name) -> - do_multi_call(Nodes, Name, Req, infinity). - -multi_call(Nodes, Name, Req, infinity) -> - do_multi_call(Nodes, Name, Req, infinity); -multi_call(Nodes, Name, Req, Timeout) - when is_list(Nodes), is_atom(Name), is_integer(Timeout), Timeout >= 0 -> - do_multi_call(Nodes, Name, Req, Timeout). - -%%% ----------------------------------------------------------------- -%%% Make multiple calls to multiple servers, given pairs of servers -%%% and messages. -%%% Returns: {[{Dest, Reply}], [{Dest, Error}]} -%%% -%%% Dest can be pid() | RegName :: atom() | -%%% {Name :: atom(), Node :: atom()} | {global, Name :: atom()} -%%% -%%% A middleman process is used to avoid clogging up the callers -%%% message queue. -%%% ----------------------------------------------------------------- -mcall(CallSpecs) -> - Tag = make_ref(), - {_, MRef} = spawn_monitor( - fun() -> - Refs = lists:foldl( - fun ({Dest, _Request}=S, Dict) -> - dict:store(do_mcall(S), Dest, Dict) - end, dict:new(), CallSpecs), - collect_replies(Tag, Refs, [], []) - end), - receive - {'DOWN', MRef, _, _, {Tag, Result}} -> Result; - {'DOWN', MRef, _, _, Reason} -> exit(Reason) - end. - -do_mcall({{global,Name}=Dest, Request}) -> - %% whereis_name is simply an ets lookup, and is precisely what - %% global:send/2 does, yet we need a Ref to put in the call to the - %% server, so invoking whereis_name makes a lot more sense here. - case global:whereis_name(Name) of - Pid when is_pid(Pid) -> - MRef = erlang:monitor(process, Pid), - catch msend(Pid, MRef, Request), - MRef; - undefined -> - Ref = make_ref(), - self() ! {'DOWN', Ref, process, Dest, noproc}, - Ref - end; -do_mcall({{Name,Node}=Dest, Request}) when is_atom(Name), is_atom(Node) -> - {_Node, MRef} = start_monitor(Node, Name), %% NB: we don't handle R6 - catch msend(Dest, MRef, Request), - MRef; -do_mcall({Dest, Request}) when is_atom(Dest); is_pid(Dest) -> - MRef = erlang:monitor(process, Dest), - catch msend(Dest, MRef, Request), - MRef. - -msend(Dest, MRef, Request) -> - erlang:send(Dest, {'$gen_call', {self(), MRef}, Request}, [noconnect]). - -collect_replies(Tag, Refs, Replies, Errors) -> - case dict:size(Refs) of - 0 -> exit({Tag, {Replies, Errors}}); - _ -> receive - {MRef, Reply} -> - {Refs1, Replies1} = handle_call_result(MRef, Reply, - Refs, Replies), - collect_replies(Tag, Refs1, Replies1, Errors); - {'DOWN', MRef, _, _, Reason} -> - Reason1 = case Reason of - noconnection -> nodedown; - _ -> Reason - end, - {Refs1, Errors1} = handle_call_result(MRef, Reason1, - Refs, Errors), - collect_replies(Tag, Refs1, Replies, Errors1) - end - end. - -handle_call_result(MRef, Result, Refs, AccList) -> - %% we avoid the mailbox scanning cost of a call to erlang:demonitor/{1,2} - %% here, so we must cope with MRefs that we've already seen and erased - case dict:find(MRef, Refs) of - {ok, Pid} -> {dict:erase(MRef, Refs), [{Pid, Result}|AccList]}; - _ -> {Refs, AccList} - end. - -%% ----------------------------------------------------------------- -%% Apply a function to a generic server's state. -%% ----------------------------------------------------------------- -with_state(Name, Fun) -> - case catch gen:call(Name, '$with_state', Fun, infinity) of - {ok,Res} -> - Res; - {'EXIT',Reason} -> - exit({Reason, {?MODULE, with_state, [Name, Fun]}}) - end. - -%%----------------------------------------------------------------- -%% enter_loop(Mod, Options, State, <ServerName>, <TimeOut>, <Backoff>) ->_ -%% -%% Description: Makes an existing process into a gen_server. -%% The calling process will enter the gen_server receive -%% loop and become a gen_server process. -%% The process *must* have been started using one of the -%% start functions in proc_lib, see proc_lib(3). -%% The user is responsible for any initialization of the -%% process, including registering a name for it. -%%----------------------------------------------------------------- -enter_loop(Mod, Options, State) -> - enter_loop(Mod, Options, State, self(), infinity, undefined). - -enter_loop(Mod, Options, State, Backoff = {backoff, _, _ , _}) -> - enter_loop(Mod, Options, State, self(), infinity, Backoff); - -enter_loop(Mod, Options, State, ServerName = {_, _}) -> - enter_loop(Mod, Options, State, ServerName, infinity, undefined); - -enter_loop(Mod, Options, State, Timeout) -> - enter_loop(Mod, Options, State, self(), Timeout, undefined). - -enter_loop(Mod, Options, State, ServerName, Backoff = {backoff, _, _, _}) -> - enter_loop(Mod, Options, State, ServerName, infinity, Backoff); - -enter_loop(Mod, Options, State, ServerName, Timeout) -> - enter_loop(Mod, Options, State, ServerName, Timeout, undefined). - -enter_loop(Mod, Options, State, ServerName, Timeout, Backoff) -> - Name = get_proc_name(ServerName), - Parent = get_parent(), - Debug = debug_options(Name, Options), - Queue = priority_queue:new(), - Backoff1 = extend_backoff(Backoff), - loop(find_prioritisers( - #gs2_state { parent = Parent, name = Name, state = State, - mod = Mod, time = Timeout, timeout_state = Backoff1, - queue = Queue, debug = Debug })). - -%%%======================================================================== -%%% Gen-callback functions -%%%======================================================================== - -%%% --------------------------------------------------- -%%% Initiate the new process. -%%% Register the name using the Rfunc function -%%% Calls the Mod:init/Args function. -%%% Finally an acknowledge is sent to Parent and the main -%%% loop is entered. -%%% --------------------------------------------------- -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 = name(Name0), - Debug = debug_options(Name, Options), - Queue = priority_queue:new(), - GS2State = find_prioritisers( - #gs2_state { parent = Parent, - name = Name, - mod = Mod, - queue = Queue, - debug = Debug }), - case catch Mod:init(Args) of - {ok, State} -> - proc_lib:init_ack(Starter, {ok, self()}), - loop(GS2State #gs2_state { state = State, - time = infinity, - timeout_state = undefined }); - {ok, State, Timeout} -> - proc_lib:init_ack(Starter, {ok, self()}), - loop(GS2State #gs2_state { state = State, - time = Timeout, - timeout_state = undefined }); - {ok, State, Timeout, Backoff = {backoff, _, _, _}} -> - Backoff1 = extend_backoff(Backoff), - proc_lib:init_ack(Starter, {ok, self()}), - loop(GS2State #gs2_state { state = State, - time = Timeout, - timeout_state = Backoff1 }); - {ok, State, Timeout, Backoff = {backoff, _, _, _}, Mod1} -> - Backoff1 = extend_backoff(Backoff), - proc_lib:init_ack(Starter, {ok, self()}), - loop(GS2State #gs2_state { mod = Mod1, - state = State, - time = Timeout, - timeout_state = Backoff1 }); - {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.) - unregister_name(Name0), - proc_lib:init_ack(Starter, {error, Reason}), - exit(Reason); - ignore -> - unregister_name(Name0), - proc_lib:init_ack(Starter, ignore), - exit(normal); - {'EXIT', Reason} -> - unregister_name(Name0), - proc_lib:init_ack(Starter, {error, Reason}), - exit(Reason); - Else -> - Error = {bad_return_value, Else}, - proc_lib:init_ack(Starter, {error, Error}), - exit(Error) - end. - -name({local,Name}) -> Name; -name({global,Name}) -> Name; -%% name(Pid) when is_pid(Pid) -> Pid; -%% when R12 goes away, drop the line beneath and uncomment the line above -name(Name) -> Name. - -unregister_name({local,Name}) -> - _ = (catch unregister(Name)); -unregister_name({global,Name}) -> - _ = global:unregister_name(Name); -unregister_name(Pid) when is_pid(Pid) -> - Pid; -%% Under R12 let's just ignore it, as we have a single term as Name. -%% On R13 it will never get here, as we get tuple with 'local/global' atom. -unregister_name(_Name) -> ok. - -extend_backoff(undefined) -> - undefined; -extend_backoff({backoff, InitialTimeout, MinimumTimeout, DesiredHibPeriod}) -> - {backoff, InitialTimeout, MinimumTimeout, DesiredHibPeriod, now()}. - -%%%======================================================================== -%%% Internal functions -%%%======================================================================== -%%% --------------------------------------------------- -%%% The MAIN loop. -%%% --------------------------------------------------- -loop(GS2State = #gs2_state { time = hibernate, - timeout_state = undefined }) -> - pre_hibernate(GS2State); -loop(GS2State) -> - process_next_msg(drain(GS2State)). - -drain(GS2State) -> - receive - Input -> drain(in(Input, GS2State)) - after 0 -> GS2State - end. - -process_next_msg(GS2State = #gs2_state { time = Time, - timeout_state = TimeoutState, - queue = Queue }) -> - case priority_queue:out(Queue) of - {{value, Msg}, Queue1} -> - process_msg(Msg, GS2State #gs2_state { queue = Queue1 }); - {empty, Queue1} -> - {Time1, HibOnTimeout} - = case {Time, TimeoutState} of - {hibernate, {backoff, Current, _Min, _Desired, _RSt}} -> - {Current, true}; - {hibernate, _} -> - %% wake_hib/7 will set Time to hibernate. If - %% we were woken and didn't receive a msg - %% then we will get here and need a sensible - %% value for Time1, otherwise we crash. - %% R13B1 always waits infinitely when waking - %% from hibernation, so that's what we do - %% here too. - {infinity, false}; - _ -> {Time, false} - end, - receive - Input -> - %% Time could be 'hibernate' here, so *don't* call loop - process_next_msg( - drain(in(Input, GS2State #gs2_state { queue = Queue1 }))) - after Time1 -> - case HibOnTimeout of - true -> - pre_hibernate( - GS2State #gs2_state { queue = Queue1 }); - false -> - process_msg(timeout, - GS2State #gs2_state { queue = Queue1 }) - end - end - end. - -wake_hib(GS2State = #gs2_state { timeout_state = TS }) -> - TimeoutState1 = case TS of - undefined -> - undefined; - {SleptAt, TimeoutState} -> - adjust_timeout_state(SleptAt, now(), TimeoutState) - end, - post_hibernate( - drain(GS2State #gs2_state { timeout_state = TimeoutState1 })). - -hibernate(GS2State = #gs2_state { timeout_state = TimeoutState }) -> - TS = case TimeoutState of - undefined -> undefined; - {backoff, _, _, _, _} -> {now(), TimeoutState} - end, - proc_lib:hibernate(?MODULE, wake_hib, - [GS2State #gs2_state { timeout_state = TS }]). - -pre_hibernate(GS2State = #gs2_state { state = State, - mod = Mod }) -> - case erlang:function_exported(Mod, handle_pre_hibernate, 1) of - true -> - case catch Mod:handle_pre_hibernate(State) of - {hibernate, NState} -> - hibernate(GS2State #gs2_state { state = NState } ); - Reply -> - handle_common_termination(Reply, pre_hibernate, GS2State) - end; - false -> - hibernate(GS2State) - end. - -post_hibernate(GS2State = #gs2_state { state = State, - mod = Mod }) -> - case erlang:function_exported(Mod, handle_post_hibernate, 1) of - true -> - case catch Mod:handle_post_hibernate(State) of - {noreply, NState} -> - process_next_msg(GS2State #gs2_state { state = NState, - time = infinity }); - {noreply, NState, Time} -> - process_next_msg(GS2State #gs2_state { state = NState, - time = Time }); - Reply -> - handle_common_termination(Reply, post_hibernate, GS2State) - end; - false -> - %% use hibernate here, not infinity. This matches - %% R13B. The key is that we should be able to get through - %% to process_msg calling sys:handle_system_msg with Time - %% still set to hibernate, iff that msg is the very msg - %% that woke us up (or the first msg we receive after - %% waking up). - process_next_msg(GS2State #gs2_state { time = hibernate }) - end. - -adjust_timeout_state(SleptAt, AwokeAt, {backoff, CurrentTO, MinimumTO, - DesiredHibPeriod, RandomState}) -> - NapLengthMicros = timer:now_diff(AwokeAt, SleptAt), - CurrentMicros = CurrentTO * 1000, - MinimumMicros = MinimumTO * 1000, - DesiredHibMicros = DesiredHibPeriod * 1000, - GapBetweenMessagesMicros = NapLengthMicros + CurrentMicros, - Base = - %% If enough time has passed between the last two messages then we - %% should consider sleeping sooner. Otherwise stay awake longer. - case GapBetweenMessagesMicros > (MinimumMicros + DesiredHibMicros) of - true -> lists:max([MinimumTO, CurrentTO div 2]); - false -> CurrentTO - end, - {Extra, RandomState1} = random:uniform_s(Base, RandomState), - CurrentTO1 = Base + Extra, - {backoff, CurrentTO1, MinimumTO, DesiredHibPeriod, RandomState1}. - -in({'$gen_cast', Msg} = Input, - GS2State = #gs2_state { prioritisers = {_, F, _} }) -> - in(Input, F(Msg, GS2State), GS2State); -in({'$gen_call', From, Msg} = Input, - GS2State = #gs2_state { prioritisers = {F, _, _} }) -> - in(Input, F(Msg, From, GS2State), GS2State); -in({'$with_state', _From, _Fun} = Input, GS2State) -> - in(Input, 0, GS2State); -in({'EXIT', Parent, _R} = Input, GS2State = #gs2_state { parent = Parent }) -> - in(Input, infinity, GS2State); -in({system, _From, _Req} = Input, GS2State) -> - in(Input, infinity, GS2State); -in(Input, GS2State = #gs2_state { prioritisers = {_, _, F} }) -> - in(Input, F(Input, GS2State), GS2State). - -in(_Input, drop, GS2State) -> - GS2State; - -in(Input, Priority, GS2State = #gs2_state { queue = Queue }) -> - GS2State # gs2_state { queue = priority_queue:in(Input, Priority, Queue) }. - -process_msg({system, From, Req}, - GS2State = #gs2_state { parent = Parent, debug = Debug }) -> - %% gen_server puts Hib on the end as the 7th arg, but that version - %% of the fun seems not to be documented so leaving out for now. - sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, GS2State); -process_msg({'$with_state', From, Fun}, - GS2State = #gs2_state{state = State}) -> - reply(From, catch Fun(State)), - loop(GS2State); -process_msg({'EXIT', Parent, Reason} = Msg, - GS2State = #gs2_state { parent = Parent }) -> - terminate(Reason, Msg, GS2State); -process_msg(Msg, GS2State = #gs2_state { debug = [] }) -> - handle_msg(Msg, GS2State); -process_msg(Msg, GS2State = #gs2_state { name = Name, debug = Debug }) -> - Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {in, Msg}), - handle_msg(Msg, GS2State #gs2_state { debug = Debug1 }). - -%%% --------------------------------------------------- -%%% Send/recive functions -%%% --------------------------------------------------- -do_send(Dest, Msg) -> - catch erlang:send(Dest, Msg). - -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 !!! - unmonitor(R), - rec_nodes(Tag, Tail, Name, Badnodes, - [{N,Reply}|Replies], Time, TimerId); - {timeout, TimerId, _} -> - unmonitor(R), - %% 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 !!! - unmonitor(R), - rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies]) - after 0 -> - unmonitor(R), - 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. - -%% Cancels a monitor started with Ref=erlang:monitor(_, _). -unmonitor(Ref) when is_reference(Ref) -> - erlang:demonitor(Ref), - receive - {'DOWN', Ref, _, _, _} -> - true - after 0 -> - true - end. - -%%% --------------------------------------------------- -%%% Message handling functions -%%% --------------------------------------------------- - -dispatch({'$gen_cast', Msg}, Mod, State) -> - Mod:handle_cast(Msg, State); -dispatch(Info, Mod, State) -> - Mod:handle_info(Info, State). - -common_reply(_Name, From, Reply, _NState, [] = _Debug) -> - reply(From, Reply), - []; -common_reply(Name, {To, _Tag} = From, Reply, NState, Debug) -> - reply(From, Reply), - sys:handle_debug(Debug, fun print_event/3, Name, {out, Reply, To, NState}). - -common_noreply(_Name, _NState, [] = _Debug) -> - []; -common_noreply(Name, NState, Debug) -> - sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}). - -common_become(_Name, _Mod, _NState, [] = _Debug) -> - []; -common_become(Name, Mod, NState, Debug) -> - sys:handle_debug(Debug, fun print_event/3, Name, {become, Mod, NState}). - -handle_msg({'$gen_call', From, Msg}, GS2State = #gs2_state { mod = Mod, - state = State, - name = Name, - debug = Debug }) -> - case catch Mod:handle_call(Msg, From, State) of - {reply, Reply, NState} -> - Debug1 = common_reply(Name, From, Reply, NState, Debug), - loop(GS2State #gs2_state { state = NState, - time = infinity, - debug = Debug1 }); - {reply, Reply, NState, Time1} -> - Debug1 = common_reply(Name, From, Reply, NState, Debug), - loop(GS2State #gs2_state { state = NState, - time = Time1, - debug = Debug1}); - {stop, Reason, Reply, NState} -> - {'EXIT', R} = - (catch terminate(Reason, Msg, - GS2State #gs2_state { state = NState })), - common_reply(Name, From, Reply, NState, Debug), - exit(R); - Other -> - handle_common_reply(Other, Msg, GS2State) - end; -handle_msg(Msg, GS2State = #gs2_state { mod = Mod, state = State }) -> - Reply = (catch dispatch(Msg, Mod, State)), - handle_common_reply(Reply, Msg, GS2State). - -handle_common_reply(Reply, Msg, GS2State = #gs2_state { name = Name, - debug = Debug}) -> - case Reply of - {noreply, NState} -> - Debug1 = common_noreply(Name, NState, Debug), - loop(GS2State #gs2_state {state = NState, - time = infinity, - debug = Debug1}); - {noreply, NState, Time1} -> - Debug1 = common_noreply(Name, NState, Debug), - loop(GS2State #gs2_state {state = NState, - time = Time1, - debug = Debug1}); - {become, Mod, NState} -> - Debug1 = common_become(Name, Mod, NState, Debug), - loop(find_prioritisers( - GS2State #gs2_state { mod = Mod, - state = NState, - time = infinity, - debug = Debug1 })); - {become, Mod, NState, Time1} -> - Debug1 = common_become(Name, Mod, NState, Debug), - loop(find_prioritisers( - GS2State #gs2_state { mod = Mod, - state = NState, - time = Time1, - debug = Debug1 })); - _ -> - handle_common_termination(Reply, Msg, GS2State) - end. - -handle_common_termination(Reply, Msg, GS2State) -> - case Reply of - {stop, Reason, NState} -> - terminate(Reason, Msg, GS2State #gs2_state { state = NState }); - {'EXIT', What} -> - terminate(What, Msg, GS2State); - _ -> - terminate({bad_return_value, Reply}, Msg, GS2State) - end. - -%%----------------------------------------------------------------- -%% Callback functions for system messages handling. -%%----------------------------------------------------------------- -system_continue(Parent, Debug, GS2State) -> - loop(GS2State #gs2_state { parent = Parent, debug = Debug }). - -system_terminate(Reason, _Parent, Debug, GS2State) -> - terminate(Reason, [], GS2State #gs2_state { debug = Debug }). - -system_code_change(GS2State = #gs2_state { mod = Mod, - state = State }, - _Module, OldVsn, Extra) -> - case catch Mod:code_change(OldVsn, State, Extra) of - {ok, NewState} -> - NewGS2State = find_prioritisers( - GS2State #gs2_state { state = NewState }), - {ok, [NewGS2State]}; - Else -> - Else - end. - -%%----------------------------------------------------------------- -%% Format debug messages. Print them as the call-back module sees -%% them, not as the real erlang messages. Use trace for that. -%%----------------------------------------------------------------- -print_event(Dev, {in, Msg}, Name) -> - case Msg of - {'$gen_call', {From, _Tag}, Call} -> - io:format(Dev, "*DBG* ~p got call ~p from ~w~n", - [Name, Call, From]); - {'$gen_cast', Cast} -> - io:format(Dev, "*DBG* ~p got cast ~p~n", - [Name, Cast]); - _ -> - io:format(Dev, "*DBG* ~p got ~p~n", [Name, Msg]) - end; -print_event(Dev, {out, Msg, To, State}, Name) -> - io:format(Dev, "*DBG* ~p sent ~p to ~w, new state ~w~n", - [Name, Msg, To, State]); -print_event(Dev, {noreply, State}, Name) -> - io:format(Dev, "*DBG* ~p new state ~w~n", [Name, State]); -print_event(Dev, Event, Name) -> - io:format(Dev, "*DBG* ~p dbg ~p~n", [Name, Event]). - - -%%% --------------------------------------------------- -%%% Terminate the server. -%%% --------------------------------------------------- - -terminate(Reason, Msg, #gs2_state { name = Name, - mod = Mod, - state = State, - debug = Debug }) -> - case catch Mod:terminate(Reason, State) of - {'EXIT', R} -> - error_info(R, Reason, Name, Msg, State, Debug), - exit(R); - _ -> - case Reason of - normal -> - exit(normal); - shutdown -> - exit(shutdown); - {shutdown,_}=Shutdown -> - exit(Shutdown); - _ -> - error_info(Reason, undefined, Name, Msg, State, Debug), - exit(Reason) - end - end. - -error_info(_Reason, _RootCause, application_controller, _Msg, _State, _Debug) -> - %% OTP-5811 Don't send an error report if it's the system process - %% application_controller which is terminating - let init take care - %% of it instead - ok; -error_info(Reason, RootCause, Name, Msg, State, Debug) -> - Reason1 = error_reason(Reason), - Fmt = - "** Generic server ~p terminating~n" - "** Last message in was ~p~n" - "** When Server state == ~p~n" - "** Reason for termination == ~n** ~p~n", - case RootCause of - undefined -> format(Fmt, [Name, Msg, State, Reason1]); - _ -> format(Fmt ++ "** In 'terminate' callback " - "with reason ==~n** ~p~n", - [Name, Msg, State, Reason1, - error_reason(RootCause)]) - end, - sys:print_log(Debug), - ok. - -error_reason({undef,[{M,F,A}|MFAs]} = Reason) -> - case code:is_loaded(M) of - false -> {'module could not be loaded',[{M,F,A}|MFAs]}; - _ -> case erlang:function_exported(M, F, length(A)) of - true -> Reason; - false -> {'function not exported',[{M,F,A}|MFAs]} - end - end; -error_reason(Reason) -> - Reason. - -%%% --------------------------------------------------- -%%% Misc. functions. -%%% --------------------------------------------------- - -opt(Op, [{Op, Value}|_]) -> - {ok, Value}; -opt(Op, [_|Options]) -> - opt(Op, Options); -opt(_, []) -> - false. - -debug_options(Name, Opts) -> - case opt(debug, Opts) of - {ok, Options} -> dbg_options(Name, Options); - _ -> dbg_options(Name, []) - end. - -dbg_options(Name, []) -> - Opts = - case init:get_argument(generic_debug) of - error -> - []; - _ -> - [log, statistics] - end, - dbg_opts(Name, Opts); -dbg_options(Name, Opts) -> - dbg_opts(Name, Opts). - -dbg_opts(Name, Opts) -> - case catch sys:debug_options(Opts) of - {'EXIT',_} -> - format("~p: ignoring erroneous debug options - ~p~n", - [Name, Opts]), - []; - Dbg -> - Dbg - end. - -get_proc_name(Pid) when is_pid(Pid) -> - Pid; -get_proc_name({local, Name}) -> - case process_info(self(), registered_name) of - {registered_name, Name} -> - Name; - {registered_name, _Name} -> - exit(process_not_registered); - [] -> - exit(process_not_registered) - end; -get_proc_name({global, Name}) -> - case whereis_name(Name) of - undefined -> - exit(process_not_registered_globally); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit(process_not_registered_globally) - end. - -get_parent() -> - case get('$ancestors') of - [Parent | _] when is_pid(Parent)-> - Parent; - [Parent | _] when is_atom(Parent)-> - name_to_pid(Parent); - _ -> - exit(process_was_not_started_by_proc_lib) - end. - -name_to_pid(Name) -> - case whereis(Name) of - undefined -> - case whereis_name(Name) of - undefined -> - exit(could_not_find_registerd_name); - Pid -> - Pid - end; - Pid -> - Pid - end. - -whereis_name(Name) -> - case ets:lookup(global_names, Name) of - [{_Name, Pid, _Method, _RPid, _Ref}] -> - if node(Pid) == node() -> - case is_process_alive(Pid) of - true -> Pid; - false -> undefined - end; - true -> - Pid - end; - [] -> undefined - end. - -find_prioritisers(GS2State = #gs2_state { mod = Mod }) -> - PCall = function_exported_or_default(Mod, 'prioritise_call', 4, - fun (_Msg, _From, _State) -> 0 end), - PCast = function_exported_or_default(Mod, 'prioritise_cast', 3, - fun (_Msg, _State) -> 0 end), - PInfo = function_exported_or_default(Mod, 'prioritise_info', 3, - fun (_Msg, _State) -> 0 end), - GS2State #gs2_state { prioritisers = {PCall, PCast, PInfo} }. - -function_exported_or_default(Mod, Fun, Arity, Default) -> - case erlang:function_exported(Mod, Fun, Arity) of - true -> case Arity of - 3 -> fun (Msg, GS2State = #gs2_state { queue = Queue, - state = State }) -> - Length = priority_queue:len(Queue), - case catch Mod:Fun(Msg, Length, State) of - drop -> - drop; - Res when is_integer(Res) -> - Res; - Err -> - handle_common_termination(Err, Msg, GS2State) - end - end; - 4 -> fun (Msg, From, GS2State = #gs2_state { queue = Queue, - state = State }) -> - Length = priority_queue:len(Queue), - case catch Mod:Fun(Msg, From, Length, State) of - Res when is_integer(Res) -> - Res; - Err -> - handle_common_termination(Err, Msg, GS2State) - end - end - end; - false -> Default - end. - -%%----------------------------------------------------------------- -%% Status information -%%----------------------------------------------------------------- -format_status(Opt, StatusData) -> - [PDict, SysState, Parent, Debug, - #gs2_state{name = Name, state = State, mod = Mod, queue = Queue}] = - StatusData, - NameTag = if is_pid(Name) -> - pid_to_list(Name); - is_atom(Name) -> - Name - end, - Header = lists:concat(["Status for generic server ", NameTag]), - Log = sys:get_debug(log, Debug, []), - Specfic = callback(Mod, format_status, [Opt, [PDict, State]], - fun () -> [{data, [{"State", State}]}] end), - Messages = callback(Mod, format_message_queue, [Opt, Queue], - fun () -> priority_queue:to_list(Queue) end), - [{header, Header}, - {data, [{"Status", SysState}, - {"Parent", Parent}, - {"Logged events", Log}, - {"Queued messages", Messages}]} | - Specfic]. - -callback(Mod, FunName, Args, DefaultThunk) -> - case erlang:function_exported(Mod, FunName, length(Args)) of - true -> case catch apply(Mod, FunName, Args) of - {'EXIT', _} -> DefaultThunk(); - Success -> Success - end; - false -> DefaultThunk() - end. diff --git a/src/gm.erl b/src/gm.erl deleted file mode 100644 index 636e63e4..00000000 --- a/src/gm.erl +++ /dev/null @@ -1,1493 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(gm). - -%% Guaranteed Multicast -%% ==================== -%% -%% This module provides the ability to create named groups of -%% processes to which members can be dynamically added and removed, -%% and for messages to be broadcast within the group that are -%% guaranteed to reach all members of the group during the lifetime of -%% the message. The lifetime of a message is defined as being, at a -%% minimum, the time from which the message is first sent to any -%% member of the group, up until the time at which it is known by the -%% member who published the message that the message has reached all -%% group members. -%% -%% The guarantee given is that provided a message, once sent, makes it -%% to members who do not all leave the group, the message will -%% continue to propagate to all group members. -%% -%% Another way of stating the guarantee is that if member P publishes -%% messages m and m', then for all members P', if P' is a member of -%% the group prior to the publication of m, and P' receives m', then -%% P' will receive m. -%% -%% Note that only local-ordering is enforced: i.e. if member P sends -%% message m and then message m', then for-all members P', if P' -%% receives m and m', then they will receive m' after m. Causality -%% ordering is _not_ enforced. I.e. if member P receives message m -%% and as a result publishes message m', there is no guarantee that -%% other members P' will receive m before m'. -%% -%% -%% API Use -%% ------- -%% -%% Mnesia must be started. Use the idempotent create_tables/0 function -%% to create the tables required. -%% -%% start_link/3 -%% Provide the group name, the callback module name, and any arguments -%% you wish to be passed into the callback module's functions. The -%% joined/2 function will be called when we have joined the group, -%% with the arguments passed to start_link and a list of the current -%% members of the group. See the callbacks specs and the comments -%% below for further details of the callback functions. -%% -%% leave/1 -%% Provide the Pid. Removes the Pid from the group. The callback -%% handle_terminate/2 function will be called. -%% -%% broadcast/2 -%% Provide the Pid and a Message. The message will be sent to all -%% members of the group as per the guarantees given above. This is a -%% cast and the function call will return immediately. There is no -%% guarantee that the message will reach any member of the group. -%% -%% confirmed_broadcast/2 -%% Provide the Pid and a Message. As per broadcast/2 except that this -%% is a call, not a cast, and only returns 'ok' once the Message has -%% reached every member of the group. Do not call -%% confirmed_broadcast/2 directly from the callback module otherwise -%% you will deadlock the entire group. -%% -%% info/1 -%% Provide the Pid. Returns a proplist with various facts, including -%% the group name and the current group members. -%% -%% validate_members/2 -%% Check whether a given member list agrees with the chosen member's -%% view. Any differences will be communicated via the members_changed -%% callback. If there are no differences then there will be no reply. -%% Note that members will not necessarily share the same view. -%% -%% forget_group/1 -%% Provide the group name. Removes its mnesia record. Makes no attempt -%% to ensure the group is empty. -%% -%% Implementation Overview -%% ----------------------- -%% -%% One possible means of implementation would be a fan-out from the -%% sender to every member of the group. This would require that the -%% group is fully connected, and, in the event that the original -%% sender of the message disappears from the group before the message -%% has made it to every member of the group, raises questions as to -%% who is responsible for sending on the message to new group members. -%% In particular, the issue is with [ Pid ! Msg || Pid <- Members ] - -%% if the sender dies part way through, who is responsible for -%% ensuring that the remaining Members receive the Msg? In the event -%% that within the group, messages sent are broadcast from a subset of -%% the members, the fan-out arrangement has the potential to -%% substantially impact the CPU and network workload of such members, -%% as such members would have to accommodate the cost of sending each -%% message to every group member. -%% -%% Instead, if the members of the group are arranged in a chain, then -%% it becomes easier to reason about who within the group has received -%% each message and who has not. It eases issues of responsibility: in -%% the event of a group member disappearing, the nearest upstream -%% member of the chain is responsible for ensuring that messages -%% continue to propagate down the chain. It also results in equal -%% distribution of sending and receiving workload, even if all -%% messages are being sent from just a single group member. This -%% configuration has the further advantage that it is not necessary -%% for every group member to know of every other group member, and -%% even that a group member does not have to be accessible from all -%% other group members. -%% -%% Performance is kept high by permitting pipelining and all -%% communication between joined group members is asynchronous. In the -%% chain A -> B -> C -> D, if A sends a message to the group, it will -%% not directly contact C or D. However, it must know that D receives -%% the message (in addition to B and C) before it can consider the -%% message fully sent. A simplistic implementation would require that -%% D replies to C, C replies to B and B then replies to A. This would -%% result in a propagation delay of twice the length of the chain. It -%% would also require, in the event of the failure of C, that D knows -%% to directly contact B and issue the necessary replies. Instead, the -%% chain forms a ring: D sends the message on to A: D does not -%% distinguish A as the sender, merely as the next member (downstream) -%% within the chain (which has now become a ring). When A receives -%% from D messages that A sent, it knows that all members have -%% received the message. However, the message is not dead yet: if C -%% died as B was sending to C, then B would need to detect the death -%% of C and forward the message on to D instead: thus every node has -%% to remember every message published until it is told that it can -%% forget about the message. This is essential not just for dealing -%% with failure of members, but also for the addition of new members. -%% -%% Thus once A receives the message back again, it then sends to B an -%% acknowledgement for the message, indicating that B can now forget -%% about the message. B does so, and forwards the ack to C. C forgets -%% the message, and forwards the ack to D, which forgets the message -%% and finally forwards the ack back to A. At this point, A takes no -%% further action: the message and its acknowledgement have made it to -%% every member of the group. The message is now dead, and any new -%% member joining the group at this point will not receive the -%% message. -%% -%% We therefore have two roles: -%% -%% 1. The sender, who upon receiving their own messages back, must -%% then send out acknowledgements, and upon receiving their own -%% acknowledgements back perform no further action. -%% -%% 2. The other group members who upon receiving messages and -%% acknowledgements must update their own internal state accordingly -%% (the sending member must also do this in order to be able to -%% accommodate failures), and forwards messages on to their downstream -%% neighbours. -%% -%% -%% Implementation: It gets trickier -%% -------------------------------- -%% -%% Chain A -> B -> C -> D -%% -%% A publishes a message which B receives. A now dies. B and D will -%% detect the death of A, and will link up, thus the chain is now B -> -%% C -> D. B forwards A's message on to C, who forwards it to D, who -%% forwards it to B. Thus B is now responsible for A's messages - both -%% publications and acknowledgements that were in flight at the point -%% at which A died. Even worse is that this is transitive: after B -%% forwards A's message to C, B dies as well. Now C is not only -%% responsible for B's in-flight messages, but is also responsible for -%% A's in-flight messages. -%% -%% Lemma 1: A member can only determine which dead members they have -%% inherited responsibility for if there is a total ordering on the -%% conflicting additions and subtractions of members from the group. -%% -%% Consider the simultaneous death of B and addition of B' that -%% transitions a chain from A -> B -> C to A -> B' -> C. Either B' or -%% C is responsible for in-flight messages from B. It is easy to -%% ensure that at least one of them thinks they have inherited B, but -%% if we do not ensure that exactly one of them inherits B, then we -%% could have B' converting publishes to acks, which then will crash C -%% as C does not believe it has issued acks for those messages. -%% -%% More complex scenarios are easy to concoct: A -> B -> C -> D -> E -%% becoming A -> C' -> E. Who has inherited which of B, C and D? -%% -%% However, for non-conflicting membership changes, only a partial -%% ordering is required. For example, A -> B -> C becoming A -> A' -> -%% B. The addition of A', between A and B can have no conflicts with -%% the death of C: it is clear that A has inherited C's messages. -%% -%% For ease of implementation, we adopt the simple solution, of -%% imposing a total order on all membership changes. -%% -%% On the death of a member, it is ensured the dead member's -%% neighbours become aware of the death, and the upstream neighbour -%% now sends to its new downstream neighbour its state, including the -%% messages pending acknowledgement. The downstream neighbour can then -%% use this to calculate which publishes and acknowledgements it has -%% missed out on, due to the death of its old upstream. Thus the -%% downstream can catch up, and continues the propagation of messages -%% through the group. -%% -%% Lemma 2: When a member is joining, it must synchronously -%% communicate with its upstream member in order to receive its -%% starting state atomically with its addition to the group. -%% -%% New members must start with the same state as their nearest -%% upstream neighbour. This ensures that it is not surprised by -%% acknowledgements they are sent, and that should their downstream -%% neighbour die, they are able to send the correct state to their new -%% downstream neighbour to ensure it can catch up. Thus in the -%% transition A -> B -> C becomes A -> A' -> B -> C becomes A -> A' -> -%% C, A' must start with the state of A, so that it can send C the -%% correct state when B dies, allowing C to detect any missed -%% messages. -%% -%% If A' starts by adding itself to the group membership, A could then -%% die, without A' having received the necessary state from A. This -%% would leave A' responsible for in-flight messages from A, but -%% having the least knowledge of all, of those messages. Thus A' must -%% start by synchronously calling A, which then immediately sends A' -%% back its state. A then adds A' to the group. If A dies at this -%% point then A' will be able to see this (as A' will fail to appear -%% in the group membership), and thus A' will ignore the state it -%% receives from A, and will simply repeat the process, trying to now -%% join downstream from some other member. This ensures that should -%% the upstream die as soon as the new member has been joined, the new -%% member is guaranteed to receive the correct state, allowing it to -%% correctly process messages inherited due to the death of its -%% upstream neighbour. -%% -%% The canonical definition of the group membership is held by a -%% distributed database. Whilst this allows the total ordering of -%% changes to be achieved, it is nevertheless undesirable to have to -%% query this database for the current view, upon receiving each -%% message. Instead, we wish for members to be able to cache a view of -%% the group membership, which then requires a cache invalidation -%% mechanism. Each member maintains its own view of the group -%% membership. Thus when the group's membership changes, members may -%% need to become aware of such changes in order to be able to -%% accurately process messages they receive. Because of the -%% requirement of a total ordering of conflicting membership changes, -%% it is not possible to use the guaranteed broadcast mechanism to -%% communicate these changes: to achieve the necessary ordering, it -%% would be necessary for such messages to be published by exactly one -%% member, which can not be guaranteed given that such a member could -%% die. -%% -%% The total ordering we enforce on membership changes gives rise to a -%% view version number: every change to the membership creates a -%% different view, and the total ordering permits a simple -%% monotonically increasing view version number. -%% -%% Lemma 3: If a message is sent from a member that holds view version -%% N, it can be correctly processed by any member receiving the -%% message with a view version >= N. -%% -%% Initially, let us suppose that each view contains the ordering of -%% every member that was ever part of the group. Dead members are -%% marked as such. Thus we have a ring of members, some of which are -%% dead, and are thus inherited by the nearest alive downstream -%% member. -%% -%% In the chain A -> B -> C, all three members initially have view -%% version 1, which reflects reality. B publishes a message, which is -%% forward by C to A. B now dies, which A notices very quickly. Thus A -%% updates the view, creating version 2. It now forwards B's -%% publication, sending that message to its new downstream neighbour, -%% C. This happens before C is aware of the death of B. C must become -%% aware of the view change before it interprets the message its -%% received, otherwise it will fail to learn of the death of B, and -%% thus will not realise it has inherited B's messages (and will -%% likely crash). -%% -%% Thus very simply, we have that each subsequent view contains more -%% information than the preceding view. -%% -%% However, to avoid the views growing indefinitely, we need to be -%% able to delete members which have died _and_ for which no messages -%% are in-flight. This requires that upon inheriting a dead member, we -%% know the last publication sent by the dead member (this is easy: we -%% inherit a member because we are the nearest downstream member which -%% implies that we know at least as much than everyone else about the -%% publications of the dead member), and we know the earliest message -%% for which the acknowledgement is still in flight. -%% -%% In the chain A -> B -> C, when B dies, A will send to C its state -%% (as C is the new downstream from A), allowing C to calculate which -%% messages it has missed out on (described above). At this point, C -%% also inherits B's messages. If that state from A also includes the -%% last message published by B for which an acknowledgement has been -%% seen, then C knows exactly which further acknowledgements it must -%% receive (also including issuing acknowledgements for publications -%% still in-flight that it receives), after which it is known there -%% are no more messages in flight for B, thus all evidence that B was -%% ever part of the group can be safely removed from the canonical -%% group membership. -%% -%% Thus, for every message that a member sends, it includes with that -%% message its view version. When a member receives a message it will -%% update its view from the canonical copy, should its view be older -%% than the view version included in the message it has received. -%% -%% The state held by each member therefore includes the messages from -%% each publisher pending acknowledgement, the last publication seen -%% from that publisher, and the last acknowledgement from that -%% publisher. In the case of the member's own publications or -%% inherited members, this last acknowledgement seen state indicates -%% the last acknowledgement retired, rather than sent. -%% -%% -%% Proof sketch -%% ------------ -%% -%% We need to prove that with the provided operational semantics, we -%% can never reach a state that is not well formed from a well-formed -%% starting state. -%% -%% Operational semantics (small step): straight-forward message -%% sending, process monitoring, state updates. -%% -%% Well formed state: dead members inherited by exactly one non-dead -%% member; for every entry in anyone's pending-acks, either (the -%% publication of the message is in-flight downstream from the member -%% and upstream from the publisher) or (the acknowledgement of the -%% message is in-flight downstream from the publisher and upstream -%% from the member). -%% -%% Proof by induction on the applicable operational semantics. -%% -%% -%% Related work -%% ------------ -%% -%% The ring configuration and double traversal of messages around the -%% ring is similar (though developed independently) to the LCR -%% protocol by [Levy 2008]. However, LCR differs in several -%% ways. Firstly, by using vector clocks, it enforces a total order of -%% message delivery, which is unnecessary for our purposes. More -%% significantly, it is built on top of a "group communication system" -%% which performs the group management functions, taking -%% responsibility away from the protocol as to how to cope with safely -%% adding and removing members. When membership changes do occur, the -%% protocol stipulates that every member must perform communication -%% with every other member of the group, to ensure all outstanding -%% deliveries complete, before the entire group transitions to the new -%% view. This, in total, requires two sets of all-to-all synchronous -%% communications. -%% -%% This is not only rather inefficient, but also does not explain what -%% happens upon the failure of a member during this process. It does -%% though entirely avoid the need for inheritance of responsibility of -%% dead members that our protocol incorporates. -%% -%% In [Marandi et al 2010], a Paxos-based protocol is described. This -%% work explicitly focuses on the efficiency of communication. LCR -%% (and our protocol too) are more efficient, but at the cost of -%% higher latency. The Ring-Paxos protocol is itself built on top of -%% IP-multicast, which rules it out for many applications where -%% point-to-point communication is all that can be required. They also -%% have an excellent related work section which I really ought to -%% read... -%% -%% -%% [Levy 2008] The Complexity of Reliable Distributed Storage, 2008. -%% [Marandi et al 2010] Ring Paxos: A High-Throughput Atomic Broadcast -%% Protocol - - --behaviour(gen_server2). - --export([create_tables/0, start_link/4, leave/1, broadcast/2, broadcast/3, - confirmed_broadcast/2, info/1, validate_members/2, forget_group/1]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3, prioritise_info/3]). - -%% For INSTR_MOD callbacks --export([call/3, cast/2, monitor/1, demonitor/1]). - --ifndef(use_specs). --export([behaviour_info/1]). --endif. - --export([table_definitions/0]). - --define(GROUP_TABLE, gm_group). --define(MAX_BUFFER_SIZE, 100000000). %% 100MB --define(HIBERNATE_AFTER_MIN, 1000). --define(DESIRED_HIBERNATE, 10000). --define(BROADCAST_TIMER, 25). --define(VERSION_START, 0). --define(SETS, ordsets). --define(DICT, orddict). - --record(state, - { self, - left, - right, - group_name, - module, - view, - pub_count, - members_state, - callback_args, - confirms, - broadcast_buffer, - broadcast_buffer_sz, - broadcast_timer, - txn_executor - }). - --record(gm_group, { name, version, members }). - --record(view_member, { id, aliases, left, right }). - --record(member, { pending_ack, last_pub, last_ack }). - --define(TABLE, {?GROUP_TABLE, [{record_name, gm_group}, - {attributes, record_info(fields, gm_group)}]}). --define(TABLE_MATCH, {match, #gm_group { _ = '_' }}). - --define(TAG, '$gm'). - --ifdef(use_specs). - --export_type([group_name/0]). - --type(group_name() :: any()). --type(txn_fun() :: fun((fun(() -> any())) -> any())). - --spec(create_tables/0 :: () -> 'ok' | {'aborted', any()}). --spec(start_link/4 :: (group_name(), atom(), any(), txn_fun()) -> - rabbit_types:ok_pid_or_error()). --spec(leave/1 :: (pid()) -> 'ok'). --spec(broadcast/2 :: (pid(), any()) -> 'ok'). --spec(confirmed_broadcast/2 :: (pid(), any()) -> 'ok'). --spec(info/1 :: (pid()) -> rabbit_types:infos()). --spec(validate_members/2 :: (pid(), [pid()]) -> 'ok'). --spec(forget_group/1 :: (group_name()) -> 'ok'). - -%% The joined, members_changed and handle_msg callbacks can all return -%% any of the following terms: -%% -%% 'ok' - the callback function returns normally -%% -%% {'stop', Reason} - the callback indicates the member should stop -%% with reason Reason and should leave the group. -%% -%% {'become', Module, Args} - the callback indicates that the callback -%% module should be changed to Module and that the callback functions -%% should now be passed the arguments Args. This allows the callback -%% module to be dynamically changed. - -%% Called when we've successfully joined the group. Supplied with Args -%% provided in start_link, plus current group members. --callback joined(Args :: term(), Members :: [pid()]) -> - ok | {stop, Reason :: term()} | {become, Module :: atom(), Args :: any()}. - -%% Supplied with Args provided in start_link, the list of new members -%% and the list of members previously known to us that have since -%% died. Note that if a member joins and dies very quickly, it's -%% possible that we will never see that member appear in either births -%% or deaths. However we are guaranteed that (1) we will see a member -%% joining either in the births here, or in the members passed to -%% joined/2 before receiving any messages from it; and (2) we will not -%% see members die that we have not seen born (or supplied in the -%% members to joined/2). --callback members_changed(Args :: term(), - Births :: [pid()], Deaths :: [pid()]) -> - ok | {stop, Reason :: term()} | {become, Module :: atom(), Args :: any()}. - -%% Supplied with Args provided in start_link, the sender, and the -%% message. This does get called for messages injected by this member, -%% however, in such cases, there is no special significance of this -%% invocation: it does not indicate that the message has made it to -%% any other members, let alone all other members. --callback handle_msg(Args :: term(), From :: pid(), Message :: term()) -> - ok | {stop, Reason :: term()} | {become, Module :: atom(), Args :: any()}. - -%% Called on gm member termination as per rules in gen_server, with -%% the Args provided in start_link plus the termination Reason. --callback handle_terminate(Args :: term(), Reason :: term()) -> - ok | term(). - --else. - -behaviour_info(callbacks) -> - [{joined, 2}, {members_changed, 3}, {handle_msg, 3}, {handle_terminate, 2}]; -behaviour_info(_Other) -> - undefined. - --endif. - -create_tables() -> - create_tables([?TABLE]). - -create_tables([]) -> - ok; -create_tables([{Table, Attributes} | Tables]) -> - case mnesia:create_table(Table, Attributes) of - {atomic, ok} -> create_tables(Tables); - {aborted, {already_exists, Table}} -> create_tables(Tables); - Err -> Err - end. - -table_definitions() -> - {Name, Attributes} = ?TABLE, - [{Name, [?TABLE_MATCH | Attributes]}]. - -start_link(GroupName, Module, Args, TxnFun) -> - gen_server2:start_link(?MODULE, [GroupName, Module, Args, TxnFun], []). - -leave(Server) -> - gen_server2:cast(Server, leave). - -broadcast(Server, Msg) -> broadcast(Server, Msg, 0). - -broadcast(Server, Msg, SizeHint) -> - gen_server2:cast(Server, {broadcast, Msg, SizeHint}). - -confirmed_broadcast(Server, Msg) -> - gen_server2:call(Server, {confirmed_broadcast, Msg}, infinity). - -info(Server) -> - gen_server2:call(Server, info, infinity). - -validate_members(Server, Members) -> - gen_server2:cast(Server, {validate_members, Members}). - -forget_group(GroupName) -> - {atomic, ok} = mnesia:sync_transaction( - fun () -> - mnesia:delete({?GROUP_TABLE, GroupName}) - end), - ok. - -init([GroupName, Module, Args, TxnFun]) -> - put(process_name, {?MODULE, GroupName}), - {MegaSecs, Secs, MicroSecs} = now(), - random:seed(MegaSecs, Secs, MicroSecs), - Self = make_member(GroupName), - gen_server2:cast(self(), join), - {ok, #state { self = Self, - left = {Self, undefined}, - right = {Self, undefined}, - group_name = GroupName, - module = Module, - view = undefined, - pub_count = -1, - members_state = undefined, - callback_args = Args, - confirms = queue:new(), - broadcast_buffer = [], - broadcast_buffer_sz = 0, - broadcast_timer = undefined, - txn_executor = TxnFun }, hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. - - -handle_call({confirmed_broadcast, _Msg}, _From, - State = #state { members_state = undefined }) -> - reply(not_joined, State); - -handle_call({confirmed_broadcast, Msg}, _From, - State = #state { self = Self, - right = {Self, undefined}, - module = Module, - callback_args = Args }) -> - handle_callback_result({Module:handle_msg(Args, get_pid(Self), Msg), - ok, State}); - -handle_call({confirmed_broadcast, Msg}, From, State) -> - {Result, State1 = #state { pub_count = PubCount, confirms = Confirms }} = - internal_broadcast(Msg, 0, State), - Confirms1 = queue:in({PubCount, From}, Confirms), - handle_callback_result({Result, flush_broadcast_buffer( - State1 #state { confirms = Confirms1 })}); - -handle_call(info, _From, - State = #state { members_state = undefined }) -> - reply(not_joined, State); - -handle_call(info, _From, State = #state { group_name = GroupName, - module = Module, - view = View }) -> - reply([{group_name, GroupName}, - {module, Module}, - {group_members, get_pids(alive_view_members(View))}], State); - -handle_call({add_on_right, _NewMember}, _From, - State = #state { members_state = undefined }) -> - reply(not_ready, State); - -handle_call({add_on_right, NewMember}, _From, - State = #state { self = Self, - group_name = GroupName, - members_state = MembersState, - txn_executor = TxnFun }) -> - Group = record_new_member_in_group(NewMember, Self, GroupName, TxnFun), - View1 = group_to_view(Group), - MembersState1 = remove_erased_members(MembersState, View1), - ok = send_right(NewMember, View1, - {catchup, Self, prepare_members_state(MembersState1)}), - {Result, State1} = change_view(View1, State #state { - members_state = MembersState1 }), - handle_callback_result({Result, {ok, Group}, State1}). - -%% add_on_right causes a catchup to be sent immediately from the left, -%% so we can never see this from the left neighbour. However, it's -%% possible for the right neighbour to send us a check_neighbours -%% immediately before that. We can't possibly handle it, but if we're -%% in this state we know a catchup is coming imminently anyway. So -%% just ignore it. -handle_cast({?TAG, _ReqVer, check_neighbours}, - State = #state { members_state = undefined }) -> - noreply(State); - -handle_cast({?TAG, ReqVer, Msg}, - State = #state { view = View, - members_state = MembersState, - group_name = GroupName }) -> - {Result, State1} = - case needs_view_update(ReqVer, View) of - true -> View1 = group_to_view(dirty_read_group(GroupName)), - MemberState1 = remove_erased_members(MembersState, View1), - change_view(View1, State #state { - members_state = MemberState1 }); - false -> {ok, State} - end, - handle_callback_result( - if_callback_success( - Result, fun handle_msg_true/3, fun handle_msg_false/3, Msg, State1)); - -handle_cast({broadcast, _Msg, _SizeHint}, - State = #state { members_state = undefined }) -> - noreply(State); - -handle_cast({broadcast, Msg, _SizeHint}, - State = #state { self = Self, - right = {Self, undefined}, - module = Module, - callback_args = Args }) -> - handle_callback_result({Module:handle_msg(Args, get_pid(Self), Msg), - State}); - -handle_cast({broadcast, Msg, SizeHint}, State) -> - {Result, State1} = internal_broadcast(Msg, SizeHint, State), - handle_callback_result({Result, maybe_flush_broadcast_buffer(State1)}); - -handle_cast(join, State = #state { self = Self, - group_name = GroupName, - members_state = undefined, - module = Module, - callback_args = Args, - txn_executor = TxnFun }) -> - View = join_group(Self, GroupName, TxnFun), - MembersState = - case alive_view_members(View) of - [Self] -> blank_member_state(); - _ -> undefined - end, - State1 = check_neighbours(State #state { view = View, - members_state = MembersState }), - handle_callback_result( - {Module:joined(Args, get_pids(all_known_members(View))), State1}); - -handle_cast({validate_members, OldMembers}, - State = #state { view = View, - module = Module, - callback_args = Args }) -> - NewMembers = get_pids(all_known_members(View)), - Births = NewMembers -- OldMembers, - Deaths = OldMembers -- NewMembers, - case {Births, Deaths} of - {[], []} -> noreply(State); - _ -> Result = Module:members_changed(Args, Births, Deaths), - handle_callback_result({Result, State}) - end; - -handle_cast(leave, State) -> - {stop, normal, State}. - - -handle_info(flush, State) -> - noreply( - flush_broadcast_buffer(State #state { broadcast_timer = undefined })); - -handle_info(timeout, State) -> - noreply(flush_broadcast_buffer(State)); - -handle_info({'DOWN', MRef, process, _Pid, Reason}, - State = #state { self = Self, - left = Left, - right = Right, - group_name = GroupName, - confirms = Confirms, - txn_executor = TxnFun }) -> - Member = case {Left, Right} of - {{Member1, MRef}, _} -> Member1; - {_, {Member1, MRef}} -> Member1; - _ -> undefined - end, - case {Member, Reason} of - {undefined, _} -> - noreply(State); - {_, {shutdown, ring_shutdown}} -> - noreply(State); - _ -> - %% In the event of a partial partition we could see another member - %% go down and then remove them from Mnesia. While they can - %% recover from this they'd have to restart the queue - not - %% ideal. So let's sleep here briefly just in case this was caused - %% by a partial partition; in which case by the time we record the - %% member death in Mnesia we will probably be in a full - %% partition and will not be assassinating another member. - timer:sleep(100), - View1 = group_to_view(record_dead_member_in_group( - Member, GroupName, TxnFun)), - handle_callback_result( - case alive_view_members(View1) of - [Self] -> maybe_erase_aliases( - State #state { - members_state = blank_member_state(), - confirms = purge_confirms(Confirms) }, - View1); - _ -> change_view(View1, State) - end) - end. - - -terminate(Reason, State = #state { module = Module, - callback_args = Args }) -> - flush_broadcast_buffer(State), - Module:handle_terminate(Args, Reason). - - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -prioritise_info(flush, _Len, _State) -> - 1; -%% DOWN messages should not overtake initial catchups; if they do we -%% will receive a DOWN we do not know what to do with. -prioritise_info({'DOWN', _MRef, process, _Pid, _Reason}, _Len, - #state { members_state = undefined }) -> - 0; -%% We should not prioritise DOWN messages from our left since -%% otherwise the DOWN can overtake any last activity from the left, -%% causing that activity to be lost. -prioritise_info({'DOWN', _MRef, process, LeftPid, _Reason}, _Len, - #state { left = {{_LeftVer, LeftPid}, _MRef2} }) -> - 0; -%% But prioritise all other DOWNs - we want to make sure we are not -%% sending activity into the void for too long because our right is -%% down but we don't know it. -prioritise_info({'DOWN', _MRef, process, _Pid, _Reason}, _Len, _State) -> - 1; -prioritise_info(_, _Len, _State) -> - 0. - - -handle_msg(check_neighbours, State) -> - %% no-op - it's already been done by the calling handle_cast - {ok, State}; - -handle_msg({catchup, Left, MembersStateLeft}, - State = #state { self = Self, - left = {Left, _MRefL}, - right = {Right, _MRefR}, - view = View, - members_state = undefined }) -> - ok = send_right(Right, View, {catchup, Self, MembersStateLeft}), - MembersStateLeft1 = build_members_state(MembersStateLeft), - {ok, State #state { members_state = MembersStateLeft1 }}; - -handle_msg({catchup, Left, MembersStateLeft}, - State = #state { self = Self, - left = {Left, _MRefL}, - view = View, - members_state = MembersState }) - when MembersState =/= undefined -> - MembersStateLeft1 = build_members_state(MembersStateLeft), - AllMembers = lists:usort(?DICT:fetch_keys(MembersState) ++ - ?DICT:fetch_keys(MembersStateLeft1)), - {MembersState1, Activity} = - lists:foldl( - fun (Id, MembersStateActivity) -> - #member { pending_ack = PALeft, last_ack = LA } = - find_member_or_blank(Id, MembersStateLeft1), - with_member_acc( - fun (#member { pending_ack = PA } = Member, Activity1) -> - case is_member_alias(Id, Self, View) of - true -> - {_AcksInFlight, Pubs, _PA1} = - find_prefix_common_suffix(PALeft, PA), - {Member #member { last_ack = LA }, - activity_cons(Id, pubs_from_queue(Pubs), - [], Activity1)}; - false -> - {Acks, _Common, Pubs} = - find_prefix_common_suffix(PA, PALeft), - {Member, - activity_cons(Id, pubs_from_queue(Pubs), - acks_from_queue(Acks), - Activity1)} - end - end, Id, MembersStateActivity) - end, {MembersState, activity_nil()}, AllMembers), - handle_msg({activity, Left, activity_finalise(Activity)}, - State #state { members_state = MembersState1 }); - -handle_msg({catchup, _NotLeft, _MembersState}, State) -> - {ok, State}; - -handle_msg({activity, Left, Activity}, - State = #state { self = Self, - left = {Left, _MRefL}, - view = View, - members_state = MembersState, - confirms = Confirms }) - when MembersState =/= undefined -> - {MembersState1, {Confirms1, Activity1}} = - lists:foldl( - fun ({Id, Pubs, Acks}, MembersStateConfirmsActivity) -> - with_member_acc( - fun (Member = #member { pending_ack = PA, - last_pub = LP, - last_ack = LA }, - {Confirms2, Activity2}) -> - case is_member_alias(Id, Self, View) of - true -> - {ToAck, PA1} = - find_common(queue_from_pubs(Pubs), PA, - queue:new()), - LA1 = last_ack(Acks, LA), - AckNums = acks_from_queue(ToAck), - Confirms3 = maybe_confirm( - Self, Id, Confirms2, AckNums), - {Member #member { pending_ack = PA1, - last_ack = LA1 }, - {Confirms3, - activity_cons( - Id, [], AckNums, Activity2)}}; - false -> - PA1 = apply_acks(Acks, join_pubs(PA, Pubs)), - LA1 = last_ack(Acks, LA), - LP1 = last_pub(Pubs, LP), - {Member #member { pending_ack = PA1, - last_pub = LP1, - last_ack = LA1 }, - {Confirms2, - activity_cons(Id, Pubs, Acks, Activity2)}} - end - end, Id, MembersStateConfirmsActivity) - end, {MembersState, {Confirms, activity_nil()}}, Activity), - State1 = State #state { members_state = MembersState1, - confirms = Confirms1 }, - Activity3 = activity_finalise(Activity1), - ok = maybe_send_activity(Activity3, State1), - {Result, State2} = maybe_erase_aliases(State1, View), - if_callback_success( - Result, fun activity_true/3, fun activity_false/3, Activity3, State2); - -handle_msg({activity, _NotLeft, _Activity}, State) -> - {ok, State}. - - -noreply(State) -> - {noreply, ensure_broadcast_timer(State), flush_timeout(State)}. - -reply(Reply, State) -> - {reply, Reply, ensure_broadcast_timer(State), flush_timeout(State)}. - -flush_timeout(#state{broadcast_buffer = []}) -> hibernate; -flush_timeout(_) -> 0. - -ensure_broadcast_timer(State = #state { broadcast_buffer = [], - broadcast_timer = undefined }) -> - State; -ensure_broadcast_timer(State = #state { broadcast_buffer = [], - broadcast_timer = TRef }) -> - erlang:cancel_timer(TRef), - State #state { broadcast_timer = undefined }; -ensure_broadcast_timer(State = #state { broadcast_timer = undefined }) -> - TRef = erlang:send_after(?BROADCAST_TIMER, self(), flush), - State #state { broadcast_timer = TRef }; -ensure_broadcast_timer(State) -> - State. - -internal_broadcast(Msg, SizeHint, - State = #state { self = Self, - pub_count = PubCount, - module = Module, - callback_args = Args, - broadcast_buffer = Buffer, - broadcast_buffer_sz = BufferSize }) -> - PubCount1 = PubCount + 1, - {Module:handle_msg(Args, get_pid(Self), Msg), - State #state { pub_count = PubCount1, - broadcast_buffer = [{PubCount1, Msg} | Buffer], - broadcast_buffer_sz = BufferSize + SizeHint}}. - -%% The Erlang distribution mechanism has an interesting quirk - it -%% will kill the VM cold with "Absurdly large distribution output data -%% buffer" if you attempt to send a message which serialises out to -%% more than 2^31 bytes in size. It's therefore a very good idea to -%% make sure that we don't exceed that size! -%% -%% Now, we could figure out the size of messages as they come in using -%% size(term_to_binary(Msg)) or similar. The trouble is, that requires -%% us to serialise the message only to throw the serialised form -%% away. Hard to believe that's a sensible thing to do. So instead we -%% accept a size hint from the application, via broadcast/3. This size -%% hint can be the size of anything in the message which we expect -%% could be large, and we just ignore the size of any small bits of -%% the message term. Therefore MAX_BUFFER_SIZE is set somewhat -%% conservatively at 100MB - but the buffer is only to allow us to -%% buffer tiny messages anyway, so 100MB is plenty. - -maybe_flush_broadcast_buffer(State = #state{broadcast_buffer_sz = Size}) -> - case Size > ?MAX_BUFFER_SIZE of - true -> flush_broadcast_buffer(State); - false -> State - end. - -flush_broadcast_buffer(State = #state { broadcast_buffer = [] }) -> - State; -flush_broadcast_buffer(State = #state { self = Self, - members_state = MembersState, - broadcast_buffer = Buffer, - pub_count = PubCount }) -> - [{PubCount, _Msg}|_] = Buffer, %% ASSERTION match on PubCount - Pubs = lists:reverse(Buffer), - Activity = activity_cons(Self, Pubs, [], activity_nil()), - ok = maybe_send_activity(activity_finalise(Activity), State), - MembersState1 = with_member( - fun (Member = #member { pending_ack = PA }) -> - PA1 = queue:join(PA, queue:from_list(Pubs)), - Member #member { pending_ack = PA1, - last_pub = PubCount } - end, Self, MembersState), - State #state { members_state = MembersState1, - broadcast_buffer = [], - broadcast_buffer_sz = 0}. - - -%% --------------------------------------------------------------------------- -%% View construction and inspection -%% --------------------------------------------------------------------------- - -needs_view_update(ReqVer, {Ver, _View}) -> Ver < ReqVer. - -view_version({Ver, _View}) -> Ver. - -is_member_alive({dead, _Member}) -> false; -is_member_alive(_) -> true. - -is_member_alias(Self, Self, _View) -> - true; -is_member_alias(Member, Self, View) -> - ?SETS:is_element(Member, - ((fetch_view_member(Self, View)) #view_member.aliases)). - -dead_member_id({dead, Member}) -> Member. - -store_view_member(VMember = #view_member { id = Id }, {Ver, View}) -> - {Ver, ?DICT:store(Id, VMember, View)}. - -with_view_member(Fun, View, Id) -> - store_view_member(Fun(fetch_view_member(Id, View)), View). - -fetch_view_member(Id, {_Ver, View}) -> ?DICT:fetch(Id, View). - -find_view_member(Id, {_Ver, View}) -> ?DICT:find(Id, View). - -blank_view(Ver) -> {Ver, ?DICT:new()}. - -alive_view_members({_Ver, View}) -> ?DICT:fetch_keys(View). - -all_known_members({_Ver, View}) -> - ?DICT:fold( - fun (Member, #view_member { aliases = Aliases }, Acc) -> - ?SETS:to_list(Aliases) ++ [Member | Acc] - end, [], View). - -group_to_view(#gm_group { members = Members, version = Ver }) -> - Alive = lists:filter(fun is_member_alive/1, Members), - [_|_] = Alive, %% ASSERTION - can't have all dead members - add_aliases(link_view(Alive ++ Alive ++ Alive, blank_view(Ver)), Members). - -link_view([Left, Middle, Right | Rest], View) -> - case find_view_member(Middle, View) of - error -> - link_view( - [Middle, Right | Rest], - store_view_member(#view_member { id = Middle, - aliases = ?SETS:new(), - left = Left, - right = Right }, View)); - {ok, _} -> - View - end; -link_view(_, View) -> - View. - -add_aliases(View, Members) -> - Members1 = ensure_alive_suffix(Members), - {EmptyDeadSet, View1} = - lists:foldl( - fun (Member, {DeadAcc, ViewAcc}) -> - case is_member_alive(Member) of - true -> - {?SETS:new(), - with_view_member( - fun (VMember = - #view_member { aliases = Aliases }) -> - VMember #view_member { - aliases = ?SETS:union(Aliases, DeadAcc) } - end, ViewAcc, Member)}; - false -> - {?SETS:add_element(dead_member_id(Member), DeadAcc), - ViewAcc} - end - end, {?SETS:new(), View}, Members1), - 0 = ?SETS:size(EmptyDeadSet), %% ASSERTION - View1. - -ensure_alive_suffix(Members) -> - queue:to_list(ensure_alive_suffix1(queue:from_list(Members))). - -ensure_alive_suffix1(MembersQ) -> - {{value, Member}, MembersQ1} = queue:out_r(MembersQ), - case is_member_alive(Member) of - true -> MembersQ; - false -> ensure_alive_suffix1(queue:in_r(Member, MembersQ1)) - end. - - -%% --------------------------------------------------------------------------- -%% View modification -%% --------------------------------------------------------------------------- - -join_group(Self, GroupName, TxnFun) -> - join_group(Self, GroupName, dirty_read_group(GroupName), TxnFun). - -join_group(Self, GroupName, {error, not_found}, TxnFun) -> - join_group(Self, GroupName, - prune_or_create_group(Self, GroupName, TxnFun), TxnFun); -join_group(Self, _GroupName, #gm_group { members = [Self] } = Group, _TxnFun) -> - group_to_view(Group); -join_group(Self, GroupName, #gm_group { members = Members } = Group, TxnFun) -> - case lists:member(Self, Members) of - true -> - group_to_view(Group); - false -> - case lists:filter(fun is_member_alive/1, Members) of - [] -> - join_group(Self, GroupName, - prune_or_create_group(Self, GroupName, TxnFun), - TxnFun); - Alive -> - Left = lists:nth(random:uniform(length(Alive)), Alive), - Handler = - fun () -> - join_group( - Self, GroupName, - record_dead_member_in_group( - Left, GroupName, TxnFun), - TxnFun) - end, - try - case neighbour_call(Left, {add_on_right, Self}) of - {ok, Group1} -> group_to_view(Group1); - not_ready -> join_group(Self, GroupName, TxnFun) - end - catch - exit:{R, _} - when R =:= noproc; R =:= normal; R =:= shutdown -> - Handler(); - exit:{{R, _}, _} - when R =:= nodedown; R =:= shutdown -> - Handler() - end - end - end. - -dirty_read_group(GroupName) -> - case mnesia:dirty_read(?GROUP_TABLE, GroupName) of - [] -> {error, not_found}; - [Group] -> Group - end. - -read_group(GroupName) -> - case mnesia:read({?GROUP_TABLE, GroupName}) of - [] -> {error, not_found}; - [Group] -> Group - end. - -write_group(Group) -> mnesia:write(?GROUP_TABLE, Group, write), Group. - -prune_or_create_group(Self, GroupName, TxnFun) -> - TxnFun( - fun () -> - GroupNew = #gm_group { name = GroupName, - members = [Self], - version = get_version(Self) }, - case read_group(GroupName) of - {error, not_found} -> - write_group(GroupNew); - Group = #gm_group { members = Members } -> - case lists:any(fun is_member_alive/1, Members) of - true -> Group; - false -> write_group(GroupNew) - end - end - end). - -record_dead_member_in_group(Member, GroupName, TxnFun) -> - TxnFun( - fun () -> - Group = #gm_group { members = Members, version = Ver } = - read_group(GroupName), - case lists:splitwith( - fun (Member1) -> Member1 =/= Member end, Members) of - {_Members1, []} -> %% not found - already recorded dead - Group; - {Members1, [Member | Members2]} -> - Members3 = Members1 ++ [{dead, Member} | Members2], - write_group(Group #gm_group { members = Members3, - version = Ver + 1 }) - end - end). - -record_new_member_in_group(NewMember, Left, GroupName, TxnFun) -> - TxnFun( - fun () -> - Group = #gm_group { members = Members, version = Ver } = - read_group(GroupName), - {Prefix, [Left | Suffix]} = - lists:splitwith(fun (M) -> M =/= Left end, Members), - write_group(Group #gm_group { - members = Prefix ++ [Left, NewMember | Suffix], - version = Ver + 1 }) - end). - -erase_members_in_group(Members, GroupName, TxnFun) -> - DeadMembers = [{dead, Id} || Id <- Members], - TxnFun( - fun () -> - Group = #gm_group { members = [_|_] = Members1, version = Ver } = - read_group(GroupName), - case Members1 -- DeadMembers of - Members1 -> Group; - Members2 -> write_group( - Group #gm_group { members = Members2, - version = Ver + 1 }) - end - end). - -maybe_erase_aliases(State = #state { self = Self, - group_name = GroupName, - members_state = MembersState, - txn_executor = TxnFun }, View) -> - #view_member { aliases = Aliases } = fetch_view_member(Self, View), - {Erasable, MembersState1} - = ?SETS:fold( - fun (Id, {ErasableAcc, MembersStateAcc} = Acc) -> - #member { last_pub = LP, last_ack = LA } = - find_member_or_blank(Id, MembersState), - case can_erase_view_member(Self, Id, LA, LP) of - true -> {[Id | ErasableAcc], - erase_member(Id, MembersStateAcc)}; - false -> Acc - end - end, {[], MembersState}, Aliases), - View1 = case Erasable of - [] -> View; - _ -> group_to_view( - erase_members_in_group(Erasable, GroupName, TxnFun)) - end, - change_view(View1, State #state { members_state = MembersState1 }). - -can_erase_view_member(Self, Self, _LA, _LP) -> false; -can_erase_view_member(_Self, _Id, N, N) -> true; -can_erase_view_member(_Self, _Id, _LA, _LP) -> false. - -neighbour_cast(N, Msg) -> ?INSTR_MOD:cast(get_pid(N), Msg). -neighbour_call(N, Msg) -> ?INSTR_MOD:call(get_pid(N), Msg, infinity). - -%% --------------------------------------------------------------------------- -%% View monitoring and maintanence -%% --------------------------------------------------------------------------- - -ensure_neighbour(_Ver, Self, {Self, undefined}, Self) -> - {Self, undefined}; -ensure_neighbour(Ver, Self, {Self, undefined}, RealNeighbour) -> - ok = neighbour_cast(RealNeighbour, {?TAG, Ver, check_neighbours}), - {RealNeighbour, maybe_monitor(RealNeighbour, Self)}; -ensure_neighbour(_Ver, _Self, {RealNeighbour, MRef}, RealNeighbour) -> - {RealNeighbour, MRef}; -ensure_neighbour(Ver, Self, {RealNeighbour, MRef}, Neighbour) -> - true = ?INSTR_MOD:demonitor(MRef), - Msg = {?TAG, Ver, check_neighbours}, - ok = neighbour_cast(RealNeighbour, Msg), - ok = case Neighbour of - Self -> ok; - _ -> neighbour_cast(Neighbour, Msg) - end, - {Neighbour, maybe_monitor(Neighbour, Self)}. - -maybe_monitor( Self, Self) -> undefined; -maybe_monitor(Other, _Self) -> ?INSTR_MOD:monitor(get_pid(Other)). - -check_neighbours(State = #state { self = Self, - left = Left, - right = Right, - view = View, - broadcast_buffer = Buffer }) -> - #view_member { left = VLeft, right = VRight } - = fetch_view_member(Self, View), - Ver = view_version(View), - Left1 = ensure_neighbour(Ver, Self, Left, VLeft), - Right1 = ensure_neighbour(Ver, Self, Right, VRight), - Buffer1 = case Right1 of - {Self, undefined} -> []; - _ -> Buffer - end, - State1 = State #state { left = Left1, right = Right1, - broadcast_buffer = Buffer1 }, - ok = maybe_send_catchup(Right, State1), - State1. - -maybe_send_catchup(Right, #state { right = Right }) -> - ok; -maybe_send_catchup(_Right, #state { self = Self, - right = {Self, undefined} }) -> - ok; -maybe_send_catchup(_Right, #state { members_state = undefined }) -> - ok; -maybe_send_catchup(_Right, #state { self = Self, - right = {Right, _MRef}, - view = View, - members_state = MembersState }) -> - send_right(Right, View, - {catchup, Self, prepare_members_state(MembersState)}). - - -%% --------------------------------------------------------------------------- -%% Catch_up delta detection -%% --------------------------------------------------------------------------- - -find_prefix_common_suffix(A, B) -> - {Prefix, A1} = find_prefix(A, B, queue:new()), - {Common, Suffix} = find_common(A1, B, queue:new()), - {Prefix, Common, Suffix}. - -%% Returns the elements of A that occur before the first element of B, -%% plus the remainder of A. -find_prefix(A, B, Prefix) -> - case {queue:out(A), queue:out(B)} of - {{{value, Val}, _A1}, {{value, Val}, _B1}} -> - {Prefix, A}; - {{empty, A1}, {{value, _A}, _B1}} -> - {Prefix, A1}; - {{{value, {NumA, _MsgA} = Val}, A1}, - {{value, {NumB, _MsgB}}, _B1}} when NumA < NumB -> - find_prefix(A1, B, queue:in(Val, Prefix)); - {_, {empty, _B1}} -> - {A, Prefix} %% Prefix well be empty here - end. - -%% A should be a prefix of B. Returns the commonality plus the -%% remainder of B. -find_common(A, B, Common) -> - case {queue:out(A), queue:out(B)} of - {{{value, Val}, A1}, {{value, Val}, B1}} -> - find_common(A1, B1, queue:in(Val, Common)); - {{empty, _A}, _} -> - {Common, B} - end. - - -%% --------------------------------------------------------------------------- -%% Members helpers -%% --------------------------------------------------------------------------- - -with_member(Fun, Id, MembersState) -> - store_member( - Id, Fun(find_member_or_blank(Id, MembersState)), MembersState). - -with_member_acc(Fun, Id, {MembersState, Acc}) -> - {MemberState, Acc1} = Fun(find_member_or_blank(Id, MembersState), Acc), - {store_member(Id, MemberState, MembersState), Acc1}. - -find_member_or_blank(Id, MembersState) -> - case ?DICT:find(Id, MembersState) of - {ok, Result} -> Result; - error -> blank_member() - end. - -erase_member(Id, MembersState) -> ?DICT:erase(Id, MembersState). - -blank_member() -> - #member { pending_ack = queue:new(), last_pub = -1, last_ack = -1 }. - -blank_member_state() -> ?DICT:new(). - -store_member(Id, MemberState, MembersState) -> - ?DICT:store(Id, MemberState, MembersState). - -prepare_members_state(MembersState) -> ?DICT:to_list(MembersState). - -build_members_state(MembersStateList) -> ?DICT:from_list(MembersStateList). - -make_member(GroupName) -> - {case dirty_read_group(GroupName) of - #gm_group { version = Version } -> Version; - {error, not_found} -> ?VERSION_START - end, self()}. - -remove_erased_members(MembersState, View) -> - lists:foldl(fun (Id, MembersState1) -> - store_member(Id, find_member_or_blank(Id, MembersState), - MembersState1) - end, blank_member_state(), all_known_members(View)). - -get_version({Version, _Pid}) -> Version. - -get_pid({_Version, Pid}) -> Pid. - -get_pids(Ids) -> [Pid || {_Version, Pid} <- Ids]. - -%% --------------------------------------------------------------------------- -%% Activity assembly -%% --------------------------------------------------------------------------- - -activity_nil() -> queue:new(). - -activity_cons( _Id, [], [], Tail) -> Tail; -activity_cons(Sender, Pubs, Acks, Tail) -> queue:in({Sender, Pubs, Acks}, Tail). - -activity_finalise(Activity) -> queue:to_list(Activity). - -maybe_send_activity([], _State) -> - ok; -maybe_send_activity(Activity, #state { self = Self, - right = {Right, _MRefR}, - view = View }) -> - send_right(Right, View, {activity, Self, Activity}). - -send_right(Right, View, Msg) -> - ok = neighbour_cast(Right, {?TAG, view_version(View), Msg}). - -callback(Args, Module, Activity) -> - Result = - lists:foldl( - fun ({Id, Pubs, _Acks}, {Args1, Module1, ok}) -> - lists:foldl(fun ({_PubNum, Pub}, Acc = {Args2, Module2, ok}) -> - case Module2:handle_msg( - Args2, get_pid(Id), Pub) of - ok -> - Acc; - {become, Module3, Args3} -> - {Args3, Module3, ok}; - {stop, _Reason} = Error -> - Error - end; - (_, Error = {stop, _Reason}) -> - Error - end, {Args1, Module1, ok}, Pubs); - (_, Error = {stop, _Reason}) -> - Error - end, {Args, Module, ok}, Activity), - case Result of - {Args, Module, ok} -> ok; - {Args1, Module1, ok} -> {become, Module1, Args1}; - {stop, _Reason} = Error -> Error - end. - -change_view(View, State = #state { view = View0, - module = Module, - callback_args = Args }) -> - OldMembers = all_known_members(View0), - NewMembers = all_known_members(View), - Births = NewMembers -- OldMembers, - Deaths = OldMembers -- NewMembers, - Result = case {Births, Deaths} of - {[], []} -> ok; - _ -> Module:members_changed( - Args, get_pids(Births), get_pids(Deaths)) - end, - {Result, check_neighbours(State #state { view = View })}. - -handle_callback_result({Result, State}) -> - if_callback_success( - Result, fun no_reply_true/3, fun no_reply_false/3, undefined, State); -handle_callback_result({Result, Reply, State}) -> - if_callback_success( - Result, fun reply_true/3, fun reply_false/3, Reply, State). - -no_reply_true (_Result, _Undefined, State) -> noreply(State). -no_reply_false({stop, Reason}, _Undefined, State) -> {stop, Reason, State}. - -reply_true (_Result, Reply, State) -> reply(Reply, State). -reply_false({stop, Reason}, Reply, State) -> {stop, Reason, Reply, State}. - -handle_msg_true (_Result, Msg, State) -> handle_msg(Msg, State). -handle_msg_false(Result, _Msg, State) -> {Result, State}. - -activity_true(_Result, Activity, State = #state { module = Module, - callback_args = Args }) -> - {callback(Args, Module, Activity), State}. -activity_false(Result, _Activity, State) -> - {Result, State}. - -if_callback_success(ok, True, _False, Arg, State) -> - True(ok, Arg, State); -if_callback_success( - {become, Module, Args} = Result, True, _False, Arg, State) -> - True(Result, Arg, State #state { module = Module, - callback_args = Args }); -if_callback_success({stop, _Reason} = Result, _True, False, Arg, State) -> - False(Result, Arg, State). - -maybe_confirm(_Self, _Id, Confirms, []) -> - Confirms; -maybe_confirm(Self, Self, Confirms, [PubNum | PubNums]) -> - case queue:out(Confirms) of - {empty, _Confirms} -> - Confirms; - {{value, {PubNum, From}}, Confirms1} -> - gen_server2:reply(From, ok), - maybe_confirm(Self, Self, Confirms1, PubNums); - {{value, {PubNum1, _From}}, _Confirms} when PubNum1 > PubNum -> - maybe_confirm(Self, Self, Confirms, PubNums) - end; -maybe_confirm(_Self, _Id, Confirms, _PubNums) -> - Confirms. - -purge_confirms(Confirms) -> - [gen_server2:reply(From, ok) || {_PubNum, From} <- queue:to_list(Confirms)], - queue:new(). - - -%% --------------------------------------------------------------------------- -%% Msg transformation -%% --------------------------------------------------------------------------- - -acks_from_queue(Q) -> [PubNum || {PubNum, _Msg} <- queue:to_list(Q)]. - -pubs_from_queue(Q) -> queue:to_list(Q). - -queue_from_pubs(Pubs) -> queue:from_list(Pubs). - -apply_acks( [], Pubs) -> Pubs; -apply_acks(List, Pubs) -> {_, Pubs1} = queue:split(length(List), Pubs), - Pubs1. - -join_pubs(Q, []) -> Q; -join_pubs(Q, Pubs) -> queue:join(Q, queue_from_pubs(Pubs)). - -last_ack( [], LA) -> LA; -last_ack(List, LA) -> LA1 = lists:last(List), - true = LA1 > LA, %% ASSERTION - LA1. - -last_pub( [], LP) -> LP; -last_pub(List, LP) -> {PubNum, _Msg} = lists:last(List), - true = PubNum > LP, %% ASSERTION - PubNum. - -%% --------------------------------------------------------------------------- - -%% Uninstrumented versions - -call(Pid, Msg, Timeout) -> gen_server2:call(Pid, Msg, Timeout). -cast(Pid, Msg) -> gen_server2:cast(Pid, Msg). -monitor(Pid) -> erlang:monitor(process, Pid). -demonitor(MRef) -> erlang:demonitor(MRef). diff --git a/src/lqueue.erl b/src/lqueue.erl deleted file mode 100644 index 62f60d5f..00000000 --- a/src/lqueue.erl +++ /dev/null @@ -1,90 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. -%% - --module(lqueue). - --export([new/0, is_empty/1, len/1, in/2, in_r/2, out/1, out_r/1, join/2, - foldl/3, foldr/3, from_list/1, to_list/1, peek/1, peek_r/1]). - --define(QUEUE, queue). - --ifdef(use_specs). - --export_type([?MODULE/0]). - --opaque(?MODULE() :: {non_neg_integer(), ?QUEUE:?QUEUE()}). --type(value() :: any()). --type(result() :: 'empty' | {'value', value()}). - --spec(new/0 :: () -> ?MODULE()). --spec(is_empty/1 :: (?MODULE()) -> boolean()). --spec(len/1 :: (?MODULE()) -> non_neg_integer()). --spec(in/2 :: (value(), ?MODULE()) -> ?MODULE()). --spec(in_r/2 :: (value(), ?MODULE()) -> ?MODULE()). --spec(out/1 :: (?MODULE()) -> {result(), ?MODULE()}). --spec(out_r/1 :: (?MODULE()) -> {result(), ?MODULE()}). --spec(join/2 :: (?MODULE(), ?MODULE()) -> ?MODULE()). --spec(foldl/3 :: (fun ((value(), B) -> B), B, ?MODULE()) -> B). --spec(foldr/3 :: (fun ((value(), B) -> B), B, ?MODULE()) -> B). --spec(from_list/1 :: ([value()]) -> ?MODULE()). --spec(to_list/1 :: (?MODULE()) -> [value()]). --spec(peek/1 :: (?MODULE()) -> result()). --spec(peek_r/1 :: (?MODULE()) -> result()). - --endif. - -new() -> {0, ?QUEUE:new()}. - -is_empty({0, _Q}) -> true; -is_empty(_) -> false. - -in(V, {L, Q}) -> {L+1, ?QUEUE:in(V, Q)}. - -in_r(V, {L, Q}) -> {L+1, ?QUEUE:in_r(V, Q)}. - -out({0, _Q} = Q) -> {empty, Q}; -out({L, Q}) -> {Result, Q1} = ?QUEUE:out(Q), - {Result, {L-1, Q1}}. - -out_r({0, _Q} = Q) -> {empty, Q}; -out_r({L, Q}) -> {Result, Q1} = ?QUEUE:out_r(Q), - {Result, {L-1, Q1}}. - -join({L1, Q1}, {L2, Q2}) -> {L1 + L2, ?QUEUE:join(Q1, Q2)}. - -to_list({_L, Q}) -> ?QUEUE:to_list(Q). - -from_list(L) -> {length(L), ?QUEUE:from_list(L)}. - -foldl(Fun, Init, Q) -> - case out(Q) of - {empty, _Q} -> Init; - {{value, V}, Q1} -> foldl(Fun, Fun(V, Init), Q1) - end. - -foldr(Fun, Init, Q) -> - case out_r(Q) of - {empty, _Q} -> Init; - {{value, V}, Q1} -> foldr(Fun, Fun(V, Init), Q1) - end. - -len({L, _Q}) -> L. - -peek({ 0, _Q}) -> empty; -peek({_L, Q}) -> ?QUEUE:peek(Q). - -peek_r({ 0, _Q}) -> empty; -peek_r({_L, Q}) -> ?QUEUE:peek_r(Q). diff --git a/src/mirrored_supervisor.erl b/src/mirrored_supervisor.erl deleted file mode 100644 index 289b9cc4..00000000 --- a/src/mirrored_supervisor.erl +++ /dev/null @@ -1,517 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. -%% - --module(mirrored_supervisor). - -%% Mirrored Supervisor -%% =================== -%% -%% This module implements a new type of supervisor. It acts like a -%% normal supervisor, but at creation time you also provide the name -%% of a process group to join. All the supervisors within the -%% process group act like a single large distributed supervisor: -%% -%% * A process with a given child_id will only exist on one -%% supervisor within the group. -%% -%% * If one supervisor fails, children may migrate to surviving -%% supervisors within the group. -%% -%% In almost all cases you will want to use the module name for the -%% process group. Using multiple process groups with the same module -%% name is supported. Having multiple module names for the same -%% process group will lead to undefined behaviour. -%% -%% Motivation -%% ---------- -%% -%% Sometimes you have processes which: -%% -%% * Only need to exist once per cluster. -%% -%% * Does not contain much state (or can reconstruct its state easily). -%% -%% * Needs to be restarted elsewhere should it be running on a node -%% which fails. -%% -%% By creating a mirrored supervisor group with one supervisor on -%% each node, that's what you get. -%% -%% -%% API use -%% ------- -%% -%% This is basically the same as for supervisor, except that: -%% -%% 1) start_link(Module, Args) becomes -%% start_link(Group, TxFun, Module, Args). -%% -%% 2) start_link({local, Name}, Module, Args) becomes -%% start_link({local, Name}, Group, TxFun, Module, Args). -%% -%% 3) start_link({global, Name}, Module, Args) is not available. -%% -%% 4) The restart strategy simple_one_for_one is not available. -%% -%% 5) Mnesia is used to hold global state. At some point your -%% application should invoke create_tables() (or table_definitions() -%% if it wants to manage table creation itself). -%% -%% The TxFun parameter to start_link/{4,5} is a function which the -%% mirrored supervisor can use to execute Mnesia transactions. In the -%% RabbitMQ server this goes via a worker pool; in other cases a -%% function like: -%% -%% tx_fun(Fun) -> -%% case mnesia:sync_transaction(Fun) of -%% {atomic, Result} -> Result; -%% {aborted, Reason} -> throw({error, Reason}) -%% end. -%% -%% could be used. -%% -%% Internals -%% --------- -%% -%% Each mirrored_supervisor consists of three processes - the overall -%% supervisor, the delegate supervisor and the mirroring server. The -%% overall supervisor supervises the other two processes. Its pid is -%% the one returned from start_link; the pids of the other two -%% processes are effectively hidden in the API. -%% -%% The delegate supervisor is in charge of supervising all the child -%% processes that are added to the supervisor as usual. -%% -%% The mirroring server intercepts calls to the supervisor API -%% (directed at the overall supervisor), does any special handling, -%% and forwards everything to the delegate supervisor. -%% -%% This module implements all three, hence init/1 is somewhat overloaded. -%% -%% The mirroring server creates and joins a process group on -%% startup. It monitors all the existing members of this group, and -%% broadcasts a "hello" message to them so that they can monitor it in -%% turn. When it receives a 'DOWN' message, it checks to see if it's -%% the "first" server in the group and restarts all the child -%% processes from the dead supervisor if so. -%% -%% In the future we might load balance this. -%% -%% Startup is slightly fiddly. The mirroring server needs to know the -%% Pid of the overall supervisor, but we don't have that until it has -%% started. Therefore we set this after the fact. We also start any -%% children we found in Module:init() at this point, since starting -%% children requires knowing the overall supervisor pid. - --define(SUPERVISOR, supervisor2). --define(GEN_SERVER, gen_server2). --define(PG2, pg2_fixed). --define(SUP_MODULE, mirrored_supervisor_sups). - --define(TABLE, mirrored_sup_childspec). --define(TABLE_DEF, - {?TABLE, - [{record_name, mirrored_sup_childspec}, - {type, ordered_set}, - {attributes, record_info(fields, mirrored_sup_childspec)}]}). --define(TABLE_MATCH, {match, #mirrored_sup_childspec{ _ = '_' }}). - --export([start_link/4, start_link/5, - start_child/2, restart_child/2, - delete_child/2, terminate_child/2, - which_children/1, count_children/1, check_childspecs/1]). - --behaviour(?GEN_SERVER). - --export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3, - handle_cast/2]). - --export([start_internal/3]). --export([create_tables/0, table_definitions/0]). - --record(mirrored_sup_childspec, {key, mirroring_pid, childspec}). - --record(state, {overall, - delegate, - group, - tx_fun, - initial_childspecs}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - -%%-------------------------------------------------------------------------- -%% Callback behaviour -%%-------------------------------------------------------------------------- - --callback init(Args :: term()) -> - {ok, {{RestartStrategy :: supervisor2:strategy(), - MaxR :: non_neg_integer(), - MaxT :: non_neg_integer()}, - [ChildSpec :: supervisor2:child_spec()]}} - | ignore. - -%%-------------------------------------------------------------------------- -%% Specs -%%-------------------------------------------------------------------------- - --type startlink_err() :: {'already_started', pid()} | 'shutdown' | term(). --type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}. - --type group_name() :: any(). - --type(tx_fun() :: fun((fun(() -> A)) -> A)). - --spec start_link(GroupName, TxFun, Module, Args) -> startlink_ret() when - GroupName :: group_name(), - TxFun :: tx_fun(), - Module :: module(), - Args :: term(). - --spec start_link(SupName, GroupName, TxFun, Module, Args) -> - startlink_ret() when - SupName :: supervisor2:sup_name(), - GroupName :: group_name(), - TxFun :: tx_fun(), - Module :: module(), - Args :: term(). - --spec start_internal(Group, TxFun, ChildSpecs) -> Result when - Group :: group_name(), - TxFun :: tx_fun(), - ChildSpecs :: [supervisor2:child_spec()], - Result :: {'ok', pid()} | {'error', term()}. - --spec create_tables() -> Result when - Result :: 'ok'. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> [{init,1}]; -behaviour_info(_Other) -> undefined. - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Group, TxFun, Mod, Args) -> - start_link0([], Group, TxFun, init(Mod, Args)). - -start_link({local, SupName}, Group, TxFun, Mod, Args) -> - start_link0([{local, SupName}], Group, TxFun, init(Mod, Args)); - -start_link({global, _SupName}, _Group, _TxFun, _Mod, _Args) -> - erlang:error(badarg). - -start_link0(Prefix, Group, TxFun, Init) -> - case apply(?SUPERVISOR, start_link, - Prefix ++ [?SUP_MODULE, {overall, Group, TxFun, Init}]) of - {ok, Pid} -> case catch call(Pid, {init, Pid}) of - ok -> {ok, Pid}; - E -> E - end; - Other -> Other - end. - -init(Mod, Args) -> - case Mod:init(Args) of - {ok, {{Bad, _, _}, _ChildSpecs}} when - Bad =:= simple_one_for_one -> erlang:error(badarg); - Init -> Init - end. - -start_child(Sup, ChildSpec) -> call(Sup, {start_child, ChildSpec}). -delete_child(Sup, Id) -> find_call(Sup, Id, {delete_child, Id}). -restart_child(Sup, Id) -> find_call(Sup, Id, {msg, restart_child, [Id]}). -terminate_child(Sup, Id) -> find_call(Sup, Id, {msg, terminate_child, [Id]}). -which_children(Sup) -> fold(which_children, Sup, fun lists:append/2). -count_children(Sup) -> fold(count_children, Sup, fun add_proplists/2). -check_childspecs(Specs) -> ?SUPERVISOR:check_childspecs(Specs). - -call(Sup, Msg) -> ?GEN_SERVER:call(mirroring(Sup), Msg, infinity). -cast(Sup, Msg) -> with_exit_handler( - fun() -> ok end, - fun() -> ?GEN_SERVER:cast(mirroring(Sup), Msg) end). - -find_call(Sup, Id, Msg) -> - Group = call(Sup, group), - MatchHead = #mirrored_sup_childspec{mirroring_pid = '$1', - key = {Group, Id}, - _ = '_'}, - %% If we did this inside a tx we could still have failover - %% immediately after the tx - we can't be 100% here. So we may as - %% well dirty_select. - case mnesia:dirty_select(?TABLE, [{MatchHead, [], ['$1']}]) of - [Mirror] -> call(Mirror, Msg); - [] -> {error, not_found} - end. - -fold(FunAtom, Sup, AggFun) -> - Group = call(Sup, group), - lists:foldl(AggFun, [], - [apply(?SUPERVISOR, FunAtom, [D]) || - M <- ?PG2:get_members(Group), - D <- [delegate(M)]]). - -child(Sup, Id) -> - [Pid] = [Pid || {Id1, Pid, _, _} <- ?SUPERVISOR:which_children(Sup), - Id1 =:= Id], - Pid. - -delegate(Sup) -> child(Sup, delegate). -mirroring(Sup) -> child(Sup, mirroring). - -%%---------------------------------------------------------------------------- - -start_internal(Group, TxFun, ChildSpecs) -> - ?GEN_SERVER:start_link(?MODULE, {Group, TxFun, ChildSpecs}, - [{timeout, infinity}]). - -%%---------------------------------------------------------------------------- - -init({Group, TxFun, ChildSpecs}) -> - {ok, #state{group = Group, - tx_fun = TxFun, - initial_childspecs = ChildSpecs}}. - -handle_call({init, Overall}, _From, - State = #state{overall = undefined, - delegate = undefined, - group = Group, - tx_fun = TxFun, - initial_childspecs = ChildSpecs}) -> - process_flag(trap_exit, true), - ?PG2:create(Group), - ok = ?PG2:join(Group, Overall), - Rest = ?PG2:get_members(Group) -- [Overall], - case Rest of - [] -> TxFun(fun() -> delete_all(Group) end); - _ -> ok - end, - [begin - ?GEN_SERVER:cast(mirroring(Pid), {ensure_monitoring, Overall}), - erlang:monitor(process, Pid) - end || Pid <- Rest], - Delegate = delegate(Overall), - erlang:monitor(process, Delegate), - State1 = State#state{overall = Overall, delegate = Delegate}, - case errors([maybe_start(Group, TxFun, Overall, Delegate, S) - || S <- ChildSpecs]) of - [] -> {reply, ok, State1}; - Errors -> {stop, {shutdown, Errors}, State1} - end; - -handle_call({start_child, ChildSpec}, _From, - State = #state{overall = Overall, - delegate = Delegate, - group = Group, - tx_fun = TxFun}) -> - {reply, case maybe_start(Group, TxFun, Overall, Delegate, ChildSpec) of - already_in_mnesia -> {error, already_present}; - {already_in_mnesia, Pid} -> {error, {already_started, Pid}}; - Else -> Else - end, State}; - -handle_call({delete_child, Id}, _From, State = #state{delegate = Delegate, - group = Group, - tx_fun = TxFun}) -> - {reply, stop(Group, TxFun, Delegate, Id), State}; - -handle_call({msg, F, A}, _From, State = #state{delegate = Delegate}) -> - {reply, apply(?SUPERVISOR, F, [Delegate | A]), State}; - -handle_call(group, _From, State = #state{group = Group}) -> - {reply, Group, State}; - -handle_call(Msg, _From, State) -> - {stop, {unexpected_call, Msg}, State}. - -handle_cast({ensure_monitoring, Pid}, State) -> - erlang:monitor(process, Pid), - {noreply, State}; - -handle_cast({die, Reason}, State = #state{group = Group}) -> - tell_all_peers_to_die(Group, Reason), - {stop, Reason, State}; - -handle_cast(Msg, State) -> - {stop, {unexpected_cast, Msg}, State}. - -handle_info({'DOWN', _Ref, process, Pid, Reason}, - State = #state{delegate = Pid, group = Group}) -> - %% Since the delegate is temporary, its death won't cause us to - %% die. Since the overall supervisor kills processes in reverse - %% order when shutting down "from above" and we started after the - %% delegate, if we see the delegate die then that means it died - %% "from below" i.e. due to the behaviour of its children, not - %% because the whole app was being torn down. - %% - %% Therefore if we get here we know we need to cause the entire - %% mirrored sup to shut down, not just fail over. - tell_all_peers_to_die(Group, Reason), - {stop, Reason, State}; - -handle_info({'DOWN', _Ref, process, Pid, _Reason}, - State = #state{delegate = Delegate, - group = Group, - tx_fun = TxFun, - overall = O}) -> - %% TODO load balance this - %% No guarantee pg2 will have received the DOWN before us. - R = case lists:sort(?PG2:get_members(Group)) -- [Pid] of - [O | _] -> ChildSpecs = - TxFun(fun() -> update_all(O, Pid) end), - [start(Delegate, ChildSpec) || ChildSpec <- ChildSpecs]; - _ -> [] - end, - case errors(R) of - [] -> {noreply, State}; - Errors -> {stop, {shutdown, Errors}, State} - end; - -handle_info(Info, State) -> - {stop, {unexpected_info, Info}, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%---------------------------------------------------------------------------- - -tell_all_peers_to_die(Group, Reason) -> - [cast(P, {die, Reason}) || P <- ?PG2:get_members(Group) -- [self()]]. - -maybe_start(Group, TxFun, Overall, Delegate, ChildSpec) -> - try TxFun(fun() -> check_start(Group, Overall, Delegate, ChildSpec) end) of - start -> start(Delegate, ChildSpec); - undefined -> already_in_mnesia; - Pid -> {already_in_mnesia, Pid} - catch - %% If we are torn down while in the transaction... - {error, E} -> {error, E} - end. - -check_start(Group, Overall, Delegate, ChildSpec) -> - case mnesia:wread({?TABLE, {Group, id(ChildSpec)}}) of - [] -> write(Group, Overall, ChildSpec), - start; - [S] -> #mirrored_sup_childspec{key = {Group, Id}, - mirroring_pid = Pid} = S, - case Overall of - Pid -> child(Delegate, Id); - _ -> case supervisor(Pid) of - dead -> write(Group, Overall, ChildSpec), - start; - Delegate0 -> child(Delegate0, Id) - end - end - end. - -supervisor(Pid) -> with_exit_handler(fun() -> dead end, - fun() -> delegate(Pid) end). - -write(Group, Overall, ChildSpec) -> - S = #mirrored_sup_childspec{key = {Group, id(ChildSpec)}, - mirroring_pid = Overall, - childspec = ChildSpec}, - ok = mnesia:write(?TABLE, S, write), - ChildSpec. - -delete(Group, Id) -> - ok = mnesia:delete({?TABLE, {Group, Id}}). - -start(Delegate, ChildSpec) -> - apply(?SUPERVISOR, start_child, [Delegate, ChildSpec]). - -stop(Group, TxFun, Delegate, Id) -> - try TxFun(fun() -> check_stop(Group, Delegate, Id) end) of - deleted -> apply(?SUPERVISOR, delete_child, [Delegate, Id]); - running -> {error, running} - catch - {error, E} -> {error, E} - end. - -check_stop(Group, Delegate, Id) -> - case child(Delegate, Id) of - undefined -> delete(Group, Id), - deleted; - _ -> running - end. - -id({Id, _, _, _, _, _}) -> Id. - -update_all(Overall, OldOverall) -> - MatchHead = #mirrored_sup_childspec{mirroring_pid = OldOverall, - key = '$1', - childspec = '$2', - _ = '_'}, - [write(Group, Overall, C) || - [{Group, _Id}, C] <- mnesia:select(?TABLE, [{MatchHead, [], ['$$']}])]. - -delete_all(Group) -> - MatchHead = #mirrored_sup_childspec{key = {Group, '_'}, - childspec = '$1', - _ = '_'}, - [delete(Group, id(C)) || - C <- mnesia:select(?TABLE, [{MatchHead, [], ['$1']}])]. - -errors(Results) -> [E || {error, E} <- Results]. - -%%---------------------------------------------------------------------------- - -create_tables() -> create_tables([?TABLE_DEF]). - -create_tables([]) -> - ok; -create_tables([{Table, Attributes} | Ts]) -> - case mnesia:create_table(Table, Attributes) of - {atomic, ok} -> create_tables(Ts); - {aborted, {already_exists, ?TABLE}} -> create_tables(Ts); - Err -> Err - end. - -table_definitions() -> - {Name, Attributes} = ?TABLE_DEF, - [{Name, [?TABLE_MATCH | Attributes]}]. - -%%---------------------------------------------------------------------------- - -with_exit_handler(Handler, Thunk) -> - try - Thunk() - catch - exit:{R, _} when R =:= noproc; R =:= nodedown; - R =:= normal; R =:= shutdown -> - Handler(); - exit:{{R, _}, _} when R =:= nodedown; R =:= shutdown -> - Handler() - end. - -add_proplists(P1, P2) -> - add_proplists(lists:keysort(1, P1), lists:keysort(1, P2), []). -add_proplists([], P2, Acc) -> P2 ++ Acc; -add_proplists(P1, [], Acc) -> P1 ++ Acc; -add_proplists([{K, V1} | P1], [{K, V2} | P2], Acc) -> - add_proplists(P1, P2, [{K, V1 + V2} | Acc]); -add_proplists([{K1, _} = KV | P1], [{K2, _} | _] = P2, Acc) when K1 < K2 -> - add_proplists(P1, P2, [KV | Acc]); -add_proplists(P1, [KV | P2], Acc) -> - add_proplists(P1, P2, [KV | Acc]). diff --git a/src/mirrored_supervisor_sups.erl b/src/mirrored_supervisor_sups.erl deleted file mode 100644 index 6ec08435..00000000 --- a/src/mirrored_supervisor_sups.erl +++ /dev/null @@ -1,43 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. -%% - --module(mirrored_supervisor_sups). - --define(SUPERVISOR, supervisor2). --define(GS_MODULE, mirrored_supervisor). - --behaviour(?SUPERVISOR). - --export([init/1]). - -%%---------------------------------------------------------------------------- - -init({overall, _Group, _TxFun, ignore}) -> ignore; -init({overall, Group, TxFun, {ok, {Restart, ChildSpecs}}}) -> - %% Important: Delegate MUST start before Mirroring so that when we - %% shut down from above it shuts down last, so Mirroring does not - %% see it die. - %% - %% See comment in handle_info('DOWN', ...) in mirrored_supervisor - {ok, {{one_for_all, 0, 1}, - [{delegate, {?SUPERVISOR, start_link, [?MODULE, {delegate, Restart}]}, - temporary, 16#ffffffff, supervisor, [?SUPERVISOR]}, - {mirroring, {?GS_MODULE, start_internal, [Group, TxFun, ChildSpecs]}, - permanent, 16#ffffffff, worker, [?MODULE]}]}}; - - -init({delegate, Restart}) -> - {ok, {Restart, []}}. diff --git a/src/mnesia_sync.erl b/src/mnesia_sync.erl deleted file mode 100644 index 8fa54d65..00000000 --- a/src/mnesia_sync.erl +++ /dev/null @@ -1,77 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(mnesia_sync). - -%% mnesia:sync_transaction/3 fails to guarantee that the log is flushed to disk -%% at commit. This module is an attempt to minimise the risk of data loss by -%% performing a coalesced log fsync. Unfortunately this is performed regardless -%% of whether or not the log was appended to. - --behaviour(gen_server). - --export([sync/0]). - --export([start_link/0, init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --define(SERVER, ?MODULE). - --record(state, {waiting, disc_node}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(sync/0 :: () -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - -sync() -> - gen_server:call(?SERVER, sync, infinity). - -%%---------------------------------------------------------------------------- - -init([]) -> - {ok, #state{disc_node = mnesia:system_info(use_dir), waiting = []}}. - -handle_call(sync, _From, #state{disc_node = false} = State) -> - {reply, ok, State}; -handle_call(sync, From, #state{waiting = Waiting} = State) -> - {noreply, State#state{waiting = [From | Waiting]}, 0}; -handle_call(Request, _From, State) -> - {stop, {unhandled_call, Request}, State}. - -handle_cast(Request, State) -> - {stop, {unhandled_cast, Request}, State}. - -handle_info(timeout, #state{waiting = Waiting} = State) -> - ok = disk_log:sync(latest_log), - [gen_server:reply(From, ok) || From <- Waiting], - {noreply, State#state{waiting = []}}; -handle_info(Message, State) -> - {stop, {unhandled_info, Message}, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. diff --git a/src/mochijson2.erl b/src/mochijson2.erl deleted file mode 100644 index bddb52cc..00000000 --- a/src/mochijson2.erl +++ /dev/null @@ -1,893 +0,0 @@ -%% This file is a copy of `mochijson2.erl' from mochiweb, revision -%% d541e9a0f36c00dcadc2e589f20e47fbf46fc76f. For the license, see -%% `LICENSE-MIT-Mochi'. - -%% @author Bob Ippolito <bob@mochimedia.com> -%% @copyright 2007 Mochi Media, Inc. - -%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works -%% with binaries as strings, arrays as lists (without an {array, _}) -%% wrapper and it only knows how to decode UTF-8 (and ASCII). -%% -%% JSON terms are decoded as follows (javascript -> erlang): -%% <ul> -%% <li>{"key": "value"} -> -%% {struct, [{<<"key">>, <<"value">>}]}</li> -%% <li>["array", 123, 12.34, true, false, null] -> -%% [<<"array">>, 123, 12.34, true, false, null] -%% </li> -%% </ul> -%% <ul> -%% <li>Strings in JSON decode to UTF-8 binaries in Erlang</li> -%% <li>Objects decode to {struct, PropList}</li> -%% <li>Numbers decode to integer or float</li> -%% <li>true, false, null decode to their respective terms.</li> -%% </ul> -%% The encoder will accept the same format that the decoder will produce, -%% but will also allow additional cases for leniency: -%% <ul> -%% <li>atoms other than true, false, null will be considered UTF-8 -%% strings (even as a proplist key) -%% </li> -%% <li>{json, IoList} will insert IoList directly into the output -%% with no validation -%% </li> -%% <li>{array, Array} will be encoded as Array -%% (legacy mochijson style) -%% </li> -%% <li>A non-empty raw proplist will be encoded as an object as long -%% as the first pair does not have an atom key of json, struct, -%% or array -%% </li> -%% </ul> - --module(mochijson2). --author('bob@mochimedia.com'). --export([encoder/1, encode/1]). --export([decoder/1, decode/1, decode/2]). - -%% This is a macro to placate syntax highlighters.. --define(Q, $\"). --define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset, - column=N+S#decoder.column}). --define(INC_COL(S), S#decoder{offset=1+S#decoder.offset, - column=1+S#decoder.column}). --define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset, - column=1, - line=1+S#decoder.line}). --define(INC_CHAR(S, C), - case C of - $\n -> - S#decoder{column=1, - line=1+S#decoder.line, - offset=1+S#decoder.offset}; - _ -> - S#decoder{column=1+S#decoder.column, - offset=1+S#decoder.offset} - end). --define(IS_WHITESPACE(C), - (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). - -%% @type json_string() = atom | binary() -%% @type json_number() = integer() | float() -%% @type json_array() = [json_term()] -%% @type json_object() = {struct, [{json_string(), json_term()}]} -%% @type json_eep18_object() = {[{json_string(), json_term()}]} -%% @type json_iolist() = {json, iolist()} -%% @type json_term() = json_string() | json_number() | json_array() | -%% json_object() | json_eep18_object() | json_iolist() - --record(encoder, {handler=null, - utf8=false}). - --record(decoder, {object_hook=null, - offset=0, - line=1, - column=1, - state=null}). - -%% @spec encoder([encoder_option()]) -> function() -%% @doc Create an encoder/1 with the given options. -%% @type encoder_option() = handler_option() | utf8_option() -%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false) -encoder(Options) -> - State = parse_encoder_options(Options, #encoder{}), - fun (O) -> json_encode(O, State) end. - -%% @spec encode(json_term()) -> iolist() -%% @doc Encode the given as JSON to an iolist. -encode(Any) -> - json_encode(Any, #encoder{}). - -%% @spec decoder([decoder_option()]) -> function() -%% @doc Create a decoder/1 with the given options. -decoder(Options) -> - State = parse_decoder_options(Options, #decoder{}), - fun (O) -> json_decode(O, State) end. - -%% @spec decode(iolist(), [{format, proplist | eep18 | struct}]) -> json_term() -%% @doc Decode the given iolist to Erlang terms using the given object format -%% for decoding, where proplist returns JSON objects as [{binary(), json_term()}] -%% proplists, eep18 returns JSON objects as {[binary(), json_term()]}, and struct -%% returns them as-is. -decode(S, Options) -> - json_decode(S, parse_decoder_options(Options, #decoder{})). - -%% @spec decode(iolist()) -> json_term() -%% @doc Decode the given iolist to Erlang terms. -decode(S) -> - json_decode(S, #decoder{}). - -%% Internal API - -parse_encoder_options([], State) -> - State; -parse_encoder_options([{handler, Handler} | Rest], State) -> - parse_encoder_options(Rest, State#encoder{handler=Handler}); -parse_encoder_options([{utf8, Switch} | Rest], State) -> - parse_encoder_options(Rest, State#encoder{utf8=Switch}). - -parse_decoder_options([], State) -> - State; -parse_decoder_options([{object_hook, Hook} | Rest], State) -> - parse_decoder_options(Rest, State#decoder{object_hook=Hook}); -parse_decoder_options([{format, Format} | Rest], State) - when Format =:= struct orelse Format =:= eep18 orelse Format =:= proplist -> - parse_decoder_options(Rest, State#decoder{object_hook=Format}). - -json_encode(true, _State) -> - <<"true">>; -json_encode(false, _State) -> - <<"false">>; -json_encode(null, _State) -> - <<"null">>; -json_encode(I, _State) when is_integer(I) -> - integer_to_list(I); -json_encode(F, _State) when is_float(F) -> - mochinum:digits(F); -json_encode(S, State) when is_binary(S); is_atom(S) -> - json_encode_string(S, State); -json_encode([{K, _}|_] = Props, State) when (K =/= struct andalso - K =/= array andalso - K =/= json) -> - json_encode_proplist(Props, State); -json_encode({struct, Props}, State) when is_list(Props) -> - json_encode_proplist(Props, State); -json_encode({Props}, State) when is_list(Props) -> - json_encode_proplist(Props, State); -json_encode({}, State) -> - json_encode_proplist([], State); -json_encode(Array, State) when is_list(Array) -> - json_encode_array(Array, State); -json_encode({array, Array}, State) when is_list(Array) -> - json_encode_array(Array, State); -json_encode({json, IoList}, _State) -> - IoList; -json_encode(Bad, #encoder{handler=null}) -> - exit({json_encode, {bad_term, Bad}}); -json_encode(Bad, State=#encoder{handler=Handler}) -> - json_encode(Handler(Bad), State). - -json_encode_array([], _State) -> - <<"[]">>; -json_encode_array(L, State) -> - F = fun (O, Acc) -> - [$,, json_encode(O, State) | Acc] - end, - [$, | Acc1] = lists:foldl(F, "[", L), - lists:reverse([$\] | Acc1]). - -json_encode_proplist([], _State) -> - <<"{}">>; -json_encode_proplist(Props, State) -> - F = fun ({K, V}, Acc) -> - KS = json_encode_string(K, State), - VS = json_encode(V, State), - [$,, VS, $:, KS | Acc] - end, - [$, | Acc1] = lists:foldl(F, "{", Props), - lists:reverse([$\} | Acc1]). - -json_encode_string(A, State) when is_atom(A) -> - L = atom_to_list(A), - case json_string_is_safe(L) of - true -> - [?Q, L, ?Q]; - false -> - json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q]) - end; -json_encode_string(B, State) when is_binary(B) -> - case json_bin_is_safe(B) of - true -> - [?Q, B, ?Q]; - false -> - json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q]) - end; -json_encode_string(I, _State) when is_integer(I) -> - [?Q, integer_to_list(I), ?Q]; -json_encode_string(L, State) when is_list(L) -> - case json_string_is_safe(L) of - true -> - [?Q, L, ?Q]; - false -> - json_encode_string_unicode(L, State, [?Q]) - end. - -json_string_is_safe([]) -> - true; -json_string_is_safe([C | Rest]) -> - case C of - ?Q -> - false; - $\\ -> - false; - $\b -> - false; - $\f -> - false; - $\n -> - false; - $\r -> - false; - $\t -> - false; - C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> - false; - C when C < 16#7f -> - json_string_is_safe(Rest); - _ -> - false - end. - -json_bin_is_safe(<<>>) -> - true; -json_bin_is_safe(<<C, Rest/binary>>) -> - case C of - ?Q -> - false; - $\\ -> - false; - $\b -> - false; - $\f -> - false; - $\n -> - false; - $\r -> - false; - $\t -> - false; - C when C >= 0, C < $\s; C >= 16#7f -> - false; - C when C < 16#7f -> - json_bin_is_safe(Rest) - end. - -json_encode_string_unicode([], _State, Acc) -> - lists:reverse([$\" | Acc]); -json_encode_string_unicode([C | Cs], State, Acc) -> - Acc1 = case C of - ?Q -> - [?Q, $\\ | Acc]; - %% Escaping solidus is only useful when trying to protect - %% against "</script>" injection attacks which are only - %% possible when JSON is inserted into a HTML document - %% in-line. mochijson2 does not protect you from this, so - %% if you do insert directly into HTML then you need to - %% uncomment the following case or escape the output of encode. - %% - %% $/ -> - %% [$/, $\\ | Acc]; - %% - $\\ -> - [$\\, $\\ | Acc]; - $\b -> - [$b, $\\ | Acc]; - $\f -> - [$f, $\\ | Acc]; - $\n -> - [$n, $\\ | Acc]; - $\r -> - [$r, $\\ | Acc]; - $\t -> - [$t, $\\ | Acc]; - C when C >= 0, C < $\s -> - [unihex(C) | Acc]; - C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 -> - [xmerl_ucs:to_utf8(C) | Acc]; - C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 -> - [unihex(C) | Acc]; - C when C < 16#7f -> - [C | Acc]; - _ -> - exit({json_encode, {bad_char, C}}) - end, - json_encode_string_unicode(Cs, State, Acc1). - -hexdigit(C) when C >= 0, C =< 9 -> - C + $0; -hexdigit(C) when C =< 15 -> - C + $a - 10. - -unihex(C) when C < 16#10000 -> - <<D3:4, D2:4, D1:4, D0:4>> = <<C:16>>, - Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], - [$\\, $u | Digits]; -unihex(C) when C =< 16#10FFFF -> - N = C - 16#10000, - S1 = 16#d800 bor ((N bsr 10) band 16#3ff), - S2 = 16#dc00 bor (N band 16#3ff), - [unihex(S1), unihex(S2)]. - -json_decode(L, S) when is_list(L) -> - json_decode(iolist_to_binary(L), S); -json_decode(B, S) -> - {Res, S1} = decode1(B, S), - {eof, _} = tokenize(B, S1#decoder{state=trim}), - Res. - -decode1(B, S=#decoder{state=null}) -> - case tokenize(B, S#decoder{state=any}) of - {{const, C}, S1} -> - {C, S1}; - {start_array, S1} -> - decode_array(B, S1); - {start_object, S1} -> - decode_object(B, S1) - end. - -make_object(V, #decoder{object_hook=N}) when N =:= null orelse N =:= struct -> - V; -make_object({struct, P}, #decoder{object_hook=eep18}) -> - {P}; -make_object({struct, P}, #decoder{object_hook=proplist}) -> - P; -make_object(V, #decoder{object_hook=Hook}) -> - Hook(V). - -decode_object(B, S) -> - decode_object(B, S#decoder{state=key}, []). - -decode_object(B, S=#decoder{state=key}, Acc) -> - case tokenize(B, S) of - {end_object, S1} -> - V = make_object({struct, lists:reverse(Acc)}, S1), - {V, S1#decoder{state=null}}; - {{const, K}, S1} -> - {colon, S2} = tokenize(B, S1), - {V, S3} = decode1(B, S2#decoder{state=null}), - decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) - end; -decode_object(B, S=#decoder{state=comma}, Acc) -> - case tokenize(B, S) of - {end_object, S1} -> - V = make_object({struct, lists:reverse(Acc)}, S1), - {V, S1#decoder{state=null}}; - {comma, S1} -> - decode_object(B, S1#decoder{state=key}, Acc) - end. - -decode_array(B, S) -> - decode_array(B, S#decoder{state=any}, []). - -decode_array(B, S=#decoder{state=any}, Acc) -> - case tokenize(B, S) of - {end_array, S1} -> - {lists:reverse(Acc), S1#decoder{state=null}}; - {start_array, S1} -> - {Array, S2} = decode_array(B, S1), - decode_array(B, S2#decoder{state=comma}, [Array | Acc]); - {start_object, S1} -> - {Array, S2} = decode_object(B, S1), - decode_array(B, S2#decoder{state=comma}, [Array | Acc]); - {{const, Const}, S1} -> - decode_array(B, S1#decoder{state=comma}, [Const | Acc]) - end; -decode_array(B, S=#decoder{state=comma}, Acc) -> - case tokenize(B, S) of - {end_array, S1} -> - {lists:reverse(Acc), S1#decoder{state=null}}; - {comma, S1} -> - decode_array(B, S1#decoder{state=any}, Acc) - end. - -tokenize_string(B, S=#decoder{offset=O}) -> - case tokenize_string_fast(B, O) of - {escape, O1} -> - Length = O1 - O, - S1 = ?ADV_COL(S, Length), - <<_:O/binary, Head:Length/binary, _/binary>> = B, - tokenize_string(B, S1, lists:reverse(binary_to_list(Head))); - O1 -> - Length = O1 - O, - <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B, - {{const, String}, ?ADV_COL(S, Length + 1)} - end. - -tokenize_string_fast(B, O) -> - case B of - <<_:O/binary, ?Q, _/binary>> -> - O; - <<_:O/binary, $\\, _/binary>> -> - {escape, O}; - <<_:O/binary, C1, _/binary>> when C1 < 128 -> - tokenize_string_fast(B, 1 + O); - <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, - C2 >= 128, C2 =< 191 -> - tokenize_string_fast(B, 2 + O); - <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, - C2 >= 128, C2 =< 191, - C3 >= 128, C3 =< 191 -> - tokenize_string_fast(B, 3 + O); - <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, - C2 >= 128, C2 =< 191, - C3 >= 128, C3 =< 191, - C4 >= 128, C4 =< 191 -> - tokenize_string_fast(B, 4 + O); - _ -> - throw(invalid_utf8) - end. - -tokenize_string(B, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, ?Q, _/binary>> -> - {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)}; - <<_:O/binary, "\\\"", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]); - <<_:O/binary, "\\\\", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]); - <<_:O/binary, "\\/", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]); - <<_:O/binary, "\\b", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]); - <<_:O/binary, "\\f", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]); - <<_:O/binary, "\\n", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]); - <<_:O/binary, "\\r", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]); - <<_:O/binary, "\\t", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]); - <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> -> - C = erlang:list_to_integer([C3, C2, C1, C0], 16), - if C > 16#D7FF, C < 16#DC00 -> - %% coalesce UTF-16 surrogate pair - <<"\\u", D3, D2, D1, D0, _/binary>> = Rest, - D = erlang:list_to_integer([D3,D2,D1,D0], 16), - [CodePoint] = xmerl_ucs:from_utf16be(<<C:16/big-unsigned-integer, - D:16/big-unsigned-integer>>), - Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), - tokenize_string(B, ?ADV_COL(S, 12), Acc1); - true -> - Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc), - tokenize_string(B, ?ADV_COL(S, 6), Acc1) - end; - <<_:O/binary, C1, _/binary>> when C1 < 128 -> - tokenize_string(B, ?INC_CHAR(S, C1), [C1 | Acc]); - <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, - C2 >= 128, C2 =< 191 -> - tokenize_string(B, ?ADV_COL(S, 2), [C2, C1 | Acc]); - <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, - C2 >= 128, C2 =< 191, - C3 >= 128, C3 =< 191 -> - tokenize_string(B, ?ADV_COL(S, 3), [C3, C2, C1 | Acc]); - <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, - C2 >= 128, C2 =< 191, - C3 >= 128, C3 =< 191, - C4 >= 128, C4 =< 191 -> - tokenize_string(B, ?ADV_COL(S, 4), [C4, C3, C2, C1 | Acc]); - _ -> - throw(invalid_utf8) - end. - -tokenize_number(B, S) -> - case tokenize_number(B, sign, S, []) of - {{int, Int}, S1} -> - {{const, list_to_integer(Int)}, S1}; - {{float, Float}, S1} -> - {{const, list_to_float(Float)}, S1} - end. - -tokenize_number(B, sign, S=#decoder{offset=O}, []) -> - case B of - <<_:O/binary, $-, _/binary>> -> - tokenize_number(B, int, ?INC_COL(S), [$-]); - _ -> - tokenize_number(B, int, S, []) - end; -tokenize_number(B, int, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, $0, _/binary>> -> - tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]); - <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 -> - tokenize_number(B, int1, ?INC_COL(S), [C | Acc]) - end; -tokenize_number(B, int1, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> - tokenize_number(B, int1, ?INC_COL(S), [C | Acc]); - _ -> - tokenize_number(B, frac, S, Acc) - end; -tokenize_number(B, frac, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 -> - tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); - <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> - tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]); - _ -> - {{int, lists:reverse(Acc)}, S} - end; -tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> - tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]); - <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> - tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]); - _ -> - {{float, lists:reverse(Acc)}, S} - end; -tokenize_number(B, esign, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ -> - tokenize_number(B, eint, ?INC_COL(S), [C | Acc]); - _ -> - tokenize_number(B, eint, S, Acc) - end; -tokenize_number(B, eint, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> - tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]) - end; -tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> - tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]); - _ -> - {{float, lists:reverse(Acc)}, S} - end. - -tokenize(B, S=#decoder{offset=O}) -> - case B of - <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> - tokenize(B, ?INC_CHAR(S, C)); - <<_:O/binary, "{", _/binary>> -> - {start_object, ?INC_COL(S)}; - <<_:O/binary, "}", _/binary>> -> - {end_object, ?INC_COL(S)}; - <<_:O/binary, "[", _/binary>> -> - {start_array, ?INC_COL(S)}; - <<_:O/binary, "]", _/binary>> -> - {end_array, ?INC_COL(S)}; - <<_:O/binary, ",", _/binary>> -> - {comma, ?INC_COL(S)}; - <<_:O/binary, ":", _/binary>> -> - {colon, ?INC_COL(S)}; - <<_:O/binary, "null", _/binary>> -> - {{const, null}, ?ADV_COL(S, 4)}; - <<_:O/binary, "true", _/binary>> -> - {{const, true}, ?ADV_COL(S, 4)}; - <<_:O/binary, "false", _/binary>> -> - {{const, false}, ?ADV_COL(S, 5)}; - <<_:O/binary, "\"", _/binary>> -> - tokenize_string(B, ?INC_COL(S)); - <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9) - orelse C =:= $- -> - tokenize_number(B, S); - <<_:O/binary>> -> - trim = S#decoder.state, - {eof, S} - end. -%% -%% Tests -%% --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - - -%% testing constructs borrowed from the Yaws JSON implementation. - -%% Create an object from a list of Key/Value pairs. - -obj_new() -> - {struct, []}. - -is_obj({struct, Props}) -> - F = fun ({K, _}) when is_binary(K) -> true end, - lists:all(F, Props). - -obj_from_list(Props) -> - Obj = {struct, Props}, - ?assert(is_obj(Obj)), - Obj. - -%% Test for equivalence of Erlang terms. -%% Due to arbitrary order of construction, equivalent objects might -%% compare unequal as erlang terms, so we need to carefully recurse -%% through aggregates (tuples and objects). - -equiv({struct, Props1}, {struct, Props2}) -> - equiv_object(Props1, Props2); -equiv(L1, L2) when is_list(L1), is_list(L2) -> - equiv_list(L1, L2); -equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; -equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; -equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true. - -%% Object representation and traversal order is unknown. -%% Use the sledgehammer and sort property lists. - -equiv_object(Props1, Props2) -> - L1 = lists:keysort(1, Props1), - L2 = lists:keysort(1, Props2), - Pairs = lists:zip(L1, L2), - true = lists:all(fun({{K1, V1}, {K2, V2}}) -> - equiv(K1, K2) and equiv(V1, V2) - end, Pairs). - -%% Recursively compare tuple elements for equivalence. - -equiv_list([], []) -> - true; -equiv_list([V1 | L1], [V2 | L2]) -> - equiv(V1, V2) andalso equiv_list(L1, L2). - -decode_test() -> - [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>), - <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]). - -e2j_vec_test() -> - test_one(e2j_test_vec(utf8), 1). - -test_one([], _N) -> - %% io:format("~p tests passed~n", [N-1]), - ok; -test_one([{E, J} | Rest], N) -> - %% io:format("[~p] ~p ~p~n", [N, E, J]), - true = equiv(E, decode(J)), - true = equiv(E, decode(encode(E))), - test_one(Rest, 1+N). - -e2j_test_vec(utf8) -> - [ - {1, "1"}, - {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes - {-1, "-1"}, - {-3.1416, "-3.14160"}, - {12.0e10, "1.20000e+11"}, - {1.234E+10, "1.23400e+10"}, - {-1.234E-10, "-1.23400e-10"}, - {10.0, "1.0e+01"}, - {123.456, "1.23456E+2"}, - {10.0, "1e1"}, - {<<"foo">>, "\"foo\""}, - {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""}, - {<<"">>, "\"\""}, - {<<"\n\n\n">>, "\"\\n\\n\\n\""}, - {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, - {obj_new(), "{}"}, - {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"}, - {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]), - "{\"foo\":\"bar\",\"baz\":123}"}, - {[], "[]"}, - {[[]], "[[]]"}, - {[1, <<"foo">>], "[1,\"foo\"]"}, - - %% json array in a json object - {obj_from_list([{<<"foo">>, [123]}]), - "{\"foo\":[123]}"}, - - %% json object in a json object - {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]), - "{\"foo\":{\"bar\":true}}"}, - - %% fold evaluation order - {obj_from_list([{<<"foo">>, []}, - {<<"bar">>, obj_from_list([{<<"baz">>, true}])}, - {<<"alice">>, <<"bob">>}]), - "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, - - %% json object in a json array - {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null], - "[-123,\"foo\",{\"bar\":[]},null]"} - ]. - -%% test utf8 encoding -encoder_utf8_test() -> - %% safe conversion case (default) - [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = - encode(<<1,"\321\202\320\265\321\201\321\202">>), - - %% raw utf8 output (optional) - Enc = mochijson2:encoder([{utf8, true}]), - [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] = - Enc(<<1,"\321\202\320\265\321\201\321\202">>). - -input_validation_test() -> - Good = [ - {16#00A3, <<?Q, 16#C2, 16#A3, ?Q>>}, %% pound - {16#20AC, <<?Q, 16#E2, 16#82, 16#AC, ?Q>>}, %% euro - {16#10196, <<?Q, 16#F0, 16#90, 16#86, 16#96, ?Q>>} %% denarius - ], - lists:foreach(fun({CodePoint, UTF8}) -> - Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)), - Expect = decode(UTF8) - end, Good), - - Bad = [ - %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte - <<?Q, 16#80, ?Q>>, - %% missing continuations, last byte in each should be 80-BF - <<?Q, 16#C2, 16#7F, ?Q>>, - <<?Q, 16#E0, 16#80,16#7F, ?Q>>, - <<?Q, 16#F0, 16#80, 16#80, 16#7F, ?Q>>, - %% we don't support code points > 10FFFF per RFC 3629 - <<?Q, 16#F5, 16#80, 16#80, 16#80, ?Q>>, - %% escape characters trigger a different code path - <<?Q, $\\, $\n, 16#80, ?Q>> - ], - lists:foreach( - fun(X) -> - ok = try decode(X) catch invalid_utf8 -> ok end, - %% could be {ucs,{bad_utf8_character_code}} or - %% {json_encode,{bad_char,_}} - {'EXIT', _} = (catch encode(X)) - end, Bad). - -inline_json_test() -> - ?assertEqual(<<"\"iodata iodata\"">>, - iolist_to_binary( - encode({json, [<<"\"iodata">>, " iodata\""]}))), - ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]}, - decode( - encode({struct, - [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))), - ok. - -big_unicode_test() -> - UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)), - ?assertEqual( - <<"\"\\ud834\\udd20\"">>, - iolist_to_binary(encode(UTF8Seq))), - ?assertEqual( - UTF8Seq, - decode(iolist_to_binary(encode(UTF8Seq)))), - ok. - -custom_decoder_test() -> - ?assertEqual( - {struct, [{<<"key">>, <<"value">>}]}, - (decoder([]))("{\"key\": \"value\"}")), - F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end, - ?assertEqual( - win, - (decoder([{object_hook, F}]))("{\"key\": \"value\"}")), - ok. - -atom_test() -> - %% JSON native atoms - [begin - ?assertEqual(A, decode(atom_to_list(A))), - ?assertEqual(iolist_to_binary(atom_to_list(A)), - iolist_to_binary(encode(A))) - end || A <- [true, false, null]], - %% Atom to string - ?assertEqual( - <<"\"foo\"">>, - iolist_to_binary(encode(foo))), - ?assertEqual( - <<"\"\\ud834\\udd20\"">>, - iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))), - ok. - -key_encode_test() -> - %% Some forms are accepted as keys that would not be strings in other - %% cases - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode({struct, [{foo, 1}]}))), - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))), - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode({struct, [{"foo", 1}]}))), - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode([{foo, 1}]))), - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode([{<<"foo">>, 1}]))), - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode([{"foo", 1}]))), - ?assertEqual( - <<"{\"\\ud834\\udd20\":1}">>, - iolist_to_binary( - encode({struct, [{[16#0001d120], 1}]}))), - ?assertEqual( - <<"{\"1\":1}">>, - iolist_to_binary(encode({struct, [{1, 1}]}))), - ok. - -unsafe_chars_test() -> - Chars = "\"\\\b\f\n\r\t", - [begin - ?assertEqual(false, json_string_is_safe([C])), - ?assertEqual(false, json_bin_is_safe(<<C>>)), - ?assertEqual(<<C>>, decode(encode(<<C>>))) - end || C <- Chars], - ?assertEqual( - false, - json_string_is_safe([16#0001d120])), - ?assertEqual( - false, - json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))), - ?assertEqual( - [16#0001d120], - xmerl_ucs:from_utf8( - binary_to_list( - decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))), - ?assertEqual( - false, - json_string_is_safe([16#110000])), - ?assertEqual( - false, - json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))), - %% solidus can be escaped but isn't unsafe by default - ?assertEqual( - <<"/">>, - decode(<<"\"\\/\"">>)), - ok. - -int_test() -> - ?assertEqual(0, decode("0")), - ?assertEqual(1, decode("1")), - ?assertEqual(11, decode("11")), - ok. - -large_int_test() -> - ?assertEqual(<<"-2147483649214748364921474836492147483649">>, - iolist_to_binary(encode(-2147483649214748364921474836492147483649))), - ?assertEqual(<<"2147483649214748364921474836492147483649">>, - iolist_to_binary(encode(2147483649214748364921474836492147483649))), - ok. - -float_test() -> - ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649.0))), - ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648.0))), - ok. - -handler_test() -> - ?assertEqual( - {'EXIT',{json_encode,{bad_term,{x,y}}}}, - catch encode({x,y})), - F = fun ({x,y}) -> [] end, - ?assertEqual( - <<"[]">>, - iolist_to_binary((encoder([{handler, F}]))({x, y}))), - ok. - -encode_empty_test_() -> - [{A, ?_assertEqual(<<"{}">>, iolist_to_binary(encode(B)))} - || {A, B} <- [{"eep18 {}", {}}, - {"eep18 {[]}", {[]}}, - {"{struct, []}", {struct, []}}]]. - -encode_test_() -> - P = [{<<"k">>, <<"v">>}], - JSON = iolist_to_binary(encode({struct, P})), - [{atom_to_list(F), - ?_assertEqual(JSON, iolist_to_binary(encode(decode(JSON, [{format, F}]))))} - || F <- [struct, eep18, proplist]]. - -format_test_() -> - P = [{<<"k">>, <<"v">>}], - JSON = iolist_to_binary(encode({struct, P})), - [{atom_to_list(F), - ?_assertEqual(A, decode(JSON, [{format, F}]))} - || {F, A} <- [{struct, {struct, P}}, - {eep18, {P}}, - {proplist, P}]]. - --endif. diff --git a/src/mochinum.erl b/src/mochinum.erl deleted file mode 100644 index 4ea7a22a..00000000 --- a/src/mochinum.erl +++ /dev/null @@ -1,358 +0,0 @@ -%% This file is a copy of `mochijson2.erl' from mochiweb, revision -%% d541e9a0f36c00dcadc2e589f20e47fbf46fc76f. For the license, see -%% `LICENSE-MIT-Mochi'. - -%% @copyright 2007 Mochi Media, Inc. -%% @author Bob Ippolito <bob@mochimedia.com> - -%% @doc Useful numeric algorithms for floats that cover some deficiencies -%% in the math module. More interesting is digits/1, which implements -%% the algorithm from: -%% http://www.cs.indiana.edu/~burger/fp/index.html -%% See also "Printing Floating-Point Numbers Quickly and Accurately" -%% in Proceedings of the SIGPLAN '96 Conference on Programming Language -%% Design and Implementation. - --module(mochinum). --author("Bob Ippolito <bob@mochimedia.com>"). --export([digits/1, frexp/1, int_pow/2, int_ceil/1]). - -%% IEEE 754 Float exponent bias --define(FLOAT_BIAS, 1022). --define(MIN_EXP, -1074). --define(BIG_POW, 4503599627370496). - -%% External API - -%% @spec digits(number()) -> string() -%% @doc Returns a string that accurately represents the given integer or float -%% using a conservative amount of digits. Great for generating -%% human-readable output, or compact ASCII serializations for floats. -digits(N) when is_integer(N) -> - integer_to_list(N); -digits(0.0) -> - "0.0"; -digits(Float) -> - {Frac1, Exp1} = frexp_int(Float), - [Place0 | Digits0] = digits1(Float, Exp1, Frac1), - {Place, Digits} = transform_digits(Place0, Digits0), - R = insert_decimal(Place, Digits), - case Float < 0 of - true -> - [$- | R]; - _ -> - R - end. - -%% @spec frexp(F::float()) -> {Frac::float(), Exp::float()} -%% @doc Return the fractional and exponent part of an IEEE 754 double, -%% equivalent to the libc function of the same name. -%% F = Frac * pow(2, Exp). -frexp(F) -> - frexp1(unpack(F)). - -%% @spec int_pow(X::integer(), N::integer()) -> Y::integer() -%% @doc Moderately efficient way to exponentiate integers. -%% int_pow(10, 2) = 100. -int_pow(_X, 0) -> - 1; -int_pow(X, N) when N > 0 -> - int_pow(X, N, 1). - -%% @spec int_ceil(F::float()) -> integer() -%% @doc Return the ceiling of F as an integer. The ceiling is defined as -%% F when F == trunc(F); -%% trunc(F) when F < 0; -%% trunc(F) + 1 when F > 0. -int_ceil(X) -> - T = trunc(X), - case (X - T) of - Pos when Pos > 0 -> T + 1; - _ -> T - end. - - -%% Internal API - -int_pow(X, N, R) when N < 2 -> - R * X; -int_pow(X, N, R) -> - int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end). - -insert_decimal(0, S) -> - "0." ++ S; -insert_decimal(Place, S) when Place > 0 -> - L = length(S), - case Place - L of - 0 -> - S ++ ".0"; - N when N < 0 -> - {S0, S1} = lists:split(L + N, S), - S0 ++ "." ++ S1; - N when N < 6 -> - %% More places than digits - S ++ lists:duplicate(N, $0) ++ ".0"; - _ -> - insert_decimal_exp(Place, S) - end; -insert_decimal(Place, S) when Place > -6 -> - "0." ++ lists:duplicate(abs(Place), $0) ++ S; -insert_decimal(Place, S) -> - insert_decimal_exp(Place, S). - -insert_decimal_exp(Place, S) -> - [C | S0] = S, - S1 = case S0 of - [] -> - "0"; - _ -> - S0 - end, - Exp = case Place < 0 of - true -> - "e-"; - false -> - "e+" - end, - [C] ++ "." ++ S1 ++ Exp ++ integer_to_list(abs(Place - 1)). - - -digits1(Float, Exp, Frac) -> - Round = ((Frac band 1) =:= 0), - case Exp >= 0 of - true -> - BExp = 1 bsl Exp, - case (Frac =/= ?BIG_POW) of - true -> - scale((Frac * BExp * 2), 2, BExp, BExp, - Round, Round, Float); - false -> - scale((Frac * BExp * 4), 4, (BExp * 2), BExp, - Round, Round, Float) - end; - false -> - case (Exp =:= ?MIN_EXP) orelse (Frac =/= ?BIG_POW) of - true -> - scale((Frac * 2), 1 bsl (1 - Exp), 1, 1, - Round, Round, Float); - false -> - scale((Frac * 4), 1 bsl (2 - Exp), 2, 1, - Round, Round, Float) - end - end. - -scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) -> - Est = int_ceil(math:log10(abs(Float)) - 1.0e-10), - %% Note that the scheme implementation uses a 326 element look-up table - %% for int_pow(10, N) where we do not. - case Est >= 0 of - true -> - fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est, - LowOk, HighOk); - false -> - Scale = int_pow(10, -Est), - fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est, - LowOk, HighOk) - end. - -fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) -> - TooLow = case HighOk of - true -> - (R + MPlus) >= S; - false -> - (R + MPlus) > S - end, - case TooLow of - true -> - [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)]; - false -> - [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)] - end. - -generate(R0, S, MPlus, MMinus, LowOk, HighOk) -> - D = R0 div S, - R = R0 rem S, - TC1 = case LowOk of - true -> - R =< MMinus; - false -> - R < MMinus - end, - TC2 = case HighOk of - true -> - (R + MPlus) >= S; - false -> - (R + MPlus) > S - end, - case TC1 of - false -> - case TC2 of - false -> - [D | generate(R * 10, S, MPlus * 10, MMinus * 10, - LowOk, HighOk)]; - true -> - [D + 1] - end; - true -> - case TC2 of - false -> - [D]; - true -> - case R * 2 < S of - true -> - [D]; - false -> - [D + 1] - end - end - end. - -unpack(Float) -> - <<Sign:1, Exp:11, Frac:52>> = <<Float:64/float>>, - {Sign, Exp, Frac}. - -frexp1({_Sign, 0, 0}) -> - {0.0, 0}; -frexp1({Sign, 0, Frac}) -> - Exp = log2floor(Frac), - <<Frac1:64/float>> = <<Sign:1, ?FLOAT_BIAS:11, (Frac-1):52>>, - {Frac1, -(?FLOAT_BIAS) - 52 + Exp}; -frexp1({Sign, Exp, Frac}) -> - <<Frac1:64/float>> = <<Sign:1, ?FLOAT_BIAS:11, Frac:52>>, - {Frac1, Exp - ?FLOAT_BIAS}. - -log2floor(Int) -> - log2floor(Int, 0). - -log2floor(0, N) -> - N; -log2floor(Int, N) -> - log2floor(Int bsr 1, 1 + N). - - -transform_digits(Place, [0 | Rest]) -> - transform_digits(Place, Rest); -transform_digits(Place, Digits) -> - {Place, [$0 + D || D <- Digits]}. - - -frexp_int(F) -> - case unpack(F) of - {_Sign, 0, Frac} -> - {Frac, ?MIN_EXP}; - {_Sign, Exp, Frac} -> - {Frac + (1 bsl 52), Exp - 53 - ?FLOAT_BIAS} - end. - -%% -%% Tests -%% --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - -int_ceil_test() -> - ?assertEqual(1, int_ceil(0.0001)), - ?assertEqual(0, int_ceil(0.0)), - ?assertEqual(1, int_ceil(0.99)), - ?assertEqual(1, int_ceil(1.0)), - ?assertEqual(-1, int_ceil(-1.5)), - ?assertEqual(-2, int_ceil(-2.0)), - ok. - -int_pow_test() -> - ?assertEqual(1, int_pow(1, 1)), - ?assertEqual(1, int_pow(1, 0)), - ?assertEqual(1, int_pow(10, 0)), - ?assertEqual(10, int_pow(10, 1)), - ?assertEqual(100, int_pow(10, 2)), - ?assertEqual(1000, int_pow(10, 3)), - ok. - -digits_test() -> - ?assertEqual("0", - digits(0)), - ?assertEqual("0.0", - digits(0.0)), - ?assertEqual("1.0", - digits(1.0)), - ?assertEqual("-1.0", - digits(-1.0)), - ?assertEqual("0.1", - digits(0.1)), - ?assertEqual("0.01", - digits(0.01)), - ?assertEqual("0.001", - digits(0.001)), - ?assertEqual("1.0e+6", - digits(1000000.0)), - ?assertEqual("0.5", - digits(0.5)), - ?assertEqual("4503599627370496.0", - digits(4503599627370496.0)), - %% small denormalized number - %% 4.94065645841246544177e-324 =:= 5.0e-324 - <<SmallDenorm/float>> = <<0,0,0,0,0,0,0,1>>, - ?assertEqual("5.0e-324", - digits(SmallDenorm)), - ?assertEqual(SmallDenorm, - list_to_float(digits(SmallDenorm))), - %% large denormalized number - %% 2.22507385850720088902e-308 - <<BigDenorm/float>> = <<0,15,255,255,255,255,255,255>>, - ?assertEqual("2.225073858507201e-308", - digits(BigDenorm)), - ?assertEqual(BigDenorm, - list_to_float(digits(BigDenorm))), - %% small normalized number - %% 2.22507385850720138309e-308 - <<SmallNorm/float>> = <<0,16,0,0,0,0,0,0>>, - ?assertEqual("2.2250738585072014e-308", - digits(SmallNorm)), - ?assertEqual(SmallNorm, - list_to_float(digits(SmallNorm))), - %% large normalized number - %% 1.79769313486231570815e+308 - <<LargeNorm/float>> = <<127,239,255,255,255,255,255,255>>, - ?assertEqual("1.7976931348623157e+308", - digits(LargeNorm)), - ?assertEqual(LargeNorm, - list_to_float(digits(LargeNorm))), - %% issue #10 - mochinum:frexp(math:pow(2, -1074)). - ?assertEqual("5.0e-324", - digits(math:pow(2, -1074))), - ok. - -frexp_test() -> - %% zero - ?assertEqual({0.0, 0}, frexp(0.0)), - %% one - ?assertEqual({0.5, 1}, frexp(1.0)), - %% negative one - ?assertEqual({-0.5, 1}, frexp(-1.0)), - %% small denormalized number - %% 4.94065645841246544177e-324 - <<SmallDenorm/float>> = <<0,0,0,0,0,0,0,1>>, - ?assertEqual({0.5, -1073}, frexp(SmallDenorm)), - %% large denormalized number - %% 2.22507385850720088902e-308 - <<BigDenorm/float>> = <<0,15,255,255,255,255,255,255>>, - ?assertEqual( - {0.99999999999999978, -1022}, - frexp(BigDenorm)), - %% small normalized number - %% 2.22507385850720138309e-308 - <<SmallNorm/float>> = <<0,16,0,0,0,0,0,0>>, - ?assertEqual({0.5, -1021}, frexp(SmallNorm)), - %% large normalized number - %% 1.79769313486231570815e+308 - <<LargeNorm/float>> = <<127,239,255,255,255,255,255,255>>, - ?assertEqual( - {0.99999999999999989, 1024}, - frexp(LargeNorm)), - %% issue #10 - mochinum:frexp(math:pow(2, -1074)). - ?assertEqual( - {0.5, -1073}, - frexp(math:pow(2, -1074))), - ok. - --endif. diff --git a/src/pg2_fixed.erl b/src/pg2_fixed.erl deleted file mode 100644 index 8926b83b..00000000 --- a/src/pg2_fixed.erl +++ /dev/null @@ -1,400 +0,0 @@ -%% This is the version of pg2 from R14B02, which contains the fix -%% described at -%% http://erlang.2086793.n4.nabble.com/pg2-still-busted-in-R13B04-td2230601.html. -%% The changes are a search-and-replace to rename the module and avoid -%% clashes with other versions of pg2, and also a simple rewrite of -%% "andalso" and "orelse" expressions to case statements where the second -%% operand is not a boolean since R12B does not allow this. - -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% --module(pg2_fixed). - --export([create/1, delete/1, join/2, leave/2]). --export([get_members/1, get_local_members/1]). --export([get_closest_pid/1, which_groups/0]). --export([start/0,start_link/0,init/1,handle_call/3,handle_cast/2,handle_info/2, - terminate/2]). - -%%% As of R13B03 monitors are used instead of links. - -%%% -%%% Exported functions -%%% - --spec start_link() -> {'ok', pid()} | {'error', term()}. - -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - --spec start() -> {'ok', pid()} | {'error', term()}. - -start() -> - ensure_started(). - --spec create(term()) -> 'ok'. - -create(Name) -> - ensure_started(), - case ets:member(pg2_fixed_table, {group, Name}) of - false -> - global:trans({{?MODULE, Name}, self()}, - fun() -> - gen_server:multi_call(?MODULE, {create, Name}) - end), - ok; - true -> - ok - end. - --type name() :: term(). - --spec delete(name()) -> 'ok'. - -delete(Name) -> - ensure_started(), - global:trans({{?MODULE, Name}, self()}, - fun() -> - gen_server:multi_call(?MODULE, {delete, Name}) - end), - ok. - --spec join(name(), pid()) -> 'ok' | {'error', {'no_such_group', term()}}. - -join(Name, Pid) when is_pid(Pid) -> - ensure_started(), - case ets:member(pg2_fixed_table, {group, Name}) of - false -> - {error, {no_such_group, Name}}; - true -> - global:trans({{?MODULE, Name}, self()}, - fun() -> - gen_server:multi_call(?MODULE, - {join, Name, Pid}) - end), - ok - end. - --spec leave(name(), pid()) -> 'ok' | {'error', {'no_such_group', name()}}. - -leave(Name, Pid) when is_pid(Pid) -> - ensure_started(), - case ets:member(pg2_fixed_table, {group, Name}) of - false -> - {error, {no_such_group, Name}}; - true -> - global:trans({{?MODULE, Name}, self()}, - fun() -> - gen_server:multi_call(?MODULE, - {leave, Name, Pid}) - end), - ok - end. - --type get_members_ret() :: [pid()] | {'error', {'no_such_group', name()}}. - --spec get_members(name()) -> get_members_ret(). - -get_members(Name) -> - ensure_started(), - case ets:member(pg2_fixed_table, {group, Name}) of - true -> - group_members(Name); - false -> - {error, {no_such_group, Name}} - end. - --spec get_local_members(name()) -> get_members_ret(). - -get_local_members(Name) -> - ensure_started(), - case ets:member(pg2_fixed_table, {group, Name}) of - true -> - local_group_members(Name); - false -> - {error, {no_such_group, Name}} - end. - --spec which_groups() -> [name()]. - -which_groups() -> - ensure_started(), - all_groups(). - --type gcp_error_reason() :: {'no_process', term()} | {'no_such_group', term()}. - --spec get_closest_pid(term()) -> pid() | {'error', gcp_error_reason()}. - -get_closest_pid(Name) -> - case get_local_members(Name) of - [Pid] -> - Pid; - [] -> - {_,_,X} = erlang:now(), - case get_members(Name) of - [] -> {error, {no_process, Name}}; - Members -> - lists:nth((X rem length(Members))+1, Members) - end; - Members when is_list(Members) -> - {_,_,X} = erlang:now(), - lists:nth((X rem length(Members))+1, Members); - Else -> - Else - end. - -%%% -%%% Callback functions from gen_server -%%% - --record(state, {}). - --spec init([]) -> {'ok', #state{}}. - -init([]) -> - Ns = nodes(), - net_kernel:monitor_nodes(true), - lists:foreach(fun(N) -> - {?MODULE, N} ! {new_pg2_fixed, node()}, - self() ! {nodeup, N} - end, Ns), - pg2_fixed_table = ets:new(pg2_fixed_table, [ordered_set, protected, named_table]), - {ok, #state{}}. - --type call() :: {'create', name()} - | {'delete', name()} - | {'join', name(), pid()} - | {'leave', name(), pid()}. - --spec handle_call(call(), _, #state{}) -> - {'reply', 'ok', #state{}}. - -handle_call({create, Name}, _From, S) -> - assure_group(Name), - {reply, ok, S}; -handle_call({join, Name, Pid}, _From, S) -> - case ets:member(pg2_fixed_table, {group, Name}) of - true -> join_group(Name, Pid); - _ -> ok - end, - {reply, ok, S}; -handle_call({leave, Name, Pid}, _From, S) -> - case ets:member(pg2_fixed_table, {group, Name}) of - true -> leave_group(Name, Pid); - _ -> ok - end, - {reply, ok, S}; -handle_call({delete, Name}, _From, S) -> - delete_group(Name), - {reply, ok, S}; -handle_call(Request, From, S) -> - error_logger:warning_msg("The pg2_fixed server received an unexpected message:\n" - "handle_call(~p, ~p, _)\n", - [Request, From]), - {noreply, S}. - --type all_members() :: [[name(),...]]. --type cast() :: {'exchange', node(), all_members()} - | {'del_member', name(), pid()}. - --spec handle_cast(cast(), #state{}) -> {'noreply', #state{}}. - -handle_cast({exchange, _Node, List}, S) -> - store(List), - {noreply, S}; -handle_cast(_, S) -> - %% Ignore {del_member, Name, Pid}. - {noreply, S}. - --spec handle_info(tuple(), #state{}) -> {'noreply', #state{}}. - -handle_info({'DOWN', MonitorRef, process, _Pid, _Info}, S) -> - member_died(MonitorRef), - {noreply, S}; -handle_info({nodeup, Node}, S) -> - gen_server:cast({?MODULE, Node}, {exchange, node(), all_members()}), - {noreply, S}; -handle_info({new_pg2_fixed, Node}, S) -> - gen_server:cast({?MODULE, Node}, {exchange, node(), all_members()}), - {noreply, S}; -handle_info(_, S) -> - {noreply, S}. - --spec terminate(term(), #state{}) -> 'ok'. - -terminate(_Reason, _S) -> - true = ets:delete(pg2_fixed_table), - ok. - -%%% -%%% Local functions -%%% - -%%% One ETS table, pg2_fixed_table, is used for bookkeeping. The type of the -%%% table is ordered_set, and the fast matching of partially -%%% instantiated keys is used extensively. -%%% -%%% {{group, Name}} -%%% Process group Name. -%%% {{ref, Pid}, RPid, MonitorRef, Counter} -%%% {{ref, MonitorRef}, Pid} -%%% Each process has one monitor. Sometimes a process is spawned to -%%% monitor the pid (RPid). Counter is incremented when the Pid joins -%%% some group. -%%% {{member, Name, Pid}, GroupCounter} -%%% {{local_member, Name, Pid}} -%%% Pid is a member of group Name, GroupCounter is incremented when the -%%% Pid joins the group Name. -%%% {{pid, Pid, Name}} -%%% Pid is a member of group Name. - -store(List) -> - _ = [case assure_group(Name) of - true -> - [join_group(Name, P) || P <- Members -- group_members(Name)]; - _ -> - ok - end || [Name, Members] <- List], - ok. - -assure_group(Name) -> - Key = {group, Name}, - ets:member(pg2_fixed_table, Key) orelse true =:= ets:insert(pg2_fixed_table, {Key}). - -delete_group(Name) -> - _ = [leave_group(Name, Pid) || Pid <- group_members(Name)], - true = ets:delete(pg2_fixed_table, {group, Name}), - ok. - -member_died(Ref) -> - [{{ref, Ref}, Pid}] = ets:lookup(pg2_fixed_table, {ref, Ref}), - Names = member_groups(Pid), - _ = [leave_group(Name, P) || - Name <- Names, - P <- member_in_group(Pid, Name)], - %% Kept for backward compatibility with links. Can be removed, eventually. - _ = [gen_server:abcast(nodes(), ?MODULE, {del_member, Name, Pid}) || - Name <- Names], - ok. - -join_group(Name, Pid) -> - Ref_Pid = {ref, Pid}, - try _ = ets:update_counter(pg2_fixed_table, Ref_Pid, {4, +1}) - catch _:_ -> - {RPid, Ref} = do_monitor(Pid), - true = ets:insert(pg2_fixed_table, {Ref_Pid, RPid, Ref, 1}), - true = ets:insert(pg2_fixed_table, {{ref, Ref}, Pid}) - end, - Member_Name_Pid = {member, Name, Pid}, - try _ = ets:update_counter(pg2_fixed_table, Member_Name_Pid, {2, +1, 1, 1}) - catch _:_ -> - true = ets:insert(pg2_fixed_table, {Member_Name_Pid, 1}), - _ = [ets:insert(pg2_fixed_table, {{local_member, Name, Pid}}) || - node(Pid) =:= node()], - true = ets:insert(pg2_fixed_table, {{pid, Pid, Name}}) - end. - -leave_group(Name, Pid) -> - Member_Name_Pid = {member, Name, Pid}, - try ets:update_counter(pg2_fixed_table, Member_Name_Pid, {2, -1, 0, 0}) of - N -> - if - N =:= 0 -> - true = ets:delete(pg2_fixed_table, {pid, Pid, Name}), - _ = [ets:delete(pg2_fixed_table, {local_member, Name, Pid}) || - node(Pid) =:= node()], - true = ets:delete(pg2_fixed_table, Member_Name_Pid); - true -> - ok - end, - Ref_Pid = {ref, Pid}, - case ets:update_counter(pg2_fixed_table, Ref_Pid, {4, -1}) of - 0 -> - [{Ref_Pid,RPid,Ref,0}] = ets:lookup(pg2_fixed_table, Ref_Pid), - true = ets:delete(pg2_fixed_table, {ref, Ref}), - true = ets:delete(pg2_fixed_table, Ref_Pid), - true = erlang:demonitor(Ref, [flush]), - kill_monitor_proc(RPid, Pid); - _ -> - ok - end - catch _:_ -> - ok - end. - -all_members() -> - [[G, group_members(G)] || G <- all_groups()]. - -group_members(Name) -> - [P || - [P, N] <- ets:match(pg2_fixed_table, {{member, Name, '$1'},'$2'}), - _ <- lists:seq(1, N)]. - -local_group_members(Name) -> - [P || - [Pid] <- ets:match(pg2_fixed_table, {{local_member, Name, '$1'}}), - P <- member_in_group(Pid, Name)]. - -member_in_group(Pid, Name) -> - case ets:lookup(pg2_fixed_table, {member, Name, Pid}) of - [] -> []; - [{{member, Name, Pid}, N}] -> - lists:duplicate(N, Pid) - end. - -member_groups(Pid) -> - [Name || [Name] <- ets:match(pg2_fixed_table, {{pid, Pid, '$1'}})]. - -all_groups() -> - [N || [N] <- ets:match(pg2_fixed_table, {{group,'$1'}})]. - -ensure_started() -> - case whereis(?MODULE) of - undefined -> - C = {pg2_fixed, {?MODULE, start_link, []}, permanent, - 1000, worker, [?MODULE]}, - supervisor:start_child(kernel_safe_sup, C); - Pg2_FixedPid -> - {ok, Pg2_FixedPid} - end. - - -kill_monitor_proc(RPid, Pid) -> - case RPid of - Pid -> ok; - _ -> exit(RPid, kill) - end. - -%% When/if erlang:monitor() returns before trying to connect to the -%% other node this function can be removed. -do_monitor(Pid) -> - case (node(Pid) =:= node()) orelse lists:member(node(Pid), nodes()) of - true -> - %% Assume the node is still up - {Pid, erlang:monitor(process, Pid)}; - false -> - F = fun() -> - Ref = erlang:monitor(process, Pid), - receive - {'DOWN', Ref, process, Pid, _Info} -> - exit(normal) - end - end, - erlang:spawn_monitor(F) - end. diff --git a/src/pg_local.erl b/src/pg_local.erl deleted file mode 100644 index 4d9914d9..00000000 --- a/src/pg_local.erl +++ /dev/null @@ -1,230 +0,0 @@ -%% This file is a copy of pg2.erl from the R13B-3 Erlang/OTP -%% distribution, with the following modifications: -%% -%% 1) Process groups are node-local only. -%% -%% 2) Groups are created/deleted implicitly. -%% -%% 3) 'join' and 'leave' are asynchronous. -%% -%% 4) the type specs of the exported non-callback functions have been -%% extracted into a separate, guarded section, and rewritten in -%% old-style spec syntax, for better compatibility with older -%% versions of Erlang/OTP. The remaining type specs have been -%% removed. - -%% All modifications are (C) 2010-2013 GoPivotal, Inc. - -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% --module(pg_local). - --export([join/2, leave/2, get_members/1, in_group/2]). --export([sync/0]). %% intended for testing only; not part of official API --export([start/0, start_link/0, init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(name() :: term()). - --spec(start_link/0 :: () -> {'ok', pid()} | {'error', any()}). --spec(start/0 :: () -> {'ok', pid()} | {'error', any()}). --spec(join/2 :: (name(), pid()) -> 'ok'). --spec(leave/2 :: (name(), pid()) -> 'ok'). --spec(get_members/1 :: (name()) -> [pid()]). --spec(in_group/2 :: (name(), pid()) -> boolean()). - --spec(sync/0 :: () -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -%%% As of R13B03 monitors are used instead of links. - -%%% -%%% Exported functions -%%% - -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -start() -> - ensure_started(). - -join(Name, Pid) when is_pid(Pid) -> - ensure_started(), - gen_server:cast(?MODULE, {join, Name, Pid}). - -leave(Name, Pid) when is_pid(Pid) -> - ensure_started(), - gen_server:cast(?MODULE, {leave, Name, Pid}). - -get_members(Name) -> - ensure_started(), - group_members(Name). - -in_group(Name, Pid) -> - ensure_started(), - %% The join message is a cast and thus can race, but we want to - %% keep it that way to be fast in the common case. - case member_present(Name, Pid) of - true -> true; - false -> sync(), - member_present(Name, Pid) - end. - -sync() -> - ensure_started(), - gen_server:call(?MODULE, sync, infinity). - -%%% -%%% Callback functions from gen_server -%%% - --record(state, {}). - -init([]) -> - pg_local_table = ets:new(pg_local_table, [ordered_set, protected, named_table]), - {ok, #state{}}. - -handle_call(sync, _From, S) -> - {reply, ok, S}; - -handle_call(Request, From, S) -> - error_logger:warning_msg("The pg_local server received an unexpected message:\n" - "handle_call(~p, ~p, _)\n", - [Request, From]), - {noreply, S}. - -handle_cast({join, Name, Pid}, S) -> - join_group(Name, Pid), - {noreply, S}; -handle_cast({leave, Name, Pid}, S) -> - leave_group(Name, Pid), - {noreply, S}; -handle_cast(_, S) -> - {noreply, S}. - -handle_info({'DOWN', MonitorRef, process, _Pid, _Info}, S) -> - member_died(MonitorRef), - {noreply, S}; -handle_info(_, S) -> - {noreply, S}. - -terminate(_Reason, _S) -> - true = ets:delete(pg_local_table), - ok. - -%%% -%%% Local functions -%%% - -%%% One ETS table, pg_local_table, is used for bookkeeping. The type of the -%%% table is ordered_set, and the fast matching of partially -%%% instantiated keys is used extensively. -%%% -%%% {{ref, Pid}, MonitorRef, Counter} -%%% {{ref, MonitorRef}, Pid} -%%% Each process has one monitor. Counter is incremented when the -%%% Pid joins some group. -%%% {{member, Name, Pid}, _} -%%% Pid is a member of group Name, GroupCounter is incremented when the -%%% Pid joins the group Name. -%%% {{pid, Pid, Name}} -%%% Pid is a member of group Name. - -member_died(Ref) -> - [{{ref, Ref}, Pid}] = ets:lookup(pg_local_table, {ref, Ref}), - Names = member_groups(Pid), - _ = [leave_group(Name, P) || - Name <- Names, - P <- member_in_group(Pid, Name)], - ok. - -join_group(Name, Pid) -> - Ref_Pid = {ref, Pid}, - try _ = ets:update_counter(pg_local_table, Ref_Pid, {3, +1}) - catch _:_ -> - Ref = erlang:monitor(process, Pid), - true = ets:insert(pg_local_table, {Ref_Pid, Ref, 1}), - true = ets:insert(pg_local_table, {{ref, Ref}, Pid}) - end, - Member_Name_Pid = {member, Name, Pid}, - try _ = ets:update_counter(pg_local_table, Member_Name_Pid, {2, +1}) - catch _:_ -> - true = ets:insert(pg_local_table, {Member_Name_Pid, 1}), - true = ets:insert(pg_local_table, {{pid, Pid, Name}}) - end. - -leave_group(Name, Pid) -> - Member_Name_Pid = {member, Name, Pid}, - try ets:update_counter(pg_local_table, Member_Name_Pid, {2, -1}) of - N -> - if - N =:= 0 -> - true = ets:delete(pg_local_table, {pid, Pid, Name}), - true = ets:delete(pg_local_table, Member_Name_Pid); - true -> - ok - end, - Ref_Pid = {ref, Pid}, - case ets:update_counter(pg_local_table, Ref_Pid, {3, -1}) of - 0 -> - [{Ref_Pid,Ref,0}] = ets:lookup(pg_local_table, Ref_Pid), - true = ets:delete(pg_local_table, {ref, Ref}), - true = ets:delete(pg_local_table, Ref_Pid), - true = erlang:demonitor(Ref, [flush]), - ok; - _ -> - ok - end - catch _:_ -> - ok - end. - -group_members(Name) -> - [P || - [P, N] <- ets:match(pg_local_table, {{member, Name, '$1'},'$2'}), - _ <- lists:seq(1, N)]. - -member_in_group(Pid, Name) -> - [{{member, Name, Pid}, N}] = ets:lookup(pg_local_table, {member, Name, Pid}), - lists:duplicate(N, Pid). - -member_present(Name, Pid) -> - case ets:lookup(pg_local_table, {member, Name, Pid}) of - [_] -> true; - [] -> false - end. - -member_groups(Pid) -> - [Name || [Name] <- ets:match(pg_local_table, {{pid, Pid, '$1'}})]. - -ensure_started() -> - case whereis(?MODULE) of - undefined -> - C = {pg_local, {?MODULE, start_link, []}, permanent, - 16#ffffffff, worker, [?MODULE]}, - supervisor:start_child(kernel_safe_sup, C); - PgLocalPid -> - {ok, PgLocalPid} - end. diff --git a/src/pmon.erl b/src/pmon.erl deleted file mode 100644 index a94f5a67..00000000 --- a/src/pmon.erl +++ /dev/null @@ -1,96 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. -%% - --module(pmon). - --export([new/0, new/1, monitor/2, monitor_all/2, demonitor/2, - is_monitored/2, erase/2, monitored/1, is_empty/1]). - --compile({no_auto_import, [monitor/2]}). - --record(state, {dict, module}). - --ifdef(use_specs). - -%%---------------------------------------------------------------------------- - --export_type([?MODULE/0]). - --opaque(?MODULE() :: #state{dict :: dict:dict(), - module :: atom()}). - --type(item() :: pid() | {atom(), node()}). - --spec(new/0 :: () -> ?MODULE()). --spec(new/1 :: ('erlang' | 'delegate') -> ?MODULE()). --spec(monitor/2 :: (item(), ?MODULE()) -> ?MODULE()). --spec(monitor_all/2 :: ([item()], ?MODULE()) -> ?MODULE()). --spec(demonitor/2 :: (item(), ?MODULE()) -> ?MODULE()). --spec(is_monitored/2 :: (item(), ?MODULE()) -> boolean()). --spec(erase/2 :: (item(), ?MODULE()) -> ?MODULE()). --spec(monitored/1 :: (?MODULE()) -> [item()]). --spec(is_empty/1 :: (?MODULE()) -> boolean()). - --endif. - -new() -> new(erlang). - -new(Module) -> #state{dict = dict:new(), - module = Module}. - -monitor(Item, S = #state{dict = M, module = Module}) -> - case dict:is_key(Item, M) of - true -> S; - false -> case node_alive_shortcut(Item) of - true -> Ref = Module:monitor(process, Item), - S#state{dict = dict:store(Item, Ref, M)}; - false -> self() ! {'DOWN', fake_ref, process, Item, - nodedown}, - S - end - end. - -monitor_all([], S) -> S; %% optimisation -monitor_all([Item], S) -> monitor(Item, S); %% optimisation -monitor_all(Items, S) -> lists:foldl(fun monitor/2, S, Items). - -demonitor(Item, S = #state{dict = M, module = Module}) -> - case dict:find(Item, M) of - {ok, MRef} -> Module:demonitor(MRef), - S#state{dict = dict:erase(Item, M)}; - error -> M - end. - -is_monitored(Item, #state{dict = M}) -> dict:is_key(Item, M). - -erase(Item, S = #state{dict = M}) -> S#state{dict = dict:erase(Item, M)}. - -monitored(#state{dict = M}) -> dict:fetch_keys(M). - -is_empty(#state{dict = M}) -> dict:size(M) == 0. - -%%---------------------------------------------------------------------------- - -%% We check here to see if the node is alive in order to avoid trying -%% to connect to it if it isn't - this can cause substantial -%% slowdowns. We can't perform this shortcut if passed {Name, Node} -%% since we would need to convert that into a pid for the fake 'DOWN' -%% message, so we always return true here - but that's OK, it's just -%% an optimisation. -node_alive_shortcut(P) when is_pid(P) -> - lists:member(node(P), [node() | nodes()]); -node_alive_shortcut({_Name, _Node}) -> - true. diff --git a/src/priority_queue.erl b/src/priority_queue.erl deleted file mode 100644 index a3573bbd..00000000 --- a/src/priority_queue.erl +++ /dev/null @@ -1,227 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - -%% Priority queues have essentially the same interface as ordinary -%% queues, except that a) there is an in/3 that takes a priority, and -%% b) we have only implemented the core API we need. -%% -%% Priorities should be integers - the higher the value the higher the -%% priority - but we don't actually check that. -%% -%% in/2 inserts items with priority 0. -%% -%% We optimise the case where a priority queue is being used just like -%% an ordinary queue. When that is the case we represent the priority -%% queue as an ordinary queue. We could just call into the 'queue' -%% module for that, but for efficiency we implement the relevant -%% functions directly in here, thus saving on inter-module calls and -%% eliminating a level of boxing. -%% -%% When the queue contains items with non-zero priorities, it is -%% represented as a sorted kv list with the inverted Priority as the -%% key and an ordinary queue as the value. Here again we use our own -%% ordinary queue implemention for efficiency, often making recursive -%% calls into the same function knowing that ordinary queues represent -%% a base case. - - --module(priority_queue). - --export([new/0, is_queue/1, is_empty/1, len/1, to_list/1, from_list/1, - in/2, in/3, out/1, out_p/1, join/2, filter/2, fold/3, highest/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([q/0]). - --type(q() :: pqueue()). --type(priority() :: integer() | 'infinity'). --type(squeue() :: {queue, [any()], [any()], non_neg_integer()}). --type(pqueue() :: squeue() | {pqueue, [{priority(), squeue()}]}). - --spec(new/0 :: () -> pqueue()). --spec(is_queue/1 :: (any()) -> boolean()). --spec(is_empty/1 :: (pqueue()) -> boolean()). --spec(len/1 :: (pqueue()) -> non_neg_integer()). --spec(to_list/1 :: (pqueue()) -> [{priority(), any()}]). --spec(from_list/1 :: ([{priority(), any()}]) -> pqueue()). --spec(in/2 :: (any(), pqueue()) -> pqueue()). --spec(in/3 :: (any(), priority(), pqueue()) -> pqueue()). --spec(out/1 :: (pqueue()) -> {empty | {value, any()}, pqueue()}). --spec(out_p/1 :: (pqueue()) -> {empty | {value, any(), priority()}, pqueue()}). --spec(join/2 :: (pqueue(), pqueue()) -> pqueue()). --spec(filter/2 :: (fun ((any()) -> boolean()), pqueue()) -> pqueue()). --spec(fold/3 :: - (fun ((any(), priority(), A) -> A), A, pqueue()) -> A). --spec(highest/1 :: (pqueue()) -> priority() | 'empty'). - --endif. - -%%---------------------------------------------------------------------------- - -new() -> - {queue, [], [], 0}. - -is_queue({queue, R, F, L}) when is_list(R), is_list(F), is_integer(L) -> - true; -is_queue({pqueue, Queues}) when is_list(Queues) -> - lists:all(fun ({infinity, Q}) -> is_queue(Q); - ({P, Q}) -> is_integer(P) andalso is_queue(Q) - end, Queues); -is_queue(_) -> - false. - -is_empty({queue, [], [], 0}) -> - true; -is_empty(_) -> - false. - -len({queue, _R, _F, L}) -> - L; -len({pqueue, Queues}) -> - lists:sum([len(Q) || {_, Q} <- Queues]). - -to_list({queue, In, Out, _Len}) when is_list(In), is_list(Out) -> - [{0, V} || V <- Out ++ lists:reverse(In, [])]; -to_list({pqueue, Queues}) -> - [{maybe_negate_priority(P), V} || {P, Q} <- Queues, - {0, V} <- to_list(Q)]. - -from_list(L) -> - lists:foldl(fun ({P, E}, Q) -> in(E, P, Q) end, new(), L). - -in(Item, Q) -> - in(Item, 0, Q). - -in(X, 0, {queue, [_] = In, [], 1}) -> - {queue, [X], In, 2}; -in(X, 0, {queue, In, Out, Len}) when is_list(In), is_list(Out) -> - {queue, [X|In], Out, Len + 1}; -in(X, Priority, _Q = {queue, [], [], 0}) -> - in(X, Priority, {pqueue, []}); -in(X, Priority, Q = {queue, _, _, _}) -> - in(X, Priority, {pqueue, [{0, Q}]}); -in(X, Priority, {pqueue, Queues}) -> - P = maybe_negate_priority(Priority), - {pqueue, case lists:keysearch(P, 1, Queues) of - {value, {_, Q}} -> - lists:keyreplace(P, 1, Queues, {P, in(X, Q)}); - false when P == infinity -> - [{P, {queue, [X], [], 1}} | Queues]; - false -> - case Queues of - [{infinity, InfQueue} | Queues1] -> - [{infinity, InfQueue} | - lists:keysort(1, [{P, {queue, [X], [], 1}} | Queues1])]; - _ -> - lists:keysort(1, [{P, {queue, [X], [], 1}} | Queues]) - end - end}. - -out({queue, [], [], 0} = Q) -> - {empty, Q}; -out({queue, [V], [], 1}) -> - {{value, V}, {queue, [], [], 0}}; -out({queue, [Y|In], [], Len}) -> - [V|Out] = lists:reverse(In, []), - {{value, V}, {queue, [Y], Out, Len - 1}}; -out({queue, In, [V], Len}) when is_list(In) -> - {{value,V}, r2f(In, Len - 1)}; -out({queue, In,[V|Out], Len}) when is_list(In) -> - {{value, V}, {queue, In, Out, Len - 1}}; -out({pqueue, [{P, Q} | Queues]}) -> - {R, Q1} = out(Q), - NewQ = case is_empty(Q1) of - true -> case Queues of - [] -> {queue, [], [], 0}; - [{0, OnlyQ}] -> OnlyQ; - [_|_] -> {pqueue, Queues} - end; - false -> {pqueue, [{P, Q1} | Queues]} - end, - {R, NewQ}. - -out_p({queue, _, _, _} = Q) -> add_p(out(Q), 0); -out_p({pqueue, [{P, _} | _]} = Q) -> add_p(out(Q), maybe_negate_priority(P)). - -add_p(R, P) -> case R of - {empty, Q} -> {empty, Q}; - {{value, V}, Q} -> {{value, V, P}, Q} - end. - -join(A, {queue, [], [], 0}) -> - A; -join({queue, [], [], 0}, B) -> - B; -join({queue, AIn, AOut, ALen}, {queue, BIn, BOut, BLen}) -> - {queue, BIn, AOut ++ lists:reverse(AIn, BOut), ALen + BLen}; -join(A = {queue, _, _, _}, {pqueue, BPQ}) -> - {Pre, Post} = - lists:splitwith(fun ({P, _}) -> P < 0 orelse P == infinity end, BPQ), - Post1 = case Post of - [] -> [ {0, A} ]; - [ {0, ZeroQueue} | Rest ] -> [ {0, join(A, ZeroQueue)} | Rest ]; - _ -> [ {0, A} | Post ] - end, - {pqueue, Pre ++ Post1}; -join({pqueue, APQ}, B = {queue, _, _, _}) -> - {Pre, Post} = - lists:splitwith(fun ({P, _}) -> P < 0 orelse P == infinity end, APQ), - Post1 = case Post of - [] -> [ {0, B} ]; - [ {0, ZeroQueue} | Rest ] -> [ {0, join(ZeroQueue, B)} | Rest ]; - _ -> [ {0, B} | Post ] - end, - {pqueue, Pre ++ Post1}; -join({pqueue, APQ}, {pqueue, BPQ}) -> - {pqueue, merge(APQ, BPQ, [])}. - -merge([], BPQ, Acc) -> - lists:reverse(Acc, BPQ); -merge(APQ, [], Acc) -> - lists:reverse(Acc, APQ); -merge([{P, A}|As], [{P, B}|Bs], Acc) -> - merge(As, Bs, [ {P, join(A, B)} | Acc ]); -merge([{PA, A}|As], Bs = [{PB, _}|_], Acc) when PA < PB orelse PA == infinity -> - merge(As, Bs, [ {PA, A} | Acc ]); -merge(As = [{_, _}|_], [{PB, B}|Bs], Acc) -> - merge(As, Bs, [ {PB, B} | Acc ]). - -filter(Pred, Q) -> fold(fun(V, P, Acc) -> - case Pred(V) of - true -> in(V, P, Acc); - false -> Acc - end - end, new(), Q). - -fold(Fun, Init, Q) -> case out_p(Q) of - {empty, _Q} -> Init; - {{value, V, P}, Q1} -> fold(Fun, Fun(V, P, Init), Q1) - end. - -highest({queue, [], [], 0}) -> empty; -highest({queue, _, _, _}) -> 0; -highest({pqueue, [{P, _} | _]}) -> maybe_negate_priority(P). - -r2f([], 0) -> {queue, [], [], 0}; -r2f([_] = R, 1) -> {queue, [], R, 1}; -r2f([X,Y], 2) -> {queue, [X], [Y], 2}; -r2f([X,Y|R], L) -> {queue, [X,Y], lists:reverse(R, []), L}. - -maybe_negate_priority(infinity) -> infinity; -maybe_negate_priority(P) -> -P. diff --git a/src/rabbit.erl b/src/rabbit.erl deleted file mode 100644 index 40f24efc..00000000 --- a/src/rabbit.erl +++ /dev/null @@ -1,890 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit). - --behaviour(application). - --export([start/0, boot/0, stop/0, - stop_and_halt/0, await_startup/0, status/0, is_running/0, - is_running/1, environment/0, rotate_logs/1, force_event_refresh/1, - start_fhc/0]). --export([start/2, stop/1]). --export([start_apps/1, stop_apps/1]). --export([log_location/1, config_files/0]). %% for testing and mgmt-agent - -%%--------------------------------------------------------------------------- -%% Boot steps. --export([maybe_insert_default_data/0, boot_delegate/0, recover/0]). - --rabbit_boot_step({pre_boot, [{description, "rabbit boot start"}]}). - --rabbit_boot_step({codec_correctness_check, - [{description, "codec correctness check"}, - {mfa, {rabbit_binary_generator, - check_empty_frame_size, - []}}, - {requires, pre_boot}, - {enables, external_infrastructure}]}). - --rabbit_boot_step({database, - [{mfa, {rabbit_mnesia, init, []}}, - {requires, file_handle_cache}, - {enables, external_infrastructure}]}). - --rabbit_boot_step({database_sync, - [{description, "database sync"}, - {mfa, {rabbit_sup, start_child, [mnesia_sync]}}, - {requires, database}, - {enables, external_infrastructure}]}). - --rabbit_boot_step({file_handle_cache, - [{description, "file handle cache server"}, - {mfa, {rabbit, start_fhc, []}}, - {requires, pre_boot}, - {enables, worker_pool}]}). - --rabbit_boot_step({worker_pool, - [{description, "worker pool"}, - {mfa, {rabbit_sup, start_supervisor_child, - [worker_pool_sup]}}, - {requires, pre_boot}, - {enables, external_infrastructure}]}). - --rabbit_boot_step({external_infrastructure, - [{description, "external infrastructure ready"}]}). - --rabbit_boot_step({rabbit_registry, - [{description, "plugin registry"}, - {mfa, {rabbit_sup, start_child, - [rabbit_registry]}}, - {requires, external_infrastructure}, - {enables, kernel_ready}]}). - --rabbit_boot_step({rabbit_event, - [{description, "statistics event manager"}, - {mfa, {rabbit_sup, start_restartable_child, - [rabbit_event]}}, - {requires, external_infrastructure}, - {enables, kernel_ready}]}). - --rabbit_boot_step({kernel_ready, - [{description, "kernel ready"}, - {requires, external_infrastructure}]}). - --rabbit_boot_step({rabbit_alarm, - [{description, "alarm handler"}, - {mfa, {rabbit_alarm, start, []}}, - {requires, kernel_ready}, - {enables, core_initialized}]}). - --rabbit_boot_step({rabbit_memory_monitor, - [{description, "memory monitor"}, - {mfa, {rabbit_sup, start_restartable_child, - [rabbit_memory_monitor]}}, - {requires, rabbit_alarm}, - {enables, core_initialized}]}). - --rabbit_boot_step({guid_generator, - [{description, "guid generator"}, - {mfa, {rabbit_sup, start_restartable_child, - [rabbit_guid]}}, - {requires, kernel_ready}, - {enables, core_initialized}]}). - --rabbit_boot_step({delegate_sup, - [{description, "cluster delegate"}, - {mfa, {rabbit, boot_delegate, []}}, - {requires, kernel_ready}, - {enables, core_initialized}]}). - --rabbit_boot_step({rabbit_node_monitor, - [{description, "node monitor"}, - {mfa, {rabbit_sup, start_restartable_child, - [rabbit_node_monitor]}}, - {requires, [rabbit_alarm, guid_generator]}, - {enables, core_initialized}]}). - --rabbit_boot_step({core_initialized, - [{description, "core initialized"}, - {requires, kernel_ready}]}). - --rabbit_boot_step({empty_db_check, - [{description, "empty DB check"}, - {mfa, {?MODULE, maybe_insert_default_data, []}}, - {requires, core_initialized}, - {enables, routing_ready}]}). - --rabbit_boot_step({recovery, - [{description, "exchange, queue and binding recovery"}, - {mfa, {rabbit, recover, []}}, - {requires, core_initialized}, - {enables, routing_ready}]}). - --rabbit_boot_step({mirrored_queues, - [{description, "adding mirrors to queues"}, - {mfa, {rabbit_mirror_queue_misc, on_node_up, []}}, - {requires, recovery}, - {enables, routing_ready}]}). - --rabbit_boot_step({routing_ready, - [{description, "message delivery logic ready"}, - {requires, core_initialized}]}). - --rabbit_boot_step({log_relay, - [{description, "error log relay"}, - {mfa, {rabbit_sup, start_child, - [rabbit_error_logger_lifecycle, - supervised_lifecycle, - [rabbit_error_logger_lifecycle, - {rabbit_error_logger, start, []}, - {rabbit_error_logger, stop, []}]]}}, - {requires, routing_ready}, - {enables, networking}]}). - --rabbit_boot_step({direct_client, - [{description, "direct client"}, - {mfa, {rabbit_direct, boot, []}}, - {requires, log_relay}]}). - --rabbit_boot_step({networking, - [{mfa, {rabbit_networking, boot, []}}, - {requires, log_relay}]}). - --rabbit_boot_step({notify_cluster, - [{description, "notify cluster nodes"}, - {mfa, {rabbit_node_monitor, notify_node_up, []}}, - {requires, networking}]}). - --rabbit_boot_step({background_gc, - [{description, "background garbage collection"}, - {mfa, {rabbit_sup, start_restartable_child, - [background_gc]}}, - {enables, networking}]}). - -%%--------------------------------------------------------------------------- - --include("rabbit_framing.hrl"). --include("rabbit.hrl"). - --define(APPS, [os_mon, mnesia, rabbit]). - -%% HiPE compilation uses multiple cores anyway, but some bits are -%% IO-bound so we can go faster if we parallelise a bit more. In -%% practice 2 processes seems just as fast as any other number > 1, -%% and keeps the progress bar realistic-ish. --define(HIPE_PROCESSES, 2). --define(ASYNC_THREADS_WARNING_THRESHOLD, 8). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(file_suffix() :: binary()). -%% this really should be an abstract type --type(log_location() :: 'tty' | 'undefined' | file:filename()). --type(param() :: atom()). --type(app_name() :: atom()). - --spec(start/0 :: () -> 'ok'). --spec(boot/0 :: () -> 'ok'). --spec(stop/0 :: () -> 'ok'). --spec(stop_and_halt/0 :: () -> no_return()). --spec(await_startup/0 :: () -> 'ok'). --spec(status/0 :: - () -> [{pid, integer()} | - {running_applications, [{atom(), string(), string()}]} | - {os, {atom(), atom()}} | - {erlang_version, string()} | - {memory, any()}]). --spec(is_running/0 :: () -> boolean()). --spec(is_running/1 :: (node()) -> boolean()). --spec(environment/0 :: () -> [{param(), term()}]). --spec(rotate_logs/1 :: (file_suffix()) -> rabbit_types:ok_or_error(any())). --spec(force_event_refresh/1 :: (reference()) -> 'ok'). - --spec(log_location/1 :: ('sasl' | 'kernel') -> log_location()). - --spec(start/2 :: ('normal',[]) -> - {'error', - {'erlang_version_too_old', - {'found',[any()]}, - {'required',[any(),...]}}} | - {'ok',pid()}). --spec(stop/1 :: (_) -> 'ok'). - --spec(maybe_insert_default_data/0 :: () -> 'ok'). --spec(boot_delegate/0 :: () -> 'ok'). --spec(recover/0 :: () -> 'ok'). --spec(start_apps/1 :: ([app_name()]) -> 'ok'). --spec(stop_apps/1 :: ([app_name()]) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -%% HiPE compilation happens before we have log handlers - so we have -%% to io:format/2, it's all we can do. - -maybe_hipe_compile() -> - {ok, Want} = application:get_env(rabbit, hipe_compile), - Can = code:which(hipe) =/= non_existing, - case {Want, Can} of - {true, true} -> hipe_compile(); - {true, false} -> false; - {false, _} -> {ok, disabled} - end. - -log_hipe_result({ok, disabled}) -> - ok; -log_hipe_result({ok, Count, Duration}) -> - rabbit_log:info( - "HiPE in use: compiled ~B modules in ~Bs.~n", [Count, Duration]); -log_hipe_result(false) -> - io:format( - "~nNot HiPE compiling: HiPE not found in this Erlang installation.~n"), - rabbit_log:warning( - "Not HiPE compiling: HiPE not found in this Erlang installation.~n"). - -%% HiPE compilation happens before we have log handlers and can take a -%% long time, so make an exception to our no-stdout policy and display -%% progress via stdout. -hipe_compile() -> - {ok, HipeModulesAll} = application:get_env(rabbit, hipe_modules), - HipeModules = [HM || HM <- HipeModulesAll, code:which(HM) =/= non_existing], - Count = length(HipeModules), - io:format("~nHiPE compiling: |~s|~n |", - [string:copies("-", Count)]), - T1 = erlang:now(), - PidMRefs = [spawn_monitor(fun () -> [begin - {ok, M} = hipe:c(M, [o3]), - io:format("#") - end || M <- Ms] - end) || - Ms <- split(HipeModules, ?HIPE_PROCESSES)], - [receive - {'DOWN', MRef, process, _, normal} -> ok; - {'DOWN', MRef, process, _, Reason} -> exit(Reason) - end || {_Pid, MRef} <- PidMRefs], - T2 = erlang:now(), - Duration = timer:now_diff(T2, T1) div 1000000, - io:format("|~n~nCompiled ~B modules in ~Bs~n", [Count, Duration]), - {ok, Count, Duration}. - -split(L, N) -> split0(L, [[] || _ <- lists:seq(1, N)]). - -split0([], Ls) -> Ls; -split0([I | Is], [L | Ls]) -> split0(Is, Ls ++ [[I | L]]). - -ensure_application_loaded() -> - %% We end up looking at the rabbit app's env for HiPE and log - %% handling, so it needs to be loaded. But during the tests, it - %% may end up getting loaded twice, so guard against that. - case application:load(rabbit) of - ok -> ok; - {error, {already_loaded, rabbit}} -> ok - end. - -start() -> - start_it(fun() -> - %% We do not want to HiPE compile or upgrade - %% mnesia after just restarting the app - ok = ensure_application_loaded(), - ok = ensure_working_log_handlers(), - rabbit_node_monitor:prepare_cluster_status_files(), - rabbit_mnesia:check_cluster_consistency(), - broker_start() - end). - -boot() -> - start_it(fun() -> - ok = ensure_application_loaded(), - HipeResult = maybe_hipe_compile(), - ok = ensure_working_log_handlers(), - log_hipe_result(HipeResult), - rabbit_node_monitor:prepare_cluster_status_files(), - ok = rabbit_upgrade:maybe_upgrade_mnesia(), - %% It's important that the consistency check happens after - %% the upgrade, since if we are a secondary node the - %% primary node will have forgotten us - rabbit_mnesia:check_cluster_consistency(), - broker_start() - end). - -broker_start() -> - Plugins = rabbit_plugins:setup(), - ToBeLoaded = Plugins ++ ?APPS, - start_apps(ToBeLoaded), - ok = log_broker_started(rabbit_plugins:active()). - -start_it(StartFun) -> - Marker = spawn_link(fun() -> receive stop -> ok end end), - case catch register(rabbit_boot, Marker) of - true -> try - case is_running() of - true -> ok; - false -> StartFun() - end - catch - throw:{could_not_start, _App, _Reason}=Err -> - boot_error(Err, not_available); - _:Reason -> - boot_error(Reason, erlang:get_stacktrace()) - after - unlink(Marker), - Marker ! stop, - %% give the error loggers some time to catch up - timer:sleep(100) - end; - _ -> unlink(Marker), - Marker ! stop - end. - -stop() -> - case whereis(rabbit_boot) of - undefined -> ok; - _ -> await_startup(true) - end, - rabbit_log:info("Stopping RabbitMQ~n", []), - Apps = ?APPS ++ rabbit_plugins:active(), - stop_apps(app_utils:app_dependency_order(Apps, true)), - rabbit_log:info("Stopped RabbitMQ application~n", []). - -stop_and_halt() -> - try - stop() - after - rabbit_log:info("Halting Erlang VM~n", []), - init:stop() - end, - ok. - -start_apps(Apps) -> - app_utils:load_applications(Apps), - OrderedApps = app_utils:app_dependency_order(Apps, false), - case lists:member(rabbit, Apps) of - false -> run_boot_steps(Apps); %% plugin activation - true -> ok %% will run during start of rabbit app - end, - ok = app_utils:start_applications(OrderedApps, - handle_app_error(could_not_start)). - -stop_apps(Apps) -> - ok = app_utils:stop_applications( - Apps, handle_app_error(error_during_shutdown)), - case lists:member(rabbit, Apps) of - false -> run_cleanup_steps(Apps); %% plugin deactivation - true -> ok %% it's all going anyway - end, - ok. - -handle_app_error(Term) -> - fun(App, {bad_return, {_MFA, {'EXIT', {ExitReason, _}}}}) -> - throw({Term, App, ExitReason}); - (App, Reason) -> - throw({Term, App, Reason}) - end. - -run_cleanup_steps(Apps) -> - [run_step(Name, Attrs, cleanup) || {_, Name, Attrs} <- find_steps(Apps)], - ok. - -await_startup() -> - await_startup(false). - -await_startup(HaveSeenRabbitBoot) -> - %% We don't take absence of rabbit_boot as evidence we've started, - %% since there's a small window before it is registered. - case whereis(rabbit_boot) of - undefined -> case HaveSeenRabbitBoot orelse is_running() of - true -> ok; - false -> timer:sleep(100), - await_startup(false) - end; - _ -> timer:sleep(100), - await_startup(true) - end. - -status() -> - S1 = [{pid, list_to_integer(os:getpid())}, - {running_applications, rabbit_misc:which_applications()}, - {os, os:type()}, - {erlang_version, erlang:system_info(system_version)}, - {memory, rabbit_vm:memory()}, - {alarms, alarms()}, - {listeners, listeners()}], - S2 = rabbit_misc:filter_exit_map( - fun ({Key, {M, F, A}}) -> {Key, erlang:apply(M, F, A)} end, - [{vm_memory_high_watermark, {vm_memory_monitor, - get_vm_memory_high_watermark, []}}, - {vm_memory_limit, {vm_memory_monitor, - get_memory_limit, []}}, - {disk_free_limit, {rabbit_disk_monitor, - get_disk_free_limit, []}}, - {disk_free, {rabbit_disk_monitor, - get_disk_free, []}}]), - S3 = rabbit_misc:with_exit_handler( - fun () -> [] end, - fun () -> [{file_descriptors, file_handle_cache:info()}] end), - S4 = [{processes, [{limit, erlang:system_info(process_limit)}, - {used, erlang:system_info(process_count)}]}, - {run_queue, erlang:statistics(run_queue)}, - {uptime, begin - {T,_} = erlang:statistics(wall_clock), - T div 1000 - end}], - S1 ++ S2 ++ S3 ++ S4. - -alarms() -> - Alarms = rabbit_misc:with_exit_handler(rabbit_misc:const([]), - fun rabbit_alarm:get_alarms/0), - N = node(), - %% [{{resource_limit,memory,rabbit@mercurio},[]}] - [Limit || {{resource_limit, Limit, Node}, _} <- Alarms, Node =:= N]. - -listeners() -> - Listeners = try - rabbit_networking:active_listeners() - catch - exit:{aborted, _} -> [] - end, - [{Protocol, Port, rabbit_misc:ntoa(IP)} || - #listener{node = Node, - protocol = Protocol, - ip_address = IP, - port = Port} <- Listeners, Node =:= node()]. - -%% TODO this only determines if the rabbit application has started, -%% not if it is running, never mind plugins. It would be nice to have -%% more nuance here. -is_running() -> is_running(node()). - -is_running(Node) -> rabbit_nodes:is_process_running(Node, rabbit). - -environment() -> - [{A, environment(A)} || - {A, _, _} <- lists:keysort(1, application:which_applications())]. - -environment(App) -> - Ignore = [default_pass, included_applications], - lists:keysort(1, [P || P = {K, _} <- application:get_all_env(App), - not lists:member(K, Ignore)]). - -rotate_logs(BinarySuffix) -> - Suffix = binary_to_list(BinarySuffix), - rabbit_log:info("Rotating logs with suffix '~s'~n", [Suffix]), - log_rotation_result(rotate_logs(log_location(kernel), - Suffix, - rabbit_error_logger_file_h), - rotate_logs(log_location(sasl), - Suffix, - rabbit_sasl_report_file_h)). - -%%-------------------------------------------------------------------- - -start(normal, []) -> - case erts_version_check() of - ok -> - rabbit_log:info("Starting RabbitMQ ~s on Erlang ~s~n~s~n~s~n", - [rabbit_misc:version(), rabbit_misc:otp_release(), - ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), - {ok, SupPid} = rabbit_sup:start_link(), - true = register(rabbit, self()), - print_banner(), - log_banner(), - warn_if_kernel_config_dubious(), - run_boot_steps(), - {ok, SupPid}; - Error -> - Error - end. - -stop(_State) -> - ok = rabbit_alarm:stop(), - ok = case rabbit_mnesia:is_clustered() of - true -> rabbit_amqqueue:on_node_down(node()); - false -> rabbit_table:clear_ram_only_tables() - end, - ok. - -%%--------------------------------------------------------------------------- -%% boot step logic - -run_boot_steps() -> - run_boot_steps([App || {App, _, _} <- application:loaded_applications()]). - -run_boot_steps(Apps) -> - [ok = run_step(Step, Attrs, mfa) || {_, Step, Attrs} <- find_steps(Apps)], - ok. - -find_steps(Apps) -> - All = sort_boot_steps(rabbit_misc:all_module_attributes(rabbit_boot_step)), - [Step || {App, _, _} = Step <- All, lists:member(App, Apps)]. - -run_step(StepName, Attributes, AttributeName) -> - case [MFA || {Key, MFA} <- Attributes, - Key =:= AttributeName] of - [] -> - ok; - MFAs -> - [try - apply(M,F,A) - of - ok -> ok; - {error, Reason} -> boot_error({boot_step, StepName, Reason}, - not_available) - catch - _:Reason -> boot_error({boot_step, StepName, Reason}, - erlang:get_stacktrace()) - end || {M,F,A} <- MFAs], - ok - end. - -vertices({AppName, _Module, Steps}) -> - [{StepName, {AppName, StepName, Atts}} || {StepName, Atts} <- Steps]. - -edges({_AppName, _Module, Steps}) -> - EnsureList = fun (L) when is_list(L) -> L; - (T) -> [T] - end, - [case Key of - requires -> {StepName, OtherStep}; - enables -> {OtherStep, StepName} - end || {StepName, Atts} <- Steps, - {Key, OtherStepOrSteps} <- Atts, - OtherStep <- EnsureList(OtherStepOrSteps), - Key =:= requires orelse Key =:= enables]. - -sort_boot_steps(UnsortedSteps) -> - case rabbit_misc:build_acyclic_graph(fun vertices/1, fun edges/1, - UnsortedSteps) of - {ok, G} -> - %% Use topological sort to find a consistent ordering (if - %% there is one, otherwise fail). - SortedSteps = lists:reverse( - [begin - {StepName, Step} = digraph:vertex(G, - StepName), - Step - end || StepName <- digraph_utils:topsort(G)]), - digraph:delete(G), - %% Check that all mentioned {M,F,A} triples are exported. - case [{StepName, {M,F,A}} || - {_App, StepName, Attributes} <- SortedSteps, - {mfa, {M,F,A}} <- Attributes, - not erlang:function_exported(M, F, length(A))] of - [] -> SortedSteps; - MissingFunctions -> basic_boot_error( - {missing_functions, MissingFunctions}, - "Boot step functions not exported: ~p~n", - [MissingFunctions]) - end; - {error, {vertex, duplicate, StepName}} -> - basic_boot_error({duplicate_boot_step, StepName}, - "Duplicate boot step name: ~w~n", [StepName]); - {error, {edge, Reason, From, To}} -> - basic_boot_error( - {invalid_boot_step_dependency, From, To}, - "Could not add boot step dependency of ~w on ~w:~n~s", - [To, From, - case Reason of - {bad_vertex, V} -> - io_lib:format("Boot step not registered: ~w~n", [V]); - {bad_edge, [First | Rest]} -> - [io_lib:format("Cyclic dependency: ~w", [First]), - [io_lib:format(" depends on ~w", [Next]) || - Next <- Rest], - io_lib:format(" depends on ~w~n", [First])] - end]) - end. - --ifdef(use_specs). --spec(boot_error/2 :: (term(), not_available | [tuple()]) -> no_return()). --endif. -boot_error(Term={error, {timeout_waiting_for_tables, _}}, _Stacktrace) -> - AllNodes = rabbit_mnesia:cluster_nodes(all), - {Err, Nodes} = - case AllNodes -- [node()] of - [] -> {"Timeout contacting cluster nodes. Since RabbitMQ was" - " shut down forcefully~nit cannot determine which nodes" - " are timing out.~n", []}; - Ns -> {rabbit_misc:format( - "Timeout contacting cluster nodes: ~p.~n", [Ns]), - Ns} - end, - basic_boot_error(Term, - Err ++ rabbit_nodes:diagnostics(Nodes) ++ "~n~n", []); -boot_error(Reason, Stacktrace) -> - Fmt = "Error description:~n ~p~n~n" ++ - "Log files (may contain more information):~n ~s~n ~s~n~n", - Args = [Reason, log_location(kernel), log_location(sasl)], - boot_error(Reason, Fmt, Args, Stacktrace). - --ifdef(use_specs). --spec(boot_error/4 :: (term(), string(), [any()], not_available | [tuple()]) - -> no_return()). --endif. -boot_error(Reason, Fmt, Args, not_available) -> - basic_boot_error(Reason, Fmt, Args); -boot_error(Reason, Fmt, Args, Stacktrace) -> - basic_boot_error(Reason, Fmt ++ "Stack trace:~n ~p~n~n", - Args ++ [Stacktrace]). - -basic_boot_error(Reason, Format, Args) -> - io:format("~n~nBOOT FAILED~n===========~n~n" ++ Format, Args), - rabbit_log:info(Format, Args), - timer:sleep(1000), - exit({?MODULE, failure_during_boot, Reason}). - -%%--------------------------------------------------------------------------- -%% boot step functions - -boot_delegate() -> - {ok, Count} = application:get_env(rabbit, delegate_count), - rabbit_sup:start_supervisor_child(delegate_sup, [Count]). - -recover() -> - rabbit_policy:recover(), - Qs = rabbit_amqqueue:recover(), - ok = rabbit_binding:recover(rabbit_exchange:recover(), - [QName || #amqqueue{name = QName} <- Qs]), - rabbit_amqqueue:start(Qs). - -maybe_insert_default_data() -> - case rabbit_table:needs_default_data() of - true -> insert_default_data(); - false -> ok - end. - -insert_default_data() -> - {ok, DefaultUser} = application:get_env(default_user), - {ok, DefaultPass} = application:get_env(default_pass), - {ok, DefaultTags} = application:get_env(default_user_tags), - {ok, DefaultVHost} = application:get_env(default_vhost), - {ok, [DefaultConfigurePerm, DefaultWritePerm, DefaultReadPerm]} = - application:get_env(default_permissions), - ok = rabbit_vhost:add(DefaultVHost), - ok = rabbit_auth_backend_internal:add_user(DefaultUser, DefaultPass), - ok = rabbit_auth_backend_internal:set_tags(DefaultUser, DefaultTags), - ok = rabbit_auth_backend_internal:set_permissions(DefaultUser, - DefaultVHost, - DefaultConfigurePerm, - DefaultWritePerm, - DefaultReadPerm), - ok. - -%%--------------------------------------------------------------------------- -%% logging - -ensure_working_log_handlers() -> - Handlers = gen_event:which_handlers(error_logger), - ok = ensure_working_log_handler(error_logger_tty_h, - rabbit_error_logger_file_h, - error_logger_tty_h, - log_location(kernel), - Handlers), - - ok = ensure_working_log_handler(sasl_report_tty_h, - rabbit_sasl_report_file_h, - sasl_report_tty_h, - log_location(sasl), - Handlers), - ok. - -ensure_working_log_handler(OldHandler, NewHandler, TTYHandler, - LogLocation, Handlers) -> - case LogLocation of - undefined -> ok; - tty -> case lists:member(TTYHandler, Handlers) of - true -> ok; - false -> - throw({error, {cannot_log_to_tty, - TTYHandler, not_installed}}) - end; - _ -> case lists:member(NewHandler, Handlers) of - true -> ok; - false -> case rotate_logs(LogLocation, "", - OldHandler, NewHandler) of - ok -> ok; - {error, Reason} -> - throw({error, {cannot_log_to_file, - LogLocation, Reason}}) - end - end - end. - -log_location(Type) -> - case application:get_env(rabbit, case Type of - kernel -> error_logger; - sasl -> sasl_error_logger - end) of - {ok, {file, File}} -> File; - {ok, false} -> undefined; - {ok, tty} -> tty; - {ok, silent} -> undefined; - {ok, Bad} -> throw({error, {cannot_log_to_file, Bad}}); - _ -> undefined - end. - -rotate_logs(File, Suffix, Handler) -> - rotate_logs(File, Suffix, Handler, Handler). - -rotate_logs(undefined, _Suffix, _OldHandler, _NewHandler) -> ok; -rotate_logs(tty, _Suffix, _OldHandler, _NewHandler) -> ok; -rotate_logs(File, Suffix, OldHandler, NewHandler) -> - gen_event:swap_handler(error_logger, - {OldHandler, swap}, - {NewHandler, {File, Suffix}}). - -log_rotation_result({error, MainLogError}, {error, SaslLogError}) -> - {error, {{cannot_rotate_main_logs, MainLogError}, - {cannot_rotate_sasl_logs, SaslLogError}}}; -log_rotation_result({error, MainLogError}, ok) -> - {error, {cannot_rotate_main_logs, MainLogError}}; -log_rotation_result(ok, {error, SaslLogError}) -> - {error, {cannot_rotate_sasl_logs, SaslLogError}}; -log_rotation_result(ok, ok) -> - ok. - -force_event_refresh(Ref) -> - rabbit_direct:force_event_refresh(Ref), - rabbit_networking:force_connection_event_refresh(Ref), - rabbit_channel:force_event_refresh(Ref), - rabbit_amqqueue:force_event_refresh(Ref). - -%%--------------------------------------------------------------------------- -%% misc - -log_broker_started(Plugins) -> - rabbit_log:with_local_io( - fun() -> - PluginList = iolist_to_binary([rabbit_misc:format(" * ~s~n", [P]) - || P <- Plugins]), - rabbit_log:info( - "Server startup complete; ~b plugins started.~n~s", - [length(Plugins), PluginList]), - io:format(" completed with ~p plugins.~n", [length(Plugins)]) - end). - -erts_version_check() -> - FoundVer = erlang:system_info(version), - case rabbit_misc:version_compare(?ERTS_MINIMUM, FoundVer, lte) of - true -> ok; - false -> {error, {erlang_version_too_old, - {found, FoundVer}, {required, ?ERTS_MINIMUM}}} - end. - -print_banner() -> - {ok, Product} = application:get_key(id), - {ok, Version} = application:get_key(vsn), - io:format("~n ~s ~s. ~s" - "~n ## ## ~s" - "~n ## ##" - "~n ########## Logs: ~s" - "~n ###### ## ~s" - "~n ##########" - "~n Starting broker...", - [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE, - log_location(kernel), log_location(sasl)]). - -log_banner() -> - Settings = [{"node", node()}, - {"home dir", home_dir()}, - {"config file(s)", config_files()}, - {"cookie hash", rabbit_nodes:cookie_hash()}, - {"log", log_location(kernel)}, - {"sasl log", log_location(sasl)}, - {"database dir", rabbit_mnesia:dir()}], - DescrLen = 1 + lists:max([length(K) || {K, _V} <- Settings]), - Format = fun (K, V) -> - rabbit_misc:format( - "~-" ++ integer_to_list(DescrLen) ++ "s: ~s~n", [K, V]) - end, - Banner = iolist_to_binary( - [case S of - {"config file(s)" = K, []} -> - Format(K, "(none)"); - {"config file(s)" = K, [V0 | Vs]} -> - [Format(K, V0) | [Format("", V) || V <- Vs]]; - {K, V} -> - Format(K, V) - end || S <- Settings]), - rabbit_log:info("~s", [Banner]). - -warn_if_kernel_config_dubious() -> - case erlang:system_info(kernel_poll) of - true -> ok; - false -> rabbit_log:warning( - "Kernel poll (epoll, kqueue, etc) is disabled. Throughput " - "and CPU utilization may worsen.~n") - end, - AsyncThreads = erlang:system_info(thread_pool_size), - case AsyncThreads < ?ASYNC_THREADS_WARNING_THRESHOLD of - true -> rabbit_log:warning( - "Erlang VM is running with ~b I/O threads, " - "file I/O performance may worsen~n", [AsyncThreads]); - false -> ok - end, - IDCOpts = case application:get_env(kernel, inet_default_connect_options) of - undefined -> []; - {ok, Val} -> Val - end, - case proplists:get_value(nodelay, IDCOpts, false) of - false -> rabbit_log:warning("Nagle's algorithm is enabled for sockets, " - "network I/O latency will be higher~n"); - true -> ok - end. - -home_dir() -> - case init:get_argument(home) of - {ok, [[Home]]} -> Home; - Other -> Other - end. - -config_files() -> - Abs = fun (F) -> - filename:absname(filename:rootname(F, ".config") ++ ".config") - end, - case init:get_argument(config) of - {ok, Files} -> [Abs(File) || [File] <- Files]; - error -> case config_setting() of - none -> []; - File -> [Abs(File) ++ " (not found)"] - end - end. - -%% This is a pain. We want to know where the config file is. But we -%% can't specify it on the command line if it is missing or the VM -%% will fail to start, so we need to find it by some mechanism other -%% than init:get_arguments/0. We can look at the environment variable -%% which is responsible for setting it... but that doesn't work for a -%% Windows service since the variable can change and the service not -%% be reinstalled, so in that case we add a magic application env. -config_setting() -> - case application:get_env(rabbit, windows_service_config) of - {ok, File1} -> File1; - undefined -> case os:getenv("RABBITMQ_CONFIG_FILE") of - false -> none; - File2 -> File2 - end - end. - -%% We don't want this in fhc since it references rabbit stuff. And we can't put -%% this in the bootstep directly. -start_fhc() -> - rabbit_sup:start_restartable_child( - file_handle_cache, - [fun rabbit_alarm:set_alarm/1, fun rabbit_alarm:clear_alarm/1]). diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl deleted file mode 100644 index d1577432..00000000 --- a/src/rabbit_access_control.erl +++ /dev/null @@ -1,179 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_access_control). - --include("rabbit.hrl"). - --export([check_user_pass_login/2, check_user_login/2, check_user_loopback/2, - check_vhost_access/3, check_resource_access/3]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([permission_atom/0]). - --type(permission_atom() :: 'configure' | 'read' | 'write'). - --spec(check_user_pass_login/2 :: - (rabbit_types:username(), rabbit_types:password()) - -> {'ok', rabbit_types:user()} | {'refused', string(), [any()]}). --spec(check_user_login/2 :: - (rabbit_types:username(), [{atom(), any()}]) - -> {'ok', rabbit_types:user()} | {'refused', string(), [any()]}). --spec(check_user_loopback/2 :: (rabbit_types:username(), - rabbit_net:socket() | inet:ip_address()) - -> 'ok' | 'not_allowed'). --spec(check_vhost_access/3 :: - (rabbit_types:user(), rabbit_types:vhost(), rabbit_net:socket()) - -> 'ok' | rabbit_types:channel_exit()). --spec(check_resource_access/3 :: - (rabbit_types:user(), rabbit_types:r(atom()), permission_atom()) - -> 'ok' | rabbit_types:channel_exit()). - --endif. - -%%---------------------------------------------------------------------------- - -check_user_pass_login(Username, Password) -> - check_user_login(Username, [{password, Password}]). - -check_user_login(Username, AuthProps) -> - {ok, Modules} = application:get_env(rabbit, auth_backends), - R = lists:foldl( - fun ({ModN, ModZs0}, {refused, _, _}) -> - ModZs = case ModZs0 of - A when is_atom(A) -> [A]; - L when is_list(L) -> L - end, - %% Different modules for authN vs authZ. So authenticate - %% with authN module, then if that succeeds do - %% passwordless (i.e pre-authenticated) login with authZ. - case try_authenticate(ModN, Username, AuthProps) of - {ok, ModNUser = #auth_user{username = Username2}} -> - user(ModNUser, try_authorize(ModZs, Username2)); - Else -> - Else - end; - (Mod, {refused, _, _}) -> - %% Same module for authN and authZ. Just take the result - %% it gives us - case try_authenticate(Mod, Username, AuthProps) of - {ok, ModNUser = #auth_user{impl = Impl}} -> - user(ModNUser, {ok, [{Mod, Impl}]}); - Else -> - Else - end; - (_, {ok, User}) -> - %% We've successfully authenticated. Skip to the end... - {ok, User} - end, {refused, "No modules checked '~s'", [Username]}, Modules), - rabbit_event:notify(case R of - {ok, _User} -> user_authentication_success; - _ -> user_authentication_failure - end, [{name, Username}]), - R. - -try_authenticate(Module, Username, AuthProps) -> - case Module:user_login_authentication(Username, AuthProps) of - {ok, AuthUser} -> {ok, AuthUser}; - {error, E} -> {refused, "~s failed authenticating ~s: ~p~n", - [Module, Username, E]}; - {refused, F, A} -> {refused, F, A} - end. - -try_authorize(Modules, Username) -> - lists:foldr( - fun (Module, {ok, ModsImpls}) -> - case Module:user_login_authorization(Username) of - {ok, Impl} -> {ok, [{Module, Impl} | ModsImpls]}; - {error, E} -> {refused, "~s failed authorizing ~s: ~p~n", - [Module, Username, E]}; - {refused, F, A} -> {refused, F, A} - end; - (_, {refused, _, _} = Error) -> - Error - end, {ok, []}, Modules). - -user(#auth_user{username = Username, tags = Tags}, {ok, ModZImpls}) -> - {ok, #user{username = Username, - tags = Tags, - authz_backends = ModZImpls}}; -user(_AuthUser, Error) -> - Error. - -auth_user(#user{username = Username, tags = Tags}, Impl) -> - #auth_user{username = Username, - tags = Tags, - impl = Impl}. - -check_user_loopback(Username, SockOrAddr) -> - {ok, Users} = application:get_env(rabbit, loopback_users), - case rabbit_net:is_loopback(SockOrAddr) - orelse not lists:member(Username, Users) of - true -> ok; - false -> not_allowed - end. - -check_vhost_access(User = #user{username = Username, - authz_backends = Modules}, VHostPath, Sock) -> - lists:foldl( - fun({Mod, Impl}, ok) -> - check_access( - fun() -> - rabbit_vhost:exists(VHostPath) andalso - Mod:check_vhost_access( - auth_user(User, Impl), VHostPath, Sock) - end, - Mod, "access to vhost '~s' refused for user '~s'", - [VHostPath, Username]); - (_, Else) -> - Else - end, ok, Modules). - -check_resource_access(User, R = #resource{kind = exchange, name = <<"">>}, - Permission) -> - check_resource_access(User, R#resource{name = <<"amq.default">>}, - Permission); -check_resource_access(User = #user{username = Username, - authz_backends = Modules}, - Resource, Permission) -> - lists:foldl( - fun({Module, Impl}, ok) -> - check_access( - fun() -> Module:check_resource_access( - auth_user(User, Impl), Resource, Permission) end, - Module, "access to ~s refused for user '~s'", - [rabbit_misc:rs(Resource), Username]); - (_, Else) -> Else - end, ok, Modules). - -check_access(Fun, Module, ErrStr, ErrArgs) -> - Allow = case Fun() of - {error, E} -> - rabbit_log:error(ErrStr ++ " by ~s: ~p~n", - ErrArgs ++ [Module, E]), - false; - Else -> - Else - end, - case Allow of - true -> - ok; - false -> - rabbit_misc:protocol_error(access_refused, ErrStr, ErrArgs) - end. diff --git a/src/rabbit_alarm.erl b/src/rabbit_alarm.erl deleted file mode 100644 index 308f9a2e..00000000 --- a/src/rabbit_alarm.erl +++ /dev/null @@ -1,239 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_alarm). - --behaviour(gen_event). - --export([start_link/0, start/0, stop/0, register/2, set_alarm/1, - clear_alarm/1, get_alarms/0, on_node_up/1, on_node_down/1]). - --export([init/1, handle_call/2, handle_event/2, handle_info/2, - terminate/2, code_change/3]). - --export([remote_conserve_resources/3]). %% Internal use only - --define(SERVER, ?MODULE). - --record(alarms, {alertees, alarmed_nodes, alarms}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(start/0 :: () -> 'ok'). --spec(stop/0 :: () -> 'ok'). --spec(register/2 :: (pid(), rabbit_types:mfargs()) -> [atom()]). --spec(set_alarm/1 :: (any()) -> 'ok'). --spec(clear_alarm/1 :: (any()) -> 'ok'). --spec(on_node_up/1 :: (node()) -> 'ok'). --spec(on_node_down/1 :: (node()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> - gen_event:start_link({local, ?SERVER}). - -start() -> - ok = rabbit_sup:start_restartable_child(?MODULE), - ok = gen_event:add_handler(?SERVER, ?MODULE, []), - {ok, MemoryWatermark} = application:get_env(vm_memory_high_watermark), - rabbit_sup:start_restartable_child( - vm_memory_monitor, [MemoryWatermark, - fun (Alarm) -> - background_gc:run(), - set_alarm(Alarm) - end, - fun clear_alarm/1]), - {ok, DiskLimit} = application:get_env(disk_free_limit), - rabbit_sup:start_delayed_restartable_child( - rabbit_disk_monitor, [DiskLimit]), - ok. - -stop() -> ok. - -register(Pid, AlertMFA) -> - gen_event:call(?SERVER, ?MODULE, {register, Pid, AlertMFA}, infinity). - -set_alarm(Alarm) -> gen_event:notify(?SERVER, {set_alarm, Alarm}). -clear_alarm(Alarm) -> gen_event:notify(?SERVER, {clear_alarm, Alarm}). - -get_alarms() -> gen_event:call(?SERVER, ?MODULE, get_alarms, infinity). - -on_node_up(Node) -> gen_event:notify(?SERVER, {node_up, Node}). -on_node_down(Node) -> gen_event:notify(?SERVER, {node_down, Node}). - -remote_conserve_resources(Pid, Source, true) -> - gen_event:notify({?SERVER, node(Pid)}, - {set_alarm, {{resource_limit, Source, node()}, []}}); -remote_conserve_resources(Pid, Source, false) -> - gen_event:notify({?SERVER, node(Pid)}, - {clear_alarm, {resource_limit, Source, node()}}). - - -%%---------------------------------------------------------------------------- - -init([]) -> - {ok, #alarms{alertees = dict:new(), - alarmed_nodes = dict:new(), - alarms = []}}. - -handle_call({register, Pid, AlertMFA}, State = #alarms{alarmed_nodes = AN}) -> - {ok, lists:usort(lists:append([V || {_, V} <- dict:to_list(AN)])), - internal_register(Pid, AlertMFA, State)}; - -handle_call(get_alarms, State = #alarms{alarms = Alarms}) -> - {ok, Alarms, State}; - -handle_call(_Request, State) -> - {ok, not_understood, State}. - -handle_event({set_alarm, Alarm}, State = #alarms{alarms = Alarms}) -> - case lists:member(Alarm, Alarms) of - true -> {ok, State}; - false -> UpdatedAlarms = lists:usort([Alarm|Alarms]), - handle_set_alarm(Alarm, State#alarms{alarms = UpdatedAlarms}) - end; - -handle_event({clear_alarm, Alarm}, State = #alarms{alarms = Alarms}) -> - case lists:keymember(Alarm, 1, Alarms) of - true -> handle_clear_alarm( - Alarm, State#alarms{alarms = lists:keydelete( - Alarm, 1, Alarms)}); - false -> {ok, State} - - end; - -handle_event({node_up, Node}, State) -> - %% Must do this via notify and not call to avoid possible deadlock. - ok = gen_event:notify( - {?SERVER, Node}, - {register, self(), {?MODULE, remote_conserve_resources, []}}), - {ok, State}; - -handle_event({node_down, Node}, State) -> - {ok, maybe_alert(fun dict_unappend_all/3, Node, [], false, State)}; - -handle_event({register, Pid, AlertMFA}, State) -> - {ok, internal_register(Pid, AlertMFA, State)}; - -handle_event(_Event, State) -> - {ok, State}. - -handle_info({'DOWN', _MRef, process, Pid, _Reason}, - State = #alarms{alertees = Alertees}) -> - {ok, State#alarms{alertees = dict:erase(Pid, Alertees)}}; - -handle_info(_Info, State) -> - {ok, State}. - -terminate(_Arg, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%---------------------------------------------------------------------------- - -dict_append(Key, Val, Dict) -> - L = case dict:find(Key, Dict) of - {ok, V} -> V; - error -> [] - end, - dict:store(Key, lists:usort([Val|L]), Dict). - -dict_unappend_all(Key, _Val, Dict) -> - dict:erase(Key, Dict). - -dict_unappend(Key, Val, Dict) -> - L = case dict:find(Key, Dict) of - {ok, V} -> V; - error -> [] - end, - - case lists:delete(Val, L) of - [] -> dict:erase(Key, Dict); - X -> dict:store(Key, X, Dict) - end. - -maybe_alert(UpdateFun, Node, Source, Alert, - State = #alarms{alarmed_nodes = AN, - alertees = Alertees}) -> - AN1 = UpdateFun(Node, Source, AN), - case node() of - Node -> ok = alert_remote(Alert, Alertees, Source); - _ -> ok - end, - ok = alert_local(Alert, Alertees, Source), - State#alarms{alarmed_nodes = AN1}. - -alert_local(Alert, Alertees, Source) -> - alert(Alertees, Source, Alert, fun erlang:'=:='/2). - -alert_remote(Alert, Alertees, Source) -> - alert(Alertees, Source, Alert, fun erlang:'=/='/2). - -alert(Alertees, Source, Alert, NodeComparator) -> - Node = node(), - dict:fold(fun (Pid, {M, F, A}, ok) -> - case NodeComparator(Node, node(Pid)) of - true -> apply(M, F, A ++ [Pid, Source, Alert]); - false -> ok - end - end, ok, Alertees). - -internal_register(Pid, {M, F, A} = AlertMFA, - State = #alarms{alertees = Alertees}) -> - _MRef = erlang:monitor(process, Pid), - case dict:find(node(), State#alarms.alarmed_nodes) of - {ok, Sources} -> [apply(M, F, A ++ [Pid, R, true]) || R <- Sources]; - error -> ok - end, - NewAlertees = dict:store(Pid, AlertMFA, Alertees), - State#alarms{alertees = NewAlertees}. - -handle_set_alarm({{resource_limit, Source, Node}, []}, State) -> - rabbit_log:warning( - "~s resource limit alarm set on node ~p.~n~n" - "**********************************************************~n" - "*** Publishers will be blocked until this alarm clears ***~n" - "**********************************************************~n", - [Source, Node]), - {ok, maybe_alert(fun dict_append/3, Node, Source, true, State)}; -handle_set_alarm({file_descriptor_limit, []}, State) -> - rabbit_log:warning( - "file descriptor limit alarm set.~n~n" - "********************************************************************~n" - "*** New connections will not be accepted until this alarm clears ***~n" - "********************************************************************~n"), - {ok, State}; -handle_set_alarm(Alarm, State) -> - rabbit_log:warning("alarm '~p' set~n", [Alarm]), - {ok, State}. - -handle_clear_alarm({resource_limit, Source, Node}, State) -> - rabbit_log:warning("~s resource limit alarm cleared on node ~p~n", - [Source, Node]), - {ok, maybe_alert(fun dict_unappend/3, Node, Source, false, State)}; -handle_clear_alarm(file_descriptor_limit, State) -> - rabbit_log:warning("file descriptor limit alarm cleared~n"), - {ok, State}; -handle_clear_alarm(Alarm, State) -> - rabbit_log:warning("alarm '~p' cleared~n", [Alarm]), - {ok, State}. diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl deleted file mode 100644 index 0dfca854..00000000 --- a/src/rabbit_amqqueue.erl +++ /dev/null @@ -1,849 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_amqqueue). - --export([recover/0, stop/0, start/1, declare/5, declare/6, - delete_immediately/1, delete/3, purge/1, forget_all_durable/1, - delete_crashed/1, delete_crashed_internal/1]). --export([pseudo_queue/2, immutable/1]). --export([lookup/1, not_found_or_absent/1, with/2, with/3, with_or_die/2, - assert_equivalence/5, - check_exclusive_access/2, with_exclusive_access_or_die/3, - stat/1, deliver/2, deliver_flow/2, requeue/3, ack/3, reject/4]). --export([list/0, list/1, info_keys/0, info/1, info/2, info_all/1, info_all/2]). --export([list_down/1]). --export([force_event_refresh/1, notify_policy_changed/1]). --export([consumers/1, consumers_all/1, consumer_info_keys/0]). --export([basic_get/4, basic_consume/10, basic_cancel/4, notify_decorators/1]). --export([notify_sent/2, notify_sent_queue_down/1, resume/2]). --export([notify_down_all/2, activate_limit_all/2, credit/5]). --export([on_node_up/1, on_node_down/1]). --export([update/2, store_queue/1, update_decorators/1, policy_changed/2]). --export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1, - cancel_sync_mirrors/1]). - -%% internal --export([internal_declare/2, internal_delete/1, run_backing_queue/3, - set_ram_duration_target/2, set_maximum_since_use/2]). - --include("rabbit.hrl"). --include_lib("stdlib/include/qlc.hrl"). - --define(INTEGER_ARG_TYPES, [byte, short, signedint, long]). - --define(MORE_CONSUMER_CREDIT_AFTER, 50). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([name/0, qmsg/0, absent_reason/0]). - --type(name() :: rabbit_types:r('queue')). --type(qpids() :: [pid()]). --type(qlen() :: rabbit_types:ok(non_neg_integer())). --type(qfun(A) :: fun ((rabbit_types:amqqueue()) -> A | no_return())). --type(qmsg() :: {name(), pid(), msg_id(), boolean(), rabbit_types:message()}). --type(msg_id() :: non_neg_integer()). --type(ok_or_errors() :: - 'ok' | {'error', [{'error' | 'exit' | 'throw', any()}]}). --type(absent_reason() :: 'nodedown' | 'crashed'). --type(queue_or_absent() :: rabbit_types:amqqueue() | - {'absent', rabbit_types:amqqueue(),absent_reason()}). --type(not_found_or_absent() :: - 'not_found' | {'absent', rabbit_types:amqqueue(), absent_reason()}). --spec(recover/0 :: () -> [rabbit_types:amqqueue()]). --spec(stop/0 :: () -> 'ok'). --spec(start/1 :: ([rabbit_types:amqqueue()]) -> 'ok'). --spec(declare/5 :: - (name(), boolean(), boolean(), - rabbit_framing:amqp_table(), rabbit_types:maybe(pid())) - -> {'new' | 'existing' | 'absent' | 'owner_died', - rabbit_types:amqqueue()} | rabbit_types:channel_exit()). --spec(declare/6 :: - (name(), boolean(), boolean(), - rabbit_framing:amqp_table(), rabbit_types:maybe(pid()), node()) - -> {'new' | 'existing' | 'owner_died', rabbit_types:amqqueue()} | - {'absent', rabbit_types:amqqueue(), absent_reason()} | - rabbit_types:channel_exit()). --spec(internal_declare/2 :: - (rabbit_types:amqqueue(), boolean()) - -> queue_or_absent() | rabbit_misc:thunk(queue_or_absent())). --spec(update/2 :: - (name(), - fun((rabbit_types:amqqueue()) -> rabbit_types:amqqueue())) - -> 'not_found' | rabbit_types:amqqueue()). --spec(lookup/1 :: - (name()) -> rabbit_types:ok(rabbit_types:amqqueue()) | - rabbit_types:error('not_found'); - ([name()]) -> [rabbit_types:amqqueue()]). --spec(not_found_or_absent/1 :: (name()) -> not_found_or_absent()). --spec(with/2 :: (name(), qfun(A)) -> - A | rabbit_types:error(not_found_or_absent())). --spec(with/3 :: (name(), qfun(A), fun((not_found_or_absent()) -> B)) -> A | B). --spec(with_or_die/2 :: - (name(), qfun(A)) -> A | rabbit_types:channel_exit()). --spec(assert_equivalence/5 :: - (rabbit_types:amqqueue(), boolean(), boolean(), - rabbit_framing:amqp_table(), rabbit_types:maybe(pid())) - -> 'ok' | rabbit_types:channel_exit() | - rabbit_types:connection_exit()). --spec(check_exclusive_access/2 :: - (rabbit_types:amqqueue(), pid()) - -> 'ok' | rabbit_types:channel_exit()). --spec(with_exclusive_access_or_die/3 :: - (name(), pid(), qfun(A)) -> A | rabbit_types:channel_exit()). --spec(list/0 :: () -> [rabbit_types:amqqueue()]). --spec(list/1 :: (rabbit_types:vhost()) -> [rabbit_types:amqqueue()]). --spec(list_down/1 :: (rabbit_types:vhost()) -> [rabbit_types:amqqueue()]). --spec(info_keys/0 :: () -> rabbit_types:info_keys()). --spec(info/1 :: (rabbit_types:amqqueue()) -> rabbit_types:infos()). --spec(info/2 :: - (rabbit_types:amqqueue(), rabbit_types:info_keys()) - -> rabbit_types:infos()). --spec(info_all/1 :: (rabbit_types:vhost()) -> [rabbit_types:infos()]). --spec(info_all/2 :: (rabbit_types:vhost(), rabbit_types:info_keys()) - -> [rabbit_types:infos()]). --spec(force_event_refresh/1 :: (reference()) -> 'ok'). --spec(notify_policy_changed/1 :: (rabbit_types:amqqueue()) -> 'ok'). --spec(consumers/1 :: (rabbit_types:amqqueue()) - -> [{pid(), rabbit_types:ctag(), boolean(), - non_neg_integer(), rabbit_framing:amqp_table()}]). --spec(consumer_info_keys/0 :: () -> rabbit_types:info_keys()). --spec(consumers_all/1 :: - (rabbit_types:vhost()) - -> [{name(), pid(), rabbit_types:ctag(), boolean(), - non_neg_integer(), rabbit_framing:amqp_table()}]). --spec(stat/1 :: - (rabbit_types:amqqueue()) - -> {'ok', non_neg_integer(), non_neg_integer()}). --spec(delete_immediately/1 :: (qpids()) -> 'ok'). --spec(delete/3 :: - (rabbit_types:amqqueue(), 'false', 'false') - -> qlen(); - (rabbit_types:amqqueue(), 'true' , 'false') - -> qlen() | rabbit_types:error('in_use'); - (rabbit_types:amqqueue(), 'false', 'true' ) - -> qlen() | rabbit_types:error('not_empty'); - (rabbit_types:amqqueue(), 'true' , 'true' ) - -> qlen() | - rabbit_types:error('in_use') | - rabbit_types:error('not_empty')). --spec(delete_crashed/1 :: (rabbit_types:amqqueue()) -> 'ok'). --spec(delete_crashed_internal/1 :: (rabbit_types:amqqueue()) -> 'ok'). --spec(purge/1 :: (rabbit_types:amqqueue()) -> qlen()). --spec(forget_all_durable/1 :: (node()) -> 'ok'). --spec(deliver/2 :: ([rabbit_types:amqqueue()], rabbit_types:delivery()) -> - qpids()). --spec(deliver_flow/2 :: ([rabbit_types:amqqueue()], rabbit_types:delivery()) -> - qpids()). --spec(requeue/3 :: (pid(), [msg_id()], pid()) -> 'ok'). --spec(ack/3 :: (pid(), [msg_id()], pid()) -> 'ok'). --spec(reject/4 :: (pid(), [msg_id()], boolean(), pid()) -> 'ok'). --spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). --spec(activate_limit_all/2 :: (qpids(), pid()) -> ok_or_errors()). --spec(basic_get/4 :: (rabbit_types:amqqueue(), pid(), boolean(), pid()) -> - {'ok', non_neg_integer(), qmsg()} | 'empty'). --spec(credit/5 :: (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), - non_neg_integer(), boolean()) -> 'ok'). --spec(basic_consume/10 :: - (rabbit_types:amqqueue(), boolean(), pid(), pid(), boolean(), - non_neg_integer(), rabbit_types:ctag(), boolean(), - rabbit_framing:amqp_table(), any()) - -> rabbit_types:ok_or_error('exclusive_consume_unavailable')). --spec(basic_cancel/4 :: - (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), any()) -> 'ok'). --spec(notify_decorators/1 :: (rabbit_types:amqqueue()) -> 'ok'). --spec(notify_sent/2 :: (pid(), pid()) -> 'ok'). --spec(notify_sent_queue_down/1 :: (pid()) -> 'ok'). --spec(resume/2 :: (pid(), pid()) -> 'ok'). --spec(internal_delete/1 :: - (name()) -> rabbit_types:ok_or_error('not_found') | - rabbit_types:connection_exit() | - fun (() -> rabbit_types:ok_or_error('not_found') | - rabbit_types:connection_exit())). --spec(run_backing_queue/3 :: - (pid(), atom(), - (fun ((atom(), A) -> {[rabbit_types:msg_id()], A}))) -> 'ok'). --spec(set_ram_duration_target/2 :: (pid(), number() | 'infinity') -> 'ok'). --spec(set_maximum_since_use/2 :: (pid(), non_neg_integer()) -> 'ok'). --spec(on_node_up/1 :: (node()) -> 'ok'). --spec(on_node_down/1 :: (node()) -> 'ok'). --spec(pseudo_queue/2 :: (name(), pid()) -> rabbit_types:amqqueue()). --spec(immutable/1 :: (rabbit_types:amqqueue()) -> rabbit_types:amqqueue()). --spec(store_queue/1 :: (rabbit_types:amqqueue()) -> 'ok'). --spec(update_decorators/1 :: (name()) -> 'ok'). --spec(policy_changed/2 :: - (rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'). --spec(start_mirroring/1 :: (pid()) -> 'ok'). --spec(stop_mirroring/1 :: (pid()) -> 'ok'). --spec(sync_mirrors/1 :: (pid()) -> 'ok' | rabbit_types:error('not_mirrored')). --spec(cancel_sync_mirrors/1 :: (pid()) -> 'ok' | {'ok', 'not_syncing'}). - --endif. - -%%---------------------------------------------------------------------------- - --define(CONSUMER_INFO_KEYS, - [queue_name, channel_pid, consumer_tag, ack_required, prefetch_count, - arguments]). - -recover() -> - %% Clear out remnants of old incarnation, in case we restarted - %% faster than other nodes handled DOWN messages from us. - on_node_down(node()), - DurableQueues = find_durable_queues(), - {ok, BQ} = application:get_env(rabbit, backing_queue_module), - - %% We rely on BQ:start/1 returning the recovery terms in the same - %% order as the supplied queue names, so that we can zip them together - %% for further processing in recover_durable_queues. - {ok, OrderedRecoveryTerms} = - BQ:start([QName || #amqqueue{name = QName} <- DurableQueues]), - {ok,_} = supervisor:start_child( - rabbit_sup, - {rabbit_amqqueue_sup_sup, - {rabbit_amqqueue_sup_sup, start_link, []}, - transient, infinity, supervisor, [rabbit_amqqueue_sup_sup]}), - recover_durable_queues(lists:zip(DurableQueues, OrderedRecoveryTerms)). - -stop() -> - ok = supervisor:terminate_child(rabbit_sup, rabbit_amqqueue_sup_sup), - ok = supervisor:delete_child(rabbit_sup, rabbit_amqqueue_sup_sup), - {ok, BQ} = application:get_env(rabbit, backing_queue_module), - ok = BQ:stop(). - -start(Qs) -> - %% At this point all recovered queues and their bindings are - %% visible to routing, so now it is safe for them to complete - %% their initialisation (which may involve interacting with other - %% queues). - [Pid ! {self(), go} || #amqqueue{pid = Pid} <- Qs], - ok. - -find_durable_queues() -> - Node = node(), - mnesia:async_dirty( - fun () -> - qlc:e(qlc:q([Q || Q = #amqqueue{name = Name, - pid = Pid} - <- mnesia:table(rabbit_durable_queue), - node(Pid) == Node, - mnesia:read(rabbit_queue, Name, read) =:= []])) - end). - -recover_durable_queues(QueuesAndRecoveryTerms) -> - {Results, Failures} = - gen_server2:mcall( - [{rabbit_amqqueue_sup_sup:start_queue_process(node(), Q, recovery), - {init, {self(), Terms}}} || {Q, Terms} <- QueuesAndRecoveryTerms]), - [rabbit_log:error("Queue ~p failed to initialise: ~p~n", - [Pid, Error]) || {Pid, Error} <- Failures], - [Q || {_, {new, Q}} <- Results]. - -declare(QueueName, Durable, AutoDelete, Args, Owner) -> - declare(QueueName, Durable, AutoDelete, Args, Owner, node()). - - -%% The Node argument suggests where the queue (master if mirrored) -%% should be. Note that in some cases (e.g. with "nodes" policy in -%% effect) this might not be possible to satisfy. -declare(QueueName, Durable, AutoDelete, Args, Owner, Node) -> - ok = check_declare_arguments(QueueName, Args), - Q = rabbit_queue_decorator:set( - rabbit_policy:set(#amqqueue{name = QueueName, - durable = Durable, - auto_delete = AutoDelete, - arguments = Args, - exclusive_owner = Owner, - pid = none, - slave_pids = [], - sync_slave_pids = [], - down_slave_nodes = [], - gm_pids = [], - state = live})), - Node = rabbit_mirror_queue_misc:initial_queue_node(Q, Node), - gen_server2:call( - rabbit_amqqueue_sup_sup:start_queue_process(Node, Q, declare), - {init, new}, infinity). - -internal_declare(Q, true) -> - rabbit_misc:execute_mnesia_tx_with_tail( - fun () -> - ok = store_queue(Q#amqqueue{state = live}), - rabbit_misc:const(Q) - end); -internal_declare(Q = #amqqueue{name = QueueName}, false) -> - rabbit_misc:execute_mnesia_tx_with_tail( - fun () -> - case mnesia:wread({rabbit_queue, QueueName}) of - [] -> - case not_found_or_absent(QueueName) of - not_found -> Q1 = rabbit_policy:set(Q), - Q2 = Q1#amqqueue{state = live}, - ok = store_queue(Q2), - B = add_default_binding(Q1), - fun () -> B(), Q1 end; - {absent, _Q, _} = R -> rabbit_misc:const(R) - end; - [ExistingQ] -> - rabbit_misc:const(ExistingQ) - end - end). - -update(Name, Fun) -> - case mnesia:wread({rabbit_queue, Name}) of - [Q = #amqqueue{durable = Durable}] -> - Q1 = Fun(Q), - ok = mnesia:write(rabbit_queue, Q1, write), - case Durable of - true -> ok = mnesia:write(rabbit_durable_queue, Q1, write); - _ -> ok - end, - Q1; - [] -> - not_found - end. - -store_queue(Q = #amqqueue{durable = true}) -> - ok = mnesia:write(rabbit_durable_queue, - Q#amqqueue{slave_pids = [], - sync_slave_pids = [], - gm_pids = [], - decorators = undefined}, write), - store_queue_ram(Q); -store_queue(Q = #amqqueue{durable = false}) -> - store_queue_ram(Q). - -store_queue_ram(Q) -> - ok = mnesia:write(rabbit_queue, rabbit_queue_decorator:set(Q), write). - -update_decorators(Name) -> - rabbit_misc:execute_mnesia_transaction( - fun() -> - case mnesia:wread({rabbit_queue, Name}) of - [Q] -> store_queue_ram(Q), - ok; - [] -> ok - end - end). - -policy_changed(Q1 = #amqqueue{decorators = Decorators1}, - Q2 = #amqqueue{decorators = Decorators2}) -> - rabbit_mirror_queue_misc:update_mirrors(Q1, Q2), - D1 = rabbit_queue_decorator:select(Decorators1), - D2 = rabbit_queue_decorator:select(Decorators2), - [ok = M:policy_changed(Q1, Q2) || M <- lists:usort(D1 ++ D2)], - %% Make sure we emit a stats event even if nothing - %% mirroring-related has changed - the policy may have changed anyway. - notify_policy_changed(Q1). - -add_default_binding(#amqqueue{name = QueueName}) -> - ExchangeName = rabbit_misc:r(QueueName, exchange, <<>>), - RoutingKey = QueueName#resource.name, - rabbit_binding:add(#binding{source = ExchangeName, - destination = QueueName, - key = RoutingKey, - args = []}). - -lookup([]) -> []; %% optimisation -lookup([Name]) -> ets:lookup(rabbit_queue, Name); %% optimisation -lookup(Names) when is_list(Names) -> - %% Normally we'd call mnesia:dirty_read/1 here, but that is quite - %% expensive for reasons explained in rabbit_misc:dirty_read/1. - lists:append([ets:lookup(rabbit_queue, Name) || Name <- Names]); -lookup(Name) -> - rabbit_misc:dirty_read({rabbit_queue, Name}). - -not_found_or_absent(Name) -> - %% NB: we assume that the caller has already performed a lookup on - %% rabbit_queue and not found anything - case mnesia:read({rabbit_durable_queue, Name}) of - [] -> not_found; - [Q] -> {absent, Q, nodedown} %% Q exists on stopped node - end. - -not_found_or_absent_dirty(Name) -> - %% We should read from both tables inside a tx, to get a - %% consistent view. But the chances of an inconsistency are small, - %% and only affect the error kind. - case rabbit_misc:dirty_read({rabbit_durable_queue, Name}) of - {error, not_found} -> not_found; - {ok, Q} -> {absent, Q, nodedown} - end. - -with(Name, F, E) -> - case lookup(Name) of - {ok, Q = #amqqueue{state = crashed}} -> - E({absent, Q, crashed}); - {ok, Q = #amqqueue{pid = QPid}} -> - %% We check is_process_alive(QPid) in case we receive a - %% nodedown (for example) in F() that has nothing to do - %% with the QPid. F() should be written s.t. that this - %% cannot happen, so we bail if it does since that - %% indicates a code bug and we don't want to get stuck in - %% the retry loop. - rabbit_misc:with_exit_handler( - fun () -> false = rabbit_mnesia:is_process_alive(QPid), - timer:sleep(25), - with(Name, F, E) - end, fun () -> F(Q) end); - {error, not_found} -> - E(not_found_or_absent_dirty(Name)) - end. - -with(Name, F) -> with(Name, F, fun (E) -> {error, E} end). - -with_or_die(Name, F) -> - with(Name, F, fun (not_found) -> rabbit_misc:not_found(Name); - ({absent, Q, Reason}) -> rabbit_misc:absent(Q, Reason) - end). - -assert_equivalence(#amqqueue{name = QName, - durable = Durable, - auto_delete = AD} = Q, - Durable1, AD1, Args1, Owner) -> - rabbit_misc:assert_field_equivalence(Durable, Durable1, QName, durable), - rabbit_misc:assert_field_equivalence(AD, AD1, QName, auto_delete), - assert_args_equivalence(Q, Args1), - check_exclusive_access(Q, Owner, strict). - -check_exclusive_access(Q, Owner) -> check_exclusive_access(Q, Owner, lax). - -check_exclusive_access(#amqqueue{exclusive_owner = Owner}, Owner, _MatchType) -> - ok; -check_exclusive_access(#amqqueue{exclusive_owner = none}, _ReaderPid, lax) -> - ok; -check_exclusive_access(#amqqueue{name = QueueName}, _ReaderPid, _MatchType) -> - rabbit_misc:protocol_error( - resource_locked, - "cannot obtain exclusive access to locked ~s", - [rabbit_misc:rs(QueueName)]). - -with_exclusive_access_or_die(Name, ReaderPid, F) -> - with_or_die(Name, - fun (Q) -> check_exclusive_access(Q, ReaderPid), F(Q) end). - -assert_args_equivalence(#amqqueue{name = QueueName, arguments = Args}, - RequiredArgs) -> - rabbit_misc:assert_args_equivalence(Args, RequiredArgs, QueueName, - [Key || {Key, _Fun} <- declare_args()]). - -check_declare_arguments(QueueName, Args) -> - check_arguments(QueueName, Args, declare_args()). - -check_consume_arguments(QueueName, Args) -> - check_arguments(QueueName, Args, consume_args()). - -check_arguments(QueueName, Args, Validators) -> - [case rabbit_misc:table_lookup(Args, Key) of - undefined -> ok; - TypeVal -> case Fun(TypeVal, Args) of - ok -> ok; - {error, Error} -> rabbit_misc:protocol_error( - precondition_failed, - "invalid arg '~s' for ~s: ~255p", - [Key, rabbit_misc:rs(QueueName), - Error]) - end - end || {Key, Fun} <- Validators], - ok. - -declare_args() -> - [{<<"x-expires">>, fun check_expires_arg/2}, - {<<"x-message-ttl">>, fun check_message_ttl_arg/2}, - {<<"x-dead-letter-exchange">>, fun check_dlxname_arg/2}, - {<<"x-dead-letter-routing-key">>, fun check_dlxrk_arg/2}, - {<<"x-max-length">>, fun check_non_neg_int_arg/2}, - {<<"x-max-length-bytes">>, fun check_non_neg_int_arg/2}]. - -consume_args() -> [{<<"x-priority">>, fun check_int_arg/2}, - {<<"x-cancel-on-ha-failover">>, fun check_bool_arg/2}]. - -check_int_arg({Type, _}, _) -> - case lists:member(Type, ?INTEGER_ARG_TYPES) of - true -> ok; - false -> {error, {unacceptable_type, Type}} - end. - -check_bool_arg({bool, _}, _) -> ok; -check_bool_arg({Type, _}, _) -> {error, {unacceptable_type, Type}}. - -check_non_neg_int_arg({Type, Val}, Args) -> - case check_int_arg({Type, Val}, Args) of - ok when Val >= 0 -> ok; - ok -> {error, {value_negative, Val}}; - Error -> Error - end. - -check_expires_arg({Type, Val}, Args) -> - case check_int_arg({Type, Val}, Args) of - ok when Val == 0 -> {error, {value_zero, Val}}; - ok -> rabbit_misc:check_expiry(Val); - Error -> Error - end. - -check_message_ttl_arg({Type, Val}, Args) -> - case check_int_arg({Type, Val}, Args) of - ok -> rabbit_misc:check_expiry(Val); - Error -> Error - end. - -%% Note that the validity of x-dead-letter-exchange is already verified -%% by rabbit_channel's queue.declare handler. -check_dlxname_arg({longstr, _}, _) -> ok; -check_dlxname_arg({Type, _}, _) -> {error, {unacceptable_type, Type}}. - -check_dlxrk_arg({longstr, _}, Args) -> - case rabbit_misc:table_lookup(Args, <<"x-dead-letter-exchange">>) of - undefined -> {error, routing_key_but_no_dlx_defined}; - _ -> ok - end; -check_dlxrk_arg({Type, _}, _Args) -> - {error, {unacceptable_type, Type}}. - -list() -> mnesia:dirty_match_object(rabbit_queue, #amqqueue{_ = '_'}). - -list(VHostPath) -> list(VHostPath, rabbit_queue). - -%% Not dirty_match_object since that would not be transactional when used in a -%% tx context -list(VHostPath, TableName) -> - mnesia:async_dirty( - fun () -> - mnesia:match_object( - TableName, - #amqqueue{name = rabbit_misc:r(VHostPath, queue), _ = '_'}, - read) - end). - -list_down(VHostPath) -> - Present = list(VHostPath), - Durable = list(VHostPath, rabbit_durable_queue), - PresentS = sets:from_list([N || #amqqueue{name = N} <- Present]), - sets:to_list(sets:filter(fun (#amqqueue{name = N}) -> - not sets:is_element(N, PresentS) - end, sets:from_list(Durable))). - -info_keys() -> rabbit_amqqueue_process:info_keys(). - -map(Qs, F) -> rabbit_misc:filter_exit_map(F, Qs). - -info(Q = #amqqueue{ state = crashed }) -> info_down(Q, crashed); -info(#amqqueue{ pid = QPid }) -> delegate:call(QPid, info). - -info(Q = #amqqueue{ state = crashed }, Items) -> - info_down(Q, Items, crashed); -info(#amqqueue{ pid = QPid }, Items) -> - case delegate:call(QPid, {info, Items}) of - {ok, Res} -> Res; - {error, Error} -> throw(Error) - end. - -info_down(Q, DownReason) -> - info_down(Q, rabbit_amqqueue_process:info_keys(), DownReason). - -info_down(Q, Items, DownReason) -> - [{Item, i_down(Item, Q, DownReason)} || Item <- Items]. - -i_down(name, #amqqueue{name = Name}, _) -> Name; -i_down(durable, #amqqueue{durable = Durable},_) -> Durable; -i_down(auto_delete, #amqqueue{auto_delete = AD}, _) -> AD; -i_down(arguments, #amqqueue{arguments = Args}, _) -> Args; -i_down(pid, #amqqueue{pid = QPid}, _) -> QPid; -i_down(down_slave_nodes, #amqqueue{down_slave_nodes = DSN}, _) -> DSN; -i_down(state, _Q, DownReason) -> DownReason; -i_down(K, _Q, _DownReason) -> - case lists:member(K, rabbit_amqqueue_process:info_keys()) of - true -> ''; - false -> throw({bad_argument, K}) - end. - -info_all(VHostPath) -> - map(list(VHostPath), fun (Q) -> info(Q) end) ++ - map(list_down(VHostPath), fun (Q) -> info_down(Q, down) end). - -info_all(VHostPath, Items) -> - map(list(VHostPath), fun (Q) -> info(Q, Items) end) ++ - map(list_down(VHostPath), fun (Q) -> info_down(Q, Items, down) end). - -force_event_refresh(Ref) -> - [gen_server2:cast(Q#amqqueue.pid, - {force_event_refresh, Ref}) || Q <- list()], - ok. - -notify_policy_changed(#amqqueue{pid = QPid}) -> - gen_server2:cast(QPid, policy_changed). - -consumers(#amqqueue{ pid = QPid }) -> delegate:call(QPid, consumers). - -consumer_info_keys() -> ?CONSUMER_INFO_KEYS. - -consumers_all(VHostPath) -> - ConsumerInfoKeys=consumer_info_keys(), - lists:append( - map(list(VHostPath), - fun (Q) -> - [lists:zip( - ConsumerInfoKeys, - [Q#amqqueue.name, ChPid, CTag, AckRequired, Prefetch, Args]) || - {ChPid, CTag, AckRequired, Prefetch, Args} <- consumers(Q)] - end)). - -stat(#amqqueue{pid = QPid}) -> delegate:call(QPid, stat). - -delete_immediately(QPids) -> - [gen_server2:cast(QPid, delete_immediately) || QPid <- QPids], - ok. - -delete(#amqqueue{ pid = QPid }, IfUnused, IfEmpty) -> - delegate:call(QPid, {delete, IfUnused, IfEmpty}). - -delete_crashed(#amqqueue{ pid = QPid } = Q) -> - ok = rpc:call(node(QPid), ?MODULE, delete_crashed_internal, [Q]). - -delete_crashed_internal(Q = #amqqueue{ name = QName }) -> - {ok, BQ} = application:get_env(rabbit, backing_queue_module), - BQ:delete_crashed(Q), - ok = internal_delete(QName). - -purge(#amqqueue{ pid = QPid }) -> delegate:call(QPid, purge). - -deliver(Qs, Delivery) -> deliver(Qs, Delivery, noflow). - -deliver_flow(Qs, Delivery) -> deliver(Qs, Delivery, flow). - -requeue(QPid, MsgIds, ChPid) -> delegate:call(QPid, {requeue, MsgIds, ChPid}). - -ack(QPid, MsgIds, ChPid) -> delegate:cast(QPid, {ack, MsgIds, ChPid}). - -reject(QPid, Requeue, MsgIds, ChPid) -> - delegate:cast(QPid, {reject, Requeue, MsgIds, ChPid}). - -notify_down_all(QPids, ChPid) -> - {_, Bads} = delegate:call(QPids, {notify_down, ChPid}), - case lists:filter( - fun ({_Pid, {exit, {R, _}, _}}) -> rabbit_misc:is_abnormal_exit(R); - ({_Pid, _}) -> false - end, Bads) of - [] -> ok; - Bads1 -> {error, Bads1} - end. - -activate_limit_all(QPids, ChPid) -> - delegate:cast(QPids, {activate_limit, ChPid}). - -credit(#amqqueue{pid = QPid}, ChPid, CTag, Credit, Drain) -> - delegate:cast(QPid, {credit, ChPid, CTag, Credit, Drain}). - -basic_get(#amqqueue{pid = QPid}, ChPid, NoAck, LimiterPid) -> - delegate:call(QPid, {basic_get, ChPid, NoAck, LimiterPid}). - -basic_consume(#amqqueue{pid = QPid, name = QName}, NoAck, ChPid, LimiterPid, - LimiterActive, ConsumerPrefetchCount, ConsumerTag, - ExclusiveConsume, Args, OkMsg) -> - ok = check_consume_arguments(QName, Args), - delegate:call(QPid, {basic_consume, NoAck, ChPid, LimiterPid, LimiterActive, - ConsumerPrefetchCount, ConsumerTag, ExclusiveConsume, - Args, OkMsg}). - -basic_cancel(#amqqueue{pid = QPid}, ChPid, ConsumerTag, OkMsg) -> - delegate:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}). - -notify_decorators(#amqqueue{pid = QPid}) -> - delegate:cast(QPid, notify_decorators). - -notify_sent(QPid, ChPid) -> - Key = {consumer_credit_to, QPid}, - put(Key, case get(Key) of - 1 -> gen_server2:cast( - QPid, {notify_sent, ChPid, - ?MORE_CONSUMER_CREDIT_AFTER}), - ?MORE_CONSUMER_CREDIT_AFTER; - undefined -> erlang:monitor(process, QPid), - ?MORE_CONSUMER_CREDIT_AFTER - 1; - C -> C - 1 - end), - ok. - -notify_sent_queue_down(QPid) -> - erase({consumer_credit_to, QPid}), - ok. - -resume(QPid, ChPid) -> delegate:cast(QPid, {resume, ChPid}). - -internal_delete1(QueueName, OnlyDurable) -> - ok = mnesia:delete({rabbit_queue, QueueName}), - %% this 'guarded' delete prevents unnecessary writes to the mnesia - %% disk log - case mnesia:wread({rabbit_durable_queue, QueueName}) of - [] -> ok; - [_] -> ok = mnesia:delete({rabbit_durable_queue, QueueName}) - end, - %% we want to execute some things, as decided by rabbit_exchange, - %% after the transaction. - rabbit_binding:remove_for_destination(QueueName, OnlyDurable). - -internal_delete(QueueName) -> - rabbit_misc:execute_mnesia_tx_with_tail( - fun () -> - case {mnesia:wread({rabbit_queue, QueueName}), - mnesia:wread({rabbit_durable_queue, QueueName})} of - {[], []} -> - rabbit_misc:const({error, not_found}); - _ -> - Deletions = internal_delete1(QueueName, false), - T = rabbit_binding:process_deletions(Deletions), - fun() -> - ok = T(), - ok = rabbit_event:notify(queue_deleted, - [{name, QueueName}]) - end - end - end). - -forget_all_durable(Node) -> - %% Note rabbit is not running so we avoid e.g. the worker pool. Also why - %% we don't invoke the return from rabbit_binding:process_deletions/1. - {atomic, ok} = - mnesia:sync_transaction( - fun () -> - Qs = mnesia:match_object(rabbit_durable_queue, - #amqqueue{_ = '_'}, write), - [forget_node_for_queue(Q) || #amqqueue{pid = Pid} = Q <- Qs, - node(Pid) =:= Node], - ok - end), - ok. - -forget_node_for_queue(#amqqueue{name = Name, - down_slave_nodes = []}) -> - %% No slaves to recover from, queue is gone. - %% Don't process_deletions since that just calls callbacks and we - %% are not really up. - internal_delete1(Name, true); - -forget_node_for_queue(Q = #amqqueue{down_slave_nodes = [H|T]}) -> - %% Promote a slave while down - it'll happily recover as a master - Q1 = Q#amqqueue{pid = rabbit_misc:node_to_fake_pid(H), - down_slave_nodes = T}, - ok = mnesia:write(rabbit_durable_queue, Q1, write). - -run_backing_queue(QPid, Mod, Fun) -> - gen_server2:cast(QPid, {run_backing_queue, Mod, Fun}). - -set_ram_duration_target(QPid, Duration) -> - gen_server2:cast(QPid, {set_ram_duration_target, Duration}). - -set_maximum_since_use(QPid, Age) -> - gen_server2:cast(QPid, {set_maximum_since_use, Age}). - -start_mirroring(QPid) -> ok = delegate:cast(QPid, start_mirroring). -stop_mirroring(QPid) -> ok = delegate:cast(QPid, stop_mirroring). - -sync_mirrors(QPid) -> delegate:call(QPid, sync_mirrors). -cancel_sync_mirrors(QPid) -> delegate:call(QPid, cancel_sync_mirrors). - -on_node_up(Node) -> - ok = rabbit_misc:execute_mnesia_transaction( - fun () -> - Qs = mnesia:match_object(rabbit_queue, - #amqqueue{_ = '_'}, write), - [case lists:member(Node, DSNs) of - true -> DSNs1 = DSNs -- [Node], - store_queue( - Q#amqqueue{down_slave_nodes = DSNs1}); - false -> ok - end || #amqqueue{down_slave_nodes = DSNs} = Q <- Qs], - ok - end). - -on_node_down(Node) -> - rabbit_misc:execute_mnesia_tx_with_tail( - fun () -> QsDels = - qlc:e(qlc:q([{QName, delete_queue(QName)} || - #amqqueue{name = QName, pid = Pid, - slave_pids = []} - <- mnesia:table(rabbit_queue), - node(Pid) == Node andalso - not rabbit_mnesia:is_process_alive(Pid)])), - {Qs, Dels} = lists:unzip(QsDels), - T = rabbit_binding:process_deletions( - lists:foldl(fun rabbit_binding:combine_deletions/2, - rabbit_binding:new_deletions(), Dels)), - fun () -> - T(), - lists:foreach( - fun(QName) -> - ok = rabbit_event:notify(queue_deleted, - [{name, QName}]) - end, Qs) - end - end). - -delete_queue(QueueName) -> - ok = mnesia:delete({rabbit_queue, QueueName}), - rabbit_binding:remove_transient_for_destination(QueueName). - -pseudo_queue(QueueName, Pid) -> - #amqqueue{name = QueueName, - durable = false, - auto_delete = false, - arguments = [], - pid = Pid, - slave_pids = []}. - -immutable(Q) -> Q#amqqueue{pid = none, - slave_pids = none, - sync_slave_pids = none, - down_slave_nodes = none, - gm_pids = none, - policy = none, - decorators = none, - state = none}. - -deliver([], _Delivery, _Flow) -> - %% /dev/null optimisation - []; - -deliver(Qs, Delivery, Flow) -> - {MPids, SPids} = qpids(Qs), - QPids = MPids ++ SPids, - case Flow of - flow -> [credit_flow:send(QPid) || QPid <- QPids]; - noflow -> ok - end, - - %% We let slaves know that they were being addressed as slaves at - %% the time - if they receive such a message from the channel - %% after they have become master they should mark the message as - %% 'delivered' since they do not know what the master may have - %% done with it. - MMsg = {deliver, Delivery, false, Flow}, - SMsg = {deliver, Delivery, true, Flow}, - delegate:cast(MPids, MMsg), - delegate:cast(SPids, SMsg), - QPids. - -qpids([]) -> {[], []}; %% optimisation -qpids([#amqqueue{pid = QPid, slave_pids = SPids}]) -> {[QPid], SPids}; %% opt -qpids(Qs) -> - {MPids, SPids} = lists:foldl(fun (#amqqueue{pid = QPid, slave_pids = SPids}, - {MPidAcc, SPidAcc}) -> - {[QPid | MPidAcc], [SPids | SPidAcc]} - end, {[], []}, Qs), - {MPids, lists:append(SPids)}. diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl deleted file mode 100644 index b06da4c1..00000000 --- a/src/rabbit_amqqueue_process.erl +++ /dev/null @@ -1,1302 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_amqqueue_process). --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - --behaviour(gen_server2). - --define(SYNC_INTERVAL, 200). %% milliseconds --define(RAM_DURATION_UPDATE_INTERVAL, 5000). --define(CONSUMER_BIAS_RATIO, 1.1). %% i.e. consume 10% faster - --export([info_keys/0]). - --export([init_with_backing_queue_state/7]). - --export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, - handle_info/2, handle_pre_hibernate/1, prioritise_call/4, - prioritise_cast/3, prioritise_info/3, format_message_queue/2]). - -%% Queue's state --record(q, {q, - exclusive_consumer, - has_had_consumers, - backing_queue, - backing_queue_state, - consumers, - expires, - sync_timer_ref, - rate_timer_ref, - expiry_timer_ref, - stats_timer, - msg_id_to_channel, - ttl, - ttl_timer_ref, - ttl_timer_expiry, - senders, - dlx, - dlx_routing_key, - max_length, - max_bytes, - args_policy_version, - status - }). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(info_keys/0 :: () -> rabbit_types:info_keys()). --spec(init_with_backing_queue_state/7 :: - (rabbit_types:amqqueue(), atom(), tuple(), any(), - [rabbit_types:delivery()], pmon:pmon(), dict:dict()) -> #q{}). - --endif. - -%%---------------------------------------------------------------------------- - --define(STATISTICS_KEYS, - [name, - policy, - exclusive_consumer_pid, - exclusive_consumer_tag, - messages_ready, - messages_unacknowledged, - messages, - consumers, - consumer_utilisation, - memory, - slave_pids, - synchronised_slave_pids, - down_slave_nodes, - backing_queue_status, - state - ]). - --define(CREATION_EVENT_KEYS, - [name, - durable, - auto_delete, - arguments, - owner_pid - ]). - --define(INFO_KEYS, [pid | ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [name]]). - -%%---------------------------------------------------------------------------- - -info_keys() -> ?INFO_KEYS ++ rabbit_backing_queue:info_keys(). -statistics_keys() -> ?STATISTICS_KEYS ++ rabbit_backing_queue:info_keys(). - -%%---------------------------------------------------------------------------- - -init(Q) -> - process_flag(trap_exit, true), - ?store_proc_name(Q#amqqueue.name), - {ok, init_state(Q#amqqueue{pid = self()}), hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}, - ?MODULE}. - -init_state(Q) -> - State = #q{q = Q, - exclusive_consumer = none, - has_had_consumers = false, - consumers = rabbit_queue_consumers:new(), - senders = pmon:new(delegate), - msg_id_to_channel = gb_trees:empty(), - status = running, - args_policy_version = 0}, - rabbit_event:init_stats_timer(State, #q.stats_timer). - -init_it(Recover, From, State = #q{q = #amqqueue{exclusive_owner = none}}) -> - init_it2(Recover, From, State); - -%% You used to be able to declare an exclusive durable queue. Sadly we -%% need to still tidy up after that case, there could be the remnants -%% of one left over from an upgrade. So that's why we don't enforce -%% Recover = new here. -init_it(Recover, From, State = #q{q = #amqqueue{exclusive_owner = Owner}}) -> - case rabbit_misc:is_process_alive(Owner) of - true -> erlang:monitor(process, Owner), - init_it2(Recover, From, State); - false -> #q{backing_queue = undefined, - backing_queue_state = undefined, - q = Q} = State, - send_reply(From, {owner_died, Q}), - BQ = backing_queue_module(Q), - {_, Terms} = recovery_status(Recover), - BQS = bq_init(BQ, Q, Terms), - %% Rely on terminate to delete the queue. - {stop, {shutdown, missing_owner}, - State#q{backing_queue = BQ, backing_queue_state = BQS}} - end. - -init_it2(Recover, From, State = #q{q = Q, - backing_queue = undefined, - backing_queue_state = undefined}) -> - {Barrier, TermsOrNew} = recovery_status(Recover), - case rabbit_amqqueue:internal_declare(Q, Recover /= new) of - #amqqueue{} = Q1 -> - case matches(Recover, Q, Q1) of - true -> - send_reply(From, {new, Q}), - ok = file_handle_cache:register_callback( - rabbit_amqqueue, set_maximum_since_use, [self()]), - ok = rabbit_memory_monitor:register( - self(), {rabbit_amqqueue, - set_ram_duration_target, [self()]}), - BQ = backing_queue_module(Q1), - BQS = bq_init(BQ, Q, TermsOrNew), - recovery_barrier(Barrier), - State1 = process_args_policy( - State#q{backing_queue = BQ, - backing_queue_state = BQS}), - notify_decorators(startup, State), - rabbit_event:notify(queue_created, - infos(?CREATION_EVENT_KEYS, State1)), - rabbit_event:if_enabled(State1, #q.stats_timer, - fun() -> emit_stats(State1) end), - noreply(State1); - false -> - {stop, normal, {existing, Q1}, State} - end; - Err -> - {stop, normal, Err, State} - end. - -recovery_status(new) -> {no_barrier, new}; -recovery_status({Recover, Terms}) -> {Recover, Terms}. - -send_reply(none, _Q) -> ok; -send_reply(From, Q) -> gen_server2:reply(From, Q). - -matches(new, Q1, Q2) -> - %% i.e. not policy - Q1#amqqueue.name =:= Q2#amqqueue.name andalso - Q1#amqqueue.durable =:= Q2#amqqueue.durable andalso - Q1#amqqueue.auto_delete =:= Q2#amqqueue.auto_delete andalso - Q1#amqqueue.exclusive_owner =:= Q2#amqqueue.exclusive_owner andalso - Q1#amqqueue.arguments =:= Q2#amqqueue.arguments andalso - Q1#amqqueue.pid =:= Q2#amqqueue.pid andalso - Q1#amqqueue.slave_pids =:= Q2#amqqueue.slave_pids; -matches(_, Q, Q) -> true; -matches(_, _Q, _Q1) -> false. - -recovery_barrier(no_barrier) -> - ok; -recovery_barrier(BarrierPid) -> - MRef = erlang:monitor(process, BarrierPid), - receive - {BarrierPid, go} -> erlang:demonitor(MRef, [flush]); - {'DOWN', MRef, process, _, _} -> ok - end. - -init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS, - RateTRef, Deliveries, Senders, MTC) -> - case Owner of - none -> ok; - _ -> erlang:monitor(process, Owner) - end, - State = init_state(Q), - State1 = State#q{backing_queue = BQ, - backing_queue_state = BQS, - rate_timer_ref = RateTRef, - senders = Senders, - msg_id_to_channel = MTC}, - State2 = process_args_policy(State1), - State3 = lists:foldl(fun (Delivery, StateN) -> - deliver_or_enqueue(Delivery, true, StateN) - end, State2, Deliveries), - notify_decorators(startup, State3), - State3. - -terminate(shutdown = R, State = #q{backing_queue = BQ}) -> - terminate_shutdown(fun (BQS) -> BQ:terminate(R, BQS) end, State); -terminate({shutdown, missing_owner} = Reason, State) -> - %% if the owner was missing then there will be no queue, so don't emit stats - terminate_shutdown(terminate_delete(false, Reason, State), State); -terminate({shutdown, _} = R, State = #q{backing_queue = BQ}) -> - terminate_shutdown(fun (BQS) -> BQ:terminate(R, BQS) end, State); -terminate(normal, State) -> %% delete case - terminate_shutdown(terminate_delete(true, normal, State), State); -%% If we crashed don't try to clean up the BQS, probably best to leave it. -terminate(_Reason, State = #q{q = Q}) -> - terminate_shutdown(fun (BQS) -> - Q2 = Q#amqqueue{state = crashed}, - rabbit_misc:execute_mnesia_transaction( - fun() -> - rabbit_amqqueue:store_queue(Q2) - end), - BQS - end, State). - -terminate_delete(EmitStats, Reason, - State = #q{q = #amqqueue{name = QName}, - backing_queue = BQ}) -> - fun (BQS) -> - BQS1 = BQ:delete_and_terminate(Reason, BQS), - if EmitStats -> rabbit_event:if_enabled(State, #q.stats_timer, - fun() -> emit_stats(State) end); - true -> ok - end, - %% don't care if the internal delete doesn't return 'ok'. - rabbit_amqqueue:internal_delete(QName), - BQS1 - end. - -terminate_shutdown(Fun, State) -> - State1 = #q{backing_queue_state = BQS, consumers = Consumers} = - lists:foldl(fun (F, S) -> F(S) end, State, - [fun stop_sync_timer/1, - fun stop_rate_timer/1, - fun stop_expiry_timer/1, - fun stop_ttl_timer/1]), - case BQS of - undefined -> State1; - _ -> ok = rabbit_memory_monitor:deregister(self()), - QName = qname(State), - notify_decorators(shutdown, State), - [emit_consumer_deleted(Ch, CTag, QName) || - {Ch, CTag, _, _, _} <- - rabbit_queue_consumers:all(Consumers)], - State1#q{backing_queue_state = Fun(BQS)} - end. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%---------------------------------------------------------------------------- - -maybe_notify_decorators(false, State) -> State; -maybe_notify_decorators(true, State) -> notify_decorators(State), State. - -notify_decorators(Event, State) -> decorator_callback(qname(State), Event, []). - -notify_decorators(State = #q{consumers = Consumers, - backing_queue = BQ, - backing_queue_state = BQS}) -> - P = rabbit_queue_consumers:max_active_priority(Consumers), - decorator_callback(qname(State), consumer_state_changed, - [P, BQ:is_empty(BQS)]). - -decorator_callback(QName, F, A) -> - %% Look up again in case policy and hence decorators have changed - case rabbit_amqqueue:lookup(QName) of - {ok, Q = #amqqueue{decorators = Ds}} -> - [ok = apply(M, F, [Q|A]) || M <- rabbit_queue_decorator:select(Ds)]; - {error, not_found} -> - ok - end. - -bq_init(BQ, Q, Recover) -> - Self = self(), - BQ:init(Q, Recover, - fun (Mod, Fun) -> - rabbit_amqqueue:run_backing_queue(Self, Mod, Fun) - end). - -process_args_policy(State = #q{q = Q, - args_policy_version = N}) -> - ArgsTable = - [{<<"expires">>, fun res_min/2, fun init_exp/2}, - {<<"dead-letter-exchange">>, fun res_arg/2, fun init_dlx/2}, - {<<"dead-letter-routing-key">>, fun res_arg/2, fun init_dlx_rkey/2}, - {<<"message-ttl">>, fun res_min/2, fun init_ttl/2}, - {<<"max-length">>, fun res_min/2, fun init_max_length/2}, - {<<"max-length-bytes">>, fun res_min/2, fun init_max_bytes/2}], - drop_expired_msgs( - lists:foldl(fun({Name, Resolve, Fun}, StateN) -> - Fun(args_policy_lookup(Name, Resolve, Q), StateN) - end, State#q{args_policy_version = N + 1}, ArgsTable)). - -args_policy_lookup(Name, Resolve, Q = #amqqueue{arguments = Args}) -> - AName = <<"x-", Name/binary>>, - case {rabbit_policy:get(Name, Q), rabbit_misc:table_lookup(Args, AName)} of - {undefined, undefined} -> undefined; - {undefined, {_Type, Val}} -> Val; - {Val, undefined} -> Val; - {PolVal, {_Type, ArgVal}} -> Resolve(PolVal, ArgVal) - end. - -res_arg(_PolVal, ArgVal) -> ArgVal. -res_min(PolVal, ArgVal) -> erlang:min(PolVal, ArgVal). - -%% In both these we init with the undefined variant first to stop any -%% existing timer, then start a new one which may fire after a -%% different time. -init_exp(undefined, State) -> stop_expiry_timer(State#q{expires = undefined}); -init_exp(Expires, State) -> State1 = init_exp(undefined, State), - ensure_expiry_timer(State1#q{expires = Expires}). - -init_ttl(undefined, State) -> stop_ttl_timer(State#q{ttl = undefined}); -init_ttl(TTL, State) -> (init_ttl(undefined, State))#q{ttl = TTL}. - -init_dlx(undefined, State) -> - State#q{dlx = undefined}; -init_dlx(DLX, State = #q{q = #amqqueue{name = QName}}) -> - State#q{dlx = rabbit_misc:r(QName, exchange, DLX)}. - -init_dlx_rkey(RoutingKey, State) -> State#q{dlx_routing_key = RoutingKey}. - -init_max_length(MaxLen, State) -> - {_Dropped, State1} = maybe_drop_head(State#q{max_length = MaxLen}), - State1. - -init_max_bytes(MaxBytes, State) -> - {_Dropped, State1} = maybe_drop_head(State#q{max_bytes = MaxBytes}), - State1. - -reply(Reply, NewState) -> - {NewState1, Timeout} = next_state(NewState), - {reply, Reply, ensure_stats_timer(ensure_rate_timer(NewState1)), Timeout}. - -noreply(NewState) -> - {NewState1, Timeout} = next_state(NewState), - {noreply, ensure_stats_timer(ensure_rate_timer(NewState1)), Timeout}. - -next_state(State = #q{backing_queue = BQ, - backing_queue_state = BQS, - msg_id_to_channel = MTC}) -> - assert_invariant(State), - {MsgIds, BQS1} = BQ:drain_confirmed(BQS), - MTC1 = confirm_messages(MsgIds, MTC), - State1 = State#q{backing_queue_state = BQS1, msg_id_to_channel = MTC1}, - case BQ:needs_timeout(BQS1) of - false -> {stop_sync_timer(State1), hibernate }; - idle -> {stop_sync_timer(State1), ?SYNC_INTERVAL}; - timed -> {ensure_sync_timer(State1), 0 } - end. - -backing_queue_module(Q) -> - case rabbit_mirror_queue_misc:is_mirrored(Q) of - false -> {ok, BQM} = application:get_env(backing_queue_module), - BQM; - true -> rabbit_mirror_queue_master - end. - -ensure_sync_timer(State) -> - rabbit_misc:ensure_timer(State, #q.sync_timer_ref, - ?SYNC_INTERVAL, sync_timeout). - -stop_sync_timer(State) -> rabbit_misc:stop_timer(State, #q.sync_timer_ref). - -ensure_rate_timer(State) -> - rabbit_misc:ensure_timer(State, #q.rate_timer_ref, - ?RAM_DURATION_UPDATE_INTERVAL, - update_ram_duration). - -stop_rate_timer(State) -> rabbit_misc:stop_timer(State, #q.rate_timer_ref). - -%% We wish to expire only when there are no consumers *and* the expiry -%% hasn't been refreshed (by queue.declare or basic.get) for the -%% configured period. -ensure_expiry_timer(State = #q{expires = undefined}) -> - State; -ensure_expiry_timer(State = #q{expires = Expires, - args_policy_version = Version}) -> - case is_unused(State) of - true -> NewState = stop_expiry_timer(State), - rabbit_misc:ensure_timer(NewState, #q.expiry_timer_ref, - Expires, {maybe_expire, Version}); - false -> State - end. - -stop_expiry_timer(State) -> rabbit_misc:stop_timer(State, #q.expiry_timer_ref). - -ensure_ttl_timer(undefined, State) -> - State; -ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = undefined, - args_policy_version = Version}) -> - After = (case Expiry - now_micros() of - V when V > 0 -> V + 999; %% always fire later - _ -> 0 - end) div 1000, - TRef = rabbit_misc:send_after(After, self(), {drop_expired, Version}), - State#q{ttl_timer_ref = TRef, ttl_timer_expiry = Expiry}; -ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = TRef, - ttl_timer_expiry = TExpiry}) - when Expiry + 1000 < TExpiry -> - rabbit_misc:cancel_timer(TRef), - ensure_ttl_timer(Expiry, State#q{ttl_timer_ref = undefined}); -ensure_ttl_timer(_Expiry, State) -> - State. - -stop_ttl_timer(State) -> rabbit_misc:stop_timer(State, #q.ttl_timer_ref). - -ensure_stats_timer(State) -> - rabbit_event:ensure_stats_timer(State, #q.stats_timer, emit_stats). - -assert_invariant(State = #q{consumers = Consumers}) -> - true = (rabbit_queue_consumers:inactive(Consumers) orelse is_empty(State)). - -is_empty(#q{backing_queue = BQ, backing_queue_state = BQS}) -> BQ:is_empty(BQS). - -maybe_send_drained(WasEmpty, State) -> - case (not WasEmpty) andalso is_empty(State) of - true -> notify_decorators(State), - rabbit_queue_consumers:send_drained(); - false -> ok - end, - State. - -confirm_messages([], MTC) -> - MTC; -confirm_messages(MsgIds, MTC) -> - {CMs, MTC1} = - lists:foldl( - fun(MsgId, {CMs, MTC0}) -> - case gb_trees:lookup(MsgId, MTC0) of - {value, {SenderPid, MsgSeqNo}} -> - {rabbit_misc:gb_trees_cons(SenderPid, - MsgSeqNo, CMs), - gb_trees:delete(MsgId, MTC0)}; - none -> - {CMs, MTC0} - end - end, {gb_trees:empty(), MTC}, MsgIds), - rabbit_misc:gb_trees_foreach(fun rabbit_misc:confirm_to_sender/2, CMs), - MTC1. - -send_or_record_confirm(#delivery{confirm = false}, State) -> - {never, State}; -send_or_record_confirm(#delivery{confirm = true, - sender = SenderPid, - msg_seq_no = MsgSeqNo, - message = #basic_message { - is_persistent = true, - id = MsgId}}, - State = #q{q = #amqqueue{durable = true}, - msg_id_to_channel = MTC}) -> - MTC1 = gb_trees:insert(MsgId, {SenderPid, MsgSeqNo}, MTC), - {eventually, State#q{msg_id_to_channel = MTC1}}; -send_or_record_confirm(#delivery{confirm = true, - sender = SenderPid, - msg_seq_no = MsgSeqNo}, State) -> - rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]), - {immediately, State}. - -send_mandatory(#delivery{mandatory = false}) -> - ok; -send_mandatory(#delivery{mandatory = true, - sender = SenderPid, - msg_seq_no = MsgSeqNo}) -> - gen_server2:cast(SenderPid, {mandatory_received, MsgSeqNo}). - -discard(#delivery{confirm = Confirm, - sender = SenderPid, - message = #basic_message{id = MsgId}}, BQ, BQS, MTC) -> - MTC1 = case Confirm of - true -> confirm_messages([MsgId], MTC); - false -> MTC - end, - BQS1 = BQ:discard(MsgId, SenderPid, BQS), - {BQS1, MTC1}. - -run_message_queue(State) -> run_message_queue(false, State). - -run_message_queue(ActiveConsumersChanged, State) -> - case is_empty(State) of - true -> maybe_notify_decorators(ActiveConsumersChanged, State); - false -> case rabbit_queue_consumers:deliver( - fun(AckRequired) -> fetch(AckRequired, State) end, - qname(State), State#q.consumers) of - {delivered, ActiveConsumersChanged1, State1, Consumers} -> - run_message_queue( - ActiveConsumersChanged or ActiveConsumersChanged1, - State1#q{consumers = Consumers}); - {undelivered, ActiveConsumersChanged1, Consumers} -> - maybe_notify_decorators( - ActiveConsumersChanged or ActiveConsumersChanged1, - State#q{consumers = Consumers}) - end - end. - -attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, - Props, Delivered, State = #q{backing_queue = BQ, - backing_queue_state = BQS, - msg_id_to_channel = MTC}) -> - case rabbit_queue_consumers:deliver( - fun (true) -> true = BQ:is_empty(BQS), - {AckTag, BQS1} = BQ:publish_delivered( - Message, Props, SenderPid, BQS), - {{Message, Delivered, AckTag}, {BQS1, MTC}}; - (false) -> {{Message, Delivered, undefined}, - discard(Delivery, BQ, BQS, MTC)} - end, qname(State), State#q.consumers) of - {delivered, ActiveConsumersChanged, {BQS1, MTC1}, Consumers} -> - {delivered, maybe_notify_decorators( - ActiveConsumersChanged, - State#q{backing_queue_state = BQS1, - msg_id_to_channel = MTC1, - consumers = Consumers})}; - {undelivered, ActiveConsumersChanged, Consumers} -> - {undelivered, maybe_notify_decorators( - ActiveConsumersChanged, - State#q{consumers = Consumers})} - end. - -deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, - Delivered, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - send_mandatory(Delivery), %% must do this before confirms - {Confirm, State1} = send_or_record_confirm(Delivery, State), - Props = message_properties(Message, Confirm, State1), - {IsDuplicate, BQS1} = BQ:is_duplicate(Message, BQS), - State2 = State1#q{backing_queue_state = BQS1}, - case IsDuplicate orelse attempt_delivery(Delivery, Props, Delivered, - State2) of - true -> - State2; - {delivered, State3} -> - State3; - %% The next one is an optimisation - {undelivered, State3 = #q{ttl = 0, dlx = undefined, - backing_queue_state = BQS2, - msg_id_to_channel = MTC}} -> - {BQS3, MTC1} = discard(Delivery, BQ, BQS2, MTC), - State3#q{backing_queue_state = BQS3, msg_id_to_channel = MTC1}; - {undelivered, State3 = #q{backing_queue_state = BQS2}} -> - BQS3 = BQ:publish(Message, Props, Delivered, SenderPid, BQS2), - {Dropped, State4 = #q{backing_queue_state = BQS4}} = - maybe_drop_head(State3#q{backing_queue_state = BQS3}), - QLen = BQ:len(BQS4), - %% optimisation: it would be perfectly safe to always - %% invoke drop_expired_msgs here, but that is expensive so - %% we only do that if a new message that might have an - %% expiry ends up at the head of the queue. If the head - %% remains unchanged, or if the newly published message - %% has no expiry and becomes the head of the queue then - %% the call is unnecessary. - case {Dropped, QLen =:= 1, Props#message_properties.expiry} of - {false, false, _} -> State4; - {true, true, undefined} -> State4; - {_, _, _} -> drop_expired_msgs(State4) - end - end. - -maybe_drop_head(State = #q{max_length = undefined, - max_bytes = undefined}) -> - {false, State}; -maybe_drop_head(State) -> - maybe_drop_head(false, State). - -maybe_drop_head(AlreadyDropped, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - case over_max_length(State) of - true -> - maybe_drop_head(true, - with_dlx( - State#q.dlx, - fun (X) -> dead_letter_maxlen_msg(X, State) end, - fun () -> - {_, BQS1} = BQ:drop(false, BQS), - State#q{backing_queue_state = BQS1} - end)); - false -> - {AlreadyDropped, State} - end. - -over_max_length(#q{max_length = MaxLen, - max_bytes = MaxBytes, - backing_queue = BQ, - backing_queue_state = BQS}) -> - BQ:len(BQS) > MaxLen orelse BQ:info(message_bytes_ready, BQS) > MaxBytes. - -requeue_and_run(AckTags, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - WasEmpty = BQ:is_empty(BQS), - {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), - {_Dropped, State1} = maybe_drop_head(State#q{backing_queue_state = BQS1}), - run_message_queue(maybe_send_drained(WasEmpty, drop_expired_msgs(State1))). - -fetch(AckRequired, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - {Result, BQS1} = BQ:fetch(AckRequired, BQS), - State1 = drop_expired_msgs(State#q{backing_queue_state = BQS1}), - {Result, maybe_send_drained(Result =:= empty, State1)}. - -ack(AckTags, ChPid, State) -> - subtract_acks(ChPid, AckTags, State, - fun (State1 = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - {_Guids, BQS1} = BQ:ack(AckTags, BQS), - State1#q{backing_queue_state = BQS1} - end). - -requeue(AckTags, ChPid, State) -> - subtract_acks(ChPid, AckTags, State, - fun (State1) -> requeue_and_run(AckTags, State1) end). - -possibly_unblock(Update, ChPid, State = #q{consumers = Consumers}) -> - case rabbit_queue_consumers:possibly_unblock(Update, ChPid, Consumers) of - unchanged -> State; - {unblocked, Consumers1} -> State1 = State#q{consumers = Consumers1}, - run_message_queue(true, State1) - end. - -should_auto_delete(#q{q = #amqqueue{auto_delete = false}}) -> false; -should_auto_delete(#q{has_had_consumers = false}) -> false; -should_auto_delete(State) -> is_unused(State). - -handle_ch_down(DownPid, State = #q{consumers = Consumers, - exclusive_consumer = Holder, - senders = Senders}) -> - State1 = State#q{senders = case pmon:is_monitored(DownPid, Senders) of - false -> Senders; - true -> credit_flow:peer_down(DownPid), - pmon:demonitor(DownPid, Senders) - end}, - case rabbit_queue_consumers:erase_ch(DownPid, Consumers) of - not_found -> - {ok, State1}; - {ChAckTags, ChCTags, Consumers1} -> - QName = qname(State1), - [emit_consumer_deleted(DownPid, CTag, QName) || CTag <- ChCTags], - Holder1 = case Holder of - {DownPid, _} -> none; - Other -> Other - end, - State2 = State1#q{consumers = Consumers1, - exclusive_consumer = Holder1}, - notify_decorators(State2), - case should_auto_delete(State2) of - true -> {stop, State2}; - false -> {ok, requeue_and_run(ChAckTags, - ensure_expiry_timer(State2))} - end - end. - -check_exclusive_access({_ChPid, _ConsumerTag}, _ExclusiveConsume, _State) -> - in_use; -check_exclusive_access(none, false, _State) -> - ok; -check_exclusive_access(none, true, State) -> - case is_unused(State) of - true -> ok; - false -> in_use - end. - -is_unused(_State) -> rabbit_queue_consumers:count() == 0. - -maybe_send_reply(_ChPid, undefined) -> ok; -maybe_send_reply(ChPid, Msg) -> ok = rabbit_channel:send_command(ChPid, Msg). - -qname(#q{q = #amqqueue{name = QName}}) -> QName. - -backing_queue_timeout(State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - State#q{backing_queue_state = BQ:timeout(BQS)}. - -subtract_acks(ChPid, AckTags, State = #q{consumers = Consumers}, Fun) -> - case rabbit_queue_consumers:subtract_acks(ChPid, AckTags, Consumers) of - not_found -> State; - unchanged -> Fun(State); - {unblocked, Consumers1} -> State1 = State#q{consumers = Consumers1}, - run_message_queue(true, Fun(State1)) - end. - -message_properties(Message = #basic_message{content = Content}, - Confirm, #q{ttl = TTL}) -> - #content{payload_fragments_rev = PFR} = Content, - #message_properties{expiry = calculate_msg_expiry(Message, TTL), - needs_confirming = Confirm == eventually, - size = iolist_size(PFR)}. - -calculate_msg_expiry(#basic_message{content = Content}, TTL) -> - #content{properties = Props} = - rabbit_binary_parser:ensure_content_decoded(Content), - %% We assert that the expiration must be valid - we check in the channel. - {ok, MsgTTL} = rabbit_basic:parse_expiration(Props), - case lists:min([TTL, MsgTTL]) of - undefined -> undefined; - T -> now_micros() + T * 1000 - end. - -%% Logically this function should invoke maybe_send_drained/2. -%% However, that is expensive. Since some frequent callers of -%% drop_expired_msgs/1, in particular deliver_or_enqueue/3, cannot -%% possibly cause the queue to become empty, we push the -%% responsibility to the callers. So be cautious when adding new ones. -drop_expired_msgs(State) -> - case is_empty(State) of - true -> State; - false -> drop_expired_msgs(now_micros(), State) - end. - -drop_expired_msgs(Now, State = #q{backing_queue_state = BQS, - backing_queue = BQ }) -> - ExpirePred = fun (#message_properties{expiry = Exp}) -> Now >= Exp end, - {Props, State1} = - with_dlx( - State#q.dlx, - fun (X) -> dead_letter_expired_msgs(ExpirePred, X, State) end, - fun () -> {Next, BQS1} = BQ:dropwhile(ExpirePred, BQS), - {Next, State#q{backing_queue_state = BQS1}} end), - ensure_ttl_timer(case Props of - undefined -> undefined; - #message_properties{expiry = Exp} -> Exp - end, State1). - -with_dlx(undefined, _With, Without) -> Without(); -with_dlx(DLX, With, Without) -> case rabbit_exchange:lookup(DLX) of - {ok, X} -> With(X); - {error, not_found} -> Without() - end. - -dead_letter_expired_msgs(ExpirePred, X, State = #q{backing_queue = BQ}) -> - dead_letter_msgs(fun (DLFun, Acc, BQS1) -> - BQ:fetchwhile(ExpirePred, DLFun, Acc, BQS1) - end, expired, X, State). - -dead_letter_rejected_msgs(AckTags, X, State = #q{backing_queue = BQ}) -> - {ok, State1} = - dead_letter_msgs( - fun (DLFun, Acc, BQS) -> - {Acc1, BQS1} = BQ:ackfold(DLFun, Acc, BQS, AckTags), - {ok, Acc1, BQS1} - end, rejected, X, State), - State1. - -dead_letter_maxlen_msg(X, State = #q{backing_queue = BQ}) -> - {ok, State1} = - dead_letter_msgs( - fun (DLFun, Acc, BQS) -> - {{Msg, _, AckTag}, BQS1} = BQ:fetch(true, BQS), - {ok, DLFun(Msg, AckTag, Acc), BQS1} - end, maxlen, X, State), - State1. - -dead_letter_msgs(Fun, Reason, X, State = #q{dlx_routing_key = RK, - backing_queue_state = BQS, - backing_queue = BQ}) -> - QName = qname(State), - {Res, Acks1, BQS1} = - Fun(fun (Msg, AckTag, Acks) -> - rabbit_dead_letter:publish(Msg, Reason, X, RK, QName), - [AckTag | Acks] - end, [], BQS), - {_Guids, BQS2} = BQ:ack(Acks1, BQS1), - {Res, State#q{backing_queue_state = BQS2}}. - -stop(State) -> stop(noreply, State). - -stop(noreply, State) -> {stop, normal, State}; -stop(Reply, State) -> {stop, normal, Reply, State}. - -now_micros() -> timer:now_diff(now(), {0,0,0}). - -infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. - -i(name, #q{q = #amqqueue{name = Name}}) -> Name; -i(durable, #q{q = #amqqueue{durable = Durable}}) -> Durable; -i(auto_delete, #q{q = #amqqueue{auto_delete = AutoDelete}}) -> AutoDelete; -i(arguments, #q{q = #amqqueue{arguments = Arguments}}) -> Arguments; -i(pid, _) -> - self(); -i(owner_pid, #q{q = #amqqueue{exclusive_owner = none}}) -> - ''; -i(owner_pid, #q{q = #amqqueue{exclusive_owner = ExclusiveOwner}}) -> - ExclusiveOwner; -i(policy, #q{q = Q}) -> - case rabbit_policy:name(Q) of - none -> ''; - Policy -> Policy - end; -i(exclusive_consumer_pid, #q{exclusive_consumer = none}) -> - ''; -i(exclusive_consumer_pid, #q{exclusive_consumer = {ChPid, _ConsumerTag}}) -> - ChPid; -i(exclusive_consumer_tag, #q{exclusive_consumer = none}) -> - ''; -i(exclusive_consumer_tag, #q{exclusive_consumer = {_ChPid, ConsumerTag}}) -> - ConsumerTag; -i(messages_ready, #q{backing_queue_state = BQS, backing_queue = BQ}) -> - BQ:len(BQS); -i(messages_unacknowledged, _) -> - rabbit_queue_consumers:unacknowledged_message_count(); -i(messages, State) -> - lists:sum([i(Item, State) || Item <- [messages_ready, - messages_unacknowledged]]); -i(consumers, _) -> - rabbit_queue_consumers:count(); -i(consumer_utilisation, #q{consumers = Consumers}) -> - case rabbit_queue_consumers:count() of - 0 -> ''; - _ -> rabbit_queue_consumers:utilisation(Consumers) - end; -i(memory, _) -> - {memory, M} = process_info(self(), memory), - M; -i(slave_pids, #q{q = #amqqueue{name = Name}}) -> - {ok, Q = #amqqueue{slave_pids = SPids}} = - rabbit_amqqueue:lookup(Name), - case rabbit_mirror_queue_misc:is_mirrored(Q) of - false -> ''; - true -> SPids - end; -i(synchronised_slave_pids, #q{q = #amqqueue{name = Name}}) -> - {ok, Q = #amqqueue{sync_slave_pids = SSPids}} = - rabbit_amqqueue:lookup(Name), - case rabbit_mirror_queue_misc:is_mirrored(Q) of - false -> ''; - true -> SSPids - end; -i(down_slave_nodes, #q{q = #amqqueue{name = Name, - durable = Durable}}) -> - {ok, Q = #amqqueue{down_slave_nodes = Nodes}} = - rabbit_amqqueue:lookup(Name), - case Durable andalso rabbit_mirror_queue_misc:is_mirrored(Q) of - false -> ''; - true -> Nodes - end; -i(state, #q{status = running}) -> credit_flow:state(); -i(state, #q{status = State}) -> State; -i(Item, #q{backing_queue_state = BQS, backing_queue = BQ}) -> - BQ:info(Item, BQS). - -emit_stats(State) -> - emit_stats(State, []). - -emit_stats(State, Extra) -> - ExtraKs = [K || {K, _} <- Extra], - Infos = [{K, V} || {K, V} <- infos(statistics_keys(), State), - not lists:member(K, ExtraKs)], - rabbit_event:notify(queue_stats, Extra ++ Infos). - -emit_consumer_created(ChPid, CTag, Exclusive, AckRequired, QName, - PrefetchCount, Args, Ref) -> - rabbit_event:notify(consumer_created, - [{consumer_tag, CTag}, - {exclusive, Exclusive}, - {ack_required, AckRequired}, - {channel, ChPid}, - {queue, QName}, - {prefetch_count, PrefetchCount}, - {arguments, Args}], - Ref). - -emit_consumer_deleted(ChPid, ConsumerTag, QName) -> - rabbit_event:notify(consumer_deleted, - [{consumer_tag, ConsumerTag}, - {channel, ChPid}, - {queue, QName}]). - -%%---------------------------------------------------------------------------- - -prioritise_call(Msg, _From, _Len, State) -> - case Msg of - info -> 9; - {info, _Items} -> 9; - consumers -> 9; - stat -> 7; - {basic_consume, _, _, _, _, _, _, _, _, _} -> consumer_bias(State); - {basic_cancel, _, _, _} -> consumer_bias(State); - _ -> 0 - end. - -prioritise_cast(Msg, _Len, State) -> - case Msg of - delete_immediately -> 8; - {set_ram_duration_target, _Duration} -> 8; - {set_maximum_since_use, _Age} -> 8; - {run_backing_queue, _Mod, _Fun} -> 6; - {ack, _AckTags, _ChPid} -> 3; %% [1] - {resume, _ChPid} -> 2; - {notify_sent, _ChPid, _Credit} -> consumer_bias(State); - _ -> 0 - end. - -%% [1] It should be safe to always prioritise ack / resume since they -%% will be rate limited by how fast consumers receive messages - -%% i.e. by notify_sent. We prioritise ack and resume to discourage -%% starvation caused by prioritising notify_sent. We don't vary their -%% prioritiy since acks should stay in order (some parts of the queue -%% stack are optimised for that) and to make things easier to reason -%% about. Finally, we prioritise ack over resume since it should -%% always reduce memory use. - -consumer_bias(#q{backing_queue = BQ, backing_queue_state = BQS}) -> - case BQ:msg_rates(BQS) of - {0.0, _} -> 0; - {Ingress, Egress} when Egress / Ingress < ?CONSUMER_BIAS_RATIO -> 1; - {_, _} -> 0 - end. - -prioritise_info(Msg, _Len, #q{q = #amqqueue{exclusive_owner = DownPid}}) -> - case Msg of - {'DOWN', _, process, DownPid, _} -> 8; - update_ram_duration -> 8; - {maybe_expire, _Version} -> 8; - {drop_expired, _Version} -> 8; - emit_stats -> 7; - sync_timeout -> 6; - _ -> 0 - end. - -handle_call({init, Recover}, From, State) -> - init_it(Recover, From, State); - -handle_call(info, _From, State) -> - reply(infos(info_keys(), State), State); - -handle_call({info, Items}, _From, State) -> - try - reply({ok, infos(Items, State)}, State) - catch Error -> reply({error, Error}, State) - end; - -handle_call(consumers, _From, State = #q{consumers = Consumers}) -> - reply(rabbit_queue_consumers:all(Consumers), State); - -handle_call({notify_down, ChPid}, _From, State) -> - %% we want to do this synchronously, so that auto_deleted queues - %% are no longer visible by the time we send a response to the - %% client. The queue is ultimately deleted in terminate/2; if we - %% return stop with a reply, terminate/2 will be called by - %% gen_server2 *before* the reply is sent. - case handle_ch_down(ChPid, State) of - {ok, State1} -> reply(ok, State1); - {stop, State1} -> stop(ok, State1) - end; - -handle_call({basic_get, ChPid, NoAck, LimiterPid}, _From, - State = #q{q = #amqqueue{name = QName}}) -> - AckRequired = not NoAck, - State1 = ensure_expiry_timer(State), - case fetch(AckRequired, State1) of - {empty, State2} -> - reply(empty, State2); - {{Message, IsDelivered, AckTag}, - #q{backing_queue = BQ, backing_queue_state = BQS} = State2} -> - case AckRequired of - true -> ok = rabbit_queue_consumers:record_ack( - ChPid, LimiterPid, AckTag); - false -> ok - end, - Msg = {QName, self(), AckTag, IsDelivered, Message}, - reply({ok, BQ:len(BQS), Msg}, State2) - end; - -handle_call({basic_consume, NoAck, ChPid, LimiterPid, LimiterActive, - PrefetchCount, ConsumerTag, ExclusiveConsume, Args, OkMsg}, - _From, State = #q{consumers = Consumers, - exclusive_consumer = Holder}) -> - case check_exclusive_access(Holder, ExclusiveConsume, State) of - in_use -> reply({error, exclusive_consume_unavailable}, State); - ok -> Consumers1 = rabbit_queue_consumers:add( - ChPid, ConsumerTag, NoAck, - LimiterPid, LimiterActive, - PrefetchCount, Args, is_empty(State), - Consumers), - ExclusiveConsumer = - if ExclusiveConsume -> {ChPid, ConsumerTag}; - true -> Holder - end, - State1 = State#q{consumers = Consumers1, - has_had_consumers = true, - exclusive_consumer = ExclusiveConsumer}, - ok = maybe_send_reply(ChPid, OkMsg), - emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, - not NoAck, qname(State1), - PrefetchCount, Args, none), - notify_decorators(State1), - reply(ok, run_message_queue(State1)) - end; - -handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, _From, - State = #q{consumers = Consumers, - exclusive_consumer = Holder}) -> - ok = maybe_send_reply(ChPid, OkMsg), - case rabbit_queue_consumers:remove(ChPid, ConsumerTag, Consumers) of - not_found -> - reply(ok, State); - Consumers1 -> - Holder1 = case Holder of - {ChPid, ConsumerTag} -> none; - _ -> Holder - end, - State1 = State#q{consumers = Consumers1, - exclusive_consumer = Holder1}, - emit_consumer_deleted(ChPid, ConsumerTag, qname(State1)), - notify_decorators(State1), - case should_auto_delete(State1) of - false -> reply(ok, ensure_expiry_timer(State1)); - true -> stop(ok, State1) - end - end; - -handle_call(stat, _From, State) -> - State1 = #q{backing_queue = BQ, backing_queue_state = BQS} = - ensure_expiry_timer(State), - reply({ok, BQ:len(BQS), rabbit_queue_consumers:count()}, State1); - -handle_call({delete, IfUnused, IfEmpty}, _From, - State = #q{backing_queue_state = BQS, backing_queue = BQ}) -> - IsEmpty = BQ:is_empty(BQS), - IsUnused = is_unused(State), - if - IfEmpty and not(IsEmpty) -> reply({error, not_empty}, State); - IfUnused and not(IsUnused) -> reply({error, in_use}, State); - true -> stop({ok, BQ:len(BQS)}, State) - end; - -handle_call(purge, _From, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - {Count, BQS1} = BQ:purge(BQS), - State1 = State#q{backing_queue_state = BQS1}, - reply({ok, Count}, maybe_send_drained(Count =:= 0, State1)); - -handle_call({requeue, AckTags, ChPid}, From, State) -> - gen_server2:reply(From, ok), - noreply(requeue(AckTags, ChPid, State)); - -handle_call(sync_mirrors, _From, - State = #q{backing_queue = rabbit_mirror_queue_master, - backing_queue_state = BQS}) -> - S = fun(BQSN) -> State#q{backing_queue_state = BQSN} end, - HandleInfo = fun (Status) -> - receive {'$gen_call', From, {info, Items}} -> - Infos = infos(Items, State#q{status = Status}), - gen_server2:reply(From, {ok, Infos}) - after 0 -> - ok - end - end, - EmitStats = fun (Status) -> - rabbit_event:if_enabled( - State, #q.stats_timer, - fun() -> emit_stats(State#q{status = Status}) end) - end, - case rabbit_mirror_queue_master:sync_mirrors(HandleInfo, EmitStats, BQS) of - {ok, BQS1} -> reply(ok, S(BQS1)); - {stop, Reason, BQS1} -> {stop, Reason, S(BQS1)} - end; - -handle_call(sync_mirrors, _From, State) -> - reply({error, not_mirrored}, State); - -%% By definition if we get this message here we do not have to do anything. -handle_call(cancel_sync_mirrors, _From, State) -> - reply({ok, not_syncing}, State). - -handle_cast(init, State) -> - init_it({no_barrier, non_clean_shutdown}, none, State); - -handle_cast({run_backing_queue, Mod, Fun}, - State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - noreply(State#q{backing_queue_state = BQ:invoke(Mod, Fun, BQS)}); - -handle_cast({deliver, Delivery = #delivery{sender = Sender}, Delivered, Flow}, - State = #q{senders = Senders}) -> - Senders1 = case Flow of - flow -> credit_flow:ack(Sender), - pmon:monitor(Sender, Senders); - noflow -> Senders - end, - State1 = State#q{senders = Senders1}, - noreply(deliver_or_enqueue(Delivery, Delivered, State1)); - -handle_cast({ack, AckTags, ChPid}, State) -> - noreply(ack(AckTags, ChPid, State)); - -handle_cast({reject, true, AckTags, ChPid}, State) -> - noreply(requeue(AckTags, ChPid, State)); - -handle_cast({reject, false, AckTags, ChPid}, State) -> - noreply(with_dlx( - State#q.dlx, - fun (X) -> subtract_acks(ChPid, AckTags, State, - fun (State1) -> - dead_letter_rejected_msgs( - AckTags, X, State1) - end) end, - fun () -> ack(AckTags, ChPid, State) end)); - -handle_cast(delete_immediately, State) -> - stop(State); - -handle_cast({resume, ChPid}, State) -> - noreply(possibly_unblock(rabbit_queue_consumers:resume_fun(), - ChPid, State)); - -handle_cast({notify_sent, ChPid, Credit}, State) -> - noreply(possibly_unblock(rabbit_queue_consumers:notify_sent_fun(Credit), - ChPid, State)); - -handle_cast({activate_limit, ChPid}, State) -> - noreply(possibly_unblock(rabbit_queue_consumers:activate_limit_fun(), - ChPid, State)); - -handle_cast({set_ram_duration_target, Duration}, - State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - BQS1 = BQ:set_ram_duration_target(Duration, BQS), - noreply(State#q{backing_queue_state = BQS1}); - -handle_cast({set_maximum_since_use, Age}, State) -> - ok = file_handle_cache:set_maximum_since_use(Age), - noreply(State); - -handle_cast(start_mirroring, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - %% lookup again to get policy for init_with_existing_bq - {ok, Q} = rabbit_amqqueue:lookup(qname(State)), - true = BQ =/= rabbit_mirror_queue_master, %% assertion - BQ1 = rabbit_mirror_queue_master, - BQS1 = BQ1:init_with_existing_bq(Q, BQ, BQS), - noreply(State#q{backing_queue = BQ1, - backing_queue_state = BQS1}); - -handle_cast(stop_mirroring, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - BQ = rabbit_mirror_queue_master, %% assertion - {BQ1, BQS1} = BQ:stop_mirroring(BQS), - noreply(State#q{backing_queue = BQ1, - backing_queue_state = BQS1}); - -handle_cast({credit, ChPid, CTag, Credit, Drain}, - State = #q{consumers = Consumers, - backing_queue = BQ, - backing_queue_state = BQS}) -> - Len = BQ:len(BQS), - rabbit_channel:send_credit_reply(ChPid, Len), - noreply( - case rabbit_queue_consumers:credit(Len == 0, Credit, Drain, ChPid, CTag, - Consumers) of - unchanged -> State; - {unblocked, Consumers1} -> State1 = State#q{consumers = Consumers1}, - run_message_queue(true, State1) - end); - -handle_cast({force_event_refresh, Ref}, - State = #q{consumers = Consumers, - exclusive_consumer = Exclusive}) -> - rabbit_event:notify(queue_created, infos(?CREATION_EVENT_KEYS, State), Ref), - QName = qname(State), - AllConsumers = rabbit_queue_consumers:all(Consumers), - case Exclusive of - none -> [emit_consumer_created( - Ch, CTag, false, AckRequired, QName, Prefetch, - Args, Ref) || - {Ch, CTag, AckRequired, Prefetch, Args} - <- AllConsumers]; - {Ch, CTag} -> [{Ch, CTag, AckRequired, Prefetch, Args}] = AllConsumers, - emit_consumer_created( - Ch, CTag, true, AckRequired, QName, Prefetch, Args, Ref) - end, - noreply(rabbit_event:init_stats_timer(State, #q.stats_timer)); - -handle_cast(notify_decorators, State) -> - notify_decorators(State), - noreply(State); - -handle_cast(policy_changed, State = #q{q = #amqqueue{name = Name}}) -> - %% We depend on the #q.q field being up to date at least WRT - %% policy (but not slave pids) in various places, so when it - %% changes we go and read it from Mnesia again. - %% - %% This also has the side effect of waking us up so we emit a - %% stats event - so event consumers see the changed policy. - {ok, Q} = rabbit_amqqueue:lookup(Name), - noreply(process_args_policy(State#q{q = Q})). - -handle_info({maybe_expire, Vsn}, State = #q{args_policy_version = Vsn}) -> - case is_unused(State) of - true -> stop(State); - false -> noreply(State#q{expiry_timer_ref = undefined}) - end; - -handle_info({maybe_expire, _Vsn}, State) -> - noreply(State); - -handle_info({drop_expired, Vsn}, State = #q{args_policy_version = Vsn}) -> - WasEmpty = is_empty(State), - State1 = drop_expired_msgs(State#q{ttl_timer_ref = undefined}), - noreply(maybe_send_drained(WasEmpty, State1)); - -handle_info({drop_expired, _Vsn}, State) -> - noreply(State); - -handle_info(emit_stats, State) -> - emit_stats(State), - %% Don't call noreply/1, we don't want to set timers - {State1, Timeout} = next_state(rabbit_event:reset_stats_timer( - State, #q.stats_timer)), - {noreply, State1, Timeout}; - -handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, - State = #q{q = #amqqueue{exclusive_owner = DownPid}}) -> - %% Exclusively owned queues must disappear with their owner. In - %% the case of clean shutdown we delete the queue synchronously in - %% the reader - although not required by the spec this seems to - %% match what people expect (see bug 21824). However we need this - %% monitor-and-async- delete in case the connection goes away - %% unexpectedly. - stop(State); - -handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, State) -> - case handle_ch_down(DownPid, State) of - {ok, State1} -> noreply(State1); - {stop, State1} -> stop(State1) - end; - -handle_info(update_ram_duration, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - {RamDuration, BQS1} = BQ:ram_duration(BQS), - DesiredDuration = - rabbit_memory_monitor:report_ram_duration(self(), RamDuration), - BQS2 = BQ:set_ram_duration_target(DesiredDuration, BQS1), - %% Don't call noreply/1, we don't want to set timers - {State1, Timeout} = next_state(State#q{rate_timer_ref = undefined, - backing_queue_state = BQS2}), - {noreply, State1, Timeout}; - -handle_info(sync_timeout, State) -> - noreply(backing_queue_timeout(State#q{sync_timer_ref = undefined})); - -handle_info(timeout, State) -> - noreply(backing_queue_timeout(State)); - -handle_info({'EXIT', _Pid, Reason}, State) -> - {stop, Reason, State}; - -handle_info({bump_credit, Msg}, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - credit_flow:handle_bump_msg(Msg), - noreply(State#q{backing_queue_state = BQ:resume(BQS)}); - -handle_info(Info, State) -> - {stop, {unhandled_info, Info}, State}. - -handle_pre_hibernate(State = #q{backing_queue_state = undefined}) -> - {hibernate, State}; -handle_pre_hibernate(State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - {RamDuration, BQS1} = BQ:ram_duration(BQS), - DesiredDuration = - rabbit_memory_monitor:report_ram_duration(self(), RamDuration), - BQS2 = BQ:set_ram_duration_target(DesiredDuration, BQS1), - BQS3 = BQ:handle_pre_hibernate(BQS2), - rabbit_event:if_enabled( - State, #q.stats_timer, - fun () -> emit_stats(State, [{idle_since, now()}, - {consumer_utilisation, ''}]) end), - State1 = rabbit_event:stop_stats_timer(State#q{backing_queue_state = BQS3}, - #q.stats_timer), - {hibernate, stop_rate_timer(State1)}. - -format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ). diff --git a/src/rabbit_amqqueue_sup.erl b/src/rabbit_amqqueue_sup.erl deleted file mode 100644 index 465c0412..00000000 --- a/src/rabbit_amqqueue_sup.erl +++ /dev/null @@ -1,50 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_amqqueue_sup). - --behaviour(supervisor2). - --export([start_link/2]). - --export([init/1]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/2 :: (rabbit_types:amqqueue(), rabbit_prequeue:start_mode()) -> - {'ok', pid(), pid()}). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Q, StartMode) -> - Marker = spawn_link(fun() -> receive stop -> ok end end), - ChildSpec = {rabbit_amqqueue, - {rabbit_prequeue, start_link, [Q, StartMode, Marker]}, - intrinsic, ?MAX_WAIT, worker, [rabbit_amqqueue_process, - rabbit_mirror_queue_slave]}, - {ok, SupPid} = supervisor2:start_link(?MODULE, []), - {ok, QPid} = supervisor2:start_child(SupPid, ChildSpec), - unlink(Marker), - Marker ! stop, - {ok, SupPid, QPid}. - -init([]) -> {ok, {{one_for_one, 5, 10}, []}}. diff --git a/src/rabbit_amqqueue_sup_sup.erl b/src/rabbit_amqqueue_sup_sup.erl deleted file mode 100644 index 793cb7c9..00000000 --- a/src/rabbit_amqqueue_sup_sup.erl +++ /dev/null @@ -1,52 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_amqqueue_sup_sup). - --behaviour(supervisor2). - --export([start_link/0, start_queue_process/3]). - --export([init/1]). - --include("rabbit.hrl"). - --define(SERVER, ?MODULE). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(start_queue_process/3 :: (node(), rabbit_types:amqqueue(), - 'declare' | 'recovery' | 'slave') -> pid()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> - supervisor2:start_link({local, ?SERVER}, ?MODULE, []). - -start_queue_process(Node, Q, StartMode) -> - {ok, _SupPid, QPid} = supervisor2:start_child( - {?SERVER, Node}, [Q, StartMode]), - QPid. - -init([]) -> - {ok, {{simple_one_for_one, 10, 10}, - [{rabbit_amqqueue_sup, {rabbit_amqqueue_sup, start_link, []}, - temporary, ?MAX_WAIT, supervisor, [rabbit_amqqueue_sup]}]}}. diff --git a/src/rabbit_auth_backend_dummy.erl b/src/rabbit_auth_backend_dummy.erl deleted file mode 100644 index d2f07c1d..00000000 --- a/src/rabbit_auth_backend_dummy.erl +++ /dev/null @@ -1,48 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_auth_backend_dummy). --include("rabbit.hrl"). - --behaviour(rabbit_authn_backend). --behaviour(rabbit_authz_backend). - --export([user/0]). --export([user_login_authentication/2, user_login_authorization/1, - check_vhost_access/3, check_resource_access/3]). - --ifdef(use_specs). - --spec(user/0 :: () -> rabbit_types:user()). - --endif. - -%% A user to be used by the direct client when permission checks are -%% not needed. This user can do anything AMQPish. -user() -> #user{username = <<"none">>, - tags = [], - authz_backends = [{?MODULE, none}]}. - -%% Implementation of rabbit_auth_backend - -user_login_authentication(_, _) -> - {refused, "cannot log in conventionally as dummy user", []}. - -user_login_authorization(_) -> - {refused, "cannot log in conventionally as dummy user", []}. - -check_vhost_access(#auth_user{}, _VHostPath, _Sock) -> true. -check_resource_access(#auth_user{}, #resource{}, _Permission) -> true. diff --git a/src/rabbit_auth_backend_internal.erl b/src/rabbit_auth_backend_internal.erl deleted file mode 100644 index 20a5766d..00000000 --- a/src/rabbit_auth_backend_internal.erl +++ /dev/null @@ -1,349 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_auth_backend_internal). --include("rabbit.hrl"). - --behaviour(rabbit_authn_backend). --behaviour(rabbit_authz_backend). - --export([user_login_authentication/2, user_login_authorization/1, - check_vhost_access/3, check_resource_access/3]). - --export([add_user/2, delete_user/1, lookup_user/1, - change_password/2, clear_password/1, - hash_password/1, change_password_hash/2, - set_tags/2, set_permissions/5, clear_permissions/2]). --export([user_info_keys/0, perms_info_keys/0, - user_perms_info_keys/0, vhost_perms_info_keys/0, - user_vhost_perms_info_keys/0, - list_users/0, list_permissions/0, - list_user_permissions/1, list_vhost_permissions/1, - list_user_vhost_permissions/2]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(regexp() :: binary()). - --spec(add_user/2 :: (rabbit_types:username(), rabbit_types:password()) -> 'ok'). --spec(delete_user/1 :: (rabbit_types:username()) -> 'ok'). --spec(lookup_user/1 :: (rabbit_types:username()) - -> rabbit_types:ok(rabbit_types:internal_user()) - | rabbit_types:error('not_found')). --spec(change_password/2 :: (rabbit_types:username(), rabbit_types:password()) - -> 'ok'). --spec(clear_password/1 :: (rabbit_types:username()) -> 'ok'). --spec(hash_password/1 :: (rabbit_types:password()) - -> rabbit_types:password_hash()). --spec(change_password_hash/2 :: (rabbit_types:username(), - rabbit_types:password_hash()) -> 'ok'). --spec(set_tags/2 :: (rabbit_types:username(), [atom()]) -> 'ok'). --spec(set_permissions/5 ::(rabbit_types:username(), rabbit_types:vhost(), - regexp(), regexp(), regexp()) -> 'ok'). --spec(clear_permissions/2 :: (rabbit_types:username(), rabbit_types:vhost()) - -> 'ok'). --spec(user_info_keys/0 :: () -> rabbit_types:info_keys()). --spec(perms_info_keys/0 :: () -> rabbit_types:info_keys()). --spec(user_perms_info_keys/0 :: () -> rabbit_types:info_keys()). --spec(vhost_perms_info_keys/0 :: () -> rabbit_types:info_keys()). --spec(user_vhost_perms_info_keys/0 :: () -> rabbit_types:info_keys()). --spec(list_users/0 :: () -> [rabbit_types:infos()]). --spec(list_permissions/0 :: () -> [rabbit_types:infos()]). --spec(list_user_permissions/1 :: - (rabbit_types:username()) -> [rabbit_types:infos()]). --spec(list_vhost_permissions/1 :: - (rabbit_types:vhost()) -> [rabbit_types:infos()]). --spec(list_user_vhost_permissions/2 :: - (rabbit_types:username(), rabbit_types:vhost()) - -> [rabbit_types:infos()]). - --endif. - -%%---------------------------------------------------------------------------- -%% Implementation of rabbit_auth_backend - -user_login_authentication(Username, []) -> - internal_check_user_login(Username, fun(_) -> true end); -user_login_authentication(Username, [{password, Cleartext}]) -> - internal_check_user_login( - Username, - fun (#internal_user{password_hash = <<Salt:4/binary, Hash/binary>>}) -> - Hash =:= salted_md5(Salt, Cleartext); - (#internal_user{}) -> - false - end); -user_login_authentication(Username, AuthProps) -> - exit({unknown_auth_props, Username, AuthProps}). - -user_login_authorization(Username) -> - case user_login_authentication(Username, []) of - {ok, #auth_user{impl = Impl}} -> {ok, Impl}; - Else -> Else - end. - -internal_check_user_login(Username, Fun) -> - Refused = {refused, "user '~s' - invalid credentials", [Username]}, - case lookup_user(Username) of - {ok, User = #internal_user{tags = Tags}} -> - case Fun(User) of - true -> {ok, #auth_user{username = Username, - tags = Tags, - impl = none}}; - _ -> Refused - end; - {error, not_found} -> - Refused - end. - -check_vhost_access(#auth_user{username = Username}, VHostPath, _Sock) -> - case mnesia:dirty_read({rabbit_user_permission, - #user_vhost{username = Username, - virtual_host = VHostPath}}) of - [] -> false; - [_R] -> true - end. - -check_resource_access(#auth_user{username = Username}, - #resource{virtual_host = VHostPath, name = Name}, - Permission) -> - case mnesia:dirty_read({rabbit_user_permission, - #user_vhost{username = Username, - virtual_host = VHostPath}}) of - [] -> - false; - [#user_permission{permission = P}] -> - PermRegexp = case element(permission_index(Permission), P) of - %% <<"^$">> breaks Emacs' erlang mode - <<"">> -> <<$^, $$>>; - RE -> RE - end, - case re:run(Name, PermRegexp, [{capture, none}]) of - match -> true; - nomatch -> false - end - end. - -permission_index(configure) -> #permission.configure; -permission_index(write) -> #permission.write; -permission_index(read) -> #permission.read. - -%%---------------------------------------------------------------------------- -%% Manipulation of the user database - -add_user(Username, Password) -> - rabbit_log:info("Creating user '~s'~n", [Username]), - R = rabbit_misc:execute_mnesia_transaction( - fun () -> - case mnesia:wread({rabbit_user, Username}) of - [] -> - ok = mnesia:write( - rabbit_user, - #internal_user{username = Username, - password_hash = - hash_password(Password), - tags = []}, - write); - _ -> - mnesia:abort({user_already_exists, Username}) - end - end), - rabbit_event:notify(user_created, [{name, Username}]), - R. - -delete_user(Username) -> - rabbit_log:info("Deleting user '~s'~n", [Username]), - R = rabbit_misc:execute_mnesia_transaction( - rabbit_misc:with_user( - Username, - fun () -> - ok = mnesia:delete({rabbit_user, Username}), - [ok = mnesia:delete_object( - rabbit_user_permission, R, write) || - R <- mnesia:match_object( - rabbit_user_permission, - #user_permission{user_vhost = #user_vhost{ - username = Username, - virtual_host = '_'}, - permission = '_'}, - write)], - ok - end)), - rabbit_event:notify(user_deleted, [{name, Username}]), - R. - -lookup_user(Username) -> - rabbit_misc:dirty_read({rabbit_user, Username}). - -change_password(Username, Password) -> - rabbit_log:info("Changing password for '~s'~n", [Username]), - R = change_password_hash(Username, hash_password(Password)), - rabbit_event:notify(user_password_changed, [{name, Username}]), - R. - -clear_password(Username) -> - rabbit_log:info("Clearing password for '~s'~n", [Username]), - R = change_password_hash(Username, <<"">>), - rabbit_event:notify(user_password_cleared, [{name, Username}]), - R. - -hash_password(Cleartext) -> - {A1,A2,A3} = now(), - random:seed(A1, A2, A3), - Salt = random:uniform(16#ffffffff), - SaltBin = <<Salt:32>>, - Hash = salted_md5(SaltBin, Cleartext), - <<SaltBin/binary, Hash/binary>>. - -change_password_hash(Username, PasswordHash) -> - update_user(Username, fun(User) -> - User#internal_user{ - password_hash = PasswordHash } - end). - -salted_md5(Salt, Cleartext) -> - Salted = <<Salt/binary, Cleartext/binary>>, - erlang:md5(Salted). - -set_tags(Username, Tags) -> - rabbit_log:info("Setting user tags for user '~s' to ~p~n", - [Username, Tags]), - R = update_user(Username, fun(User) -> - User#internal_user{tags = Tags} - end), - rabbit_event:notify(user_tags_set, [{name, Username}, {tags, Tags}]), - R. - -set_permissions(Username, VHostPath, ConfigurePerm, WritePerm, ReadPerm) -> - rabbit_log:info("Setting permissions for " - "'~s' in '~s' to '~s', '~s', '~s'~n", - [Username, VHostPath, ConfigurePerm, WritePerm, ReadPerm]), - lists:map( - fun (RegexpBin) -> - Regexp = binary_to_list(RegexpBin), - case re:compile(Regexp) of - {ok, _} -> ok; - {error, Reason} -> throw({error, {invalid_regexp, - Regexp, Reason}}) - end - end, [ConfigurePerm, WritePerm, ReadPerm]), - R = rabbit_misc:execute_mnesia_transaction( - rabbit_misc:with_user_and_vhost( - Username, VHostPath, - fun () -> ok = mnesia:write( - rabbit_user_permission, - #user_permission{user_vhost = #user_vhost{ - username = Username, - virtual_host = VHostPath}, - permission = #permission{ - configure = ConfigurePerm, - write = WritePerm, - read = ReadPerm}}, - write) - end)), - rabbit_event:notify(permission_created, [{user, Username}, - {vhost, VHostPath}, - {configure, ConfigurePerm}, - {write, WritePerm}, - {read, ReadPerm}]), - R. - -clear_permissions(Username, VHostPath) -> - R = rabbit_misc:execute_mnesia_transaction( - rabbit_misc:with_user_and_vhost( - Username, VHostPath, - fun () -> - ok = mnesia:delete({rabbit_user_permission, - #user_vhost{username = Username, - virtual_host = VHostPath}}) - end)), - rabbit_event:notify(permission_deleted, [{user, Username}, - {vhost, VHostPath}]), - R. - - -update_user(Username, Fun) -> - rabbit_misc:execute_mnesia_transaction( - rabbit_misc:with_user( - Username, - fun () -> - {ok, User} = lookup_user(Username), - ok = mnesia:write(rabbit_user, Fun(User), write) - end)). - -%%---------------------------------------------------------------------------- -%% Listing - --define(PERMS_INFO_KEYS, [configure, write, read]). --define(USER_INFO_KEYS, [user, tags]). - -user_info_keys() -> ?USER_INFO_KEYS. - -perms_info_keys() -> [user, vhost | ?PERMS_INFO_KEYS]. -vhost_perms_info_keys() -> [user | ?PERMS_INFO_KEYS]. -user_perms_info_keys() -> [vhost | ?PERMS_INFO_KEYS]. -user_vhost_perms_info_keys() -> ?PERMS_INFO_KEYS. - -list_users() -> - [[{user, Username}, {tags, Tags}] || - #internal_user{username = Username, tags = Tags} <- - mnesia:dirty_match_object(rabbit_user, #internal_user{_ = '_'})]. - -list_permissions() -> - list_permissions(perms_info_keys(), match_user_vhost('_', '_')). - -list_permissions(Keys, QueryThunk) -> - [filter_props(Keys, [{user, Username}, - {vhost, VHostPath}, - {configure, ConfigurePerm}, - {write, WritePerm}, - {read, ReadPerm}]) || - #user_permission{user_vhost = #user_vhost{username = Username, - virtual_host = VHostPath}, - permission = #permission{ configure = ConfigurePerm, - write = WritePerm, - read = ReadPerm}} <- - %% TODO: use dirty ops instead - rabbit_misc:execute_mnesia_transaction(QueryThunk)]. - -filter_props(Keys, Props) -> [T || T = {K, _} <- Props, lists:member(K, Keys)]. - -list_user_permissions(Username) -> - list_permissions( - user_perms_info_keys(), - rabbit_misc:with_user(Username, match_user_vhost(Username, '_'))). - -list_vhost_permissions(VHostPath) -> - list_permissions( - vhost_perms_info_keys(), - rabbit_vhost:with(VHostPath, match_user_vhost('_', VHostPath))). - -list_user_vhost_permissions(Username, VHostPath) -> - list_permissions( - user_vhost_perms_info_keys(), - rabbit_misc:with_user_and_vhost( - Username, VHostPath, match_user_vhost(Username, VHostPath))). - -match_user_vhost(Username, VHostPath) -> - fun () -> mnesia:match_object( - rabbit_user_permission, - #user_permission{user_vhost = #user_vhost{ - username = Username, - virtual_host = VHostPath}, - permission = '_'}, - read) - end. diff --git a/src/rabbit_auth_mechanism.erl b/src/rabbit_auth_mechanism.erl deleted file mode 100644 index d11af095..00000000 --- a/src/rabbit_auth_mechanism.erl +++ /dev/null @@ -1,56 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_auth_mechanism). - --ifdef(use_specs). - -%% A description. --callback description() -> [proplists:property()]. - -%% If this mechanism is enabled, should it be offered for a given socket? -%% (primarily so EXTERNAL can be SSL-only) --callback should_offer(rabbit_net:socket()) -> boolean(). - -%% Called before authentication starts. Should create a state -%% object to be passed through all the stages of authentication. --callback init(rabbit_net:socket()) -> any(). - -%% Handle a stage of authentication. Possible responses: -%% {ok, User} -%% Authentication succeeded, and here's the user record. -%% {challenge, Challenge, NextState} -%% Another round is needed. Here's the state I want next time. -%% {protocol_error, Msg, Args} -%% Client got the protocol wrong. Log and die. -%% {refused, Msg, Args} -%% Client failed authentication. Log and die. --callback handle_response(binary(), any()) -> - {'ok', rabbit_types:user()} | - {'challenge', binary(), any()} | - {'protocol_error', string(), [any()]} | - {'refused', string(), [any()]}. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{description, 0}, {should_offer, 1}, {init, 1}, {handle_response, 2}]; -behaviour_info(_Other) -> - undefined. - --endif. diff --git a/src/rabbit_auth_mechanism_amqplain.erl b/src/rabbit_auth_mechanism_amqplain.erl deleted file mode 100644 index e2183a99..00000000 --- a/src/rabbit_auth_mechanism_amqplain.erl +++ /dev/null @@ -1,55 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_auth_mechanism_amqplain). --include("rabbit.hrl"). - --behaviour(rabbit_auth_mechanism). - --export([description/0, should_offer/1, init/1, handle_response/2]). - --rabbit_boot_step({?MODULE, - [{description, "auth mechanism amqplain"}, - {mfa, {rabbit_registry, register, - [auth_mechanism, <<"AMQPLAIN">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - -%% AMQPLAIN, as used by Qpid Python test suite. The 0-8 spec actually -%% defines this as PLAIN, but in 0-9 that definition is gone, instead -%% referring generically to "SASL security mechanism", i.e. the above. - -description() -> - [{description, <<"QPid AMQPLAIN mechanism">>}]. - -should_offer(_Sock) -> - true. - -init(_Sock) -> - []. - -handle_response(Response, _State) -> - LoginTable = rabbit_binary_parser:parse_table(Response), - case {lists:keysearch(<<"LOGIN">>, 1, LoginTable), - lists:keysearch(<<"PASSWORD">>, 1, LoginTable)} of - {{value, {_, longstr, User}}, - {value, {_, longstr, Pass}}} -> - rabbit_access_control:check_user_pass_login(User, Pass); - _ -> - {protocol_error, - "AMQPLAIN auth info ~w is missing LOGIN or PASSWORD field", - [LoginTable]} - end. diff --git a/src/rabbit_auth_mechanism_cr_demo.erl b/src/rabbit_auth_mechanism_cr_demo.erl deleted file mode 100644 index b5751f41..00000000 --- a/src/rabbit_auth_mechanism_cr_demo.erl +++ /dev/null @@ -1,57 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_auth_mechanism_cr_demo). --include("rabbit.hrl"). - --behaviour(rabbit_auth_mechanism). - --export([description/0, should_offer/1, init/1, handle_response/2]). - --rabbit_boot_step({?MODULE, - [{description, "auth mechanism cr-demo"}, - {mfa, {rabbit_registry, register, - [auth_mechanism, <<"RABBIT-CR-DEMO">>, - ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - --record(state, {username = undefined}). - -%% Provides equivalent security to PLAIN but demos use of Connection.Secure(Ok) -%% START-OK: Username -%% SECURE: "Please tell me your password" -%% SECURE-OK: "My password is ~s", [Password] - -description() -> - [{description, <<"RabbitMQ Demo challenge-response authentication " - "mechanism">>}]. - -should_offer(_Sock) -> - true. - -init(_Sock) -> - #state{}. - -handle_response(Response, State = #state{username = undefined}) -> - {challenge, <<"Please tell me your password">>, - State#state{username = Response}}; - -handle_response(<<"My password is ", Password/binary>>, - #state{username = Username}) -> - rabbit_access_control:check_user_pass_login(Username, Password); -handle_response(Response, _State) -> - {protocol_error, "Invalid response '~s'", [Response]}. diff --git a/src/rabbit_auth_mechanism_plain.erl b/src/rabbit_auth_mechanism_plain.erl deleted file mode 100644 index c008f6a7..00000000 --- a/src/rabbit_auth_mechanism_plain.erl +++ /dev/null @@ -1,72 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_auth_mechanism_plain). --include("rabbit.hrl"). - --behaviour(rabbit_auth_mechanism). - --export([description/0, should_offer/1, init/1, handle_response/2]). - --rabbit_boot_step({?MODULE, - [{description, "auth mechanism plain"}, - {mfa, {rabbit_registry, register, - [auth_mechanism, <<"PLAIN">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - -%% SASL PLAIN, as used by the Qpid Java client and our clients. Also, -%% apparently, by OpenAMQ. - -%% TODO: reimplement this using the binary module? - that makes use of -%% BIFs to do binary matching and will thus be much faster. - -description() -> - [{description, <<"SASL PLAIN authentication mechanism">>}]. - -should_offer(_Sock) -> - true. - -init(_Sock) -> - []. - -handle_response(Response, _State) -> - case extract_user_pass(Response) of - {ok, User, Pass} -> - rabbit_access_control:check_user_pass_login(User, Pass); - error -> - {protocol_error, "response ~p invalid", [Response]} - end. - -extract_user_pass(Response) -> - case extract_elem(Response) of - {ok, User, Response1} -> case extract_elem(Response1) of - {ok, Pass, <<>>} -> {ok, User, Pass}; - _ -> error - end; - error -> error - end. - -extract_elem(<<0:8, Rest/binary>>) -> - Count = next_null_pos(Rest, 0), - <<Elem:Count/binary, Rest1/binary>> = Rest, - {ok, Elem, Rest1}; -extract_elem(_) -> - error. - -next_null_pos(<<>>, Count) -> Count; -next_null_pos(<<0:8, _Rest/binary>>, Count) -> Count; -next_null_pos(<<_:8, Rest/binary>>, Count) -> next_null_pos(Rest, Count + 1). diff --git a/src/rabbit_authn_backend.erl b/src/rabbit_authn_backend.erl deleted file mode 100644 index cfc3f5db..00000000 --- a/src/rabbit_authn_backend.erl +++ /dev/null @@ -1,49 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_authn_backend). - --include("rabbit.hrl"). - --ifdef(use_specs). - -%% Check a user can log in, given a username and a proplist of -%% authentication information (e.g. [{password, Password}]). If your -%% backend is not to be used for authentication, this should always -%% refuse access. -%% -%% Possible responses: -%% {ok, User} -%% Authentication succeeded, and here's the user record. -%% {error, Error} -%% Something went wrong. Log and die. -%% {refused, Msg, Args} -%% Client failed authentication. Log and die. --callback user_login_authentication(rabbit_types:username(), [term()]) -> - {'ok', rabbit_types:auth_user()} | - {'refused', string(), [any()]} | - {'error', any()}. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{user_login_authentication, 2}]; -behaviour_info(_Other) -> - undefined. - --endif. diff --git a/src/rabbit_authz_backend.erl b/src/rabbit_authz_backend.erl deleted file mode 100644 index ff5f014e..00000000 --- a/src/rabbit_authz_backend.erl +++ /dev/null @@ -1,74 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_authz_backend). - --include("rabbit.hrl"). - --ifdef(use_specs). - -%% Check a user can log in, when this backend is being used for -%% authorisation only. Authentication has already taken place -%% successfully, but we need to check that the user exists in this -%% backend, and initialise any impl field we will want to have passed -%% back in future calls to check_vhost_access/3 and -%% check_resource_access/3. -%% -%% Possible responses: -%% {ok, Impl} -%% User authorisation succeeded, and here's the impl field. -%% {error, Error} -%% Something went wrong. Log and die. -%% {refused, Msg, Args} -%% User authorisation failed. Log and die. --callback user_login_authorization(rabbit_types:username()) -> - {'ok', any()} | - {'refused', string(), [any()]} | - {'error', any()}. - -%% Given #auth_user and vhost, can a user log in to a vhost? -%% Possible responses: -%% true -%% false -%% {error, Error} -%% Something went wrong. Log and die. --callback check_vhost_access(rabbit_types:auth_user(), - rabbit_types:vhost(), rabbit_net:socket()) -> - boolean() | {'error', any()}. - -%% Given #auth_user, resource and permission, can a user access a resource? -%% -%% Possible responses: -%% true -%% false -%% {error, Error} -%% Something went wrong. Log and die. --callback check_resource_access(rabbit_types:auth_user(), - rabbit_types:r(atom()), - rabbit_access_control:permission_atom()) -> - boolean() | {'error', any()}. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{user_login_authorization, 1}, - {check_vhost_access, 3}, {check_resource_access, 3}]; -behaviour_info(_Other) -> - undefined. - --endif. diff --git a/src/rabbit_autoheal.erl b/src/rabbit_autoheal.erl deleted file mode 100644 index 90458741..00000000 --- a/src/rabbit_autoheal.erl +++ /dev/null @@ -1,248 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_autoheal). - --export([init/0, maybe_start/1, rabbit_down/2, node_down/2, handle_msg/3]). - -%% The named process we are running in. --define(SERVER, rabbit_node_monitor). - -%%---------------------------------------------------------------------------- - -%% In order to autoheal we want to: -%% -%% * Find the winning partition -%% * Stop all nodes in other partitions -%% * Wait for them all to be stopped -%% * Start them again -%% -%% To keep things simple, we assume all nodes are up. We don't start -%% unless all nodes are up, and if a node goes down we abandon the -%% whole process. To further keep things simple we also defer the -%% decision as to the winning node to the "leader" - arbitrarily -%% selected as the first node in the cluster. -%% -%% To coordinate the restarting nodes we pick a special node from the -%% winning partition - the "winner". Restarting nodes then stop, and -%% wait for it to tell them it is safe to start again. The winner -%% determines that a node has stopped just by seeing if its rabbit app -%% stops - if a node stops for any other reason it just gets a message -%% it will ignore, and otherwise we carry on. -%% -%% The winner and the leader are not necessarily the same node. -%% -%% Possible states: -%% -%% not_healing -%% - the default -%% -%% {winner_waiting, OutstandingStops, Notify} -%% - we are the winner and are waiting for all losing nodes to stop -%% before telling them they can restart -%% -%% {leader_waiting, OutstandingStops} -%% - we are the leader, and have already assigned the winner and losers. -%% We are neither but need to ignore further requests to autoheal. -%% -%% restarting -%% - we are restarting. Of course the node monitor immediately dies -%% then so this state does not last long. We therefore send the -%% autoheal_safe_to_start message to the rabbit_outside_app_process -%% instead. - -%%---------------------------------------------------------------------------- - -init() -> not_healing. - -maybe_start(not_healing) -> - case enabled() of - true -> [Leader | _] = lists:usort(rabbit_mnesia:cluster_nodes(all)), - send(Leader, {request_start, node()}), - rabbit_log:info("Autoheal request sent to ~p~n", [Leader]), - not_healing; - false -> not_healing - end; -maybe_start(State) -> - State. - -enabled() -> - {ok, autoheal} =:= application:get_env(rabbit, cluster_partition_handling). - - -%% This is the winner receiving its last notification that a node has -%% stopped - all nodes can now start again -rabbit_down(Node, {winner_waiting, [Node], Notify}) -> - rabbit_log:info("Autoheal: final node has stopped, starting...~n",[]), - winner_finish(Notify); - -rabbit_down(Node, {winner_waiting, WaitFor, Notify}) -> - {winner_waiting, WaitFor -- [Node], Notify}; - -rabbit_down(Node, {leader_waiting, [Node]}) -> - not_healing; - -rabbit_down(Node, {leader_waiting, WaitFor}) -> - {leader_waiting, WaitFor -- [Node]}; - -rabbit_down(_Node, State) -> - %% ignore, we already cancelled the autoheal process - State. - -node_down(_Node, not_healing) -> - not_healing; - -node_down(Node, {winner_waiting, _, Notify}) -> - abort([Node], Notify); - -node_down(Node, _State) -> - rabbit_log:info("Autoheal: aborting - ~p went down~n", [Node]), - not_healing. - -%% By receiving this message we become the leader -%% TODO should we try to debounce this? -handle_msg({request_start, Node}, - not_healing, Partitions) -> - rabbit_log:info("Autoheal request received from ~p~n", [Node]), - case check_other_nodes(Partitions) of - {error, E} -> - rabbit_log:info("Autoheal request denied: ~s~n", [fmt_error(E)]), - not_healing; - {ok, AllPartitions} -> - {Winner, Losers} = make_decision(AllPartitions), - rabbit_log:info("Autoheal decision~n" - " * Partitions: ~p~n" - " * Winner: ~p~n" - " * Losers: ~p~n", - [AllPartitions, Winner, Losers]), - Continue = fun(Msg) -> - handle_msg(Msg, not_healing, Partitions) - end, - case node() =:= Winner of - true -> Continue({become_winner, Losers}); - false -> send(Winner, {become_winner, Losers}), %% [0] - case lists:member(node(), Losers) of - true -> Continue({winner_is, Winner}); - false -> {leader_waiting, Losers} - end - end - end; -%% [0] If we are a loser we will never receive this message - but it -%% won't stick in the mailbox as we are restarting anyway - -handle_msg({request_start, Node}, - State, _Partitions) -> - rabbit_log:info("Autoheal request received from ~p when healing; " - "ignoring~n", [Node]), - State; - -handle_msg({become_winner, Losers}, - not_healing, _Partitions) -> - rabbit_log:info("Autoheal: I am the winner, waiting for ~p to stop~n", - [Losers]), - %% The leader said everything was ready - do we agree? If not then - %% give up. - Down = Losers -- rabbit_node_monitor:alive_rabbit_nodes(Losers), - case Down of - [] -> [send(L, {winner_is, node()}) || L <- Losers], - {winner_waiting, Losers, Losers}; - _ -> abort(Down, Losers) - end; - -handle_msg({winner_is, Winner}, - not_healing, _Partitions) -> - rabbit_log:warning( - "Autoheal: we were selected to restart; winner is ~p~n", [Winner]), - rabbit_node_monitor:run_outside_applications( - fun () -> - MRef = erlang:monitor(process, {?SERVER, Winner}), - rabbit:stop(), - receive - {'DOWN', MRef, process, {?SERVER, Winner}, _Reason} -> ok; - autoheal_safe_to_start -> ok - end, - erlang:demonitor(MRef, [flush]), - rabbit:start() - end), - restarting; - -handle_msg(_, restarting, _Partitions) -> - %% ignore, we can contribute no further - restarting. - -%%---------------------------------------------------------------------------- - -send(Node, Msg) -> {?SERVER, Node} ! {autoheal_msg, Msg}. - -abort(Down, Notify) -> - rabbit_log:info("Autoheal: aborting - ~p down~n", [Down]), - %% Make sure any nodes waiting for us start - it won't necessarily - %% heal the partition but at least they won't get stuck. - winner_finish(Notify). - -winner_finish(Notify) -> - [{rabbit_outside_app_process, N} ! autoheal_safe_to_start || N <- Notify], - not_healing. - -make_decision(AllPartitions) -> - Sorted = lists:sort([{partition_value(P), P} || P <- AllPartitions]), - [[Winner | _] | Rest] = lists:reverse([P || {_, P} <- Sorted]), - {Winner, lists:append(Rest)}. - -partition_value(Partition) -> - Connections = [Res || Node <- Partition, - Res <- [rpc:call(Node, rabbit_networking, - connections_local, [])], - is_list(Res)], - {length(lists:append(Connections)), length(Partition)}. - -%% We have our local understanding of what partitions exist; but we -%% only know which nodes we have been partitioned from, not which -%% nodes are partitioned from each other. -check_other_nodes(LocalPartitions) -> - Nodes = rabbit_mnesia:cluster_nodes(all), - {Results, Bad} = rabbit_node_monitor:status(Nodes -- [node()]), - RemotePartitions = [{Node, proplists:get_value(partitions, Res)} - || {Node, Res} <- Results], - RemoteDown = [{Node, Down} - || {Node, Res} <- Results, - Down <- [Nodes -- proplists:get_value(nodes, Res)], - Down =/= []], - case {Bad, RemoteDown} of - {[], []} -> Partitions = [{node(), LocalPartitions} | RemotePartitions], - {ok, all_partitions(Partitions, [Nodes])}; - {[], _} -> {error, {remote_down, RemoteDown}}; - {_, _} -> {error, {nodes_down, Bad}} - end. - -all_partitions([], Partitions) -> - Partitions; -all_partitions([{Node, CantSee} | Rest], Partitions) -> - {[Containing], Others} = - lists:partition(fun (Part) -> lists:member(Node, Part) end, Partitions), - A = Containing -- CantSee, - B = Containing -- A, - Partitions1 = case {A, B} of - {[], _} -> Partitions; - {_, []} -> Partitions; - _ -> [A, B | Others] - end, - all_partitions(Rest, Partitions1). - -fmt_error({remote_down, RemoteDown}) -> - rabbit_misc:format("Remote nodes disconnected:~n ~p", [RemoteDown]); -fmt_error({nodes_down, NodesDown}) -> - rabbit_misc:format("Local nodes down: ~p", [NodesDown]). diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl deleted file mode 100644 index 4ce133c3..00000000 --- a/src/rabbit_backing_queue.erl +++ /dev/null @@ -1,265 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_backing_queue). - --export([info_keys/0]). - --define(INFO_KEYS, [messages_ram, messages_ready_ram, - messages_unacknowledged_ram, messages_persistent, - message_bytes, message_bytes_ready, - message_bytes_unacknowledged, message_bytes_ram, - message_bytes_persistent, backing_queue_status]). - --ifdef(use_specs). - -%% We can't specify a per-queue ack/state with callback signatures --type(ack() :: any()). --type(state() :: any()). - --type(msg_ids() :: [rabbit_types:msg_id()]). --type(fetch_result(Ack) :: - ('empty' | {rabbit_types:basic_message(), boolean(), Ack})). --type(drop_result(Ack) :: - ('empty' | {rabbit_types:msg_id(), Ack})). --type(recovery_terms() :: [term()] | 'non_clean_shutdown'). --type(recovery_info() :: 'new' | recovery_terms()). --type(purged_msg_count() :: non_neg_integer()). --type(async_callback() :: - fun ((atom(), fun ((atom(), state()) -> state())) -> 'ok')). --type(duration() :: ('undefined' | 'infinity' | number())). - --type(msg_fun(A) :: fun ((rabbit_types:basic_message(), ack(), A) -> A)). --type(msg_pred() :: fun ((rabbit_types:message_properties()) -> boolean())). - --spec(info_keys/0 :: () -> rabbit_types:info_keys()). - -%% Called on startup with a list of durable queue names. The queues -%% aren't being started at this point, but this call allows the -%% backing queue to perform any checking necessary for the consistency -%% of those queues, or initialise any other shared resources. -%% -%% The list of queue recovery terms returned as {ok, Terms} must be given -%% in the same order as the list of queue names supplied. --callback start([rabbit_amqqueue:name()]) -> rabbit_types:ok(recovery_terms()). - -%% Called to tear down any state/resources. NB: Implementations should -%% not depend on this function being called on shutdown and instead -%% should hook into the rabbit supervision hierarchy. --callback stop() -> 'ok'. - -%% Initialise the backing queue and its state. -%% -%% Takes -%% 1. the amqqueue record -%% 2. a term indicating whether the queue is an existing queue that -%% should be recovered or not. When 'new' is given, no recovery is -%% taking place, otherwise a list of recovery terms is given, or -%% the atom 'non_clean_shutdown' if no recovery terms are available. -%% 3. an asynchronous callback which accepts a function of type -%% backing-queue-state to backing-queue-state. This callback -%% function can be safely invoked from any process, which makes it -%% useful for passing messages back into the backing queue, -%% especially as the backing queue does not have control of its own -%% mailbox. --callback init(rabbit_types:amqqueue(), recovery_info(), - async_callback()) -> state(). - -%% Called on queue shutdown when queue isn't being deleted. --callback terminate(any(), state()) -> state(). - -%% Called when the queue is terminating and needs to delete all its -%% content. --callback delete_and_terminate(any(), state()) -> state(). - -%% Called to clean up after a crashed queue. In this case we don't -%% have a process and thus a state(), we are just removing on-disk data. --callback delete_crashed(rabbit_types:amqqueue()) -> 'ok'. - -%% Remove all 'fetchable' messages from the queue, i.e. all messages -%% except those that have been fetched already and are pending acks. --callback purge(state()) -> {purged_msg_count(), state()}. - -%% Remove all messages in the queue which have been fetched and are -%% pending acks. --callback purge_acks(state()) -> state(). - -%% Publish a message. --callback publish(rabbit_types:basic_message(), - rabbit_types:message_properties(), boolean(), pid(), - state()) -> state(). - -%% Called for messages which have already been passed straight -%% out to a client. The queue will be empty for these calls -%% (i.e. saves the round trip through the backing queue). --callback publish_delivered(rabbit_types:basic_message(), - rabbit_types:message_properties(), pid(), state()) - -> {ack(), state()}. - -%% Called to inform the BQ about messages which have reached the -%% queue, but are not going to be further passed to BQ. --callback discard(rabbit_types:msg_id(), pid(), state()) -> state(). - -%% Return ids of messages which have been confirmed since the last -%% invocation of this function (or initialisation). -%% -%% Message ids should only appear in the result of drain_confirmed -%% under the following circumstances: -%% -%% 1. The message appears in a call to publish_delivered/4 and the -%% first argument (ack_required) is false; or -%% 2. The message is fetched from the queue with fetch/2 and the first -%% argument (ack_required) is false; or -%% 3. The message is acked (ack/2 is called for the message); or -%% 4. The message is fully fsync'd to disk in such a way that the -%% recovery of the message is guaranteed in the event of a crash of -%% this rabbit node (excluding hardware failure). -%% -%% In addition to the above conditions, a message id may only appear -%% in the result of drain_confirmed if -%% #message_properties.needs_confirming = true when the msg was -%% published (through whichever means) to the backing queue. -%% -%% It is legal for the same message id to appear in the results of -%% multiple calls to drain_confirmed, which means that the backing -%% queue is not required to keep track of which messages it has -%% already confirmed. The confirm will be issued to the publisher the -%% first time the message id appears in the result of -%% drain_confirmed. All subsequent appearances of that message id will -%% be ignored. --callback drain_confirmed(state()) -> {msg_ids(), state()}. - -%% Drop messages from the head of the queue while the supplied -%% predicate on message properties returns true. Returns the first -%% message properties for which the predictate returned false, or -%% 'undefined' if the whole backing queue was traversed w/o the -%% predicate ever returning false. --callback dropwhile(msg_pred(), state()) - -> {rabbit_types:message_properties() | undefined, state()}. - -%% Like dropwhile, except messages are fetched in "require -%% acknowledgement" mode and are passed, together with their ack tag, -%% to the supplied function. The function is also fed an -%% accumulator. The result of fetchwhile is as for dropwhile plus the -%% accumulator. --callback fetchwhile(msg_pred(), msg_fun(A), A, state()) - -> {rabbit_types:message_properties() | undefined, - A, state()}. - -%% Produce the next message. --callback fetch(true, state()) -> {fetch_result(ack()), state()}; - (false, state()) -> {fetch_result(undefined), state()}. - -%% Remove the next message. --callback drop(true, state()) -> {drop_result(ack()), state()}; - (false, state()) -> {drop_result(undefined), state()}. - -%% Acktags supplied are for messages which can now be forgotten -%% about. Must return 1 msg_id per Ack, in the same order as Acks. --callback ack([ack()], state()) -> {msg_ids(), state()}. - -%% Reinsert messages into the queue which have already been delivered -%% and were pending acknowledgement. --callback requeue([ack()], state()) -> {msg_ids(), state()}. - -%% Fold over messages by ack tag. The supplied function is called with -%% each message, its ack tag, and an accumulator. --callback ackfold(msg_fun(A), A, state(), [ack()]) -> {A, state()}. - -%% Fold over all the messages in a queue and return the accumulated -%% results, leaving the queue undisturbed. --callback fold(fun((rabbit_types:basic_message(), - rabbit_types:message_properties(), - boolean(), A) -> {('stop' | 'cont'), A}), - A, state()) -> {A, state()}. - -%% How long is my queue? --callback len(state()) -> non_neg_integer(). - -%% Is my queue empty? --callback is_empty(state()) -> boolean(). - -%% What's the queue depth, where depth = length + number of pending acks --callback depth(state()) -> non_neg_integer(). - -%% For the next three functions, the assumption is that you're -%% monitoring something like the ingress and egress rates of the -%% queue. The RAM duration is thus the length of time represented by -%% the messages held in RAM given the current rates. If you want to -%% ignore all of this stuff, then do so, and return 0 in -%% ram_duration/1. - -%% The target is to have no more messages in RAM than indicated by the -%% duration and the current queue rates. --callback set_ram_duration_target(duration(), state()) -> state(). - -%% Optionally recalculate the duration internally (likely to be just -%% update your internal rates), and report how many seconds the -%% messages in RAM represent given the current rates of the queue. --callback ram_duration(state()) -> {duration(), state()}. - -%% Should 'timeout' be called as soon as the queue process can manage -%% (either on an empty mailbox, or when a timer fires)? --callback needs_timeout(state()) -> 'false' | 'timed' | 'idle'. - -%% Called (eventually) after needs_timeout returns 'idle' or 'timed'. -%% Note this may be called more than once for each 'idle' or 'timed' -%% returned from needs_timeout --callback timeout(state()) -> state(). - -%% Called immediately before the queue hibernates. --callback handle_pre_hibernate(state()) -> state(). - -%% Called when more credit has become available for credit_flow. --callback resume(state()) -> state(). - -%% Used to help prioritisation in rabbit_amqqueue_process. The rate of -%% inbound messages and outbound messages at the moment. --callback msg_rates(state()) -> {float(), float()}. - --callback info(atom(), state()) -> any(). - -%% Passed a function to be invoked with the relevant backing queue's -%% state. Useful for when the backing queue or other components need -%% to pass functions into the backing queue. --callback invoke(atom(), fun ((atom(), A) -> A), state()) -> state(). - -%% Called prior to a publish or publish_delivered call. Allows the BQ -%% to signal that it's already seen this message, (e.g. it was published -%% or discarded previously) and thus the message should be dropped. --callback is_duplicate(rabbit_types:basic_message(), state()) - -> {boolean(), state()}. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{start, 1}, {stop, 0}, {init, 3}, {terminate, 2}, - {delete_and_terminate, 2}, {purge, 1}, {purge_acks, 1}, {publish, 5}, - {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, - {dropwhile, 2}, {fetchwhile, 4}, - {fetch, 2}, {ack, 2}, {requeue, 2}, {ackfold, 4}, {fold, 3}, {len, 1}, - {is_empty, 1}, {depth, 1}, {set_ram_duration_target, 2}, - {ram_duration, 1}, {needs_timeout, 1}, {timeout, 1}, - {handle_pre_hibernate, 1}, {resume, 1}, {msg_rates, 1}, - {info, 2}, {invoke, 3}, {is_duplicate, 2}] ; -behaviour_info(_Other) -> - undefined. - --endif. - -info_keys() -> ?INFO_KEYS. diff --git a/src/rabbit_basic.erl b/src/rabbit_basic.erl deleted file mode 100644 index 67109e7d..00000000 --- a/src/rabbit_basic.erl +++ /dev/null @@ -1,302 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_basic). --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - --export([publish/4, publish/5, publish/1, - message/3, message/4, properties/1, prepend_table_header/3, - extract_headers/1, map_headers/2, delivery/4, header_routes/1, - parse_expiration/1]). --export([build_content/2, from_content/1, msg_size/1, maybe_gc_large_msg/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(properties_input() :: - (rabbit_framing:amqp_property_record() | [{atom(), any()}])). --type(publish_result() :: - ({ok, [pid()]} | rabbit_types:error('not_found'))). --type(headers() :: rabbit_framing:amqp_table() | 'undefined'). - --type(exchange_input() :: (rabbit_types:exchange() | rabbit_exchange:name())). --type(body_input() :: (binary() | [binary()])). - --spec(publish/4 :: - (exchange_input(), rabbit_router:routing_key(), properties_input(), - body_input()) -> publish_result()). --spec(publish/5 :: - (exchange_input(), rabbit_router:routing_key(), boolean(), - properties_input(), body_input()) -> publish_result()). --spec(publish/1 :: - (rabbit_types:delivery()) -> publish_result()). --spec(delivery/4 :: - (boolean(), boolean(), rabbit_types:message(), undefined | integer()) -> - rabbit_types:delivery()). --spec(message/4 :: - (rabbit_exchange:name(), rabbit_router:routing_key(), - properties_input(), binary()) -> rabbit_types:message()). --spec(message/3 :: - (rabbit_exchange:name(), rabbit_router:routing_key(), - rabbit_types:decoded_content()) -> - rabbit_types:ok_or_error2(rabbit_types:message(), any())). --spec(properties/1 :: - (properties_input()) -> rabbit_framing:amqp_property_record()). - --spec(prepend_table_header/3 :: - (binary(), rabbit_framing:amqp_table(), headers()) -> headers()). - --spec(extract_headers/1 :: (rabbit_types:content()) -> headers()). - --spec(map_headers/2 :: (fun((headers()) -> headers()), rabbit_types:content()) - -> rabbit_types:content()). - --spec(header_routes/1 :: - (undefined | rabbit_framing:amqp_table()) -> [string()]). --spec(build_content/2 :: (rabbit_framing:amqp_property_record(), - binary() | [binary()]) -> rabbit_types:content()). --spec(from_content/1 :: (rabbit_types:content()) -> - {rabbit_framing:amqp_property_record(), binary()}). --spec(parse_expiration/1 :: - (rabbit_framing:amqp_property_record()) - -> rabbit_types:ok_or_error2('undefined' | non_neg_integer(), any())). - --spec(msg_size/1 :: (rabbit_types:content() | rabbit_types:message()) -> - non_neg_integer()). - --spec(maybe_gc_large_msg/1 :: - (rabbit_types:content() | rabbit_types:message()) -> non_neg_integer()). - --endif. - -%%---------------------------------------------------------------------------- - -%% Convenience function, for avoiding round-trips in calls across the -%% erlang distributed network. -publish(Exchange, RoutingKeyBin, Properties, Body) -> - publish(Exchange, RoutingKeyBin, false, Properties, Body). - -%% Convenience function, for avoiding round-trips in calls across the -%% erlang distributed network. -publish(X = #exchange{name = XName}, RKey, Mandatory, Props, Body) -> - Message = message(XName, RKey, properties(Props), Body), - publish(X, delivery(Mandatory, false, Message, undefined)); -publish(XName, RKey, Mandatory, Props, Body) -> - Message = message(XName, RKey, properties(Props), Body), - publish(delivery(Mandatory, false, Message, undefined)). - -publish(Delivery = #delivery{ - message = #basic_message{exchange_name = XName}}) -> - case rabbit_exchange:lookup(XName) of - {ok, X} -> publish(X, Delivery); - Err -> Err - end. - -publish(X, Delivery) -> - Qs = rabbit_amqqueue:lookup(rabbit_exchange:route(X, Delivery)), - DeliveredQPids = rabbit_amqqueue:deliver(Qs, Delivery), - {ok, DeliveredQPids}. - -delivery(Mandatory, Confirm, Message, MsgSeqNo) -> - #delivery{mandatory = Mandatory, confirm = Confirm, sender = self(), - message = Message, msg_seq_no = MsgSeqNo}. - -build_content(Properties, BodyBin) when is_binary(BodyBin) -> - build_content(Properties, [BodyBin]); - -build_content(Properties, PFR) -> - %% basic.publish hasn't changed so we can just hard-code amqp_0_9_1 - {ClassId, _MethodId} = - rabbit_framing_amqp_0_9_1:method_id('basic.publish'), - #content{class_id = ClassId, - properties = Properties, - properties_bin = none, - protocol = none, - payload_fragments_rev = PFR}. - -from_content(Content) -> - #content{class_id = ClassId, - properties = Props, - payload_fragments_rev = FragmentsRev} = - rabbit_binary_parser:ensure_content_decoded(Content), - %% basic.publish hasn't changed so we can just hard-code amqp_0_9_1 - {ClassId, _MethodId} = - rabbit_framing_amqp_0_9_1:method_id('basic.publish'), - {Props, list_to_binary(lists:reverse(FragmentsRev))}. - -%% This breaks the spec rule forbidding message modification -strip_header(#content{properties = #'P_basic'{headers = undefined}} - = DecodedContent, _Key) -> - DecodedContent; -strip_header(#content{properties = Props = #'P_basic'{headers = Headers}} - = DecodedContent, Key) -> - case lists:keysearch(Key, 1, Headers) of - false -> DecodedContent; - {value, Found} -> Headers0 = lists:delete(Found, Headers), - rabbit_binary_generator:clear_encoded_content( - DecodedContent#content{ - properties = Props#'P_basic'{ - headers = Headers0}}) - end. - -message(XName, RoutingKey, #content{properties = Props} = DecodedContent) -> - try - {ok, #basic_message{ - exchange_name = XName, - content = strip_header(DecodedContent, ?DELETED_HEADER), - id = rabbit_guid:gen(), - is_persistent = is_message_persistent(DecodedContent), - routing_keys = [RoutingKey | - header_routes(Props#'P_basic'.headers)]}} - catch - {error, _Reason} = Error -> Error - end. - -message(XName, RoutingKey, RawProperties, Body) -> - Properties = properties(RawProperties), - Content = build_content(Properties, Body), - {ok, Msg} = message(XName, RoutingKey, Content), - Msg. - -properties(P = #'P_basic'{}) -> - P; -properties(P) when is_list(P) -> - %% Yes, this is O(length(P) * record_info(size, 'P_basic') / 2), - %% i.e. slow. Use the definition of 'P_basic' directly if - %% possible! - lists:foldl(fun ({Key, Value}, Acc) -> - case indexof(record_info(fields, 'P_basic'), Key) of - 0 -> throw({unknown_basic_property, Key}); - N -> setelement(N + 1, Acc, Value) - end - end, #'P_basic'{}, P). - -prepend_table_header(Name, Info, undefined) -> - prepend_table_header(Name, Info, []); -prepend_table_header(Name, Info, Headers) -> - case rabbit_misc:table_lookup(Headers, Name) of - {array, Existing} -> - prepend_table(Name, Info, Existing, Headers); - undefined -> - prepend_table(Name, Info, [], Headers); - Other -> - Headers2 = prepend_table(Name, Info, [], Headers), - set_invalid_header(Name, Other, Headers2) - end. - -prepend_table(Name, Info, Prior, Headers) -> - rabbit_misc:set_table_value(Headers, Name, array, [{table, Info} | Prior]). - -set_invalid_header(Name, {_, _}=Value, Headers) when is_list(Headers) -> - case rabbit_misc:table_lookup(Headers, ?INVALID_HEADERS_KEY) of - undefined -> - set_invalid([{Name, array, [Value]}], Headers); - {table, ExistingHdr} -> - update_invalid(Name, Value, ExistingHdr, Headers); - Other -> - %% somehow the x-invalid-headers header is corrupt - Invalid = [{?INVALID_HEADERS_KEY, array, [Other]}], - set_invalid_header(Name, Value, set_invalid(Invalid, Headers)) - end. - -set_invalid(NewHdr, Headers) -> - rabbit_misc:set_table_value(Headers, ?INVALID_HEADERS_KEY, table, NewHdr). - -update_invalid(Name, Value, ExistingHdr, Header) -> - Values = case rabbit_misc:table_lookup(ExistingHdr, Name) of - undefined -> [Value]; - {array, Prior} -> [Value | Prior] - end, - NewHdr = rabbit_misc:set_table_value(ExistingHdr, Name, array, Values), - set_invalid(NewHdr, Header). - -extract_headers(Content) -> - #content{properties = #'P_basic'{headers = Headers}} = - rabbit_binary_parser:ensure_content_decoded(Content), - Headers. - -map_headers(F, Content) -> - Content1 = rabbit_binary_parser:ensure_content_decoded(Content), - #content{properties = #'P_basic'{headers = Headers} = Props} = Content1, - Headers1 = F(Headers), - rabbit_binary_generator:clear_encoded_content( - Content1#content{properties = Props#'P_basic'{headers = Headers1}}). - -indexof(L, Element) -> indexof(L, Element, 1). - -indexof([], _Element, _N) -> 0; -indexof([Element | _Rest], Element, N) -> N; -indexof([_ | Rest], Element, N) -> indexof(Rest, Element, N + 1). - -is_message_persistent(#content{properties = #'P_basic'{ - delivery_mode = Mode}}) -> - case Mode of - 1 -> false; - 2 -> true; - undefined -> false; - Other -> throw({error, {delivery_mode_unknown, Other}}) - end. - -%% Extract CC routes from headers -header_routes(undefined) -> - []; -header_routes(HeadersTable) -> - lists:append( - [case rabbit_misc:table_lookup(HeadersTable, HeaderKey) of - {array, Routes} -> [Route || {longstr, Route} <- Routes]; - undefined -> []; - {Type, _Val} -> throw({error, {unacceptable_type_in_header, - binary_to_list(HeaderKey), Type}}) - end || HeaderKey <- ?ROUTING_HEADERS]). - -parse_expiration(#'P_basic'{expiration = undefined}) -> - {ok, undefined}; -parse_expiration(#'P_basic'{expiration = Expiration}) -> - case string:to_integer(binary_to_list(Expiration)) of - {error, no_integer} = E -> - E; - {N, ""} -> - case rabbit_misc:check_expiry(N) of - ok -> {ok, N}; - E = {error, _} -> E - end; - {_, S} -> - {error, {leftover_string, S}} - end. - -%% Some processes (channel, writer) can get huge amounts of binary -%% garbage when processing huge messages at high speed (since we only -%% do enough reductions to GC every few hundred messages, and if each -%% message is 1MB then that's ugly). So count how many bytes of -%% message we have processed, and force a GC every so often. -maybe_gc_large_msg(Content) -> - Size = msg_size(Content), - Current = case get(msg_size_for_gc) of - undefined -> 0; - C -> C - end, - New = Current + Size, - put(msg_size_for_gc, case New > 1000000 of - true -> erlang:garbage_collect(), - 0; - false -> New - end), - Size. - -msg_size(#content{payload_fragments_rev = PFR}) -> iolist_size(PFR); -msg_size(#basic_message{content = Content}) -> msg_size(Content). diff --git a/src/rabbit_binary_generator.erl b/src/rabbit_binary_generator.erl deleted file mode 100644 index 53ba35db..00000000 --- a/src/rabbit_binary_generator.erl +++ /dev/null @@ -1,241 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_binary_generator). --include("rabbit_framing.hrl"). --include("rabbit.hrl"). - --export([build_simple_method_frame/3, - build_simple_content_frames/4, - build_heartbeat_frame/0]). --export([generate_table/1]). --export([check_empty_frame_size/0]). --export([ensure_content_encoded/2, clear_encoded_content/1]). --export([map_exception/3]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(frame() :: [binary()]). - --spec(build_simple_method_frame/3 :: - (rabbit_channel:channel_number(), rabbit_framing:amqp_method_record(), - rabbit_types:protocol()) - -> frame()). --spec(build_simple_content_frames/4 :: - (rabbit_channel:channel_number(), rabbit_types:content(), - non_neg_integer(), rabbit_types:protocol()) - -> [frame()]). --spec(build_heartbeat_frame/0 :: () -> frame()). --spec(generate_table/1 :: (rabbit_framing:amqp_table()) -> binary()). --spec(check_empty_frame_size/0 :: () -> 'ok'). --spec(ensure_content_encoded/2 :: - (rabbit_types:content(), rabbit_types:protocol()) -> - rabbit_types:encoded_content()). --spec(clear_encoded_content/1 :: - (rabbit_types:content()) -> rabbit_types:unencoded_content()). --spec(map_exception/3 :: (rabbit_channel:channel_number(), - rabbit_types:amqp_error() | any(), - rabbit_types:protocol()) -> - {rabbit_channel:channel_number(), - rabbit_framing:amqp_method_record()}). - --endif. - -%%---------------------------------------------------------------------------- - -build_simple_method_frame(ChannelInt, MethodRecord, Protocol) -> - MethodFields = Protocol:encode_method_fields(MethodRecord), - MethodName = rabbit_misc:method_record_type(MethodRecord), - {ClassId, MethodId} = Protocol:method_id(MethodName), - create_frame(1, ChannelInt, [<<ClassId:16, MethodId:16>>, MethodFields]). - -build_simple_content_frames(ChannelInt, Content, FrameMax, Protocol) -> - #content{class_id = ClassId, - properties_bin = ContentPropertiesBin, - payload_fragments_rev = PayloadFragmentsRev} = - ensure_content_encoded(Content, Protocol), - {BodySize, ContentFrames} = - build_content_frames(PayloadFragmentsRev, FrameMax, ChannelInt), - HeaderFrame = create_frame(2, ChannelInt, - [<<ClassId:16, 0:16, BodySize:64>>, - ContentPropertiesBin]), - [HeaderFrame | ContentFrames]. - -build_content_frames(FragsRev, FrameMax, ChannelInt) -> - BodyPayloadMax = if FrameMax == 0 -> iolist_size(FragsRev); - true -> FrameMax - ?EMPTY_FRAME_SIZE - end, - build_content_frames(0, [], BodyPayloadMax, [], - lists:reverse(FragsRev), BodyPayloadMax, ChannelInt). - -build_content_frames(SizeAcc, FramesAcc, _FragSizeRem, [], - [], _BodyPayloadMax, _ChannelInt) -> - {SizeAcc, lists:reverse(FramesAcc)}; -build_content_frames(SizeAcc, FramesAcc, FragSizeRem, FragAcc, - Frags, BodyPayloadMax, ChannelInt) - when FragSizeRem == 0 orelse Frags == [] -> - Frame = create_frame(3, ChannelInt, lists:reverse(FragAcc)), - FrameSize = BodyPayloadMax - FragSizeRem, - build_content_frames(SizeAcc + FrameSize, [Frame | FramesAcc], - BodyPayloadMax, [], Frags, BodyPayloadMax, ChannelInt); -build_content_frames(SizeAcc, FramesAcc, FragSizeRem, FragAcc, - [Frag | Frags], BodyPayloadMax, ChannelInt) -> - Size = size(Frag), - {NewFragSizeRem, NewFragAcc, NewFrags} = - if Size == 0 -> {FragSizeRem, FragAcc, Frags}; - Size =< FragSizeRem -> {FragSizeRem - Size, [Frag | FragAcc], Frags}; - true -> <<Head:FragSizeRem/binary, Tail/binary>> = - Frag, - {0, [Head | FragAcc], [Tail | Frags]} - end, - build_content_frames(SizeAcc, FramesAcc, NewFragSizeRem, NewFragAcc, - NewFrags, BodyPayloadMax, ChannelInt). - -build_heartbeat_frame() -> - create_frame(?FRAME_HEARTBEAT, 0, <<>>). - -create_frame(TypeInt, ChannelInt, Payload) -> - [<<TypeInt:8, ChannelInt:16, (iolist_size(Payload)):32>>, Payload, - ?FRAME_END]. - -%% table_field_to_binary supports the AMQP 0-8/0-9 standard types, S, -%% I, D, T and F, as well as the QPid extensions b, d, f, l, s, t, x, -%% and V. -table_field_to_binary({FName, T, V}) -> - [short_string_to_binary(FName) | field_value_to_binary(T, V)]. - -field_value_to_binary(longstr, V) -> [$S | long_string_to_binary(V)]; -field_value_to_binary(signedint, V) -> [$I, <<V:32/signed>>]; -field_value_to_binary(decimal, V) -> {Before, After} = V, - [$D, Before, <<After:32>>]; -field_value_to_binary(timestamp, V) -> [$T, <<V:64>>]; -field_value_to_binary(table, V) -> [$F | table_to_binary(V)]; -field_value_to_binary(array, V) -> [$A | array_to_binary(V)]; -field_value_to_binary(byte, V) -> [$b, <<V:8/signed>>]; -field_value_to_binary(double, V) -> [$d, <<V:64/float>>]; -field_value_to_binary(float, V) -> [$f, <<V:32/float>>]; -field_value_to_binary(long, V) -> [$l, <<V:64/signed>>]; -field_value_to_binary(short, V) -> [$s, <<V:16/signed>>]; -field_value_to_binary(bool, V) -> [$t, if V -> 1; true -> 0 end]; -field_value_to_binary(binary, V) -> [$x | long_string_to_binary(V)]; -field_value_to_binary(void, _V) -> [$V]. - -table_to_binary(Table) when is_list(Table) -> - BinTable = generate_table_iolist(Table), - [<<(iolist_size(BinTable)):32>> | BinTable]. - -array_to_binary(Array) when is_list(Array) -> - BinArray = generate_array_iolist(Array), - [<<(iolist_size(BinArray)):32>> | BinArray]. - -generate_table(Table) when is_list(Table) -> - list_to_binary(generate_table_iolist(Table)). - -generate_table_iolist(Table) -> - lists:map(fun table_field_to_binary/1, Table). - -generate_array_iolist(Array) -> - lists:map(fun ({T, V}) -> field_value_to_binary(T, V) end, Array). - -short_string_to_binary(String) -> - Len = string_length(String), - if Len < 256 -> [<<Len:8>>, String]; - true -> exit(content_properties_shortstr_overflow) - end. - -long_string_to_binary(String) -> - Len = string_length(String), - [<<Len:32>>, String]. - -string_length(String) when is_binary(String) -> size(String); -string_length(String) -> length(String). - -check_empty_frame_size() -> - %% Intended to ensure that EMPTY_FRAME_SIZE is defined correctly. - case iolist_size(create_frame(?FRAME_BODY, 0, <<>>)) of - ?EMPTY_FRAME_SIZE -> ok; - ComputedSize -> exit({incorrect_empty_frame_size, - ComputedSize, ?EMPTY_FRAME_SIZE}) - end. - -ensure_content_encoded(Content = #content{properties_bin = PropBin, - protocol = Protocol}, Protocol) - when PropBin =/= none -> - Content; -ensure_content_encoded(Content = #content{properties = none, - properties_bin = PropBin, - protocol = Protocol}, Protocol1) - when PropBin =/= none -> - Props = Protocol:decode_properties(Content#content.class_id, PropBin), - Content#content{properties = Props, - properties_bin = Protocol1:encode_properties(Props), - protocol = Protocol1}; -ensure_content_encoded(Content = #content{properties = Props}, Protocol) - when Props =/= none -> - Content#content{properties_bin = Protocol:encode_properties(Props), - protocol = Protocol}. - -clear_encoded_content(Content = #content{properties_bin = none, - protocol = none}) -> - Content; -clear_encoded_content(Content = #content{properties = none}) -> - %% Only clear when we can rebuild the properties_bin later in - %% accordance to the content record definition comment - maximum - %% one of properties and properties_bin can be 'none' - Content; -clear_encoded_content(Content = #content{}) -> - Content#content{properties_bin = none, protocol = none}. - -%% NB: this function is also used by the Erlang client -map_exception(Channel, Reason, Protocol) -> - {SuggestedClose, ReplyCode, ReplyText, FailedMethod} = - lookup_amqp_exception(Reason, Protocol), - {ClassId, MethodId} = case FailedMethod of - {_, _} -> FailedMethod; - none -> {0, 0}; - _ -> Protocol:method_id(FailedMethod) - end, - case SuggestedClose orelse (Channel == 0) of - true -> {0, #'connection.close'{reply_code = ReplyCode, - reply_text = ReplyText, - class_id = ClassId, - method_id = MethodId}}; - false -> {Channel, #'channel.close'{reply_code = ReplyCode, - reply_text = ReplyText, - class_id = ClassId, - method_id = MethodId}} - end. - -lookup_amqp_exception(#amqp_error{name = Name, - explanation = Expl, - method = Method}, - Protocol) -> - {ShouldClose, Code, Text} = Protocol:lookup_amqp_exception(Name), - ExplBin = amqp_exception_explanation(Text, Expl), - {ShouldClose, Code, ExplBin, Method}; -lookup_amqp_exception(Other, Protocol) -> - rabbit_log:warning("Non-AMQP exit reason '~p'~n", [Other]), - {ShouldClose, Code, Text} = Protocol:lookup_amqp_exception(internal_error), - {ShouldClose, Code, Text, none}. - -amqp_exception_explanation(Text, Expl) -> - ExplBin = list_to_binary(Expl), - CompleteTextBin = <<Text/binary, " - ", ExplBin/binary>>, - if size(CompleteTextBin) > 255 -> <<CompleteTextBin:252/binary, "...">>; - true -> CompleteTextBin - end. diff --git a/src/rabbit_binary_parser.erl b/src/rabbit_binary_parser.erl deleted file mode 100644 index ee8147f4..00000000 --- a/src/rabbit_binary_parser.erl +++ /dev/null @@ -1,161 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_binary_parser). - --include("rabbit.hrl"). - --export([parse_table/1]). --export([ensure_content_decoded/1, clear_decoded_content/1]). --export([validate_utf8/1, assert_utf8/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(parse_table/1 :: (binary()) -> rabbit_framing:amqp_table()). --spec(ensure_content_decoded/1 :: - (rabbit_types:content()) -> rabbit_types:decoded_content()). --spec(clear_decoded_content/1 :: - (rabbit_types:content()) -> rabbit_types:undecoded_content()). --spec(validate_utf8/1 :: (binary()) -> 'ok' | 'error'). --spec(assert_utf8/1 :: (binary()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -%% parse_table supports the AMQP 0-8/0-9 standard types, S, I, D, T -%% and F, as well as the QPid extensions b, d, f, l, s, t, x, and V. - --define(SIMPLE_PARSE_TABLE(BType, Pattern, RType), - parse_table(<<NLen:8/unsigned, NameString:NLen/binary, - BType, Pattern, Rest/binary>>) -> - [{NameString, RType, Value} | parse_table(Rest)]). - -%% Note that we try to put these in approximately the order we expect -%% to hit them, that's why the empty binary is half way through. - -parse_table(<<NLen:8/unsigned, NameString:NLen/binary, - $S, VLen:32/unsigned, Value:VLen/binary, Rest/binary>>) -> - [{NameString, longstr, Value} | parse_table(Rest)]; - -?SIMPLE_PARSE_TABLE($I, Value:32/signed, signedint); -?SIMPLE_PARSE_TABLE($T, Value:64/unsigned, timestamp); - -parse_table(<<>>) -> - []; - -?SIMPLE_PARSE_TABLE($b, Value:8/signed, byte); -?SIMPLE_PARSE_TABLE($d, Value:64/float, double); -?SIMPLE_PARSE_TABLE($f, Value:32/float, float); -?SIMPLE_PARSE_TABLE($l, Value:64/signed, long); -?SIMPLE_PARSE_TABLE($s, Value:16/signed, short); - -parse_table(<<NLen:8/unsigned, NameString:NLen/binary, - $t, Value:8/unsigned, Rest/binary>>) -> - [{NameString, bool, (Value /= 0)} | parse_table(Rest)]; - -parse_table(<<NLen:8/unsigned, NameString:NLen/binary, - $D, Before:8/unsigned, After:32/unsigned, Rest/binary>>) -> - [{NameString, decimal, {Before, After}} | parse_table(Rest)]; - -parse_table(<<NLen:8/unsigned, NameString:NLen/binary, - $F, VLen:32/unsigned, Value:VLen/binary, Rest/binary>>) -> - [{NameString, table, parse_table(Value)} | parse_table(Rest)]; - -parse_table(<<NLen:8/unsigned, NameString:NLen/binary, - $A, VLen:32/unsigned, Value:VLen/binary, Rest/binary>>) -> - [{NameString, array, parse_array(Value)} | parse_table(Rest)]; - -parse_table(<<NLen:8/unsigned, NameString:NLen/binary, - $x, VLen:32/unsigned, Value:VLen/binary, Rest/binary>>) -> - [{NameString, binary, Value} | parse_table(Rest)]; - -parse_table(<<NLen:8/unsigned, NameString:NLen/binary, - $V, Rest/binary>>) -> - [{NameString, void, undefined} | parse_table(Rest)]. - --define(SIMPLE_PARSE_ARRAY(BType, Pattern, RType), - parse_array(<<BType, Pattern, Rest/binary>>) -> - [{RType, Value} | parse_array(Rest)]). - -parse_array(<<$S, VLen:32/unsigned, Value:VLen/binary, Rest/binary>>) -> - [{longstr, Value} | parse_array(Rest)]; - -?SIMPLE_PARSE_ARRAY($I, Value:32/signed, signedint); -?SIMPLE_PARSE_ARRAY($T, Value:64/unsigned, timestamp); - -parse_array(<<>>) -> - []; - -?SIMPLE_PARSE_ARRAY($b, Value:8/signed, byte); -?SIMPLE_PARSE_ARRAY($d, Value:64/float, double); -?SIMPLE_PARSE_ARRAY($f, Value:32/float, float); -?SIMPLE_PARSE_ARRAY($l, Value:64/signed, long); -?SIMPLE_PARSE_ARRAY($s, Value:16/signed, short); - -parse_array(<<$t, Value:8/unsigned, Rest/binary>>) -> - [{bool, (Value /= 0)} | parse_array(Rest)]; - -parse_array(<<$D, Before:8/unsigned, After:32/unsigned, Rest/binary>>) -> - [{decimal, {Before, After}} | parse_array(Rest)]; - -parse_array(<<$F, VLen:32/unsigned, Value:VLen/binary, Rest/binary>>) -> - [{table, parse_table(Value)} | parse_array(Rest)]; - -parse_array(<<$A, VLen:32/unsigned, Value:VLen/binary, Rest/binary>>) -> - [{array, parse_array(Value)} | parse_array(Rest)]; - -parse_array(<<$x, VLen:32/unsigned, Value:VLen/binary, Rest/binary>>) -> - [{binary, Value} | parse_array(Rest)]; - -parse_array(<<$V, Rest/binary>>) -> - [{void, undefined} | parse_array(Rest)]. - -ensure_content_decoded(Content = #content{properties = Props}) - when Props =/= none -> - Content; -ensure_content_decoded(Content = #content{properties_bin = PropBin, - protocol = Protocol}) - when PropBin =/= none -> - Content#content{properties = Protocol:decode_properties( - Content#content.class_id, PropBin)}. - -clear_decoded_content(Content = #content{properties = none}) -> - Content; -clear_decoded_content(Content = #content{properties_bin = none}) -> - %% Only clear when we can rebuild the properties later in - %% accordance to the content record definition comment - maximum - %% one of properties and properties_bin can be 'none' - Content; -clear_decoded_content(Content = #content{}) -> - Content#content{properties = none}. - -assert_utf8(B) -> - case validate_utf8(B) of - ok -> ok; - error -> rabbit_misc:protocol_error( - frame_error, "Malformed UTF-8 in shortstr", []) - end. - -validate_utf8(Bin) -> - try - xmerl_ucs:from_utf8(Bin), - ok - catch exit:{ucs, _} -> - error - end. diff --git a/src/rabbit_binding.erl b/src/rabbit_binding.erl deleted file mode 100644 index 53af2f20..00000000 --- a/src/rabbit_binding.erl +++ /dev/null @@ -1,561 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_binding). --include("rabbit.hrl"). - --export([recover/2, exists/1, add/1, add/2, remove/1, remove/2, list/1]). --export([list_for_source/1, list_for_destination/1, - list_for_source_and_destination/2]). --export([new_deletions/0, combine_deletions/2, add_deletion/3, - process_deletions/1]). --export([info_keys/0, info/1, info/2, info_all/1, info_all/2]). -%% these must all be run inside a mnesia tx --export([has_for_source/1, remove_for_source/1, - remove_for_destination/2, remove_transient_for_destination/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([key/0, deletions/0]). - --type(key() :: binary()). - --type(bind_errors() :: rabbit_types:error( - {'resources_missing', - [{'not_found', (rabbit_types:binding_source() | - rabbit_types:binding_destination())} | - {'absent', rabbit_types:amqqueue()}]})). - --type(bind_ok_or_error() :: 'ok' | bind_errors() | - rabbit_types:error( - 'binding_not_found' | - {'binding_invalid', string(), [any()]})). --type(bind_res() :: bind_ok_or_error() | rabbit_misc:thunk(bind_ok_or_error())). --type(inner_fun() :: - fun((rabbit_types:exchange(), - rabbit_types:exchange() | rabbit_types:amqqueue()) -> - rabbit_types:ok_or_error(rabbit_types:amqp_error()))). --type(bindings() :: [rabbit_types:binding()]). - -%% TODO this should really be opaque but that seems to confuse 17.1's -%% dialyzer into objecting to everything that uses it. --type(deletions() :: dict:dict()). - --spec(recover/2 :: ([rabbit_exchange:name()], [rabbit_amqqueue:name()]) -> - 'ok'). --spec(exists/1 :: (rabbit_types:binding()) -> boolean() | bind_errors()). --spec(add/1 :: (rabbit_types:binding()) -> bind_res()). --spec(add/2 :: (rabbit_types:binding(), inner_fun()) -> bind_res()). --spec(remove/1 :: (rabbit_types:binding()) -> bind_res()). --spec(remove/2 :: (rabbit_types:binding(), inner_fun()) -> bind_res()). --spec(list/1 :: (rabbit_types:vhost()) -> bindings()). --spec(list_for_source/1 :: - (rabbit_types:binding_source()) -> bindings()). --spec(list_for_destination/1 :: - (rabbit_types:binding_destination()) -> bindings()). --spec(list_for_source_and_destination/2 :: - (rabbit_types:binding_source(), rabbit_types:binding_destination()) -> - bindings()). --spec(info_keys/0 :: () -> rabbit_types:info_keys()). --spec(info/1 :: (rabbit_types:binding()) -> rabbit_types:infos()). --spec(info/2 :: (rabbit_types:binding(), rabbit_types:info_keys()) -> - rabbit_types:infos()). --spec(info_all/1 :: (rabbit_types:vhost()) -> [rabbit_types:infos()]). --spec(info_all/2 ::(rabbit_types:vhost(), rabbit_types:info_keys()) - -> [rabbit_types:infos()]). --spec(has_for_source/1 :: (rabbit_types:binding_source()) -> boolean()). --spec(remove_for_source/1 :: (rabbit_types:binding_source()) -> bindings()). --spec(remove_for_destination/2 :: - (rabbit_types:binding_destination(), boolean()) -> deletions()). --spec(remove_transient_for_destination/1 :: - (rabbit_types:binding_destination()) -> deletions()). --spec(process_deletions/1 :: (deletions()) -> rabbit_misc:thunk('ok')). --spec(combine_deletions/2 :: (deletions(), deletions()) -> deletions()). --spec(add_deletion/3 :: (rabbit_exchange:name(), - {'undefined' | rabbit_types:exchange(), - 'deleted' | 'not_deleted', - bindings()}, deletions()) -> deletions()). --spec(new_deletions/0 :: () -> deletions()). - --endif. - -%%---------------------------------------------------------------------------- - --define(INFO_KEYS, [source_name, source_kind, - destination_name, destination_kind, - routing_key, arguments]). - -recover(XNames, QNames) -> - rabbit_misc:table_filter( - fun (Route) -> - mnesia:read({rabbit_semi_durable_route, Route}) =:= [] - end, - fun (Route, true) -> - ok = mnesia:write(rabbit_semi_durable_route, Route, write); - (_Route, false) -> - ok - end, rabbit_durable_route), - XNameSet = sets:from_list(XNames), - QNameSet = sets:from_list(QNames), - SelectSet = fun (#resource{kind = exchange}) -> XNameSet; - (#resource{kind = queue}) -> QNameSet - end, - {ok, Gatherer} = gatherer:start_link(), - [recover_semi_durable_route(Gatherer, R, SelectSet(Dst)) || - R = #route{binding = #binding{destination = Dst}} <- - rabbit_misc:dirty_read_all(rabbit_semi_durable_route)], - empty = gatherer:out(Gatherer), - ok = gatherer:stop(Gatherer), - ok. - -recover_semi_durable_route(Gatherer, R = #route{binding = B}, ToRecover) -> - #binding{source = Src, destination = Dst} = B, - case sets:is_element(Dst, ToRecover) of - true -> {ok, X} = rabbit_exchange:lookup(Src), - ok = gatherer:fork(Gatherer), - ok = worker_pool:submit_async( - fun () -> - recover_semi_durable_route_txn(R, X), - gatherer:finish(Gatherer) - end); - false -> ok - end. - -recover_semi_durable_route_txn(R = #route{binding = B}, X) -> - rabbit_misc:execute_mnesia_transaction( - fun () -> - case mnesia:match_object(rabbit_semi_durable_route, R, read) of - [] -> no_recover; - _ -> ok = sync_transient_route(R, fun mnesia:write/3), - rabbit_exchange:serial(X) - end - end, - fun (no_recover, _) -> ok; - (_Serial, true) -> x_callback(transaction, X, add_binding, B); - (Serial, false) -> x_callback(Serial, X, add_binding, B) - end). - -exists(Binding) -> - binding_action( - Binding, fun (_Src, _Dst, B) -> - rabbit_misc:const(mnesia:read({rabbit_route, B}) /= []) - end, fun not_found_or_absent_errs/1). - -add(Binding) -> add(Binding, fun (_Src, _Dst) -> ok end). - -add(Binding, InnerFun) -> - binding_action( - Binding, - fun (Src, Dst, B) -> - case rabbit_exchange:validate_binding(Src, B) of - ok -> - %% this argument is used to check queue exclusivity; - %% in general, we want to fail on that in preference to - %% anything else - case InnerFun(Src, Dst) of - ok -> - case mnesia:read({rabbit_route, B}) of - [] -> add(Src, Dst, B); - [_] -> fun () -> ok end - end; - {error, _} = Err -> - rabbit_misc:const(Err) - end; - {error, _} = Err -> - rabbit_misc:const(Err) - end - end, fun not_found_or_absent_errs/1). - -add(Src, Dst, B) -> - [SrcDurable, DstDurable] = [durable(E) || E <- [Src, Dst]], - case (SrcDurable andalso DstDurable andalso - mnesia:read({rabbit_durable_route, B}) =/= []) of - false -> ok = sync_route(#route{binding = B}, SrcDurable, DstDurable, - fun mnesia:write/3), - x_callback(transaction, Src, add_binding, B), - Serial = rabbit_exchange:serial(Src), - fun () -> - x_callback(Serial, Src, add_binding, B), - ok = rabbit_event:notify(binding_created, info(B)) - end; - true -> rabbit_misc:const({error, binding_not_found}) - end. - -remove(Binding) -> remove(Binding, fun (_Src, _Dst) -> ok end). - -remove(Binding, InnerFun) -> - binding_action( - Binding, - fun (Src, Dst, B) -> - case mnesia:read(rabbit_route, B, write) of - [] -> case mnesia:read(rabbit_durable_route, B, write) of - [] -> rabbit_misc:const(ok); - _ -> rabbit_misc:const({error, binding_not_found}) - end; - _ -> case InnerFun(Src, Dst) of - ok -> remove(Src, Dst, B); - {error, _} = Err -> rabbit_misc:const(Err) - end - end - end, fun absent_errs_only/1). - -remove(Src, Dst, B) -> - ok = sync_route(#route{binding = B}, durable(Src), durable(Dst), - fun mnesia:delete_object/3), - Deletions = maybe_auto_delete( - B#binding.source, [B], new_deletions(), false), - process_deletions(Deletions). - -list(VHostPath) -> - VHostResource = rabbit_misc:r(VHostPath, '_'), - Route = #route{binding = #binding{source = VHostResource, - destination = VHostResource, - _ = '_'}, - _ = '_'}, - [B || #route{binding = B} <- mnesia:dirty_match_object(rabbit_route, - Route)]. - -list_for_source(SrcName) -> - mnesia:async_dirty( - fun() -> - Route = #route{binding = #binding{source = SrcName, _ = '_'}}, - [B || #route{binding = B} - <- mnesia:match_object(rabbit_route, Route, read)] - end). - -list_for_destination(DstName) -> - mnesia:async_dirty( - fun() -> - Route = #route{binding = #binding{destination = DstName, - _ = '_'}}, - [reverse_binding(B) || - #reverse_route{reverse_binding = B} <- - mnesia:match_object(rabbit_reverse_route, - reverse_route(Route), read)] - end). - -list_for_source_and_destination(SrcName, DstName) -> - mnesia:async_dirty( - fun() -> - Route = #route{binding = #binding{source = SrcName, - destination = DstName, - _ = '_'}}, - [B || #route{binding = B} <- mnesia:match_object(rabbit_route, - Route, read)] - end). - -info_keys() -> ?INFO_KEYS. - -map(VHostPath, F) -> - %% TODO: there is scope for optimisation here, e.g. using a - %% cursor, parallelising the function invocation - lists:map(F, list(VHostPath)). - -infos(Items, B) -> [{Item, i(Item, B)} || Item <- Items]. - -i(source_name, #binding{source = SrcName}) -> SrcName#resource.name; -i(source_kind, #binding{source = SrcName}) -> SrcName#resource.kind; -i(destination_name, #binding{destination = DstName}) -> DstName#resource.name; -i(destination_kind, #binding{destination = DstName}) -> DstName#resource.kind; -i(routing_key, #binding{key = RoutingKey}) -> RoutingKey; -i(arguments, #binding{args = Arguments}) -> Arguments; -i(Item, _) -> throw({bad_argument, Item}). - -info(B = #binding{}) -> infos(?INFO_KEYS, B). - -info(B = #binding{}, Items) -> infos(Items, B). - -info_all(VHostPath) -> map(VHostPath, fun (B) -> info(B) end). - -info_all(VHostPath, Items) -> map(VHostPath, fun (B) -> info(B, Items) end). - -has_for_source(SrcName) -> - Match = #route{binding = #binding{source = SrcName, _ = '_'}}, - %% we need to check for semi-durable routes (which subsumes - %% durable routes) here too in case a bunch of routes to durable - %% queues have been removed temporarily as a result of a node - %% failure - contains(rabbit_route, Match) orelse - contains(rabbit_semi_durable_route, Match). - -remove_for_source(SrcName) -> - lock_route_tables(), - Match = #route{binding = #binding{source = SrcName, _ = '_'}}, - remove_routes( - lists:usort( - mnesia:match_object(rabbit_route, Match, write) ++ - mnesia:match_object(rabbit_semi_durable_route, Match, write))). - -remove_for_destination(DstName, OnlyDurable) -> - remove_for_destination(DstName, OnlyDurable, fun remove_routes/1). - -remove_transient_for_destination(DstName) -> - remove_for_destination(DstName, false, fun remove_transient_routes/1). - -%%---------------------------------------------------------------------------- - -durable(#exchange{durable = D}) -> D; -durable(#amqqueue{durable = D}) -> D. - -binding_action(Binding = #binding{source = SrcName, - destination = DstName, - args = Arguments}, Fun, ErrFun) -> - call_with_source_and_destination( - SrcName, DstName, - fun (Src, Dst) -> - SortedArgs = rabbit_misc:sort_field_table(Arguments), - Fun(Src, Dst, Binding#binding{args = SortedArgs}) - end, ErrFun). - -delete_object(Tab, Record, LockKind) -> - %% this 'guarded' delete prevents unnecessary writes to the mnesia - %% disk log - case mnesia:match_object(Tab, Record, LockKind) of - [] -> ok; - [_] -> mnesia:delete_object(Tab, Record, LockKind) - end. - -sync_route(Route, true, true, Fun) -> - ok = Fun(rabbit_durable_route, Route, write), - sync_route(Route, false, true, Fun); - -sync_route(Route, false, true, Fun) -> - ok = Fun(rabbit_semi_durable_route, Route, write), - sync_route(Route, false, false, Fun); - -sync_route(Route, _SrcDurable, false, Fun) -> - sync_transient_route(Route, Fun). - -sync_transient_route(Route, Fun) -> - ok = Fun(rabbit_route, Route, write), - ok = Fun(rabbit_reverse_route, reverse_route(Route), write). - -call_with_source_and_destination(SrcName, DstName, Fun, ErrFun) -> - SrcTable = table_for_resource(SrcName), - DstTable = table_for_resource(DstName), - rabbit_misc:execute_mnesia_tx_with_tail( - fun () -> - case {mnesia:read({SrcTable, SrcName}), - mnesia:read({DstTable, DstName})} of - {[Src], [Dst]} -> Fun(Src, Dst); - {[], [_] } -> ErrFun([SrcName]); - {[_], [] } -> ErrFun([DstName]); - {[], [] } -> ErrFun([SrcName, DstName]) - end - end). - -not_found_or_absent_errs(Names) -> - Errs = [not_found_or_absent(Name) || Name <- Names], - rabbit_misc:const({error, {resources_missing, Errs}}). - -absent_errs_only(Names) -> - Errs = [E || Name <- Names, - {absent, _Q, _Reason} = E <- [not_found_or_absent(Name)]], - rabbit_misc:const(case Errs of - [] -> ok; - _ -> {error, {resources_missing, Errs}} - end). - -table_for_resource(#resource{kind = exchange}) -> rabbit_exchange; -table_for_resource(#resource{kind = queue}) -> rabbit_queue. - -not_found_or_absent(#resource{kind = exchange} = Name) -> - {not_found, Name}; -not_found_or_absent(#resource{kind = queue} = Name) -> - case rabbit_amqqueue:not_found_or_absent(Name) of - not_found -> {not_found, Name}; - {absent, _Q, _Reason} = R -> R - end. - -contains(Table, MatchHead) -> - continue(mnesia:select(Table, [{MatchHead, [], ['$_']}], 1, read)). - -continue('$end_of_table') -> false; -continue({[_|_], _}) -> true; -continue({[], Continuation}) -> continue(mnesia:select(Continuation)). - -%% For bulk operations we lock the tables we are operating on in order -%% to reduce the time complexity. Without the table locks we end up -%% with num_tables*num_bulk_bindings row-level locks. Taking each lock -%% takes time proportional to the number of existing locks, thus -%% resulting in O(num_bulk_bindings^2) complexity. -%% -%% The locks need to be write locks since ultimately we end up -%% removing all these rows. -%% -%% The downside of all this is that no other binding operations except -%% lookup/routing (which uses dirty ops) can take place -%% concurrently. However, that is the case already since the bulk -%% operations involve mnesia:match_object calls with a partial key, -%% which entails taking a table lock. -lock_route_tables() -> - [mnesia:lock({table, T}, write) || T <- [rabbit_route, - rabbit_reverse_route, - rabbit_semi_durable_route, - rabbit_durable_route]]. - -remove_routes(Routes) -> - %% This partitioning allows us to suppress unnecessary delete - %% operations on disk tables, which require an fsync. - {RamRoutes, DiskRoutes} = - lists:partition(fun (R) -> mnesia:match_object( - rabbit_durable_route, R, write) == [] end, - Routes), - %% Of course the destination might not really be durable but it's - %% just as easy to try to delete it from the semi-durable table - %% than check first - [ok = sync_route(R, false, true, fun mnesia:delete_object/3) || - R <- RamRoutes], - [ok = sync_route(R, true, true, fun mnesia:delete_object/3) || - R <- DiskRoutes], - [R#route.binding || R <- Routes]. - -remove_transient_routes(Routes) -> - [begin - ok = sync_transient_route(R, fun delete_object/3), - R#route.binding - end || R <- Routes]. - -remove_for_destination(DstName, OnlyDurable, Fun) -> - lock_route_tables(), - MatchFwd = #route{binding = #binding{destination = DstName, _ = '_'}}, - MatchRev = reverse_route(MatchFwd), - Routes = case OnlyDurable of - false -> [reverse_route(R) || - R <- mnesia:match_object( - rabbit_reverse_route, MatchRev, write)]; - true -> lists:usort( - mnesia:match_object( - rabbit_durable_route, MatchFwd, write) ++ - mnesia:match_object( - rabbit_semi_durable_route, MatchFwd, write)) - end, - Bindings = Fun(Routes), - group_bindings_fold(fun maybe_auto_delete/4, new_deletions(), - lists:keysort(#binding.source, Bindings), OnlyDurable). - -%% Requires that its input binding list is sorted in exchange-name -%% order, so that the grouping of bindings (for passing to -%% group_bindings_and_auto_delete1) works properly. -group_bindings_fold(_Fun, Acc, [], _OnlyDurable) -> - Acc; -group_bindings_fold(Fun, Acc, [B = #binding{source = SrcName} | Bs], - OnlyDurable) -> - group_bindings_fold(Fun, SrcName, Acc, Bs, [B], OnlyDurable). - -group_bindings_fold( - Fun, SrcName, Acc, [B = #binding{source = SrcName} | Bs], Bindings, - OnlyDurable) -> - group_bindings_fold(Fun, SrcName, Acc, Bs, [B | Bindings], OnlyDurable); -group_bindings_fold(Fun, SrcName, Acc, Removed, Bindings, OnlyDurable) -> - %% Either Removed is [], or its head has a non-matching SrcName. - group_bindings_fold(Fun, Fun(SrcName, Bindings, Acc, OnlyDurable), Removed, - OnlyDurable). - -maybe_auto_delete(XName, Bindings, Deletions, OnlyDurable) -> - {Entry, Deletions1} = - case mnesia:read({case OnlyDurable of - true -> rabbit_durable_exchange; - false -> rabbit_exchange - end, XName}) of - [] -> {{undefined, not_deleted, Bindings}, Deletions}; - [X] -> case rabbit_exchange:maybe_auto_delete(X, OnlyDurable) of - not_deleted -> - {{X, not_deleted, Bindings}, Deletions}; - {deleted, Deletions2} -> - {{X, deleted, Bindings}, - combine_deletions(Deletions, Deletions2)} - end - end, - add_deletion(XName, Entry, Deletions1). - -reverse_route(#route{binding = Binding}) -> - #reverse_route{reverse_binding = reverse_binding(Binding)}; - -reverse_route(#reverse_route{reverse_binding = Binding}) -> - #route{binding = reverse_binding(Binding)}. - -reverse_binding(#reverse_binding{source = SrcName, - destination = DstName, - key = Key, - args = Args}) -> - #binding{source = SrcName, - destination = DstName, - key = Key, - args = Args}; - -reverse_binding(#binding{source = SrcName, - destination = DstName, - key = Key, - args = Args}) -> - #reverse_binding{source = SrcName, - destination = DstName, - key = Key, - args = Args}. - -%% ---------------------------------------------------------------------------- -%% Binding / exchange deletion abstraction API -%% ---------------------------------------------------------------------------- - -anything_but( NotThis, NotThis, NotThis) -> NotThis; -anything_but( NotThis, NotThis, This) -> This; -anything_but( NotThis, This, NotThis) -> This; -anything_but(_NotThis, This, This) -> This. - -new_deletions() -> dict:new(). - -add_deletion(XName, Entry, Deletions) -> - dict:update(XName, fun (Entry1) -> merge_entry(Entry1, Entry) end, - Entry, Deletions). - -combine_deletions(Deletions1, Deletions2) -> - dict:merge(fun (_XName, Entry1, Entry2) -> merge_entry(Entry1, Entry2) end, - Deletions1, Deletions2). - -merge_entry({X1, Deleted1, Bindings1}, {X2, Deleted2, Bindings2}) -> - {anything_but(undefined, X1, X2), - anything_but(not_deleted, Deleted1, Deleted2), - [Bindings1 | Bindings2]}. - -process_deletions(Deletions) -> - AugmentedDeletions = - dict:map(fun (_XName, {X, deleted, Bindings}) -> - Bs = lists:flatten(Bindings), - x_callback(transaction, X, delete, Bs), - {X, deleted, Bs, none}; - (_XName, {X, not_deleted, Bindings}) -> - Bs = lists:flatten(Bindings), - x_callback(transaction, X, remove_bindings, Bs), - {X, not_deleted, Bs, rabbit_exchange:serial(X)} - end, Deletions), - fun() -> - dict:fold(fun (XName, {X, deleted, Bs, Serial}, ok) -> - ok = rabbit_event:notify( - exchange_deleted, [{name, XName}]), - del_notify(Bs), - x_callback(Serial, X, delete, Bs); - (_XName, {X, not_deleted, Bs, Serial}, ok) -> - del_notify(Bs), - x_callback(Serial, X, remove_bindings, Bs) - end, ok, AugmentedDeletions) - end. - -del_notify(Bs) -> [rabbit_event:notify(binding_deleted, info(B)) || B <- Bs]. - -x_callback(Serial, X, F, Bs) -> - ok = rabbit_exchange:callback(X, F, Serial, [X, Bs]). diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl deleted file mode 100644 index 13cc925c..00000000 --- a/src/rabbit_channel.erl +++ /dev/null @@ -1,1871 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_channel). --include("rabbit_framing.hrl"). --include("rabbit.hrl"). - --behaviour(gen_server2). - --export([start_link/11, do/2, do/3, do_flow/3, flush/1, shutdown/1]). --export([send_command/2, deliver/4, deliver_reply/2, - send_credit_reply/2, send_drained/2]). --export([list/0, info_keys/0, info/1, info/2, info_all/0, info_all/1]). --export([refresh_config_local/0, ready_for_close/1]). --export([force_event_refresh/1]). - --export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, - handle_info/2, handle_pre_hibernate/1, prioritise_call/4, - prioritise_cast/3, prioritise_info/3, format_message_queue/2]). -%% Internal --export([list_local/0, deliver_reply_local/3]). - --record(ch, {state, protocol, channel, reader_pid, writer_pid, conn_pid, - conn_name, limiter, tx, next_tag, unacked_message_q, user, - virtual_host, most_recently_declared_queue, - queue_names, queue_monitors, consumer_mapping, - queue_consumers, delivering_queues, - queue_collector_pid, stats_timer, confirm_enabled, publish_seqno, - unconfirmed, confirmed, mandatory, capabilities, trace_state, - consumer_prefetch, reply_consumer}). - --define(MAX_PERMISSION_CACHE_SIZE, 12). - --define(STATISTICS_KEYS, - [pid, - transactional, - confirm, - consumer_count, - messages_unacknowledged, - messages_unconfirmed, - messages_uncommitted, - acks_uncommitted, - prefetch_count, - global_prefetch_count, - state]). - --define(CREATION_EVENT_KEYS, - [pid, - name, - connection, - number, - user, - vhost]). - --define(INFO_KEYS, ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [pid]). - --define(INCR_STATS(Incs, Measure, State), - case rabbit_event:stats_level(State, #ch.stats_timer) of - fine -> incr_stats(Incs, Measure); - _ -> ok - end). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([channel_number/0]). - --type(channel_number() :: non_neg_integer()). - --spec(start_link/11 :: - (channel_number(), pid(), pid(), pid(), string(), - rabbit_types:protocol(), rabbit_types:user(), rabbit_types:vhost(), - rabbit_framing:amqp_table(), pid(), pid()) -> - rabbit_types:ok_pid_or_error()). --spec(do/2 :: (pid(), rabbit_framing:amqp_method_record()) -> 'ok'). --spec(do/3 :: (pid(), rabbit_framing:amqp_method_record(), - rabbit_types:maybe(rabbit_types:content())) -> 'ok'). --spec(do_flow/3 :: (pid(), rabbit_framing:amqp_method_record(), - rabbit_types:maybe(rabbit_types:content())) -> 'ok'). --spec(flush/1 :: (pid()) -> 'ok'). --spec(shutdown/1 :: (pid()) -> 'ok'). --spec(send_command/2 :: (pid(), rabbit_framing:amqp_method_record()) -> 'ok'). --spec(deliver/4 :: - (pid(), rabbit_types:ctag(), boolean(), rabbit_amqqueue:qmsg()) - -> 'ok'). --spec(deliver_reply/2 :: (binary(), rabbit_types:delivery()) -> 'ok'). --spec(deliver_reply_local/3 :: - (pid(), binary(), rabbit_types:delivery()) -> 'ok'). --spec(send_credit_reply/2 :: (pid(), non_neg_integer()) -> 'ok'). --spec(send_drained/2 :: (pid(), [{rabbit_types:ctag(), non_neg_integer()}]) - -> 'ok'). --spec(list/0 :: () -> [pid()]). --spec(list_local/0 :: () -> [pid()]). --spec(info_keys/0 :: () -> rabbit_types:info_keys()). --spec(info/1 :: (pid()) -> rabbit_types:infos()). --spec(info/2 :: (pid(), rabbit_types:info_keys()) -> rabbit_types:infos()). --spec(info_all/0 :: () -> [rabbit_types:infos()]). --spec(info_all/1 :: (rabbit_types:info_keys()) -> [rabbit_types:infos()]). --spec(refresh_config_local/0 :: () -> 'ok'). --spec(ready_for_close/1 :: (pid()) -> 'ok'). --spec(force_event_refresh/1 :: (reference()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, - VHost, Capabilities, CollectorPid, Limiter) -> - gen_server2:start_link( - ?MODULE, [Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, - User, VHost, Capabilities, CollectorPid, Limiter], []). - -do(Pid, Method) -> - do(Pid, Method, none). - -do(Pid, Method, Content) -> - gen_server2:cast(Pid, {method, Method, Content, noflow}). - -do_flow(Pid, Method, Content) -> - credit_flow:send(Pid), - gen_server2:cast(Pid, {method, Method, Content, flow}). - -flush(Pid) -> - gen_server2:call(Pid, flush, infinity). - -shutdown(Pid) -> - gen_server2:cast(Pid, terminate). - -send_command(Pid, Msg) -> - gen_server2:cast(Pid, {command, Msg}). - -deliver(Pid, ConsumerTag, AckRequired, Msg) -> - gen_server2:cast(Pid, {deliver, ConsumerTag, AckRequired, Msg}). - -deliver_reply(<<"amq.rabbitmq.reply-to.", Rest/binary>>, Delivery) -> - case decode_fast_reply_to(Rest) of - {ok, Pid, Key} -> - delegate:invoke_no_result( - Pid, {?MODULE, deliver_reply_local, [Key, Delivery]}); - error -> - ok - end. - -%% We want to ensure people can't use this mechanism to send a message -%% to an arbitrary process and kill it! -deliver_reply_local(Pid, Key, Delivery) -> - case pg_local:in_group(rabbit_channels, Pid) of - true -> gen_server2:cast(Pid, {deliver_reply, Key, Delivery}); - false -> ok - end. - -declare_fast_reply_to(<<"amq.rabbitmq.reply-to">>) -> - exists; -declare_fast_reply_to(<<"amq.rabbitmq.reply-to.", Rest/binary>>) -> - case decode_fast_reply_to(Rest) of - {ok, Pid, Key} -> - Msg = {declare_fast_reply_to, Key}, - rabbit_misc:with_exit_handler( - rabbit_misc:const(not_found), - fun() -> gen_server2:call(Pid, Msg, infinity) end); - error -> - not_found - end; -declare_fast_reply_to(_) -> - not_found. - -decode_fast_reply_to(Rest) -> - case string:tokens(binary_to_list(Rest), ".") of - [PidEnc, Key] -> Pid = binary_to_term(base64:decode(PidEnc)), - {ok, Pid, Key}; - _ -> error - end. - -send_credit_reply(Pid, Len) -> - gen_server2:cast(Pid, {send_credit_reply, Len}). - -send_drained(Pid, CTagCredit) -> - gen_server2:cast(Pid, {send_drained, CTagCredit}). - -list() -> - rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running), - rabbit_channel, list_local, []). - -list_local() -> - pg_local:get_members(rabbit_channels). - -info_keys() -> ?INFO_KEYS. - -info(Pid) -> - gen_server2:call(Pid, info, infinity). - -info(Pid, Items) -> - case gen_server2:call(Pid, {info, Items}, infinity) of - {ok, Res} -> Res; - {error, Error} -> throw(Error) - end. - -info_all() -> - rabbit_misc:filter_exit_map(fun (C) -> info(C) end, list()). - -info_all(Items) -> - rabbit_misc:filter_exit_map(fun (C) -> info(C, Items) end, list()). - -refresh_config_local() -> - rabbit_misc:upmap( - fun (C) -> gen_server2:call(C, refresh_config, infinity) end, - list_local()), - ok. - -ready_for_close(Pid) -> - gen_server2:cast(Pid, ready_for_close). - -force_event_refresh(Ref) -> - [gen_server2:cast(C, {force_event_refresh, Ref}) || C <- list()], - ok. - -%%--------------------------------------------------------------------------- - -init([Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, VHost, - Capabilities, CollectorPid, LimiterPid]) -> - process_flag(trap_exit, true), - ?store_proc_name({ConnName, Channel}), - ok = pg_local:join(rabbit_channels, self()), - State = #ch{state = starting, - protocol = Protocol, - channel = Channel, - reader_pid = ReaderPid, - writer_pid = WriterPid, - conn_pid = ConnPid, - conn_name = ConnName, - limiter = rabbit_limiter:new(LimiterPid), - tx = none, - next_tag = 1, - unacked_message_q = queue:new(), - user = User, - virtual_host = VHost, - most_recently_declared_queue = <<>>, - queue_names = dict:new(), - queue_monitors = pmon:new(), - consumer_mapping = dict:new(), - queue_consumers = dict:new(), - delivering_queues = sets:new(), - queue_collector_pid = CollectorPid, - confirm_enabled = false, - publish_seqno = 1, - unconfirmed = dtree:empty(), - confirmed = [], - mandatory = dtree:empty(), - capabilities = Capabilities, - trace_state = rabbit_trace:init(VHost), - consumer_prefetch = 0, - reply_consumer = none}, - State1 = rabbit_event:init_stats_timer(State, #ch.stats_timer), - rabbit_event:notify(channel_created, infos(?CREATION_EVENT_KEYS, State1)), - rabbit_event:if_enabled(State1, #ch.stats_timer, - fun() -> emit_stats(State1) end), - {ok, State1, hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. - -prioritise_call(Msg, _From, _Len, _State) -> - case Msg of - info -> 9; - {info, _Items} -> 9; - _ -> 0 - end. - -prioritise_cast(Msg, _Len, _State) -> - case Msg of - {confirm, _MsgSeqNos, _QPid} -> 5; - {mandatory_received, _MsgSeqNo, _QPid} -> 5; - _ -> 0 - end. - -prioritise_info(Msg, _Len, _State) -> - case Msg of - emit_stats -> 7; - _ -> 0 - end. - -handle_call(flush, _From, State) -> - reply(ok, State); - -handle_call(info, _From, State) -> - reply(infos(?INFO_KEYS, State), State); - -handle_call({info, Items}, _From, State) -> - try - reply({ok, infos(Items, State)}, State) - catch Error -> reply({error, Error}, State) - end; - -handle_call(refresh_config, _From, State = #ch{virtual_host = VHost}) -> - reply(ok, State#ch{trace_state = rabbit_trace:init(VHost)}); - -handle_call({declare_fast_reply_to, Key}, _From, - State = #ch{reply_consumer = Consumer}) -> - reply(case Consumer of - {_, _, Key} -> exists; - _ -> not_found - end, State); - -handle_call(_Request, _From, State) -> - noreply(State). - -handle_cast({method, Method, Content, Flow}, - State = #ch{reader_pid = Reader, - virtual_host = VHost}) -> - case Flow of - flow -> credit_flow:ack(Reader); - noflow -> ok - end, - try handle_method(rabbit_channel_interceptor:intercept_method( - expand_shortcuts(Method, State), VHost), - Content, State) of - {reply, Reply, NewState} -> - ok = send(Reply, NewState), - noreply(NewState); - {noreply, NewState} -> - noreply(NewState); - stop -> - {stop, normal, State} - catch - exit:Reason = #amqp_error{} -> - MethodName = rabbit_misc:method_record_type(Method), - handle_exception(Reason#amqp_error{method = MethodName}, State); - _:Reason -> - {stop, {Reason, erlang:get_stacktrace()}, State} - end; - -handle_cast(ready_for_close, State = #ch{state = closing, - writer_pid = WriterPid}) -> - ok = rabbit_writer:send_command_sync(WriterPid, #'channel.close_ok'{}), - {stop, normal, State}; - -handle_cast(terminate, State = #ch{writer_pid = WriterPid}) -> - ok = rabbit_writer:flush(WriterPid), - {stop, normal, State}; - -handle_cast({command, #'basic.consume_ok'{consumer_tag = CTag} = Msg}, State) -> - ok = send(Msg, State), - noreply(consumer_monitor(CTag, State)); - -handle_cast({command, Msg}, State) -> - ok = send(Msg, State), - noreply(State); - -handle_cast({deliver, _CTag, _AckReq, _Msg}, State = #ch{state = closing}) -> - noreply(State); -handle_cast({deliver, ConsumerTag, AckRequired, - Msg = {_QName, QPid, _MsgId, Redelivered, - #basic_message{exchange_name = ExchangeName, - routing_keys = [RoutingKey | _CcRoutes], - content = Content}}}, - State = #ch{writer_pid = WriterPid, - next_tag = DeliveryTag}) -> - ok = rabbit_writer:send_command_and_notify( - WriterPid, QPid, self(), - #'basic.deliver'{consumer_tag = ConsumerTag, - delivery_tag = DeliveryTag, - redelivered = Redelivered, - exchange = ExchangeName#resource.name, - routing_key = RoutingKey}, - Content), - rabbit_basic:maybe_gc_large_msg(Content), - noreply(record_sent(ConsumerTag, AckRequired, Msg, State)); - -handle_cast({deliver_reply, _K, _Del}, State = #ch{state = closing}) -> - noreply(State); -handle_cast({deliver_reply, _K, _Del}, State = #ch{reply_consumer = none}) -> - noreply(State); -handle_cast({deliver_reply, Key, #delivery{message = - #basic_message{exchange_name = ExchangeName, - routing_keys = [RoutingKey | _CcRoutes], - content = Content}}}, - State = #ch{writer_pid = WriterPid, - next_tag = DeliveryTag, - reply_consumer = {ConsumerTag, _Suffix, Key}}) -> - ok = rabbit_writer:send_command( - WriterPid, - #'basic.deliver'{consumer_tag = ConsumerTag, - delivery_tag = DeliveryTag, - redelivered = false, - exchange = ExchangeName#resource.name, - routing_key = RoutingKey}, - Content), - noreply(State); -handle_cast({deliver_reply, _K1, _}, State=#ch{reply_consumer = {_, _, _K2}}) -> - noreply(State); - -handle_cast({send_credit_reply, Len}, State = #ch{writer_pid = WriterPid}) -> - ok = rabbit_writer:send_command( - WriterPid, #'basic.credit_ok'{available = Len}), - noreply(State); - -handle_cast({send_drained, CTagCredit}, State = #ch{writer_pid = WriterPid}) -> - [ok = rabbit_writer:send_command( - WriterPid, #'basic.credit_drained'{consumer_tag = ConsumerTag, - credit_drained = CreditDrained}) - || {ConsumerTag, CreditDrained} <- CTagCredit], - noreply(State); - -handle_cast({force_event_refresh, Ref}, State) -> - rabbit_event:notify(channel_created, infos(?CREATION_EVENT_KEYS, State), - Ref), - noreply(rabbit_event:init_stats_timer(State, #ch.stats_timer)); - -handle_cast({mandatory_received, MsgSeqNo}, State = #ch{mandatory = Mand}) -> - %% NB: don't call noreply/1 since we don't want to send confirms. - noreply_coalesce(State#ch{mandatory = dtree:drop(MsgSeqNo, Mand)}); - -handle_cast({confirm, MsgSeqNos, QPid}, State = #ch{unconfirmed = UC}) -> - {MXs, UC1} = dtree:take(MsgSeqNos, QPid, UC), - %% NB: don't call noreply/1 since we don't want to send confirms. - noreply_coalesce(record_confirms(MXs, State#ch{unconfirmed = UC1})). - -handle_info({bump_credit, Msg}, State) -> - credit_flow:handle_bump_msg(Msg), - noreply(State); - -handle_info(timeout, State) -> - noreply(State); - -handle_info(emit_stats, State) -> - emit_stats(State), - State1 = rabbit_event:reset_stats_timer(State, #ch.stats_timer), - %% NB: don't call noreply/1 since we don't want to kick off the - %% stats timer. - {noreply, send_confirms(State1), hibernate}; - -handle_info({'DOWN', _MRef, process, QPid, Reason}, State) -> - State1 = handle_publishing_queue_down(QPid, Reason, State), - State3 = handle_consuming_queue_down(QPid, State1), - State4 = handle_delivering_queue_down(QPid, State3), - credit_flow:peer_down(QPid), - #ch{queue_names = QNames, queue_monitors = QMons} = State4, - case dict:find(QPid, QNames) of - {ok, QName} -> erase_queue_stats(QName); - error -> ok - end, - noreply(State4#ch{queue_names = dict:erase(QPid, QNames), - queue_monitors = pmon:erase(QPid, QMons)}); - -handle_info({'EXIT', _Pid, Reason}, State) -> - {stop, Reason, State}. - -handle_pre_hibernate(State) -> - ok = clear_permission_cache(), - rabbit_event:if_enabled( - State, #ch.stats_timer, - fun () -> emit_stats(State, [{idle_since, now()}]) end), - {hibernate, rabbit_event:stop_stats_timer(State, #ch.stats_timer)}. - -terminate(Reason, State) -> - {Res, _State1} = notify_queues(State), - case Reason of - normal -> ok = Res; - shutdown -> ok = Res; - {shutdown, _Term} -> ok = Res; - _ -> ok - end, - pg_local:leave(rabbit_channels, self()), - rabbit_event:if_enabled(State, #ch.stats_timer, - fun() -> emit_stats(State) end), - rabbit_event:notify(channel_closed, [{pid, self()}]). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ). - -%%--------------------------------------------------------------------------- - -reply(Reply, NewState) -> {reply, Reply, next_state(NewState), hibernate}. - -noreply(NewState) -> {noreply, next_state(NewState), hibernate}. - -next_state(State) -> ensure_stats_timer(send_confirms(State)). - -noreply_coalesce(State = #ch{confirmed = C}) -> - Timeout = case C of [] -> hibernate; _ -> 0 end, - {noreply, ensure_stats_timer(State), Timeout}. - -ensure_stats_timer(State) -> - rabbit_event:ensure_stats_timer(State, #ch.stats_timer, emit_stats). - -return_ok(State, true, _Msg) -> {noreply, State}; -return_ok(State, false, Msg) -> {reply, Msg, State}. - -ok_msg(true, _Msg) -> undefined; -ok_msg(false, Msg) -> Msg. - -send(_Command, #ch{state = closing}) -> - ok; -send(Command, #ch{writer_pid = WriterPid}) -> - ok = rabbit_writer:send_command(WriterPid, Command). - -handle_exception(Reason, State = #ch{protocol = Protocol, - channel = Channel, - writer_pid = WriterPid, - reader_pid = ReaderPid, - conn_pid = ConnPid, - conn_name = ConnName, - virtual_host = VHost, - user = User}) -> - %% something bad's happened: notify_queues may not be 'ok' - {_Result, State1} = notify_queues(State), - case rabbit_binary_generator:map_exception(Channel, Reason, Protocol) of - {Channel, CloseMethod} -> - rabbit_log:error("Channel error on connection ~p (~s, vhost: '~s'," - " user: '~s'), channel ~p:~n~p~n", - [ConnPid, ConnName, VHost, User#user.username, - Channel, Reason]), - ok = rabbit_writer:send_command(WriterPid, CloseMethod), - {noreply, State1}; - {0, _} -> - ReaderPid ! {channel_exit, Channel, Reason}, - {stop, normal, State1} - end. - --ifdef(use_specs). --spec(precondition_failed/1 :: (string()) -> no_return()). --endif. -precondition_failed(Format) -> precondition_failed(Format, []). - --ifdef(use_specs). --spec(precondition_failed/2 :: (string(), [any()]) -> no_return()). --endif. -precondition_failed(Format, Params) -> - rabbit_misc:protocol_error(precondition_failed, Format, Params). - -return_queue_declare_ok(#resource{name = ActualName}, - NoWait, MessageCount, ConsumerCount, State) -> - return_ok(State#ch{most_recently_declared_queue = ActualName}, NoWait, - #'queue.declare_ok'{queue = ActualName, - message_count = MessageCount, - consumer_count = ConsumerCount}). - -check_resource_access(User, Resource, Perm) -> - V = {Resource, Perm}, - Cache = case get(permission_cache) of - undefined -> []; - Other -> Other - end, - case lists:member(V, Cache) of - true -> ok; - false -> ok = rabbit_access_control:check_resource_access( - User, Resource, Perm), - CacheTail = lists:sublist(Cache, ?MAX_PERMISSION_CACHE_SIZE-1), - put(permission_cache, [V | CacheTail]) - end. - -clear_permission_cache() -> erase(permission_cache), - ok. - -check_configure_permitted(Resource, #ch{user = User}) -> - check_resource_access(User, Resource, configure). - -check_write_permitted(Resource, #ch{user = User}) -> - check_resource_access(User, Resource, write). - -check_read_permitted(Resource, #ch{user = User}) -> - check_resource_access(User, Resource, read). - -check_user_id_header(#'P_basic'{user_id = undefined}, _) -> - ok; -check_user_id_header(#'P_basic'{user_id = Username}, - #ch{user = #user{username = Username}}) -> - ok; -check_user_id_header( - #'P_basic'{}, #ch{user = #user{authz_backends = - [{rabbit_auth_backend_dummy, _}]}}) -> - ok; -check_user_id_header(#'P_basic'{user_id = Claimed}, - #ch{user = #user{username = Actual, - tags = Tags}}) -> - case lists:member(impersonator, Tags) of - true -> ok; - false -> precondition_failed( - "user_id property set to '~s' but authenticated user was " - "'~s'", [Claimed, Actual]) - end. - -check_expiration_header(Props) -> - case rabbit_basic:parse_expiration(Props) of - {ok, _} -> ok; - {error, E} -> precondition_failed("invalid expiration '~s': ~p", - [Props#'P_basic'.expiration, E]) - end. - -check_internal_exchange(#exchange{name = Name, internal = true}) -> - rabbit_misc:protocol_error(access_refused, - "cannot publish to internal ~s", - [rabbit_misc:rs(Name)]); -check_internal_exchange(_) -> - ok. - -check_msg_size(Content) -> - Size = rabbit_basic:maybe_gc_large_msg(Content), - case Size > ?MAX_MSG_SIZE of - true -> precondition_failed("message size ~B larger than max size ~B", - [Size, ?MAX_MSG_SIZE]); - false -> ok - end. - -qbin_to_resource(QueueNameBin, State) -> - name_to_resource(queue, QueueNameBin, State). - -name_to_resource(Type, NameBin, #ch{virtual_host = VHostPath}) -> - rabbit_misc:r(VHostPath, Type, NameBin). - -expand_queue_name_shortcut(<<>>, #ch{most_recently_declared_queue = <<>>}) -> - rabbit_misc:protocol_error(not_found, "no previously declared queue", []); -expand_queue_name_shortcut(<<>>, #ch{most_recently_declared_queue = MRDQ}) -> - MRDQ; -expand_queue_name_shortcut(QueueNameBin, _) -> - QueueNameBin. - -expand_routing_key_shortcut(<<>>, <<>>, - #ch{most_recently_declared_queue = <<>>}) -> - rabbit_misc:protocol_error(not_found, "no previously declared queue", []); -expand_routing_key_shortcut(<<>>, <<>>, - #ch{most_recently_declared_queue = MRDQ}) -> - MRDQ; -expand_routing_key_shortcut(_QueueNameBin, RoutingKey, _State) -> - RoutingKey. - -expand_shortcuts(#'basic.get' {queue = Q} = M, State) -> - M#'basic.get' {queue = expand_queue_name_shortcut(Q, State)}; -expand_shortcuts(#'basic.consume'{queue = Q} = M, State) -> - M#'basic.consume'{queue = expand_queue_name_shortcut(Q, State)}; -expand_shortcuts(#'queue.delete' {queue = Q} = M, State) -> - M#'queue.delete' {queue = expand_queue_name_shortcut(Q, State)}; -expand_shortcuts(#'queue.purge' {queue = Q} = M, State) -> - M#'queue.purge' {queue = expand_queue_name_shortcut(Q, State)}; -expand_shortcuts(#'queue.bind' {queue = Q, routing_key = K} = M, State) -> - M#'queue.bind' {queue = expand_queue_name_shortcut(Q, State), - routing_key = expand_routing_key_shortcut(Q, K, State)}; -expand_shortcuts(#'queue.unbind' {queue = Q, routing_key = K} = M, State) -> - M#'queue.unbind' {queue = expand_queue_name_shortcut(Q, State), - routing_key = expand_routing_key_shortcut(Q, K, State)}; -expand_shortcuts(M, _State) -> - M. - -check_not_default_exchange(#resource{kind = exchange, name = <<"">>}) -> - rabbit_misc:protocol_error( - access_refused, "operation not permitted on the default exchange", []); -check_not_default_exchange(_) -> - ok. - -check_exchange_deletion(XName = #resource{name = <<"amq.rabbitmq.", _/binary>>, - kind = exchange}) -> - rabbit_misc:protocol_error( - access_refused, "deletion of system ~s not allowed", - [rabbit_misc:rs(XName)]); -check_exchange_deletion(_) -> - ok. - -%% check that an exchange/queue name does not contain the reserved -%% "amq." prefix. -%% -%% As per the AMQP 0-9-1 spec, the exclusion of "amq." prefixed names -%% only applies on actual creation, and not in the cases where the -%% entity already exists or passive=true. -%% -%% NB: We deliberately do not enforce the other constraints on names -%% required by the spec. -check_name(Kind, NameBin = <<"amq.", _/binary>>) -> - rabbit_misc:protocol_error( - access_refused, - "~s name '~s' contains reserved prefix 'amq.*'",[Kind, NameBin]); -check_name(_Kind, NameBin) -> - NameBin. - -maybe_set_fast_reply_to( - C = #content{properties = P = #'P_basic'{reply_to = - <<"amq.rabbitmq.reply-to">>}}, - #ch{reply_consumer = ReplyConsumer}) -> - case ReplyConsumer of - none -> rabbit_misc:protocol_error( - precondition_failed, - "fast reply consumer does not exist", []); - {_, Suf, _K} -> Rep = <<"amq.rabbitmq.reply-to.", Suf/binary>>, - rabbit_binary_generator:clear_encoded_content( - C#content{properties = P#'P_basic'{reply_to = Rep}}) - end; -maybe_set_fast_reply_to(C, _State) -> - C. - -record_confirms([], State) -> - State; -record_confirms(MXs, State = #ch{confirmed = C}) -> - State#ch{confirmed = [MXs | C]}. - -handle_method(#'channel.open'{}, _, State = #ch{state = starting}) -> - %% Don't leave "starting" as the state for 5s. TODO is this TRTTD? - State1 = State#ch{state = running}, - rabbit_event:if_enabled(State1, #ch.stats_timer, - fun() -> emit_stats(State1) end), - {reply, #'channel.open_ok'{}, State1}; - -handle_method(#'channel.open'{}, _, _State) -> - rabbit_misc:protocol_error( - command_invalid, "second 'channel.open' seen", []); - -handle_method(_Method, _, #ch{state = starting}) -> - rabbit_misc:protocol_error(channel_error, "expected 'channel.open'", []); - -handle_method(#'channel.close_ok'{}, _, #ch{state = closing}) -> - stop; - -handle_method(#'channel.close'{}, _, State = #ch{writer_pid = WriterPid, - state = closing}) -> - ok = rabbit_writer:send_command(WriterPid, #'channel.close_ok'{}), - {noreply, State}; - -handle_method(_Method, _, State = #ch{state = closing}) -> - {noreply, State}; - -handle_method(#'channel.close'{}, _, State = #ch{reader_pid = ReaderPid}) -> - {ok, State1} = notify_queues(State), - %% We issue the channel.close_ok response after a handshake with - %% the reader, the other half of which is ready_for_close. That - %% way the reader forgets about the channel before we send the - %% response (and this channel process terminates). If we didn't do - %% that, a channel.open for the same channel number, which a - %% client is entitled to send as soon as it has received the - %% close_ok, might be received by the reader before it has seen - %% the termination and hence be sent to the old, now dead/dying - %% channel process, instead of a new process, and thus lost. - ReaderPid ! {channel_closing, self()}, - {noreply, State1}; - -%% Even though the spec prohibits the client from sending commands -%% while waiting for the reply to a synchronous command, we generally -%% do allow this...except in the case of a pending tx.commit, where -%% it could wreak havoc. -handle_method(_Method, _, #ch{tx = Tx}) - when Tx =:= committing orelse Tx =:= failed -> - rabbit_misc:protocol_error( - channel_error, "unexpected command while processing 'tx.commit'", []); - -handle_method(#'access.request'{},_, State) -> - {reply, #'access.request_ok'{ticket = 1}, State}; - -handle_method(#'basic.publish'{immediate = true}, _Content, _State) -> - rabbit_misc:protocol_error(not_implemented, "immediate=true", []); - -handle_method(#'basic.publish'{exchange = ExchangeNameBin, - routing_key = RoutingKey, - mandatory = Mandatory}, - Content, State = #ch{virtual_host = VHostPath, - tx = Tx, - channel = ChannelNum, - confirm_enabled = ConfirmEnabled, - trace_state = TraceState, - user = #user{username = Username}, - conn_name = ConnName}) -> - check_msg_size(Content), - ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_write_permitted(ExchangeName, State), - Exchange = rabbit_exchange:lookup_or_die(ExchangeName), - check_internal_exchange(Exchange), - %% We decode the content's properties here because we're almost - %% certain to want to look at delivery-mode and priority. - DecodedContent = #content {properties = Props} = - maybe_set_fast_reply_to( - rabbit_binary_parser:ensure_content_decoded(Content), State), - check_user_id_header(Props, State), - check_expiration_header(Props), - DoConfirm = Tx =/= none orelse ConfirmEnabled, - {MsgSeqNo, State1} = - case DoConfirm orelse Mandatory of - false -> {undefined, State}; - true -> SeqNo = State#ch.publish_seqno, - {SeqNo, State#ch{publish_seqno = SeqNo + 1}} - end, - case rabbit_basic:message(ExchangeName, RoutingKey, DecodedContent) of - {ok, Message} -> - rabbit_trace:tap_in(Message, ConnName, ChannelNum, - Username, TraceState), - Delivery = rabbit_basic:delivery( - Mandatory, DoConfirm, Message, MsgSeqNo), - QNames = rabbit_exchange:route(Exchange, Delivery), - DQ = {Delivery, QNames}, - {noreply, case Tx of - none -> deliver_to_queues(DQ, State1); - {Msgs, Acks} -> Msgs1 = queue:in(DQ, Msgs), - State1#ch{tx = {Msgs1, Acks}} - end}; - {error, Reason} -> - precondition_failed("invalid message: ~p", [Reason]) - end; - -handle_method(#'basic.nack'{delivery_tag = DeliveryTag, - multiple = Multiple, - requeue = Requeue}, _, State) -> - reject(DeliveryTag, Requeue, Multiple, State); - -handle_method(#'basic.ack'{delivery_tag = DeliveryTag, - multiple = Multiple}, - _, State = #ch{unacked_message_q = UAMQ, tx = Tx}) -> - {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), - State1 = State#ch{unacked_message_q = Remaining}, - {noreply, case Tx of - none -> ack(Acked, State1), - State1; - {Msgs, Acks} -> Acks1 = ack_cons(ack, Acked, Acks), - State1#ch{tx = {Msgs, Acks1}} - end}; - -handle_method(#'basic.get'{queue = QueueNameBin, no_ack = NoAck}, - _, State = #ch{writer_pid = WriterPid, - conn_pid = ConnPid, - limiter = Limiter, - next_tag = DeliveryTag}) -> - QueueName = qbin_to_resource(QueueNameBin, State), - check_read_permitted(QueueName, State), - case rabbit_amqqueue:with_exclusive_access_or_die( - QueueName, ConnPid, - fun (Q) -> rabbit_amqqueue:basic_get( - Q, self(), NoAck, rabbit_limiter:pid(Limiter)) - end) of - {ok, MessageCount, - Msg = {QName, QPid, _MsgId, Redelivered, - #basic_message{exchange_name = ExchangeName, - routing_keys = [RoutingKey | _CcRoutes], - content = Content}}} -> - ok = rabbit_writer:send_command( - WriterPid, - #'basic.get_ok'{delivery_tag = DeliveryTag, - redelivered = Redelivered, - exchange = ExchangeName#resource.name, - routing_key = RoutingKey, - message_count = MessageCount}, - Content), - State1 = monitor_delivering_queue(NoAck, QPid, QName, State), - {noreply, record_sent(none, not(NoAck), Msg, State1)}; - empty -> - {reply, #'basic.get_empty'{}, State} - end; - -handle_method(#'basic.consume'{queue = <<"amq.rabbitmq.reply-to">>, - consumer_tag = CTag0, - no_ack = NoAck, - nowait = NoWait}, - _, State = #ch{reply_consumer = ReplyConsumer, - consumer_mapping = ConsumerMapping}) -> - case dict:find(CTag0, ConsumerMapping) of - error -> - case {ReplyConsumer, NoAck} of - {none, true} -> - CTag = case CTag0 of - <<>> -> rabbit_guid:binary( - rabbit_guid:gen_secure(), "amq.ctag"); - Other -> Other - end, - %% Precalculate both suffix and key; base64 encoding is - %% expensive - Key = base64:encode(rabbit_guid:gen_secure()), - PidEnc = base64:encode(term_to_binary(self())), - Suffix = <<PidEnc/binary, ".", Key/binary>>, - Consumer = {CTag, Suffix, binary_to_list(Key)}, - State1 = State#ch{reply_consumer = Consumer}, - case NoWait of - true -> {noreply, State1}; - false -> Rep = #'basic.consume_ok'{consumer_tag = CTag}, - {reply, Rep, State1} - end; - {_, false} -> - rabbit_misc:protocol_error( - precondition_failed, - "reply consumer cannot acknowledge", []); - _ -> - rabbit_misc:protocol_error( - precondition_failed, "reply consumer already set", []) - end; - {ok, _} -> - %% Attempted reuse of consumer tag. - rabbit_misc:protocol_error( - not_allowed, "attempt to reuse consumer tag '~s'", [CTag0]) - end; - -handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, nowait = NoWait}, - _, State = #ch{reply_consumer = {ConsumerTag, _, _}}) -> - State1 = State#ch{reply_consumer = none}, - case NoWait of - true -> {noreply, State1}; - false -> Rep = #'basic.cancel_ok'{consumer_tag = ConsumerTag}, - {reply, Rep, State1} - end; - -handle_method(#'basic.consume'{queue = QueueNameBin, - consumer_tag = ConsumerTag, - no_local = _, % FIXME: implement - no_ack = NoAck, - exclusive = ExclusiveConsume, - nowait = NoWait, - arguments = Args}, - _, State = #ch{consumer_prefetch = ConsumerPrefetch, - consumer_mapping = ConsumerMapping}) -> - case dict:find(ConsumerTag, ConsumerMapping) of - error -> - QueueName = qbin_to_resource(QueueNameBin, State), - check_read_permitted(QueueName, State), - ActualConsumerTag = - case ConsumerTag of - <<>> -> rabbit_guid:binary(rabbit_guid:gen_secure(), - "amq.ctag"); - Other -> Other - end, - case basic_consume( - QueueName, NoAck, ConsumerPrefetch, ActualConsumerTag, - ExclusiveConsume, Args, NoWait, State) of - {ok, State1} -> - {noreply, State1}; - {error, exclusive_consume_unavailable} -> - rabbit_misc:protocol_error( - access_refused, "~s in exclusive use", - [rabbit_misc:rs(QueueName)]) - end; - {ok, _} -> - %% Attempted reuse of consumer tag. - rabbit_misc:protocol_error( - not_allowed, "attempt to reuse consumer tag '~s'", [ConsumerTag]) - end; - -handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, nowait = NoWait}, - _, State = #ch{consumer_mapping = ConsumerMapping, - queue_consumers = QCons}) -> - OkMsg = #'basic.cancel_ok'{consumer_tag = ConsumerTag}, - case dict:find(ConsumerTag, ConsumerMapping) of - error -> - %% Spec requires we ignore this situation. - return_ok(State, NoWait, OkMsg); - {ok, {Q = #amqqueue{pid = QPid}, _CParams}} -> - ConsumerMapping1 = dict:erase(ConsumerTag, ConsumerMapping), - QCons1 = - case dict:find(QPid, QCons) of - error -> QCons; - {ok, CTags} -> CTags1 = gb_sets:delete(ConsumerTag, CTags), - case gb_sets:is_empty(CTags1) of - true -> dict:erase(QPid, QCons); - false -> dict:store(QPid, CTags1, QCons) - end - end, - NewState = State#ch{consumer_mapping = ConsumerMapping1, - queue_consumers = QCons1}, - %% In order to ensure that no more messages are sent to - %% the consumer after the cancel_ok has been sent, we get - %% the queue process to send the cancel_ok on our - %% behalf. If we were sending the cancel_ok ourselves it - %% might overtake a message sent previously by the queue. - case rabbit_misc:with_exit_handler( - fun () -> {error, not_found} end, - fun () -> - rabbit_amqqueue:basic_cancel( - Q, self(), ConsumerTag, ok_msg(NoWait, OkMsg)) - end) of - ok -> - {noreply, NewState}; - {error, not_found} -> - %% Spec requires we ignore this situation. - return_ok(NewState, NoWait, OkMsg) - end - end; - -handle_method(#'basic.qos'{prefetch_size = Size}, _, _State) when Size /= 0 -> - rabbit_misc:protocol_error(not_implemented, - "prefetch_size!=0 (~w)", [Size]); - -handle_method(#'basic.qos'{global = false, - prefetch_count = PrefetchCount}, _, State) -> - {reply, #'basic.qos_ok'{}, State#ch{consumer_prefetch = PrefetchCount}}; - -handle_method(#'basic.qos'{global = true, - prefetch_count = 0}, - _, State = #ch{limiter = Limiter}) -> - Limiter1 = rabbit_limiter:unlimit_prefetch(Limiter), - {reply, #'basic.qos_ok'{}, State#ch{limiter = Limiter1}}; - -handle_method(#'basic.qos'{global = true, - prefetch_count = PrefetchCount}, - _, State = #ch{limiter = Limiter, unacked_message_q = UAMQ}) -> - %% TODO queue:len(UAMQ) is not strictly right since that counts - %% unacked messages from basic.get too. Pretty obscure though. - Limiter1 = rabbit_limiter:limit_prefetch(Limiter, - PrefetchCount, queue:len(UAMQ)), - case ((not rabbit_limiter:is_active(Limiter)) andalso - rabbit_limiter:is_active(Limiter1)) of - true -> rabbit_amqqueue:activate_limit_all( - consumer_queues(State#ch.consumer_mapping), self()); - false -> ok - end, - {reply, #'basic.qos_ok'{}, State#ch{limiter = Limiter1}}; - -handle_method(#'basic.recover_async'{requeue = true}, - _, State = #ch{unacked_message_q = UAMQ, limiter = Limiter}) -> - OkFun = fun () -> ok end, - UAMQL = queue:to_list(UAMQ), - foreach_per_queue( - fun (QPid, MsgIds) -> - rabbit_misc:with_exit_handler( - OkFun, - fun () -> rabbit_amqqueue:requeue(QPid, MsgIds, self()) end) - end, lists:reverse(UAMQL)), - ok = notify_limiter(Limiter, UAMQL), - %% No answer required - basic.recover is the newer, synchronous - %% variant of this method - {noreply, State#ch{unacked_message_q = queue:new()}}; - -handle_method(#'basic.recover_async'{requeue = false}, _, _State) -> - rabbit_misc:protocol_error(not_implemented, "requeue=false", []); - -handle_method(#'basic.recover'{requeue = Requeue}, Content, State) -> - {noreply, State1} = handle_method(#'basic.recover_async'{requeue = Requeue}, - Content, State), - {reply, #'basic.recover_ok'{}, State1}; - -handle_method(#'basic.reject'{delivery_tag = DeliveryTag, requeue = Requeue}, - _, State) -> - reject(DeliveryTag, Requeue, false, State); - -handle_method(#'exchange.declare'{exchange = ExchangeNameBin, - type = TypeNameBin, - passive = false, - durable = Durable, - auto_delete = AutoDelete, - internal = Internal, - nowait = NoWait, - arguments = Args}, - _, State = #ch{virtual_host = VHostPath}) -> - CheckedType = rabbit_exchange:check_type(TypeNameBin), - ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_not_default_exchange(ExchangeName), - check_configure_permitted(ExchangeName, State), - X = case rabbit_exchange:lookup(ExchangeName) of - {ok, FoundX} -> FoundX; - {error, not_found} -> - check_name('exchange', ExchangeNameBin), - AeKey = <<"alternate-exchange">>, - case rabbit_misc:r_arg(VHostPath, exchange, Args, AeKey) of - undefined -> ok; - {error, {invalid_type, Type}} -> - precondition_failed( - "invalid type '~s' for arg '~s' in ~s", - [Type, AeKey, rabbit_misc:rs(ExchangeName)]); - AName -> check_read_permitted(ExchangeName, State), - check_write_permitted(AName, State), - ok - end, - rabbit_exchange:declare(ExchangeName, - CheckedType, - Durable, - AutoDelete, - Internal, - Args) - end, - ok = rabbit_exchange:assert_equivalence(X, CheckedType, Durable, - AutoDelete, Internal, Args), - return_ok(State, NoWait, #'exchange.declare_ok'{}); - -handle_method(#'exchange.declare'{exchange = ExchangeNameBin, - passive = true, - nowait = NoWait}, - _, State = #ch{virtual_host = VHostPath}) -> - ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_not_default_exchange(ExchangeName), - _ = rabbit_exchange:lookup_or_die(ExchangeName), - return_ok(State, NoWait, #'exchange.declare_ok'{}); - -handle_method(#'exchange.delete'{exchange = ExchangeNameBin, - if_unused = IfUnused, - nowait = NoWait}, - _, State = #ch{virtual_host = VHostPath}) -> - ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_not_default_exchange(ExchangeName), - check_exchange_deletion(ExchangeName), - check_configure_permitted(ExchangeName, State), - case rabbit_exchange:delete(ExchangeName, IfUnused) of - {error, not_found} -> - return_ok(State, NoWait, #'exchange.delete_ok'{}); - {error, in_use} -> - precondition_failed("~s in use", [rabbit_misc:rs(ExchangeName)]); - ok -> - return_ok(State, NoWait, #'exchange.delete_ok'{}) - end; - -handle_method(#'exchange.bind'{destination = DestinationNameBin, - source = SourceNameBin, - routing_key = RoutingKey, - nowait = NoWait, - arguments = Arguments}, _, State) -> - binding_action(fun rabbit_binding:add/2, - SourceNameBin, exchange, DestinationNameBin, RoutingKey, - Arguments, #'exchange.bind_ok'{}, NoWait, State); - -handle_method(#'exchange.unbind'{destination = DestinationNameBin, - source = SourceNameBin, - routing_key = RoutingKey, - nowait = NoWait, - arguments = Arguments}, _, State) -> - binding_action(fun rabbit_binding:remove/2, - SourceNameBin, exchange, DestinationNameBin, RoutingKey, - Arguments, #'exchange.unbind_ok'{}, NoWait, State); - -%% Note that all declares to these are effectively passive. If it -%% exists it by definition has one consumer. -handle_method(#'queue.declare'{queue = <<"amq.rabbitmq.reply-to", - _/binary>> = QueueNameBin, - nowait = NoWait}, _, - State = #ch{virtual_host = VHost}) -> - QueueName = rabbit_misc:r(VHost, queue, QueueNameBin), - case declare_fast_reply_to(QueueNameBin) of - exists -> return_queue_declare_ok(QueueName, NoWait, 0, 1, State); - not_found -> rabbit_misc:not_found(QueueName) - end; - -handle_method(#'queue.declare'{queue = QueueNameBin, - passive = false, - durable = DurableDeclare, - exclusive = ExclusiveDeclare, - auto_delete = AutoDelete, - nowait = NoWait, - arguments = Args} = Declare, - _, State = #ch{virtual_host = VHostPath, - conn_pid = ConnPid, - queue_collector_pid = CollectorPid}) -> - Owner = case ExclusiveDeclare of - true -> ConnPid; - false -> none - end, - Durable = DurableDeclare andalso not ExclusiveDeclare, - ActualNameBin = case QueueNameBin of - <<>> -> rabbit_guid:binary(rabbit_guid:gen_secure(), - "amq.gen"); - Other -> check_name('queue', Other) - end, - QueueName = rabbit_misc:r(VHostPath, queue, ActualNameBin), - check_configure_permitted(QueueName, State), - case rabbit_amqqueue:with( - QueueName, - fun (Q) -> ok = rabbit_amqqueue:assert_equivalence( - Q, Durable, AutoDelete, Args, Owner), - maybe_stat(NoWait, Q) - end) of - {ok, MessageCount, ConsumerCount} -> - return_queue_declare_ok(QueueName, NoWait, MessageCount, - ConsumerCount, State); - {error, not_found} -> - DlxKey = <<"x-dead-letter-exchange">>, - case rabbit_misc:r_arg(VHostPath, exchange, Args, DlxKey) of - undefined -> - ok; - {error, {invalid_type, Type}} -> - precondition_failed( - "invalid type '~s' for arg '~s' in ~s", - [Type, DlxKey, rabbit_misc:rs(QueueName)]); - DLX -> - check_read_permitted(QueueName, State), - check_write_permitted(DLX, State), - ok - end, - case rabbit_amqqueue:declare(QueueName, Durable, AutoDelete, - Args, Owner) of - {new, #amqqueue{pid = QPid}} -> - %% We need to notify the reader within the channel - %% process so that we can be sure there are no - %% outstanding exclusive queues being declared as - %% the connection shuts down. - ok = case Owner of - none -> ok; - _ -> rabbit_queue_collector:register( - CollectorPid, QPid) - end, - return_queue_declare_ok(QueueName, NoWait, 0, 0, State); - {existing, _Q} -> - %% must have been created between the stat and the - %% declare. Loop around again. - handle_method(Declare, none, State); - {absent, Q, Reason} -> - rabbit_misc:absent(Q, Reason); - {owner_died, _Q} -> - %% Presumably our own days are numbered since the - %% connection has died. Pretend the queue exists though, - %% just so nothing fails. - return_queue_declare_ok(QueueName, NoWait, 0, 0, State) - end; - {error, {absent, Q, Reason}} -> - rabbit_misc:absent(Q, Reason) - end; - -handle_method(#'queue.declare'{queue = QueueNameBin, - passive = true, - nowait = NoWait}, - _, State = #ch{virtual_host = VHostPath, - conn_pid = ConnPid}) -> - QueueName = rabbit_misc:r(VHostPath, queue, QueueNameBin), - {{ok, MessageCount, ConsumerCount}, #amqqueue{} = Q} = - rabbit_amqqueue:with_or_die( - QueueName, fun (Q) -> {maybe_stat(NoWait, Q), Q} end), - ok = rabbit_amqqueue:check_exclusive_access(Q, ConnPid), - return_queue_declare_ok(QueueName, NoWait, MessageCount, ConsumerCount, - State); - -handle_method(#'queue.delete'{queue = QueueNameBin, - if_unused = IfUnused, - if_empty = IfEmpty, - nowait = NoWait}, - _, State = #ch{conn_pid = ConnPid}) -> - QueueName = qbin_to_resource(QueueNameBin, State), - check_configure_permitted(QueueName, State), - case rabbit_amqqueue:with( - QueueName, - fun (Q) -> - rabbit_amqqueue:check_exclusive_access(Q, ConnPid), - rabbit_amqqueue:delete(Q, IfUnused, IfEmpty) - end, - fun (not_found) -> {ok, 0}; - ({absent, Q, crashed}) -> rabbit_amqqueue:delete_crashed(Q), - {ok, 0}; - ({absent, Q, Reason}) -> rabbit_misc:absent(Q, Reason) - end) of - {error, in_use} -> - precondition_failed("~s in use", [rabbit_misc:rs(QueueName)]); - {error, not_empty} -> - precondition_failed("~s not empty", [rabbit_misc:rs(QueueName)]); - {ok, PurgedMessageCount} -> - return_ok(State, NoWait, - #'queue.delete_ok'{message_count = PurgedMessageCount}) - end; - -handle_method(#'queue.bind'{queue = QueueNameBin, - exchange = ExchangeNameBin, - routing_key = RoutingKey, - nowait = NoWait, - arguments = Arguments}, _, State) -> - binding_action(fun rabbit_binding:add/2, - ExchangeNameBin, queue, QueueNameBin, RoutingKey, Arguments, - #'queue.bind_ok'{}, NoWait, State); - -handle_method(#'queue.unbind'{queue = QueueNameBin, - exchange = ExchangeNameBin, - routing_key = RoutingKey, - arguments = Arguments}, _, State) -> - binding_action(fun rabbit_binding:remove/2, - ExchangeNameBin, queue, QueueNameBin, RoutingKey, Arguments, - #'queue.unbind_ok'{}, false, State); - -handle_method(#'queue.purge'{queue = QueueNameBin, nowait = NoWait}, - _, State = #ch{conn_pid = ConnPid}) -> - QueueName = qbin_to_resource(QueueNameBin, State), - check_read_permitted(QueueName, State), - {ok, PurgedMessageCount} = rabbit_amqqueue:with_exclusive_access_or_die( - QueueName, ConnPid, - fun (Q) -> rabbit_amqqueue:purge(Q) end), - return_ok(State, NoWait, - #'queue.purge_ok'{message_count = PurgedMessageCount}); - -handle_method(#'tx.select'{}, _, #ch{confirm_enabled = true}) -> - precondition_failed("cannot switch from confirm to tx mode"); - -handle_method(#'tx.select'{}, _, State = #ch{tx = none}) -> - {reply, #'tx.select_ok'{}, State#ch{tx = new_tx()}}; - -handle_method(#'tx.select'{}, _, State) -> - {reply, #'tx.select_ok'{}, State}; - -handle_method(#'tx.commit'{}, _, #ch{tx = none}) -> - precondition_failed("channel is not transactional"); - -handle_method(#'tx.commit'{}, _, State = #ch{tx = {Msgs, Acks}, - limiter = Limiter}) -> - State1 = rabbit_misc:queue_fold(fun deliver_to_queues/2, State, Msgs), - Rev = fun (X) -> lists:reverse(lists:sort(X)) end, - lists:foreach(fun ({ack, A}) -> ack(Rev(A), State1); - ({Requeue, A}) -> reject(Requeue, Rev(A), Limiter) - end, lists:reverse(Acks)), - {noreply, maybe_complete_tx(State1#ch{tx = committing})}; - -handle_method(#'tx.rollback'{}, _, #ch{tx = none}) -> - precondition_failed("channel is not transactional"); - -handle_method(#'tx.rollback'{}, _, State = #ch{unacked_message_q = UAMQ, - tx = {_Msgs, Acks}}) -> - AcksL = lists:append(lists:reverse([lists:reverse(L) || {_, L} <- Acks])), - UAMQ1 = queue:from_list(lists:usort(AcksL ++ queue:to_list(UAMQ))), - {reply, #'tx.rollback_ok'{}, State#ch{unacked_message_q = UAMQ1, - tx = new_tx()}}; - -handle_method(#'confirm.select'{}, _, #ch{tx = {_, _}}) -> - precondition_failed("cannot switch from tx to confirm mode"); - -handle_method(#'confirm.select'{nowait = NoWait}, _, State) -> - return_ok(State#ch{confirm_enabled = true}, - NoWait, #'confirm.select_ok'{}); - -handle_method(#'channel.flow'{active = true}, _, State) -> - {reply, #'channel.flow_ok'{active = true}, State}; - -handle_method(#'channel.flow'{active = false}, _, _State) -> - rabbit_misc:protocol_error(not_implemented, "active=false", []); - -handle_method(#'basic.credit'{consumer_tag = CTag, - credit = Credit, - drain = Drain}, - _, State = #ch{consumer_mapping = Consumers}) -> - case dict:find(CTag, Consumers) of - {ok, {Q, _CParams}} -> ok = rabbit_amqqueue:credit( - Q, self(), CTag, Credit, Drain), - {noreply, State}; - error -> precondition_failed( - "unknown consumer tag '~s'", [CTag]) - end; - -handle_method(_MethodRecord, _Content, _State) -> - rabbit_misc:protocol_error( - command_invalid, "unimplemented method", []). - -%%---------------------------------------------------------------------------- - -%% We get the queue process to send the consume_ok on our behalf. This -%% is for symmetry with basic.cancel - see the comment in that method -%% for why. -basic_consume(QueueName, NoAck, ConsumerPrefetch, ActualConsumerTag, - ExclusiveConsume, Args, NoWait, - State = #ch{conn_pid = ConnPid, - limiter = Limiter, - consumer_mapping = ConsumerMapping}) -> - case rabbit_amqqueue:with_exclusive_access_or_die( - QueueName, ConnPid, - fun (Q) -> - {rabbit_amqqueue:basic_consume( - Q, NoAck, self(), - rabbit_limiter:pid(Limiter), - rabbit_limiter:is_active(Limiter), - ConsumerPrefetch, ActualConsumerTag, - ExclusiveConsume, Args, - ok_msg(NoWait, #'basic.consume_ok'{ - consumer_tag = ActualConsumerTag})), - Q} - end) of - {ok, Q = #amqqueue{pid = QPid, name = QName}} -> - CM1 = dict:store( - ActualConsumerTag, - {Q, {NoAck, ConsumerPrefetch, ExclusiveConsume, Args}}, - ConsumerMapping), - State1 = monitor_delivering_queue( - NoAck, QPid, QName, - State#ch{consumer_mapping = CM1}), - {ok, case NoWait of - true -> consumer_monitor(ActualConsumerTag, State1); - false -> State1 - end}; - {{error, exclusive_consume_unavailable} = E, _Q} -> - E - end. - -maybe_stat(false, Q) -> rabbit_amqqueue:stat(Q); -maybe_stat(true, _Q) -> {ok, 0, 0}. - -consumer_monitor(ConsumerTag, - State = #ch{consumer_mapping = ConsumerMapping, - queue_monitors = QMons, - queue_consumers = QCons}) -> - {#amqqueue{pid = QPid}, _CParams} = - dict:fetch(ConsumerTag, ConsumerMapping), - QCons1 = dict:update(QPid, fun (CTags) -> - gb_sets:insert(ConsumerTag, CTags) - end, - gb_sets:singleton(ConsumerTag), QCons), - State#ch{queue_monitors = pmon:monitor(QPid, QMons), - queue_consumers = QCons1}. - -monitor_delivering_queue(NoAck, QPid, QName, - State = #ch{queue_names = QNames, - queue_monitors = QMons, - delivering_queues = DQ}) -> - State#ch{queue_names = dict:store(QPid, QName, QNames), - queue_monitors = pmon:monitor(QPid, QMons), - delivering_queues = case NoAck of - true -> DQ; - false -> sets:add_element(QPid, DQ) - end}. - -handle_publishing_queue_down(QPid, Reason, State = #ch{unconfirmed = UC, - mandatory = Mand}) -> - {MMsgs, Mand1} = dtree:take(QPid, Mand), - [basic_return(Msg, State, no_route) || {_, Msg} <- MMsgs], - State1 = State#ch{mandatory = Mand1}, - case rabbit_misc:is_abnormal_exit(Reason) of - true -> {MXs, UC1} = dtree:take_all(QPid, UC), - send_nacks(MXs, State1#ch{unconfirmed = UC1}); - false -> {MXs, UC1} = dtree:take(QPid, UC), - record_confirms(MXs, State1#ch{unconfirmed = UC1}) - - end. - -handle_consuming_queue_down(QPid, State = #ch{queue_consumers = QCons, - queue_names = QNames}) -> - ConsumerTags = case dict:find(QPid, QCons) of - error -> gb_sets:new(); - {ok, CTags} -> CTags - end, - gb_sets:fold( - fun (CTag, StateN = #ch{consumer_mapping = CMap}) -> - QName = dict:fetch(QPid, QNames), - case queue_down_consumer_action(CTag, CMap) of - remove -> - cancel_consumer(CTag, QName, StateN); - {recover, {NoAck, ConsumerPrefetch, Exclusive, Args}} -> - case catch basic_consume( %% [0] - QName, NoAck, ConsumerPrefetch, CTag, - Exclusive, Args, true, StateN) of - {ok, StateN1} -> StateN1; - _ -> cancel_consumer(CTag, QName, StateN) - end - end - end, State#ch{queue_consumers = dict:erase(QPid, QCons)}, ConsumerTags). - -%% [0] There is a slight danger here that if a queue is deleted and -%% then recreated again the reconsume will succeed even though it was -%% not an HA failover. But the likelihood is not great and most users -%% are unlikely to care. - -cancel_consumer(CTag, QName, State = #ch{capabilities = Capabilities, - consumer_mapping = CMap}) -> - case rabbit_misc:table_lookup( - Capabilities, <<"consumer_cancel_notify">>) of - {bool, true} -> ok = send(#'basic.cancel'{consumer_tag = CTag, - nowait = true}, State); - _ -> ok - end, - rabbit_event:notify(consumer_deleted, [{consumer_tag, CTag}, - {channel, self()}, - {queue, QName}]), - State#ch{consumer_mapping = dict:erase(CTag, CMap)}. - -queue_down_consumer_action(CTag, CMap) -> - {_, {_, _, _, Args} = ConsumeSpec} = dict:fetch(CTag, CMap), - case rabbit_misc:table_lookup(Args, <<"x-cancel-on-ha-failover">>) of - {bool, true} -> remove; - _ -> {recover, ConsumeSpec} - end. - -handle_delivering_queue_down(QPid, State = #ch{delivering_queues = DQ}) -> - State#ch{delivering_queues = sets:del_element(QPid, DQ)}. - -binding_action(Fun, ExchangeNameBin, DestinationType, DestinationNameBin, - RoutingKey, Arguments, ReturnMethod, NoWait, - State = #ch{virtual_host = VHostPath, - conn_pid = ConnPid }) -> - DestinationName = name_to_resource(DestinationType, DestinationNameBin, State), - check_write_permitted(DestinationName, State), - ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - [check_not_default_exchange(N) || N <- [DestinationName, ExchangeName]], - check_read_permitted(ExchangeName, State), - case Fun(#binding{source = ExchangeName, - destination = DestinationName, - key = RoutingKey, - args = Arguments}, - fun (_X, Q = #amqqueue{}) -> - try rabbit_amqqueue:check_exclusive_access(Q, ConnPid) - catch exit:Reason -> {error, Reason} - end; - (_X, #exchange{}) -> - ok - end) of - {error, {resources_missing, [{not_found, Name} | _]}} -> - rabbit_misc:not_found(Name); - {error, {resources_missing, [{absent, Q, Reason} | _]}} -> - rabbit_misc:absent(Q, Reason); - {error, binding_not_found} -> - rabbit_misc:protocol_error( - not_found, "no binding ~s between ~s and ~s", - [RoutingKey, rabbit_misc:rs(ExchangeName), - rabbit_misc:rs(DestinationName)]); - {error, {binding_invalid, Fmt, Args}} -> - rabbit_misc:protocol_error(precondition_failed, Fmt, Args); - {error, #amqp_error{} = Error} -> - rabbit_misc:protocol_error(Error); - ok -> return_ok(State, NoWait, ReturnMethod) - end. - -basic_return(#basic_message{exchange_name = ExchangeName, - routing_keys = [RoutingKey | _CcRoutes], - content = Content}, - State = #ch{protocol = Protocol, writer_pid = WriterPid}, - Reason) -> - ?INCR_STATS([{exchange_stats, ExchangeName, 1}], return_unroutable, State), - {_Close, ReplyCode, ReplyText} = Protocol:lookup_amqp_exception(Reason), - ok = rabbit_writer:send_command( - WriterPid, - #'basic.return'{reply_code = ReplyCode, - reply_text = ReplyText, - exchange = ExchangeName#resource.name, - routing_key = RoutingKey}, - Content). - -reject(DeliveryTag, Requeue, Multiple, - State = #ch{unacked_message_q = UAMQ, tx = Tx}) -> - {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), - State1 = State#ch{unacked_message_q = Remaining}, - {noreply, case Tx of - none -> reject(Requeue, Acked, State1#ch.limiter), - State1; - {Msgs, Acks} -> Acks1 = ack_cons(Requeue, Acked, Acks), - State1#ch{tx = {Msgs, Acks1}} - end}. - -%% NB: Acked is in youngest-first order -reject(Requeue, Acked, Limiter) -> - foreach_per_queue( - fun (QPid, MsgIds) -> - rabbit_amqqueue:reject(QPid, Requeue, MsgIds, self()) - end, Acked), - ok = notify_limiter(Limiter, Acked). - -record_sent(ConsumerTag, AckRequired, - Msg = {QName, QPid, MsgId, Redelivered, _Message}, - State = #ch{unacked_message_q = UAMQ, - next_tag = DeliveryTag, - trace_state = TraceState, - user = #user{username = Username}, - conn_name = ConnName, - channel = ChannelNum}) -> - ?INCR_STATS([{queue_stats, QName, 1}], case {ConsumerTag, AckRequired} of - {none, true} -> get; - {none, false} -> get_no_ack; - {_ , true} -> deliver; - {_ , false} -> deliver_no_ack - end, State), - case Redelivered of - true -> ?INCR_STATS([{queue_stats, QName, 1}], redeliver, State); - false -> ok - end, - rabbit_trace:tap_out(Msg, ConnName, ChannelNum, Username, TraceState), - UAMQ1 = case AckRequired of - true -> queue:in({DeliveryTag, ConsumerTag, {QPid, MsgId}}, - UAMQ); - false -> UAMQ - end, - State#ch{unacked_message_q = UAMQ1, next_tag = DeliveryTag + 1}. - -%% NB: returns acks in youngest-first order -collect_acks(Q, 0, true) -> - {lists:reverse(queue:to_list(Q)), queue:new()}; -collect_acks(Q, DeliveryTag, Multiple) -> - collect_acks([], [], Q, DeliveryTag, Multiple). - -collect_acks(ToAcc, PrefixAcc, Q, DeliveryTag, Multiple) -> - case queue:out(Q) of - {{value, UnackedMsg = {CurrentDeliveryTag, _ConsumerTag, _Msg}}, - QTail} -> - if CurrentDeliveryTag == DeliveryTag -> - {[UnackedMsg | ToAcc], - case PrefixAcc of - [] -> QTail; - _ -> queue:join( - queue:from_list(lists:reverse(PrefixAcc)), - QTail) - end}; - Multiple -> - collect_acks([UnackedMsg | ToAcc], PrefixAcc, - QTail, DeliveryTag, Multiple); - true -> - collect_acks(ToAcc, [UnackedMsg | PrefixAcc], - QTail, DeliveryTag, Multiple) - end; - {empty, _} -> - precondition_failed("unknown delivery tag ~w", [DeliveryTag]) - end. - -%% NB: Acked is in youngest-first order -ack(Acked, State = #ch{queue_names = QNames}) -> - foreach_per_queue( - fun (QPid, MsgIds) -> - ok = rabbit_amqqueue:ack(QPid, MsgIds, self()), - ?INCR_STATS(case dict:find(QPid, QNames) of - {ok, QName} -> Count = length(MsgIds), - [{queue_stats, QName, Count}]; - error -> [] - end, ack, State) - end, Acked), - ok = notify_limiter(State#ch.limiter, Acked). - -%% {Msgs, Acks} -%% -%% Msgs is a queue. -%% -%% Acks looks s.t. like this: -%% [{false,[5,4]},{true,[3]},{ack,[2,1]}, ...] -%% -%% Each element is a pair consisting of a tag and a list of -%% ack'ed/reject'ed msg ids. The tag is one of 'ack' (to ack), 'true' -%% (reject w requeue), 'false' (reject w/o requeue). The msg ids, as -%% well as the list overall, are in "most-recent (generally youngest) -%% ack first" order. -new_tx() -> {queue:new(), []}. - -notify_queues(State = #ch{state = closing}) -> - {ok, State}; -notify_queues(State = #ch{consumer_mapping = Consumers, - delivering_queues = DQ }) -> - QPids = sets:to_list( - sets:union(sets:from_list(consumer_queues(Consumers)), DQ)), - {rabbit_amqqueue:notify_down_all(QPids, self()), State#ch{state = closing}}. - -foreach_per_queue(_F, []) -> - ok; -foreach_per_queue(F, [{_DTag, _CTag, {QPid, MsgId}}]) -> %% common case - F(QPid, [MsgId]); -%% NB: UAL should be in youngest-first order; the tree values will -%% then be in oldest-first order -foreach_per_queue(F, UAL) -> - T = lists:foldl(fun ({_DTag, _CTag, {QPid, MsgId}}, T) -> - rabbit_misc:gb_trees_cons(QPid, MsgId, T) - end, gb_trees:empty(), UAL), - rabbit_misc:gb_trees_foreach(F, T). - -consumer_queues(Consumers) -> - lists:usort([QPid || {_Key, {#amqqueue{pid = QPid}, _CParams}} - <- dict:to_list(Consumers)]). - -%% tell the limiter about the number of acks that have been received -%% for messages delivered to subscribed consumers, but not acks for -%% messages sent in a response to a basic.get (identified by their -%% 'none' consumer tag) -notify_limiter(Limiter, Acked) -> - %% optimisation: avoid the potentially expensive 'foldl' in the - %% common case. - case rabbit_limiter:is_active(Limiter) of - false -> ok; - true -> case lists:foldl(fun ({_, none, _}, Acc) -> Acc; - ({_, _, _}, Acc) -> Acc + 1 - end, 0, Acked) of - 0 -> ok; - Count -> rabbit_limiter:ack(Limiter, Count) - end - end. - -deliver_to_queues({#delivery{message = #basic_message{exchange_name = XName}, - confirm = false, - mandatory = false}, - []}, State) -> %% optimisation - ?INCR_STATS([{exchange_stats, XName, 1}], publish, State), - State; -deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{ - exchange_name = XName}, - mandatory = Mandatory, - confirm = Confirm, - msg_seq_no = MsgSeqNo}, - DelQNames}, State = #ch{queue_names = QNames, - queue_monitors = QMons}) -> - Qs = rabbit_amqqueue:lookup(DelQNames), - DeliveredQPids = rabbit_amqqueue:deliver_flow(Qs, Delivery), - %% The pmon:monitor_all/2 monitors all queues to which we - %% delivered. But we want to monitor even queues we didn't deliver - %% to, since we need their 'DOWN' messages to clean - %% queue_names. So we also need to monitor each QPid from - %% queues. But that only gets the masters (which is fine for - %% cleaning queue_names), so we need the union of both. - %% - %% ...and we need to add even non-delivered queues to queue_names - %% since alternative algorithms to update queue_names less - %% frequently would in fact be more expensive in the common case. - {QNames1, QMons1} = - lists:foldl(fun (#amqqueue{pid = QPid, name = QName}, - {QNames0, QMons0}) -> - {case dict:is_key(QPid, QNames0) of - true -> QNames0; - false -> dict:store(QPid, QName, QNames0) - end, pmon:monitor(QPid, QMons0)} - end, {QNames, pmon:monitor_all(DeliveredQPids, QMons)}, Qs), - State1 = State#ch{queue_names = QNames1, - queue_monitors = QMons1}, - %% NB: the order here is important since basic.returns must be - %% sent before confirms. - State2 = process_routing_mandatory(Mandatory, DeliveredQPids, MsgSeqNo, - Message, State1), - State3 = process_routing_confirm( Confirm, DeliveredQPids, MsgSeqNo, - XName, State2), - ?INCR_STATS([{exchange_stats, XName, 1} | - [{queue_exchange_stats, {QName, XName}, 1} || - QPid <- DeliveredQPids, - {ok, QName} <- [dict:find(QPid, QNames1)]]], - publish, State3), - State3. - -process_routing_mandatory(false, _, _MsgSeqNo, _Msg, State) -> - State; -process_routing_mandatory(true, [], _MsgSeqNo, Msg, State) -> - ok = basic_return(Msg, State, no_route), - State; -process_routing_mandatory(true, QPids, MsgSeqNo, Msg, State) -> - State#ch{mandatory = dtree:insert(MsgSeqNo, QPids, Msg, - State#ch.mandatory)}. - -process_routing_confirm(false, _, _MsgSeqNo, _XName, State) -> - State; -process_routing_confirm(true, [], MsgSeqNo, XName, State) -> - record_confirms([{MsgSeqNo, XName}], State); -process_routing_confirm(true, QPids, MsgSeqNo, XName, State) -> - State#ch{unconfirmed = dtree:insert(MsgSeqNo, QPids, XName, - State#ch.unconfirmed)}. - -send_nacks([], State) -> - State; -send_nacks(_MXs, State = #ch{state = closing, - tx = none}) -> %% optimisation - State; -send_nacks(MXs, State = #ch{tx = none}) -> - coalesce_and_send([MsgSeqNo || {MsgSeqNo, _} <- MXs], - fun(MsgSeqNo, Multiple) -> - #'basic.nack'{delivery_tag = MsgSeqNo, - multiple = Multiple} - end, State); -send_nacks(_MXs, State = #ch{state = closing}) -> %% optimisation - State#ch{tx = failed}; -send_nacks(_, State) -> - maybe_complete_tx(State#ch{tx = failed}). - -send_confirms(State = #ch{tx = none, confirmed = []}) -> - State; -send_confirms(State = #ch{tx = none, confirmed = C}) -> - case rabbit_node_monitor:pause_minority_guard() of - ok -> MsgSeqNos = - lists:foldl( - fun ({MsgSeqNo, XName}, MSNs) -> - ?INCR_STATS([{exchange_stats, XName, 1}], - confirm, State), - [MsgSeqNo | MSNs] - end, [], lists:append(C)), - send_confirms(MsgSeqNos, State#ch{confirmed = []}); - pausing -> State - end; -send_confirms(State) -> - case rabbit_node_monitor:pause_minority_guard() of - ok -> maybe_complete_tx(State); - pausing -> State - end. - -send_confirms([], State) -> - State; -send_confirms(_Cs, State = #ch{state = closing}) -> %% optimisation - State; -send_confirms([MsgSeqNo], State) -> - ok = send(#'basic.ack'{delivery_tag = MsgSeqNo}, State), - State; -send_confirms(Cs, State) -> - coalesce_and_send(Cs, fun(MsgSeqNo, Multiple) -> - #'basic.ack'{delivery_tag = MsgSeqNo, - multiple = Multiple} - end, State). - -coalesce_and_send(MsgSeqNos, MkMsgFun, State = #ch{unconfirmed = UC}) -> - SMsgSeqNos = lists:usort(MsgSeqNos), - CutOff = case dtree:is_empty(UC) of - true -> lists:last(SMsgSeqNos) + 1; - false -> {SeqNo, _XName} = dtree:smallest(UC), SeqNo - end, - {Ms, Ss} = lists:splitwith(fun(X) -> X < CutOff end, SMsgSeqNos), - case Ms of - [] -> ok; - _ -> ok = send(MkMsgFun(lists:last(Ms), true), State) - end, - [ok = send(MkMsgFun(SeqNo, false), State) || SeqNo <- Ss], - State. - -ack_cons(Tag, Acked, [{Tag, Acks} | L]) -> [{Tag, Acked ++ Acks} | L]; -ack_cons(Tag, Acked, Acks) -> [{Tag, Acked} | Acks]. - -ack_len(Acks) -> lists:sum([length(L) || {ack, L} <- Acks]). - -maybe_complete_tx(State = #ch{tx = {_, _}}) -> - State; -maybe_complete_tx(State = #ch{unconfirmed = UC}) -> - case dtree:is_empty(UC) of - false -> State; - true -> complete_tx(State#ch{confirmed = []}) - end. - -complete_tx(State = #ch{tx = committing}) -> - ok = send(#'tx.commit_ok'{}, State), - State#ch{tx = new_tx()}; -complete_tx(State = #ch{tx = failed}) -> - {noreply, State1} = handle_exception( - rabbit_misc:amqp_error( - precondition_failed, "partial tx completion", [], - 'tx.commit'), - State), - State1#ch{tx = new_tx()}. - -infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. - -i(pid, _) -> self(); -i(connection, #ch{conn_pid = ConnPid}) -> ConnPid; -i(number, #ch{channel = Channel}) -> Channel; -i(user, #ch{user = User}) -> User#user.username; -i(vhost, #ch{virtual_host = VHost}) -> VHost; -i(transactional, #ch{tx = Tx}) -> Tx =/= none; -i(confirm, #ch{confirm_enabled = CE}) -> CE; -i(name, State) -> name(State); -i(consumer_count, #ch{consumer_mapping = CM}) -> dict:size(CM); -i(messages_unconfirmed, #ch{unconfirmed = UC}) -> dtree:size(UC); -i(messages_unacknowledged, #ch{unacked_message_q = UAMQ}) -> queue:len(UAMQ); -i(messages_uncommitted, #ch{tx = {Msgs, _Acks}}) -> queue:len(Msgs); -i(messages_uncommitted, #ch{}) -> 0; -i(acks_uncommitted, #ch{tx = {_Msgs, Acks}}) -> ack_len(Acks); -i(acks_uncommitted, #ch{}) -> 0; -i(state, #ch{state = running}) -> credit_flow:state(); -i(state, #ch{state = State}) -> State; -i(prefetch_count, #ch{consumer_prefetch = C}) -> C; -i(global_prefetch_count, #ch{limiter = Limiter}) -> - rabbit_limiter:get_prefetch_limit(Limiter); -i(Item, _) -> - throw({bad_argument, Item}). - -name(#ch{conn_name = ConnName, channel = Channel}) -> - list_to_binary(rabbit_misc:format("~s (~p)", [ConnName, Channel])). - -incr_stats(Incs, Measure) -> - [update_measures(Type, Key, Inc, Measure) || {Type, Key, Inc} <- Incs]. - -update_measures(Type, Key, Inc, Measure) -> - Measures = case get({Type, Key}) of - undefined -> []; - D -> D - end, - Cur = case orddict:find(Measure, Measures) of - error -> 0; - {ok, C} -> C - end, - put({Type, Key}, orddict:store(Measure, Cur + Inc, Measures)). - -emit_stats(State) -> emit_stats(State, []). - -emit_stats(State, Extra) -> - Coarse = infos(?STATISTICS_KEYS, State), - case rabbit_event:stats_level(State, #ch.stats_timer) of - coarse -> rabbit_event:notify(channel_stats, Extra ++ Coarse); - fine -> Fine = [{channel_queue_stats, - [{QName, Stats} || - {{queue_stats, QName}, Stats} <- get()]}, - {channel_exchange_stats, - [{XName, Stats} || - {{exchange_stats, XName}, Stats} <- get()]}, - {channel_queue_exchange_stats, - [{QX, Stats} || - {{queue_exchange_stats, QX}, Stats} <- get()]}], - rabbit_event:notify(channel_stats, Extra ++ Coarse ++ Fine) - end. - -erase_queue_stats(QName) -> - erase({queue_stats, QName}), - [erase({queue_exchange_stats, QX}) || - {{queue_exchange_stats, QX = {QName0, _}}, _} <- get(), - QName0 =:= QName]. diff --git a/src/rabbit_channel_interceptor.erl b/src/rabbit_channel_interceptor.erl deleted file mode 100644 index db9349ac..00000000 --- a/src/rabbit_channel_interceptor.erl +++ /dev/null @@ -1,91 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - -%% Since the AMQP methods used here are queue related, -%% maybe we want this to be a queue_interceptor. - --module(rabbit_channel_interceptor). - --include("rabbit_framing.hrl"). --include("rabbit.hrl"). - --export([intercept_method/2]). - --ifdef(use_specs). - --type(intercept_method() :: rabbit_framing:amqp_method_name()). --type(original_method() :: rabbit_framing:amqp_method_record()). --type(processed_method() :: rabbit_framing:amqp_method_record()). - --callback description() -> [proplists:property()]. - --callback intercept(original_method(), rabbit_types:vhost()) -> - processed_method() | rabbit_misc:channel_or_connection_exit(). - -%% Whether the interceptor wishes to intercept the amqp method --callback applies_to(intercept_method()) -> boolean(). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{description, 0}, {intercept, 2}, {applies_to, 1}]; -behaviour_info(_Other) -> - undefined. - --endif. - -%%---------------------------------------------------------------------------- - -intercept_method(#'basic.publish'{} = M, _VHost) -> M; -intercept_method(#'basic.ack'{} = M, _VHost) -> M; -intercept_method(#'basic.nack'{} = M, _VHost) -> M; -intercept_method(#'basic.reject'{} = M, _VHost) -> M; -intercept_method(#'basic.credit'{} = M, _VHost) -> M; -intercept_method(M, VHost) -> - intercept_method(M, VHost, select(rabbit_misc:method_record_type(M))). - -intercept_method(M, _VHost, []) -> - M; -intercept_method(M, VHost, [I]) -> - M2 = I:intercept(M, VHost), - case validate_method(M, M2) of - true -> - M2; - _ -> - internal_error("Interceptor: ~p expected " - "to return method: ~p but returned: ~p", - [I, rabbit_misc:method_record_type(M), - rabbit_misc:method_record_type(M2)]) - end; -intercept_method(M, _VHost, Is) -> - internal_error("More than one interceptor for method: ~p -- ~p", - [rabbit_misc:method_record_type(M), Is]). - -%% select the interceptors that apply to intercept_method(). -select(Method) -> - [M || {_, M} <- rabbit_registry:lookup_all(channel_interceptor), - code:which(M) =/= non_existing, - M:applies_to(Method)]. - -validate_method(M, M2) -> - rabbit_misc:method_record_type(M) =:= rabbit_misc:method_record_type(M2). - -%% keep dialyzer happy --spec internal_error(string(), [any()]) -> no_return(). -internal_error(Format, Args) -> - rabbit_misc:protocol_error(internal_error, Format, Args). diff --git a/src/rabbit_channel_sup.erl b/src/rabbit_channel_sup.erl deleted file mode 100644 index 448d17a2..00000000 --- a/src/rabbit_channel_sup.erl +++ /dev/null @@ -1,92 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_channel_sup). - --behaviour(supervisor2). - --export([start_link/1]). - --export([init/1]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([start_link_args/0]). - --type(start_link_args() :: - {'tcp', rabbit_net:socket(), rabbit_channel:channel_number(), - non_neg_integer(), pid(), string(), rabbit_types:protocol(), - rabbit_types:user(), rabbit_types:vhost(), rabbit_framing:amqp_table(), - pid()} | - {'direct', rabbit_channel:channel_number(), pid(), string(), - rabbit_types:protocol(), rabbit_types:user(), rabbit_types:vhost(), - rabbit_framing:amqp_table(), pid()}). - --spec(start_link/1 :: (start_link_args()) -> {'ok', pid(), {pid(), any()}}). - --endif. - -%%---------------------------------------------------------------------------- - -start_link({tcp, Sock, Channel, FrameMax, ReaderPid, ConnName, Protocol, User, - VHost, Capabilities, Collector}) -> - {ok, SupPid} = supervisor2:start_link( - ?MODULE, {tcp, Sock, Channel, FrameMax, - ReaderPid, Protocol, {ConnName, Channel}}), - [LimiterPid] = supervisor2:find_child(SupPid, limiter), - [WriterPid] = supervisor2:find_child(SupPid, writer), - {ok, ChannelPid} = - supervisor2:start_child( - SupPid, - {channel, {rabbit_channel, start_link, - [Channel, ReaderPid, WriterPid, ReaderPid, ConnName, - Protocol, User, VHost, Capabilities, Collector, - LimiterPid]}, - intrinsic, ?MAX_WAIT, worker, [rabbit_channel]}), - {ok, AState} = rabbit_command_assembler:init(Protocol), - {ok, SupPid, {ChannelPid, AState}}; -start_link({direct, Channel, ClientChannelPid, ConnPid, ConnName, Protocol, - User, VHost, Capabilities, Collector}) -> - {ok, SupPid} = supervisor2:start_link( - ?MODULE, {direct, {ConnName, Channel}}), - [LimiterPid] = supervisor2:find_child(SupPid, limiter), - {ok, ChannelPid} = - supervisor2:start_child( - SupPid, - {channel, {rabbit_channel, start_link, - [Channel, ClientChannelPid, ClientChannelPid, ConnPid, - ConnName, Protocol, User, VHost, Capabilities, Collector, - LimiterPid]}, - intrinsic, ?MAX_WAIT, worker, [rabbit_channel]}), - {ok, SupPid, {ChannelPid, none}}. - -%%---------------------------------------------------------------------------- - -init(Type) -> - {ok, {{one_for_all, 0, 1}, child_specs(Type)}}. - -child_specs({tcp, Sock, Channel, FrameMax, ReaderPid, Protocol, Identity}) -> - [{writer, {rabbit_writer, start_link, - [Sock, Channel, FrameMax, Protocol, ReaderPid, Identity, true]}, - intrinsic, ?MAX_WAIT, worker, [rabbit_writer]} - | child_specs({direct, Identity})]; -child_specs({direct, Identity}) -> - [{limiter, {rabbit_limiter, start_link, [Identity]}, - transient, ?MAX_WAIT, worker, [rabbit_limiter]}]. diff --git a/src/rabbit_channel_sup_sup.erl b/src/rabbit_channel_sup_sup.erl deleted file mode 100644 index d0e82548..00000000 --- a/src/rabbit_channel_sup_sup.erl +++ /dev/null @@ -1,48 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_channel_sup_sup). - --behaviour(supervisor2). - --export([start_link/0, start_channel/2]). - --export([init/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(start_channel/2 :: (pid(), rabbit_channel_sup:start_link_args()) -> - {'ok', pid(), {pid(), any()}}). - --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> - supervisor2:start_link(?MODULE, []). - -start_channel(Pid, Args) -> - supervisor2:start_child(Pid, [Args]). - -%%---------------------------------------------------------------------------- - -init([]) -> - {ok, {{simple_one_for_one, 0, 1}, - [{channel_sup, {rabbit_channel_sup, start_link, []}, - temporary, infinity, supervisor, [rabbit_channel_sup]}]}}. diff --git a/src/rabbit_cli.erl b/src/rabbit_cli.erl deleted file mode 100644 index 47505b3d..00000000 --- a/src/rabbit_cli.erl +++ /dev/null @@ -1,198 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_cli). --include("rabbit_cli.hrl"). - --export([main/3, parse_arguments/4, rpc_call/4]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(optdef() :: flag | {option, string()}). --type(parse_result() :: {'ok', {atom(), [{string(), string()}], [string()]}} | - 'no_command'). - - --spec(main/3 :: (fun (([string()], string()) -> parse_result()), - fun ((atom(), atom(), [any()], [any()]) -> any()), - atom()) -> no_return()). --spec(usage/1 :: (atom()) -> no_return()). --spec(parse_arguments/4 :: - ([{atom(), [{string(), optdef()}]} | atom()], - [{string(), optdef()}], string(), [string()]) -> parse_result()). --spec(rpc_call/4 :: (node(), atom(), atom(), [any()]) -> any()). - --endif. - -%%---------------------------------------------------------------------------- - -main(ParseFun, DoFun, UsageMod) -> - {ok, [[NodeStr|_]|_]} = init:get_argument(nodename), - {Command, Opts, Args} = - case ParseFun(init:get_plain_arguments(), NodeStr) of - {ok, Res} -> Res; - no_command -> print_error("could not recognise command", []), - usage(UsageMod) - end, - Node = proplists:get_value(?NODE_OPT, Opts), - PrintInvalidCommandError = - fun () -> - print_error("invalid command '~s'", - [string:join([atom_to_list(Command) | Args], " ")]) - end, - - %% The reason we don't use a try/catch here is that rpc:call turns - %% thrown errors into normal return values - case catch DoFun(Command, Node, Args, Opts) of - ok -> - rabbit_misc:quit(0); - {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> %% < R15 - PrintInvalidCommandError(), - usage(UsageMod); - {'EXIT', {function_clause, [{?MODULE, action, _, _} | _]}} -> %% >= R15 - PrintInvalidCommandError(), - usage(UsageMod); - {error, {missing_dependencies, Missing, Blame}} -> - print_error("dependent plugins ~p not found; used by ~p.", - [Missing, Blame]), - rabbit_misc:quit(2); - {'EXIT', {badarg, _}} -> - print_error("invalid parameter: ~p", [Args]), - usage(UsageMod); - {error, {Problem, Reason}} when is_atom(Problem), is_binary(Reason) -> - %% We handle this common case specially to avoid ~p since - %% that has i18n issues - print_error("~s: ~s", [Problem, Reason]), - rabbit_misc:quit(2); - {error, Reason} -> - print_error("~p", [Reason]), - rabbit_misc:quit(2); - {error_string, Reason} -> - print_error("~s", [Reason]), - rabbit_misc:quit(2); - {badrpc, {'EXIT', Reason}} -> - print_error("~p", [Reason]), - rabbit_misc:quit(2); - {badrpc, Reason} -> - print_error("unable to connect to node ~w: ~w", [Node, Reason]), - print_badrpc_diagnostics([Node]), - rabbit_misc:quit(2); - {badrpc_multi, Reason, Nodes} -> - print_error("unable to connect to nodes ~p: ~w", [Nodes, Reason]), - print_badrpc_diagnostics(Nodes), - rabbit_misc:quit(2); - Other -> - print_error("~p", [Other]), - rabbit_misc:quit(2) - end. - -usage(Mod) -> - io:format("~s", [Mod:usage()]), - rabbit_misc:quit(1). - -%%---------------------------------------------------------------------------- - -parse_arguments(Commands, GlobalDefs, NodeOpt, CmdLine) -> - case parse_arguments(Commands, GlobalDefs, CmdLine) of - {ok, {Cmd, Opts0, Args}} -> - Opts = [case K of - NodeOpt -> {NodeOpt, rabbit_nodes:make(V)}; - _ -> {K, V} - end || {K, V} <- Opts0], - {ok, {Cmd, Opts, Args}}; - E -> - E - end. - -%% Takes: -%% * A list of [{atom(), [{string(), optdef()]} | atom()], where the atom()s -%% are the accepted commands and the optional [string()] is the list of -%% accepted options for that command -%% * A list [{string(), optdef()}] of options valid for all commands -%% * The list of arguments given by the user -%% -%% Returns either {ok, {atom(), [{string(), string()}], [string()]} which are -%% respectively the command, the key-value pairs of the options and the leftover -%% arguments; or no_command if no command could be parsed. -parse_arguments(Commands, GlobalDefs, As) -> - lists:foldl(maybe_process_opts(GlobalDefs, As), no_command, Commands). - -maybe_process_opts(GDefs, As) -> - fun({C, Os}, no_command) -> - process_opts(atom_to_list(C), dict:from_list(GDefs ++ Os), As); - (C, no_command) -> - (maybe_process_opts(GDefs, As))({C, []}, no_command); - (_, {ok, Res}) -> - {ok, Res} - end. - -process_opts(C, Defs, As0) -> - KVs0 = dict:map(fun (_, flag) -> false; - (_, {option, V}) -> V - end, Defs), - process_opts(Defs, C, As0, not_found, KVs0, []). - -%% Consume flags/options until you find the correct command. If there are no -%% arguments or the first argument is not the command we're expecting, fail. -%% Arguments to this are: definitions, cmd we're looking for, args we -%% haven't parsed, whether we have found the cmd, options we've found, -%% plain args we've found. -process_opts(_Defs, C, [], found, KVs, Outs) -> - {ok, {list_to_atom(C), dict:to_list(KVs), lists:reverse(Outs)}}; -process_opts(_Defs, _C, [], not_found, _, _) -> - no_command; -process_opts(Defs, C, [A | As], Found, KVs, Outs) -> - OptType = case dict:find(A, Defs) of - error -> none; - {ok, flag} -> flag; - {ok, {option, _}} -> option - end, - case {OptType, C, Found} of - {flag, _, _} -> process_opts( - Defs, C, As, Found, dict:store(A, true, KVs), - Outs); - {option, _, _} -> case As of - [] -> no_command; - [V | As1] -> process_opts( - Defs, C, As1, Found, - dict:store(A, V, KVs), Outs) - end; - {none, A, _} -> process_opts(Defs, C, As, found, KVs, Outs); - {none, _, found} -> process_opts(Defs, C, As, found, KVs, [A | Outs]); - {none, _, _} -> no_command - end. - -%%---------------------------------------------------------------------------- - -fmt_stderr(Format, Args) -> rabbit_misc:format_stderr(Format ++ "~n", Args). - -print_error(Format, Args) -> fmt_stderr("Error: " ++ Format, Args). - -print_badrpc_diagnostics(Nodes) -> - fmt_stderr(rabbit_nodes:diagnostics(Nodes), []). - -%% If the server we are talking to has non-standard net_ticktime, and -%% our connection lasts a while, we could get disconnected because of -%% a timeout unless we set our ticktime to be the same. So let's do -%% that. -rpc_call(Node, Mod, Fun, Args) -> - case rpc:call(Node, net_kernel, get_net_ticktime, [], ?RPC_TIMEOUT) of - {badrpc, _} = E -> E; - Time -> net_kernel:set_net_ticktime(Time, 0), - rpc:call(Node, Mod, Fun, Args, ?RPC_TIMEOUT) - end. diff --git a/src/rabbit_client_sup.erl b/src/rabbit_client_sup.erl deleted file mode 100644 index dcf8c9e2..00000000 --- a/src/rabbit_client_sup.erl +++ /dev/null @@ -1,57 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_client_sup). - --behaviour(supervisor2). - --export([start_link/1, start_link/2, start_link_worker/2]). - --export([init/1]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/1 :: (rabbit_types:mfargs()) -> - rabbit_types:ok_pid_or_error()). --spec(start_link/2 :: ({'local', atom()}, rabbit_types:mfargs()) -> - rabbit_types:ok_pid_or_error()). --spec(start_link_worker/2 :: ({'local', atom()}, rabbit_types:mfargs()) -> - rabbit_types:ok_pid_or_error()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Callback) -> - supervisor2:start_link(?MODULE, Callback). - -start_link(SupName, Callback) -> - supervisor2:start_link(SupName, ?MODULE, Callback). - -start_link_worker(SupName, Callback) -> - supervisor2:start_link(SupName, ?MODULE, {Callback, worker}). - -init({M,F,A}) -> - {ok, {{simple_one_for_one, 0, 1}, - [{client, {M,F,A}, temporary, infinity, supervisor, [M]}]}}; -init({{M,F,A}, worker}) -> - {ok, {{simple_one_for_one, 0, 1}, - [{client, {M,F,A}, temporary, ?MAX_WAIT, worker, [M]}]}}. - diff --git a/src/rabbit_command_assembler.erl b/src/rabbit_command_assembler.erl deleted file mode 100644 index 20397cc0..00000000 --- a/src/rabbit_command_assembler.erl +++ /dev/null @@ -1,137 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_command_assembler). --include("rabbit_framing.hrl"). --include("rabbit.hrl"). - --export([analyze_frame/3, init/1, process/2]). - -%%---------------------------------------------------------------------------- - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([frame/0]). - --type(frame_type() :: ?FRAME_METHOD | ?FRAME_HEADER | ?FRAME_BODY | - ?FRAME_OOB_METHOD | ?FRAME_OOB_HEADER | ?FRAME_OOB_BODY | - ?FRAME_TRACE | ?FRAME_HEARTBEAT). --type(protocol() :: rabbit_framing:protocol()). --type(method() :: rabbit_framing:amqp_method_record()). --type(class_id() :: rabbit_framing:amqp_class_id()). --type(weight() :: non_neg_integer()). --type(body_size() :: non_neg_integer()). --type(content() :: rabbit_types:undecoded_content()). - --type(frame() :: - {'method', rabbit_framing:amqp_method_name(), binary()} | - {'content_header', class_id(), weight(), body_size(), binary()} | - {'content_body', binary()}). - --type(state() :: - {'method', protocol()} | - {'content_header', method(), class_id(), protocol()} | - {'content_body', method(), body_size(), class_id(), protocol()}). - --spec(analyze_frame/3 :: (frame_type(), binary(), protocol()) -> - frame() | 'heartbeat' | 'error'). - --spec(init/1 :: (protocol()) -> {ok, state()}). --spec(process/2 :: (frame(), state()) -> - {ok, state()} | - {ok, method(), state()} | - {ok, method(), content(), state()} | - {error, rabbit_types:amqp_error()}). - --endif. - -%%-------------------------------------------------------------------- - -analyze_frame(?FRAME_METHOD, - <<ClassId:16, MethodId:16, MethodFields/binary>>, - Protocol) -> - MethodName = Protocol:lookup_method_name({ClassId, MethodId}), - {method, MethodName, MethodFields}; -analyze_frame(?FRAME_HEADER, - <<ClassId:16, Weight:16, BodySize:64, Properties/binary>>, - _Protocol) -> - {content_header, ClassId, Weight, BodySize, Properties}; -analyze_frame(?FRAME_BODY, Body, _Protocol) -> - {content_body, Body}; -analyze_frame(?FRAME_HEARTBEAT, <<>>, _Protocol) -> - heartbeat; -analyze_frame(_Type, _Body, _Protocol) -> - error. - -init(Protocol) -> {ok, {method, Protocol}}. - -process({method, MethodName, FieldsBin}, {method, Protocol}) -> - try - Method = Protocol:decode_method_fields(MethodName, FieldsBin), - case Protocol:method_has_content(MethodName) of - true -> {ClassId, _MethodId} = Protocol:method_id(MethodName), - {ok, {content_header, Method, ClassId, Protocol}}; - false -> {ok, Method, {method, Protocol}} - end - catch exit:#amqp_error{} = Reason -> {error, Reason} - end; -process(_Frame, {method, _Protocol}) -> - unexpected_frame("expected method frame, " - "got non method frame instead", [], none); -process({content_header, ClassId, 0, 0, PropertiesBin}, - {content_header, Method, ClassId, Protocol}) -> - Content = empty_content(ClassId, PropertiesBin, Protocol), - {ok, Method, Content, {method, Protocol}}; -process({content_header, ClassId, 0, BodySize, PropertiesBin}, - {content_header, Method, ClassId, Protocol}) -> - Content = empty_content(ClassId, PropertiesBin, Protocol), - {ok, {content_body, Method, BodySize, Content, Protocol}}; -process({content_header, HeaderClassId, 0, _BodySize, _PropertiesBin}, - {content_header, Method, ClassId, _Protocol}) -> - unexpected_frame("expected content header for class ~w, " - "got one for class ~w instead", - [ClassId, HeaderClassId], Method); -process(_Frame, {content_header, Method, ClassId, _Protocol}) -> - unexpected_frame("expected content header for class ~w, " - "got non content header frame instead", [ClassId], Method); -process({content_body, FragmentBin}, - {content_body, Method, RemainingSize, - Content = #content{payload_fragments_rev = Fragments}, Protocol}) -> - NewContent = Content#content{ - payload_fragments_rev = [FragmentBin | Fragments]}, - case RemainingSize - size(FragmentBin) of - 0 -> {ok, Method, NewContent, {method, Protocol}}; - Sz -> {ok, {content_body, Method, Sz, NewContent, Protocol}} - end; -process(_Frame, {content_body, Method, _RemainingSize, _Content, _Protocol}) -> - unexpected_frame("expected content body, " - "got non content body frame instead", [], Method). - -%%-------------------------------------------------------------------- - -empty_content(ClassId, PropertiesBin, Protocol) -> - #content{class_id = ClassId, - properties = none, - properties_bin = PropertiesBin, - protocol = Protocol, - payload_fragments_rev = []}. - -unexpected_frame(Format, Params, Method) when is_atom(Method) -> - {error, rabbit_misc:amqp_error(unexpected_frame, Format, Params, Method)}; -unexpected_frame(Format, Params, Method) -> - unexpected_frame(Format, Params, rabbit_misc:method_record_type(Method)). diff --git a/src/rabbit_connection_helper_sup.erl b/src/rabbit_connection_helper_sup.erl deleted file mode 100644 index 85266bd6..00000000 --- a/src/rabbit_connection_helper_sup.erl +++ /dev/null @@ -1,59 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_connection_helper_sup). - --behaviour(supervisor2). - --export([start_link/0]). --export([start_channel_sup_sup/1, - start_queue_collector/2]). - --export([init/1]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(start_channel_sup_sup/1 :: (pid()) -> rabbit_types:ok_pid_or_error()). --spec(start_queue_collector/2 :: (pid(), rabbit_types:proc_name()) -> - rabbit_types:ok_pid_or_error()). --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> - supervisor2:start_link(?MODULE, []). - -start_channel_sup_sup(SupPid) -> - supervisor2:start_child( - SupPid, - {channel_sup_sup, {rabbit_channel_sup_sup, start_link, []}, - intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}). - -start_queue_collector(SupPid, Identity) -> - supervisor2:start_child( - SupPid, - {collector, {rabbit_queue_collector, start_link, [Identity]}, - intrinsic, ?MAX_WAIT, worker, [rabbit_queue_collector]}). - -%%---------------------------------------------------------------------------- - -init([]) -> - {ok, {{one_for_one, 10, 10}, []}}. - diff --git a/src/rabbit_connection_sup.erl b/src/rabbit_connection_sup.erl deleted file mode 100644 index 1dfdadae..00000000 --- a/src/rabbit_connection_sup.erl +++ /dev/null @@ -1,68 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_connection_sup). - --behaviour(supervisor2). - --export([start_link/0, reader/1]). - --export([init/1]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> {'ok', pid(), pid()}). --spec(reader/1 :: (pid()) -> pid()). - --endif. - -%%-------------------------------------------------------------------------- - -start_link() -> - {ok, SupPid} = supervisor2:start_link(?MODULE, []), - %% We need to get channels in the hierarchy here so they get shut - %% down after the reader, so the reader gets a chance to terminate - %% them cleanly. But for 1.0 readers we can't start the real - %% ch_sup_sup (because we don't know if we will be 0-9-1 or 1.0) - - %% so we add another supervisor into the hierarchy. - %% - %% This supervisor also acts as an intermediary for heartbeaters and - %% the queue collector process, since these must not be siblings of the - %% reader due to the potential for deadlock if they are added/restarted - %% whilst the supervision tree is shutting down. - {ok, HelperSup} = - supervisor2:start_child( - SupPid, - {helper_sup, {rabbit_connection_helper_sup, start_link, []}, - intrinsic, infinity, supervisor, [rabbit_connection_helper_sup]}), - {ok, ReaderPid} = - supervisor2:start_child( - SupPid, - {reader, {rabbit_reader, start_link, [HelperSup]}, - intrinsic, ?MAX_WAIT, worker, [rabbit_reader]}), - {ok, SupPid, ReaderPid}. - -reader(Pid) -> - hd(supervisor2:find_child(Pid, reader)). - -%%-------------------------------------------------------------------------- - -init([]) -> - {ok, {{one_for_all, 0, 1}, []}}. diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl deleted file mode 100644 index a931eef0..00000000 --- a/src/rabbit_control_main.erl +++ /dev/null @@ -1,722 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_control_main). --include("rabbit.hrl"). --include("rabbit_cli.hrl"). - --export([start/0, stop/0, parse_arguments/2, action/5, - sync_queue/1, cancel_sync_queue/1]). - --import(rabbit_cli, [rpc_call/4]). - --define(EXTERNAL_CHECK_INTERVAL, 1000). - --define(GLOBAL_DEFS(Node), [?QUIET_DEF, ?NODE_DEF(Node)]). - --define(COMMANDS, - [stop, - stop_app, - start_app, - wait, - reset, - force_reset, - rotate_logs, - - {join_cluster, [?RAM_DEF]}, - change_cluster_node_type, - update_cluster_nodes, - {forget_cluster_node, [?OFFLINE_DEF]}, - force_boot, - cluster_status, - {sync_queue, [?VHOST_DEF]}, - {cancel_sync_queue, [?VHOST_DEF]}, - - add_user, - delete_user, - change_password, - clear_password, - set_user_tags, - list_users, - - add_vhost, - delete_vhost, - list_vhosts, - {set_permissions, [?VHOST_DEF]}, - {clear_permissions, [?VHOST_DEF]}, - {list_permissions, [?VHOST_DEF]}, - list_user_permissions, - - {set_parameter, [?VHOST_DEF]}, - {clear_parameter, [?VHOST_DEF]}, - {list_parameters, [?VHOST_DEF]}, - - {set_policy, [?VHOST_DEF, ?PRIORITY_DEF, ?APPLY_TO_DEF]}, - {clear_policy, [?VHOST_DEF]}, - {list_policies, [?VHOST_DEF]}, - - {list_queues, [?VHOST_DEF]}, - {list_exchanges, [?VHOST_DEF]}, - {list_bindings, [?VHOST_DEF]}, - {list_connections, [?VHOST_DEF]}, - list_channels, - {list_consumers, [?VHOST_DEF]}, - status, - environment, - report, - set_cluster_name, - eval, - - close_connection, - {trace_on, [?VHOST_DEF]}, - {trace_off, [?VHOST_DEF]}, - set_vm_memory_high_watermark - ]). - --define(GLOBAL_QUERIES, - [{"Connections", rabbit_networking, connection_info_all, - connection_info_keys}, - {"Channels", rabbit_channel, info_all, info_keys}]). - --define(VHOST_QUERIES, - [{"Queues", rabbit_amqqueue, info_all, info_keys}, - {"Exchanges", rabbit_exchange, info_all, info_keys}, - {"Bindings", rabbit_binding, info_all, info_keys}, - {"Consumers", rabbit_amqqueue, consumers_all, consumer_info_keys}, - {"Permissions", rabbit_auth_backend_internal, list_vhost_permissions, - vhost_perms_info_keys}, - {"Policies", rabbit_policy, list_formatted, info_keys}, - {"Parameters", rabbit_runtime_parameters, list_formatted, info_keys}]). - --define(COMMANDS_NOT_REQUIRING_APP, - [stop, stop_app, start_app, wait, reset, force_reset, rotate_logs, - join_cluster, change_cluster_node_type, update_cluster_nodes, - forget_cluster_node, cluster_status, status, environment, eval, - force_boot]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start/0 :: () -> no_return()). --spec(stop/0 :: () -> 'ok'). --spec(action/5 :: - (atom(), node(), [string()], [{string(), any()}], - fun ((string(), [any()]) -> 'ok')) - -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start() -> - start_distribution(), - rabbit_cli:main( - fun (Args, NodeStr) -> - parse_arguments(Args, NodeStr) - end, - fun (Command, Node, Args, Opts) -> - Quiet = proplists:get_bool(?QUIET_OPT, Opts), - Inform = case Quiet of - true -> fun (_Format, _Args1) -> ok end; - false -> fun (Format, Args1) -> - io:format(Format ++ " ...~n", Args1) - end - end, - do_action(Command, Node, Args, Opts, Inform) - end, rabbit_ctl_usage). - -parse_arguments(CmdLine, NodeStr) -> - rabbit_cli:parse_arguments( - ?COMMANDS, ?GLOBAL_DEFS(NodeStr), ?NODE_OPT, CmdLine). - -print_report(Node, {Descr, Module, InfoFun, KeysFun}) -> - io:format("~s:~n", [Descr]), - print_report0(Node, {Module, InfoFun, KeysFun}, []). - -print_report(Node, {Descr, Module, InfoFun, KeysFun}, VHostArg) -> - io:format("~s on ~s:~n", [Descr, VHostArg]), - print_report0(Node, {Module, InfoFun, KeysFun}, VHostArg). - -print_report0(Node, {Module, InfoFun, KeysFun}, VHostArg) -> - case rpc_call(Node, Module, InfoFun, VHostArg) of - [_|_] = Results -> InfoItems = rpc_call(Node, Module, KeysFun, []), - display_row([atom_to_list(I) || I <- InfoItems]), - display_info_list(Results, InfoItems); - _ -> ok - end, - io:nl(). - -stop() -> - ok. - -%%---------------------------------------------------------------------------- - -do_action(Command, Node, Args, Opts, Inform) -> - case lists:member(Command, ?COMMANDS_NOT_REQUIRING_APP) of - false -> case ensure_app_running(Node) of - ok -> action(Command, Node, Args, Opts, Inform); - E -> E - end; - true -> action(Command, Node, Args, Opts, Inform) - end. - -action(stop, Node, Args, _Opts, Inform) -> - Inform("Stopping and halting node ~p", [Node]), - Res = call(Node, {rabbit, stop_and_halt, []}), - case {Res, Args} of - {ok, [PidFile]} -> wait_for_process_death( - read_pid_file(PidFile, false)); - {ok, [_, _| _]} -> exit({badarg, Args}); - _ -> ok - end, - Res; - -action(stop_app, Node, [], _Opts, Inform) -> - Inform("Stopping node ~p", [Node]), - call(Node, {rabbit, stop, []}); - -action(start_app, Node, [], _Opts, Inform) -> - Inform("Starting node ~p", [Node]), - call(Node, {rabbit, start, []}); - -action(reset, Node, [], _Opts, Inform) -> - Inform("Resetting node ~p", [Node]), - call(Node, {rabbit_mnesia, reset, []}); - -action(force_reset, Node, [], _Opts, Inform) -> - Inform("Forcefully resetting node ~p", [Node]), - call(Node, {rabbit_mnesia, force_reset, []}); - -action(join_cluster, Node, [ClusterNodeS], Opts, Inform) -> - ClusterNode = list_to_atom(ClusterNodeS), - NodeType = case proplists:get_bool(?RAM_OPT, Opts) of - true -> ram; - false -> disc - end, - Inform("Clustering node ~p with ~p", [Node, ClusterNode]), - rpc_call(Node, rabbit_mnesia, join_cluster, [ClusterNode, NodeType]); - -action(change_cluster_node_type, Node, ["ram"], _Opts, Inform) -> - Inform("Turning ~p into a ram node", [Node]), - rpc_call(Node, rabbit_mnesia, change_cluster_node_type, [ram]); -action(change_cluster_node_type, Node, [Type], _Opts, Inform) - when Type =:= "disc" orelse Type =:= "disk" -> - Inform("Turning ~p into a disc node", [Node]), - rpc_call(Node, rabbit_mnesia, change_cluster_node_type, [disc]); - -action(update_cluster_nodes, Node, [ClusterNodeS], _Opts, Inform) -> - ClusterNode = list_to_atom(ClusterNodeS), - Inform("Updating cluster nodes for ~p from ~p", [Node, ClusterNode]), - rpc_call(Node, rabbit_mnesia, update_cluster_nodes, [ClusterNode]); - -action(forget_cluster_node, Node, [ClusterNodeS], Opts, Inform) -> - ClusterNode = list_to_atom(ClusterNodeS), - RemoveWhenOffline = proplists:get_bool(?OFFLINE_OPT, Opts), - Inform("Removing node ~p from cluster", [ClusterNode]), - case RemoveWhenOffline of - true -> become(Node), - rabbit_mnesia:forget_cluster_node(ClusterNode, true); - false -> rpc_call(Node, rabbit_mnesia, forget_cluster_node, - [ClusterNode, false]) - end; - -action(force_boot, Node, [], _Opts, Inform) -> - Inform("Forcing boot for Mnesia dir ~s", [mnesia:system_info(directory)]), - case rabbit:is_running(Node) of - false -> rabbit_mnesia:force_load_next_boot(); - true -> {error, rabbit_running} - end; - -action(sync_queue, Node, [Q], Opts, Inform) -> - VHost = proplists:get_value(?VHOST_OPT, Opts), - QName = rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q)), - Inform("Synchronising ~s", [rabbit_misc:rs(QName)]), - rpc_call(Node, rabbit_control_main, sync_queue, [QName]); - -action(cancel_sync_queue, Node, [Q], Opts, Inform) -> - VHost = proplists:get_value(?VHOST_OPT, Opts), - QName = rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q)), - Inform("Stopping synchronising ~s", [rabbit_misc:rs(QName)]), - rpc_call(Node, rabbit_control_main, cancel_sync_queue, [QName]); - -action(wait, Node, [PidFile], _Opts, Inform) -> - Inform("Waiting for ~p", [Node]), - wait_for_application(Node, PidFile, rabbit_and_plugins, Inform); -action(wait, Node, [PidFile, App], _Opts, Inform) -> - Inform("Waiting for ~p on ~p", [App, Node]), - wait_for_application(Node, PidFile, list_to_atom(App), Inform); - -action(status, Node, [], _Opts, Inform) -> - Inform("Status of node ~p", [Node]), - display_call_result(Node, {rabbit, status, []}); - -action(cluster_status, Node, [], _Opts, Inform) -> - Inform("Cluster status of node ~p", [Node]), - display_call_result(Node, {rabbit_mnesia, status, []}); - -action(environment, Node, _App, _Opts, Inform) -> - Inform("Application environment of node ~p", [Node]), - display_call_result(Node, {rabbit, environment, []}); - -action(rotate_logs, Node, [], _Opts, Inform) -> - Inform("Reopening logs for node ~p", [Node]), - call(Node, {rabbit, rotate_logs, [""]}); -action(rotate_logs, Node, Args = [Suffix], _Opts, Inform) -> - Inform("Rotating logs to files with suffix \"~s\"", [Suffix]), - call(Node, {rabbit, rotate_logs, Args}); - -action(close_connection, Node, [PidStr, Explanation], _Opts, Inform) -> - Inform("Closing connection \"~s\"", [PidStr]), - rpc_call(Node, rabbit_networking, close_connection, - [rabbit_misc:string_to_pid(PidStr), Explanation]); - -action(add_user, Node, Args = [Username, _Password], _Opts, Inform) -> - Inform("Creating user \"~s\"", [Username]), - call(Node, {rabbit_auth_backend_internal, add_user, Args}); - -action(delete_user, Node, Args = [_Username], _Opts, Inform) -> - Inform("Deleting user \"~s\"", Args), - call(Node, {rabbit_auth_backend_internal, delete_user, Args}); - -action(change_password, Node, Args = [Username, _Newpassword], _Opts, Inform) -> - Inform("Changing password for user \"~s\"", [Username]), - call(Node, {rabbit_auth_backend_internal, change_password, Args}); - -action(clear_password, Node, Args = [Username], _Opts, Inform) -> - Inform("Clearing password for user \"~s\"", [Username]), - call(Node, {rabbit_auth_backend_internal, clear_password, Args}); - -action(set_user_tags, Node, [Username | TagsStr], _Opts, Inform) -> - Tags = [list_to_atom(T) || T <- TagsStr], - Inform("Setting tags for user \"~s\" to ~p", [Username, Tags]), - rpc_call(Node, rabbit_auth_backend_internal, set_tags, - [list_to_binary(Username), Tags]); - -action(list_users, Node, [], _Opts, Inform) -> - Inform("Listing users", []), - display_info_list( - call(Node, {rabbit_auth_backend_internal, list_users, []}), - rabbit_auth_backend_internal:user_info_keys()); - -action(add_vhost, Node, Args = [_VHostPath], _Opts, Inform) -> - Inform("Creating vhost \"~s\"", Args), - call(Node, {rabbit_vhost, add, Args}); - -action(delete_vhost, Node, Args = [_VHostPath], _Opts, Inform) -> - Inform("Deleting vhost \"~s\"", Args), - call(Node, {rabbit_vhost, delete, Args}); - -action(list_vhosts, Node, Args, _Opts, Inform) -> - Inform("Listing vhosts", []), - ArgAtoms = default_if_empty(Args, [name]), - display_info_list(call(Node, {rabbit_vhost, info_all, []}), ArgAtoms); - -action(list_user_permissions, Node, Args = [_Username], _Opts, Inform) -> - Inform("Listing permissions for user ~p", Args), - display_info_list(call(Node, {rabbit_auth_backend_internal, - list_user_permissions, Args}), - rabbit_auth_backend_internal:user_perms_info_keys()); - -action(list_queues, Node, Args, Opts, Inform) -> - Inform("Listing queues", []), - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - ArgAtoms = default_if_empty(Args, [name, messages]), - display_info_list(rpc_call(Node, rabbit_amqqueue, info_all, - [VHostArg, ArgAtoms]), - ArgAtoms); - -action(list_exchanges, Node, Args, Opts, Inform) -> - Inform("Listing exchanges", []), - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - ArgAtoms = default_if_empty(Args, [name, type]), - display_info_list(rpc_call(Node, rabbit_exchange, info_all, - [VHostArg, ArgAtoms]), - ArgAtoms); - -action(list_bindings, Node, Args, Opts, Inform) -> - Inform("Listing bindings", []), - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - ArgAtoms = default_if_empty(Args, [source_name, source_kind, - destination_name, destination_kind, - routing_key, arguments]), - display_info_list(rpc_call(Node, rabbit_binding, info_all, - [VHostArg, ArgAtoms]), - ArgAtoms); - -action(list_connections, Node, Args, _Opts, Inform) -> - Inform("Listing connections", []), - ArgAtoms = default_if_empty(Args, [user, peer_host, peer_port, state]), - display_info_list(rpc_call(Node, rabbit_networking, connection_info_all, - [ArgAtoms]), - ArgAtoms); - -action(list_channels, Node, Args, _Opts, Inform) -> - Inform("Listing channels", []), - ArgAtoms = default_if_empty(Args, [pid, user, consumer_count, - messages_unacknowledged]), - display_info_list(rpc_call(Node, rabbit_channel, info_all, [ArgAtoms]), - ArgAtoms); - -action(list_consumers, Node, _Args, Opts, Inform) -> - Inform("Listing consumers", []), - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - display_info_list(rpc_call(Node, rabbit_amqqueue, consumers_all, [VHostArg]), - rabbit_amqqueue:consumer_info_keys()); - -action(trace_on, Node, [], Opts, Inform) -> - VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Starting tracing for vhost \"~s\"", [VHost]), - rpc_call(Node, rabbit_trace, start, [list_to_binary(VHost)]); - -action(trace_off, Node, [], Opts, Inform) -> - VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Stopping tracing for vhost \"~s\"", [VHost]), - rpc_call(Node, rabbit_trace, stop, [list_to_binary(VHost)]); - -action(set_vm_memory_high_watermark, Node, [Arg], _Opts, Inform) -> - Frac = list_to_float(case string:chr(Arg, $.) of - 0 -> Arg ++ ".0"; - _ -> Arg - end), - Inform("Setting memory threshold on ~p to ~p", [Node, Frac]), - rpc_call(Node, vm_memory_monitor, set_vm_memory_high_watermark, [Frac]); - -action(set_permissions, Node, [Username, CPerm, WPerm, RPerm], Opts, Inform) -> - VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Setting permissions for user \"~s\" in vhost \"~s\"", - [Username, VHost]), - call(Node, {rabbit_auth_backend_internal, set_permissions, - [Username, VHost, CPerm, WPerm, RPerm]}); - -action(clear_permissions, Node, [Username], Opts, Inform) -> - VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Clearing permissions for user \"~s\" in vhost \"~s\"", - [Username, VHost]), - call(Node, {rabbit_auth_backend_internal, clear_permissions, - [Username, VHost]}); - -action(list_permissions, Node, [], Opts, Inform) -> - VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Listing permissions in vhost \"~s\"", [VHost]), - display_info_list(call(Node, {rabbit_auth_backend_internal, - list_vhost_permissions, [VHost]}), - rabbit_auth_backend_internal:vhost_perms_info_keys()); - -action(set_parameter, Node, [Component, Key, Value], Opts, Inform) -> - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - Inform("Setting runtime parameter ~p for component ~p to ~p", - [Key, Component, Value]), - rpc_call( - Node, rabbit_runtime_parameters, parse_set, - [VHostArg, list_to_binary(Component), list_to_binary(Key), Value, none]); - -action(clear_parameter, Node, [Component, Key], Opts, Inform) -> - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - Inform("Clearing runtime parameter ~p for component ~p", [Key, Component]), - rpc_call(Node, rabbit_runtime_parameters, clear, [VHostArg, - list_to_binary(Component), - list_to_binary(Key)]); - -action(list_parameters, Node, [], Opts, Inform) -> - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - Inform("Listing runtime parameters", []), - display_info_list( - rpc_call(Node, rabbit_runtime_parameters, list_formatted, [VHostArg]), - rabbit_runtime_parameters:info_keys()); - -action(set_policy, Node, [Key, Pattern, Defn], Opts, Inform) -> - Msg = "Setting policy ~p for pattern ~p to ~p with priority ~p", - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - PriorityArg = proplists:get_value(?PRIORITY_OPT, Opts), - ApplyToArg = list_to_binary(proplists:get_value(?APPLY_TO_OPT, Opts)), - Inform(Msg, [Key, Pattern, Defn, PriorityArg]), - rpc_call( - Node, rabbit_policy, parse_set, - [VHostArg, list_to_binary(Key), Pattern, Defn, PriorityArg, ApplyToArg]); - -action(clear_policy, Node, [Key], Opts, Inform) -> - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - Inform("Clearing policy ~p", [Key]), - rpc_call(Node, rabbit_policy, delete, [VHostArg, list_to_binary(Key)]); - -action(list_policies, Node, [], Opts, Inform) -> - VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)), - Inform("Listing policies", []), - display_info_list(rpc_call(Node, rabbit_policy, list_formatted, [VHostArg]), - rabbit_policy:info_keys()); - -action(report, Node, _Args, _Opts, Inform) -> - Inform("Reporting server status on ~p~n~n", [erlang:universaltime()]), - [begin ok = action(Action, N, [], [], Inform), io:nl() end || - N <- unsafe_rpc(Node, rabbit_mnesia, cluster_nodes, [running]), - Action <- [status, cluster_status, environment]], - VHosts = unsafe_rpc(Node, rabbit_vhost, list, []), - [print_report(Node, Q) || Q <- ?GLOBAL_QUERIES], - [print_report(Node, Q, [V]) || Q <- ?VHOST_QUERIES, V <- VHosts], - ok; - -action(set_cluster_name, Node, [Name], _Opts, Inform) -> - Inform("Setting cluster name to ~s", [Name]), - rpc_call(Node, rabbit_nodes, set_cluster_name, [list_to_binary(Name)]); - -action(eval, Node, [Expr], _Opts, _Inform) -> - case erl_scan:string(Expr) of - {ok, Scanned, _} -> - case erl_parse:parse_exprs(Scanned) of - {ok, Parsed} -> {value, Value, _} = - unsafe_rpc( - Node, erl_eval, exprs, [Parsed, []]), - io:format("~p~n", [Value]), - ok; - {error, E} -> {error_string, format_parse_error(E)} - end; - {error, E, _} -> - {error_string, format_parse_error(E)} - end. - -format_parse_error({_Line, Mod, Err}) -> lists:flatten(Mod:format_error(Err)). - -sync_queue(Q) -> - rabbit_amqqueue:with( - Q, fun(#amqqueue{pid = QPid}) -> rabbit_amqqueue:sync_mirrors(QPid) end). - -cancel_sync_queue(Q) -> - rabbit_amqqueue:with( - Q, fun(#amqqueue{pid = QPid}) -> - rabbit_amqqueue:cancel_sync_mirrors(QPid) - end). - -%%---------------------------------------------------------------------------- - -wait_for_application(Node, PidFile, Application, Inform) -> - Pid = read_pid_file(PidFile, true), - Inform("pid is ~s", [Pid]), - wait_for_application(Node, Pid, Application). - -wait_for_application(Node, Pid, rabbit_and_plugins) -> - wait_for_startup(Node, Pid); -wait_for_application(Node, Pid, Application) -> - while_process_is_alive( - Node, Pid, fun() -> rabbit_nodes:is_running(Node, Application) end). - -wait_for_startup(Node, Pid) -> - while_process_is_alive( - Node, Pid, fun() -> rpc:call(Node, rabbit, await_startup, []) =:= ok end). - -while_process_is_alive(Node, Pid, Activity) -> - case process_up(Pid) of - true -> case Activity() of - true -> ok; - false -> timer:sleep(?EXTERNAL_CHECK_INTERVAL), - while_process_is_alive(Node, Pid, Activity) - end; - false -> {error, process_not_running} - end. - -wait_for_process_death(Pid) -> - case process_up(Pid) of - true -> timer:sleep(?EXTERNAL_CHECK_INTERVAL), - wait_for_process_death(Pid); - false -> ok - end. - -read_pid_file(PidFile, Wait) -> - case {file:read_file(PidFile), Wait} of - {{ok, Bin}, _} -> - S = binary_to_list(Bin), - {match, [PidS]} = re:run(S, "[^\\s]+", - [{capture, all, list}]), - try list_to_integer(PidS) - catch error:badarg -> - exit({error, {garbage_in_pid_file, PidFile}}) - end, - PidS; - {{error, enoent}, true} -> - timer:sleep(?EXTERNAL_CHECK_INTERVAL), - read_pid_file(PidFile, Wait); - {{error, _} = E, _} -> - exit({error, {could_not_read_pid, E}}) - end. - -% Test using some OS clunkiness since we shouldn't trust -% rpc:call(os, getpid, []) at this point -process_up(Pid) -> - with_os([{unix, fun () -> - run_ps(Pid) =:= 0 - end}, - {win32, fun () -> - Cmd = "tasklist /nh /fi \"pid eq " ++ Pid ++ "\" ", - Res = rabbit_misc:os_cmd(Cmd ++ "2>&1"), - case re:run(Res, "erl\\.exe", [{capture, none}]) of - match -> true; - _ -> false - end - end}]). - -with_os(Handlers) -> - {OsFamily, _} = os:type(), - case proplists:get_value(OsFamily, Handlers) of - undefined -> throw({unsupported_os, OsFamily}); - Handler -> Handler() - end. - -run_ps(Pid) -> - Port = erlang:open_port({spawn, "ps -p " ++ Pid}, - [exit_status, {line, 16384}, - use_stdio, stderr_to_stdout]), - exit_loop(Port). - -exit_loop(Port) -> - receive - {Port, {exit_status, Rc}} -> Rc; - {Port, _} -> exit_loop(Port) - end. - -start_distribution() -> - CtlNodeName = rabbit_misc:format("rabbitmqctl-~s", [os:getpid()]), - {ok, _} = net_kernel:start([list_to_atom(CtlNodeName), name_type()]). - -become(BecomeNode) -> - case net_adm:ping(BecomeNode) of - pong -> exit({node_running, BecomeNode}); - pang -> io:format(" * Impersonating node: ~s...", [BecomeNode]), - error_logger:tty(false), - ok = net_kernel:stop(), - {ok, _} = net_kernel:start([BecomeNode, name_type()]), - io:format(" done~n", []), - Dir = mnesia:system_info(directory), - io:format(" * Mnesia directory : ~s~n", [Dir]) - end. - -name_type() -> - case os:getenv("RABBITMQ_USE_LONGNAME") of - "true" -> longnames; - _ -> shortnames - end. - -%%---------------------------------------------------------------------------- - -default_if_empty(List, Default) when is_list(List) -> - if List == [] -> Default; - true -> [list_to_atom(X) || X <- List] - end. - -display_info_list(Results, InfoItemKeys) when is_list(Results) -> - lists:foreach( - fun (Result) -> display_row( - [format_info_item(proplists:get_value(X, Result)) || - X <- InfoItemKeys]) - end, lists:sort(Results)), - ok; -display_info_list(Other, _) -> - Other. - -display_row(Row) -> - io:fwrite(string:join(Row, "\t")), - io:nl(). - --define(IS_U8(X), (X >= 0 andalso X =< 255)). --define(IS_U16(X), (X >= 0 andalso X =< 65535)). - -format_info_item(#resource{name = Name}) -> - escape(Name); -format_info_item({N1, N2, N3, N4} = Value) when - ?IS_U8(N1), ?IS_U8(N2), ?IS_U8(N3), ?IS_U8(N4) -> - rabbit_misc:ntoa(Value); -format_info_item({K1, K2, K3, K4, K5, K6, K7, K8} = Value) when - ?IS_U16(K1), ?IS_U16(K2), ?IS_U16(K3), ?IS_U16(K4), - ?IS_U16(K5), ?IS_U16(K6), ?IS_U16(K7), ?IS_U16(K8) -> - rabbit_misc:ntoa(Value); -format_info_item(Value) when is_pid(Value) -> - rabbit_misc:pid_to_string(Value); -format_info_item(Value) when is_binary(Value) -> - escape(Value); -format_info_item(Value) when is_atom(Value) -> - escape(atom_to_list(Value)); -format_info_item([{TableEntryKey, TableEntryType, _TableEntryValue} | _] = - Value) when is_binary(TableEntryKey) andalso - is_atom(TableEntryType) -> - io_lib:format("~1000000000000p", [prettify_amqp_table(Value)]); -format_info_item([T | _] = Value) - when is_tuple(T) orelse is_pid(T) orelse is_binary(T) orelse is_atom(T) orelse - is_list(T) -> - "[" ++ - lists:nthtail(2, lists:append( - [", " ++ format_info_item(E) || E <- Value])) ++ "]"; -format_info_item(Value) -> - io_lib:format("~w", [Value]). - -display_call_result(Node, MFA) -> - case call(Node, MFA) of - {badrpc, _} = Res -> throw(Res); - Res -> io:format("~p~n", [Res]), - ok - end. - -unsafe_rpc(Node, Mod, Fun, Args) -> - case rpc_call(Node, Mod, Fun, Args) of - {badrpc, _} = Res -> throw(Res); - Normal -> Normal - end. - -ensure_app_running(Node) -> - case call(Node, {rabbit, is_running, []}) of - true -> ok; - false -> {error_string, - rabbit_misc:format( - "rabbit application is not running on node ~s.~n" - " * Suggestion: start it with \"rabbitmqctl start_app\" " - "and try again", [Node])}; - Other -> Other - end. - -call(Node, {Mod, Fun, Args}) -> - rpc_call(Node, Mod, Fun, lists:map(fun list_to_binary_utf8/1, Args)). - -list_to_binary_utf8(L) -> - B = list_to_binary(L), - case rabbit_binary_parser:validate_utf8(B) of - ok -> B; - error -> throw({error, {not_utf_8, L}}) - end. - -%% escape does C-style backslash escaping of non-printable ASCII -%% characters. We don't escape characters above 127, since they may -%% form part of UTF-8 strings. - -escape(Atom) when is_atom(Atom) -> escape(atom_to_list(Atom)); -escape(Bin) when is_binary(Bin) -> escape(binary_to_list(Bin)); -escape(L) when is_list(L) -> escape_char(lists:reverse(L), []). - -escape_char([$\\ | T], Acc) -> - escape_char(T, [$\\, $\\ | Acc]); -escape_char([X | T], Acc) when X >= 32, X /= 127 -> - escape_char(T, [X | Acc]); -escape_char([X | T], Acc) -> - escape_char(T, [$\\, $0 + (X bsr 6), $0 + (X band 8#070 bsr 3), - $0 + (X band 7) | Acc]); -escape_char([], Acc) -> - Acc. - -prettify_amqp_table(Table) -> - [{escape(K), prettify_typed_amqp_value(T, V)} || {K, T, V} <- Table]. - -prettify_typed_amqp_value(longstr, Value) -> escape(Value); -prettify_typed_amqp_value(table, Value) -> prettify_amqp_table(Value); -prettify_typed_amqp_value(array, Value) -> [prettify_typed_amqp_value(T, V) || - {T, V} <- Value]; -prettify_typed_amqp_value(_Type, Value) -> Value. diff --git a/src/rabbit_dead_letter.erl b/src/rabbit_dead_letter.erl deleted file mode 100644 index 728bc431..00000000 --- a/src/rabbit_dead_letter.erl +++ /dev/null @@ -1,148 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_dead_letter). - --export([publish/5]). - --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type reason() :: 'expired' | 'rejected' | 'maxlen'. - --spec publish(rabbit_types:message(), reason(), rabbit_types:exchange(), - 'undefined' | binary(), rabbit_amqqueue:name()) -> 'ok'. - --endif. - -%%---------------------------------------------------------------------------- - -publish(Msg, Reason, X, RK, QName) -> - DLMsg = make_msg(Msg, Reason, X#exchange.name, RK, QName), - Delivery = rabbit_basic:delivery(false, false, DLMsg, undefined), - {Queues, Cycles} = detect_cycles(Reason, DLMsg, - rabbit_exchange:route(X, Delivery)), - lists:foreach(fun log_cycle_once/1, Cycles), - rabbit_amqqueue:deliver(rabbit_amqqueue:lookup(Queues), Delivery), - ok. - -make_msg(Msg = #basic_message{content = Content, - exchange_name = Exchange, - routing_keys = RoutingKeys}, - Reason, DLX, RK, #resource{name = QName}) -> - {DeathRoutingKeys, HeadersFun1} = - case RK of - undefined -> {RoutingKeys, fun (H) -> H end}; - _ -> {[RK], fun (H) -> lists:keydelete(<<"CC">>, 1, H) end} - end, - ReasonBin = list_to_binary(atom_to_list(Reason)), - TimeSec = rabbit_misc:now_ms() div 1000, - PerMsgTTL = per_msg_ttl_header(Content#content.properties), - HeadersFun2 = - fun (Headers) -> - %% The first routing key is the one specified in the - %% basic.publish; all others are CC or BCC keys. - RKs = [hd(RoutingKeys) | rabbit_basic:header_routes(Headers)], - RKs1 = [{longstr, Key} || Key <- RKs], - Info = [{<<"reason">>, longstr, ReasonBin}, - {<<"queue">>, longstr, QName}, - {<<"time">>, timestamp, TimeSec}, - {<<"exchange">>, longstr, Exchange#resource.name}, - {<<"routing-keys">>, array, RKs1}] ++ PerMsgTTL, - HeadersFun1(rabbit_basic:prepend_table_header(<<"x-death">>, - Info, Headers)) - end, - Content1 = #content{properties = Props} = - rabbit_basic:map_headers(HeadersFun2, Content), - Content2 = Content1#content{properties = - Props#'P_basic'{expiration = undefined}}, - Msg#basic_message{exchange_name = DLX, - id = rabbit_guid:gen(), - routing_keys = DeathRoutingKeys, - content = Content2}. - -per_msg_ttl_header(#'P_basic'{expiration = undefined}) -> - []; -per_msg_ttl_header(#'P_basic'{expiration = Expiration}) -> - [{<<"original-expiration">>, longstr, Expiration}]; -per_msg_ttl_header(_) -> - []. - -detect_cycles(rejected, _Msg, Queues) -> - {Queues, []}; - -detect_cycles(_Reason, #basic_message{content = Content}, Queues) -> - #content{properties = #'P_basic'{headers = Headers}} = - rabbit_binary_parser:ensure_content_decoded(Content), - NoCycles = {Queues, []}, - case Headers of - undefined -> - NoCycles; - _ -> - case rabbit_misc:table_lookup(Headers, <<"x-death">>) of - {array, Deaths} -> - {Cycling, NotCycling} = - lists:partition(fun (#resource{name = Queue}) -> - is_cycle(Queue, Deaths) - end, Queues), - OldQueues = [rabbit_misc:table_lookup(D, <<"queue">>) || - {table, D} <- Deaths], - OldQueues1 = [QName || {longstr, QName} <- OldQueues], - {NotCycling, [[QName | OldQueues1] || - #resource{name = QName} <- Cycling]}; - _ -> - NoCycles - end - end. - -is_cycle(Queue, Deaths) -> - {Cycle, Rest} = - lists:splitwith( - fun ({table, D}) -> - {longstr, Queue} =/= rabbit_misc:table_lookup(D, <<"queue">>); - (_) -> - true - end, Deaths), - %% Is there a cycle, and if so, is it "fully automatic", i.e. with - %% no reject in it? - case Rest of - [] -> false; - [H|_] -> lists:all( - fun ({table, D}) -> - {longstr, <<"rejected">>} =/= - rabbit_misc:table_lookup(D, <<"reason">>); - (_) -> - %% There was something we didn't expect, therefore - %% a client must have put it there, therefore the - %% cycle was not "fully automatic". - false - end, Cycle ++ [H]) - end. - -log_cycle_once(Queues) -> - Key = {queue_cycle, Queues}, - case get(Key) of - true -> ok; - undefined -> rabbit_log:warning( - "Message dropped. Dead-letter queues cycle detected" ++ - ": ~p~nThis cycle will NOT be reported again.~n", - [Queues]), - put(Key, true) - end. diff --git a/src/rabbit_diagnostics.erl b/src/rabbit_diagnostics.erl deleted file mode 100644 index bf45b757..00000000 --- a/src/rabbit_diagnostics.erl +++ /dev/null @@ -1,79 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_diagnostics). - --define(PROCESS_INFO, - [current_stacktrace, initial_call, dictionary, message_queue_len, - links, monitors, monitored_by, heap_size]). - --export([maybe_stuck/0, maybe_stuck/1]). - -maybe_stuck() -> maybe_stuck(5000). - -maybe_stuck(Timeout) -> - Pids = processes(), - io:format("There are ~p processes.~n", [length(Pids)]), - maybe_stuck(Pids, Timeout). - -maybe_stuck(Pids, Timeout) when Timeout =< 0 -> - io:format("Found ~p suspicious processes.~n", [length(Pids)]), - [io:format("~p~n", [info(Pid)]) || Pid <- Pids], - ok; -maybe_stuck(Pids, Timeout) -> - Pids2 = [P || P <- Pids, looks_stuck(P)], - io:format("Investigated ~p processes this round, ~pms to go.~n", - [length(Pids2), Timeout]), - timer:sleep(500), - maybe_stuck(Pids2, Timeout - 500). - -looks_stuck(Pid) -> - case catch process_info(Pid, status) of - {status, waiting} -> - %% It's tempting to just check for message_queue_len > 0 - %% here rather than mess around with stack traces and - %% heuristics. But really, sometimes freshly stuck - %% processes can have 0 messages... - case catch erlang:process_info(Pid, current_stacktrace) of - {current_stacktrace, [H|_]} -> - maybe_stuck_stacktrace(H); - _ -> - false - end; - _ -> - false - end. - -maybe_stuck_stacktrace({gen_server2, process_next_msg, _}) -> false; -maybe_stuck_stacktrace({gen_event, fetch_msg, _}) -> false; -maybe_stuck_stacktrace({prim_inet, accept0, _}) -> false; -maybe_stuck_stacktrace({prim_inet, recv0, _}) -> false; -maybe_stuck_stacktrace({rabbit_heartbeat, heartbeater, _}) -> false; -maybe_stuck_stacktrace({rabbit_net, recv, _}) -> false; -maybe_stuck_stacktrace({mochiweb_http, request, _}) -> false; -maybe_stuck_stacktrace({group, _, _}) -> false; -maybe_stuck_stacktrace({shell, _, _}) -> false; -maybe_stuck_stacktrace({io, _, _}) -> false; -maybe_stuck_stacktrace({M, F, A, _}) -> - maybe_stuck_stacktrace({M, F, A}); -maybe_stuck_stacktrace({_M, F, _A}) -> - case string:str(atom_to_list(F), "loop") of - 0 -> true; - _ -> false - end. - -info(Pid) -> - [{pid, Pid} | process_info(Pid, ?PROCESS_INFO)]. diff --git a/src/rabbit_direct.erl b/src/rabbit_direct.erl deleted file mode 100644 index f6140f09..00000000 --- a/src/rabbit_direct.erl +++ /dev/null @@ -1,115 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_direct). - --export([boot/0, force_event_refresh/1, list/0, connect/5, - start_channel/9, disconnect/2]). -%% Internal --export([list_local/0]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(boot/0 :: () -> 'ok'). --spec(force_event_refresh/1 :: (reference()) -> 'ok'). --spec(list/0 :: () -> [pid()]). --spec(list_local/0 :: () -> [pid()]). --spec(connect/5 :: (({'none', 'none'} | {rabbit_types:username(), 'none'} | - {rabbit_types:username(), rabbit_types:password()}), - rabbit_types:vhost(), rabbit_types:protocol(), pid(), - rabbit_event:event_props()) -> - rabbit_types:ok_or_error2( - {rabbit_types:user(), rabbit_framing:amqp_table()}, - 'broker_not_found_on_node' | - {'auth_failure', string()} | 'access_refused')). --spec(start_channel/9 :: - (rabbit_channel:channel_number(), pid(), pid(), string(), - rabbit_types:protocol(), rabbit_types:user(), rabbit_types:vhost(), - rabbit_framing:amqp_table(), pid()) -> {'ok', pid()}). --spec(disconnect/2 :: (pid(), rabbit_event:event_props()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -boot() -> rabbit_sup:start_supervisor_child( - rabbit_direct_client_sup, rabbit_client_sup, - [{local, rabbit_direct_client_sup}, - {rabbit_channel_sup, start_link, []}]). - -force_event_refresh(Ref) -> - [Pid ! {force_event_refresh, Ref} || Pid <- list()], - ok. - -list_local() -> - pg_local:get_members(rabbit_direct). - -list() -> - rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running), - rabbit_direct, list_local, []). - -%%---------------------------------------------------------------------------- - -connect({none, _}, VHost, Protocol, Pid, Infos) -> - connect0(fun () -> {ok, rabbit_auth_backend_dummy:user()} end, - VHost, Protocol, Pid, Infos); - -connect({Username, none}, VHost, Protocol, Pid, Infos) -> - connect0(fun () -> rabbit_access_control:check_user_login(Username, []) end, - VHost, Protocol, Pid, Infos); - -connect({Username, Password}, VHost, Protocol, Pid, Infos) -> - connect0(fun () -> rabbit_access_control:check_user_pass_login( - Username, Password) end, - VHost, Protocol, Pid, Infos). - -connect0(AuthFun, VHost, Protocol, Pid, Infos) -> - case rabbit:is_running() of - true -> case AuthFun() of - {ok, User} -> - connect1(User, VHost, Protocol, Pid, Infos); - {refused, _M, _A} -> - {error, {auth_failure, "Refused"}} - end; - false -> {error, broker_not_found_on_node} - end. - -connect1(User, VHost, Protocol, Pid, Infos) -> - try rabbit_access_control:check_vhost_access(User, VHost, undefined) of - ok -> ok = pg_local:join(rabbit_direct, Pid), - rabbit_event:notify(connection_created, Infos), - {ok, {User, rabbit_reader:server_properties(Protocol)}} - catch - exit:#amqp_error{name = access_refused} -> - {error, access_refused} - end. - -start_channel(Number, ClientChannelPid, ConnPid, ConnName, Protocol, User, - VHost, Capabilities, Collector) -> - {ok, _, {ChannelPid, _}} = - supervisor2:start_child( - rabbit_direct_client_sup, - [{direct, Number, ClientChannelPid, ConnPid, ConnName, Protocol, - User, VHost, Capabilities, Collector}]), - {ok, ChannelPid}. - -disconnect(Pid, Infos) -> - pg_local:leave(rabbit_direct, Pid), - rabbit_event:notify(connection_closed, Infos). diff --git a/src/rabbit_disk_monitor.erl b/src/rabbit_disk_monitor.erl deleted file mode 100644 index 031a04f0..00000000 --- a/src/rabbit_disk_monitor.erl +++ /dev/null @@ -1,227 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_disk_monitor). - --behaviour(gen_server). - --export([start_link/1]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --export([get_disk_free_limit/0, set_disk_free_limit/1, - get_min_check_interval/0, set_min_check_interval/1, - get_max_check_interval/0, set_max_check_interval/1, - get_disk_free/0]). - --define(SERVER, ?MODULE). --define(DEFAULT_MIN_DISK_CHECK_INTERVAL, 100). --define(DEFAULT_MAX_DISK_CHECK_INTERVAL, 10000). -%% 250MB/s i.e. 250kB/ms --define(FAST_RATE, (250 * 1000)). - --record(state, {dir, - limit, - actual, - min_interval, - max_interval, - timer, - alarmed - }). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(disk_free_limit() :: (integer() | {'mem_relative', float()})). --spec(start_link/1 :: (disk_free_limit()) -> rabbit_types:ok_pid_or_error()). --spec(get_disk_free_limit/0 :: () -> integer()). --spec(set_disk_free_limit/1 :: (disk_free_limit()) -> 'ok'). --spec(get_min_check_interval/0 :: () -> integer()). --spec(set_min_check_interval/1 :: (integer()) -> 'ok'). --spec(get_max_check_interval/0 :: () -> integer()). --spec(set_max_check_interval/1 :: (integer()) -> 'ok'). --spec(get_disk_free/0 :: () -> (integer() | 'unknown')). - --endif. - -%%---------------------------------------------------------------------------- -%% Public API -%%---------------------------------------------------------------------------- - -get_disk_free_limit() -> - gen_server:call(?MODULE, get_disk_free_limit, infinity). - -set_disk_free_limit(Limit) -> - gen_server:call(?MODULE, {set_disk_free_limit, Limit}, infinity). - -get_min_check_interval() -> - gen_server:call(?MODULE, get_min_check_interval, infinity). - -set_min_check_interval(Interval) -> - gen_server:call(?MODULE, {set_min_check_interval, Interval}, infinity). - -get_max_check_interval() -> - gen_server:call(?MODULE, get_max_check_interval, infinity). - -set_max_check_interval(Interval) -> - gen_server:call(?MODULE, {set_max_check_interval, Interval}, infinity). - -get_disk_free() -> - gen_server:call(?MODULE, get_disk_free, infinity). - -%%---------------------------------------------------------------------------- -%% gen_server callbacks -%%---------------------------------------------------------------------------- - -start_link(Args) -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [Args], []). - -init([Limit]) -> - Dir = dir(), - State = #state{dir = Dir, - min_interval = ?DEFAULT_MIN_DISK_CHECK_INTERVAL, - max_interval = ?DEFAULT_MAX_DISK_CHECK_INTERVAL, - alarmed = false}, - case {catch get_disk_free(Dir), - vm_memory_monitor:get_total_memory()} of - {N1, N2} when is_integer(N1), is_integer(N2) -> - {ok, start_timer(set_disk_limits(State, Limit))}; - Err -> - rabbit_log:info("Disabling disk free space monitoring " - "on unsupported platform:~n~p~n", [Err]), - {stop, unsupported_platform} - end. - -handle_call(get_disk_free_limit, _From, State = #state{limit = Limit}) -> - {reply, Limit, State}; - -handle_call({set_disk_free_limit, Limit}, _From, State) -> - {reply, ok, set_disk_limits(State, Limit)}; - -handle_call(get_min_check_interval, _From, State) -> - {reply, State#state.min_interval, State}; - -handle_call(get_max_check_interval, _From, State) -> - {reply, State#state.max_interval, State}; - -handle_call({set_min_check_interval, MinInterval}, _From, State) -> - {reply, ok, State#state{min_interval = MinInterval}}; - -handle_call({set_max_check_interval, MaxInterval}, _From, State) -> - {reply, ok, State#state{max_interval = MaxInterval}}; - -handle_call(get_disk_free, _From, State = #state { actual = Actual }) -> - {reply, Actual, State}; - -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_cast(_Request, State) -> - {noreply, State}. - -handle_info(update, State) -> - {noreply, start_timer(internal_update(State))}; - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%---------------------------------------------------------------------------- -%% Server Internals -%%---------------------------------------------------------------------------- - -% the partition / drive containing this directory will be monitored -dir() -> rabbit_mnesia:dir(). - -set_disk_limits(State, Limit0) -> - Limit = interpret_limit(Limit0), - State1 = State#state { limit = Limit }, - rabbit_log:info("Disk free limit set to ~pMB~n", - [trunc(Limit / 1000000)]), - internal_update(State1). - -internal_update(State = #state { limit = Limit, - dir = Dir, - alarmed = Alarmed}) -> - CurrentFree = get_disk_free(Dir), - NewAlarmed = CurrentFree < Limit, - case {Alarmed, NewAlarmed} of - {false, true} -> - emit_update_info("insufficient", CurrentFree, Limit), - rabbit_alarm:set_alarm({{resource_limit, disk, node()}, []}); - {true, false} -> - emit_update_info("sufficient", CurrentFree, Limit), - rabbit_alarm:clear_alarm({resource_limit, disk, node()}); - _ -> - ok - end, - State #state {alarmed = NewAlarmed, actual = CurrentFree}. - -get_disk_free(Dir) -> - get_disk_free(Dir, os:type()). - -get_disk_free(Dir, {unix, Sun}) - when Sun =:= sunos; Sun =:= sunos4; Sun =:= solaris -> - parse_free_unix(rabbit_misc:os_cmd("/usr/bin/df -k " ++ Dir)); -get_disk_free(Dir, {unix, _}) -> - parse_free_unix(rabbit_misc:os_cmd("/bin/df -kP " ++ Dir)); -get_disk_free(Dir, {win32, _}) -> - parse_free_win32(rabbit_misc:os_cmd("dir /-C /W \"" ++ Dir ++ "\"")). - -parse_free_unix(Str) -> - case string:tokens(Str, "\n") of - [_, S | _] -> case string:tokens(S, " \t") of - [_, _, _, Free | _] -> list_to_integer(Free) * 1024; - _ -> exit({unparseable, Str}) - end; - _ -> exit({unparseable, Str}) - end. - -parse_free_win32(CommandResult) -> - LastLine = lists:last(string:tokens(CommandResult, "\r\n")), - {match, [Free]} = re:run(lists:reverse(LastLine), "(\\d+)", - [{capture, all_but_first, list}]), - list_to_integer(lists:reverse(Free)). - -interpret_limit({mem_relative, R}) -> - round(R * vm_memory_monitor:get_total_memory()); -interpret_limit(L) -> - L. - -emit_update_info(StateStr, CurrentFree, Limit) -> - rabbit_log:info( - "Disk free space ~s. Free bytes:~p Limit:~p~n", - [StateStr, CurrentFree, Limit]). - -start_timer(State) -> - State#state{timer = erlang:send_after(interval(State), self(), update)}. - -interval(#state{alarmed = true, - max_interval = MaxInterval}) -> - MaxInterval; -interval(#state{limit = Limit, - actual = Actual, - min_interval = MinInterval, - max_interval = MaxInterval}) -> - IdealInterval = 2 * (Actual - Limit) / ?FAST_RATE, - trunc(erlang:max(MinInterval, erlang:min(MaxInterval, IdealInterval))). diff --git a/src/rabbit_error_logger.erl b/src/rabbit_error_logger.erl deleted file mode 100644 index c2fd3c18..00000000 --- a/src/rabbit_error_logger.erl +++ /dev/null @@ -1,112 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_error_logger). --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - --define(LOG_EXCH_NAME, <<"amq.rabbitmq.log">>). - --behaviour(gen_event). - --export([start/0, stop/0]). - --export([init/1, terminate/2, code_change/3, handle_call/2, handle_event/2, - handle_info/2]). - --import(rabbit_error_logger_file_h, [safe_handle_event/3]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start/0 :: () -> 'ok'). --spec(stop/0 :: () -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start() -> - {ok, DefaultVHost} = application:get_env(default_vhost), - case error_logger:add_report_handler(?MODULE, [DefaultVHost]) of - ok -> - ok; - {error, {no_such_vhost, DefaultVHost}} -> - rabbit_log:warning("Default virtual host '~s' not found; " - "exchange '~s' disabled~n", - [DefaultVHost, ?LOG_EXCH_NAME]), - ok - end. - -stop() -> - case error_logger:delete_report_handler(rabbit_error_logger) of - terminated_ok -> ok; - {error, module_not_found} -> ok - end. - -%%---------------------------------------------------------------------------- - -init([DefaultVHost]) -> - #exchange{} = rabbit_exchange:declare( - rabbit_misc:r(DefaultVHost, exchange, ?LOG_EXCH_NAME), - topic, true, false, true, []), - {ok, #resource{virtual_host = DefaultVHost, - kind = exchange, - name = ?LOG_EXCH_NAME}}. - -terminate(_Arg, _State) -> - terminated_ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -handle_call(_Request, State) -> - {ok, not_understood, State}. - -handle_event(Event, State) -> - safe_handle_event(fun handle_event0/2, Event, State). - -handle_event0({Kind, _Gleader, {_Pid, Format, Data}}, State) -> - ok = publish(Kind, Format, Data, State), - {ok, State}; -handle_event0(_Event, State) -> - {ok, State}. - -handle_info(_Info, State) -> - {ok, State}. - -publish(error, Format, Data, State) -> - publish1(<<"error">>, Format, Data, State); -publish(warning_msg, Format, Data, State) -> - publish1(<<"warning">>, Format, Data, State); -publish(info_msg, Format, Data, State) -> - publish1(<<"info">>, Format, Data, State); -publish(_Other, _Format, _Data, _State) -> - ok. - -publish1(RoutingKey, Format, Data, LogExch) -> - %% 0-9-1 says the timestamp is a "64 bit POSIX timestamp". That's - %% second resolution, not millisecond. - Timestamp = rabbit_misc:now_ms() div 1000, - - Args = [truncate:term(A, ?LOG_TRUNC) || A <- Data], - {ok, _DeliveredQPids} = - rabbit_basic:publish(LogExch, RoutingKey, - #'P_basic'{content_type = <<"text/plain">>, - timestamp = Timestamp}, - list_to_binary(io_lib:format(Format, Args))), - ok. diff --git a/src/rabbit_error_logger_file_h.erl b/src/rabbit_error_logger_file_h.erl deleted file mode 100644 index 5f9a21e9..00000000 --- a/src/rabbit_error_logger_file_h.erl +++ /dev/null @@ -1,136 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_error_logger_file_h). --include("rabbit.hrl"). - --behaviour(gen_event). - --export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, - code_change/3]). - --export([safe_handle_event/3]). - -%% rabbit_error_logger_file_h is a wrapper around the error_logger_file_h -%% module because the original's init/1 does not match properly -%% with the result of closing the old handler when swapping handlers. -%% The first init/1 additionally allows for simple log rotation -%% when the suffix is not the empty string. -%% The original init/2 also opened the file in 'write' mode, thus -%% overwriting old logs. To remedy this, init/2 from -%% lib/stdlib/src/error_logger_file_h.erl from R14B3 was copied as -%% init_file/2 and changed so that it opens the file in 'append' mode. - -%% Used only when swapping handlers in log rotation -init({{File, Suffix}, []}) -> - case rabbit_file:append_file(File, Suffix) of - ok -> file:delete(File), - ok; - {error, Error} -> - rabbit_log:error("Failed to append contents of " - "log file '~s' to '~s':~n~p~n", - [File, [File, Suffix], Error]) - end, - init(File); -%% Used only when swapping handlers and the original handler -%% failed to terminate or was never installed -init({{File, _}, error}) -> - init(File); -%% Used only when swapping handlers without performing -%% log rotation -init({File, []}) -> - init(File); -%% Used only when taking over from the tty handler -init({{File, []}, _}) -> - init(File); -init({File, {error_logger, Buf}}) -> - rabbit_file:ensure_parent_dirs_exist(File), - init_file(File, {error_logger, Buf}); -init(File) -> - rabbit_file:ensure_parent_dirs_exist(File), - init_file(File, []). - -init_file(File, {error_logger, Buf}) -> - case init_file(File, error_logger) of - {ok, {Fd, File, PrevHandler}} -> - [handle_event(Event, {Fd, File, PrevHandler}) || - {_, Event} <- lists:reverse(Buf)], - {ok, {Fd, File, PrevHandler}}; - Error -> - Error - end; -init_file(File, PrevHandler) -> - process_flag(trap_exit, true), - case file:open(File, [append]) of - {ok,Fd} -> {ok, {Fd, File, PrevHandler}}; - Error -> Error - end. - -handle_event(Event, State) -> - safe_handle_event(fun handle_event0/2, Event, State). - -safe_handle_event(HandleEvent, Event, State) -> - try - HandleEvent(Event, State) - catch - _:Error -> - io:format( - "Error in log handler~n====================~n" - "Event: ~P~nError: ~P~nStack trace: ~p~n~n", - [Event, 30, Error, 30, erlang:get_stacktrace()]), - {ok, State} - end. - -%% filter out "application: foo; exited: stopped; type: temporary" -handle_event0({info_report, _, {_, std_info, _}}, State) -> - {ok, State}; -%% When a node restarts quickly it is possible the rest of the cluster -%% will not have had the chance to remove its queues from -%% Mnesia. That's why rabbit_amqqueue:recover/0 invokes -%% on_node_down(node()). But before we get there we can receive lots -%% of messages intended for the old version of the node. The emulator -%% logs an event for every one of those messages; in extremis this can -%% bring the server to its knees just logging "Discarding..." -%% again and again. So just log the first one, then go silent. -handle_event0(Event = {error, _, {emulator, _, ["Discarding message" ++ _]}}, - State) -> - case get(discarding_message_seen) of - true -> {ok, State}; - undefined -> put(discarding_message_seen, true), - error_logger_file_h:handle_event(t(Event), State) - end; -%% Clear this state if we log anything else (but not a progress report). -handle_event0(Event = {info_msg, _, _}, State) -> - erase(discarding_message_seen), - error_logger_file_h:handle_event(t(Event), State); -handle_event0(Event, State) -> - error_logger_file_h:handle_event(t(Event), State). - -handle_info(Info, State) -> - error_logger_file_h:handle_info(Info, State). - -handle_call(Call, State) -> - error_logger_file_h:handle_call(Call, State). - -terminate(Reason, State) -> - error_logger_file_h:terminate(Reason, State). - -code_change(OldVsn, State, Extra) -> - error_logger_file_h:code_change(OldVsn, State, Extra). - -%%---------------------------------------------------------------------- - -t(Term) -> truncate:log_event(Term, ?LOG_TRUNC). diff --git a/src/rabbit_event.erl b/src/rabbit_event.erl deleted file mode 100644 index a33103fd..00000000 --- a/src/rabbit_event.erl +++ /dev/null @@ -1,164 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_event). - --include("rabbit.hrl"). - --export([start_link/0]). --export([init_stats_timer/2, init_disabled_stats_timer/2, - ensure_stats_timer/3, stop_stats_timer/2, reset_stats_timer/2]). --export([stats_level/2, if_enabled/3]). --export([notify/2, notify/3, notify_if/3]). --export([sync_notify/2, sync_notify/3]). - -%%---------------------------------------------------------------------------- - --record(state, {level, interval, timer}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([event_type/0, event_props/0, event_timestamp/0, event/0]). - --type(event_type() :: atom()). --type(event_props() :: term()). --type(event_timestamp() :: - {non_neg_integer(), non_neg_integer(), non_neg_integer()}). - --type(event() :: #event { type :: event_type(), - props :: event_props(), - reference :: 'none' | reference(), - timestamp :: event_timestamp() }). - --type(level() :: 'none' | 'coarse' | 'fine'). - --type(timer_fun() :: fun (() -> 'ok')). --type(container() :: tuple()). --type(pos() :: non_neg_integer()). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(init_stats_timer/2 :: (container(), pos()) -> container()). --spec(init_disabled_stats_timer/2 :: (container(), pos()) -> container()). --spec(ensure_stats_timer/3 :: (container(), pos(), term()) -> container()). --spec(stop_stats_timer/2 :: (container(), pos()) -> container()). --spec(reset_stats_timer/2 :: (container(), pos()) -> container()). --spec(stats_level/2 :: (container(), pos()) -> level()). --spec(if_enabled/3 :: (container(), pos(), timer_fun()) -> 'ok'). --spec(notify/2 :: (event_type(), event_props()) -> 'ok'). --spec(notify/3 :: (event_type(), event_props(), reference() | 'none') -> 'ok'). --spec(notify_if/3 :: (boolean(), event_type(), event_props()) -> 'ok'). --spec(sync_notify/2 :: (event_type(), event_props()) -> 'ok'). --spec(sync_notify/3 :: (event_type(), event_props(), - reference() | 'none') -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> - gen_event:start_link({local, ?MODULE}). - -%% The idea is, for each stat-emitting object: -%% -%% On startup: -%% init_stats_timer(State) -%% notify(created event) -%% if_enabled(internal_emit_stats) - so we immediately send something -%% -%% On wakeup: -%% ensure_stats_timer(State, emit_stats) -%% (Note we can't emit stats immediately, the timer may have fired 1ms ago.) -%% -%% emit_stats: -%% if_enabled(internal_emit_stats) -%% reset_stats_timer(State) - just bookkeeping -%% -%% Pre-hibernation: -%% if_enabled(internal_emit_stats) -%% stop_stats_timer(State) -%% -%% internal_emit_stats: -%% notify(stats) - -init_stats_timer(C, P) -> - {ok, StatsLevel} = application:get_env(rabbit, collect_statistics), - {ok, Interval} = application:get_env(rabbit, collect_statistics_interval), - setelement(P, C, #state{level = StatsLevel, interval = Interval, - timer = undefined}). - -init_disabled_stats_timer(C, P) -> - setelement(P, C, #state{level = none, interval = 0, timer = undefined}). - -ensure_stats_timer(C, P, Msg) -> - case element(P, C) of - #state{level = Level, interval = Interval, timer = undefined} = State - when Level =/= none -> - TRef = erlang:send_after(Interval, self(), Msg), - setelement(P, C, State#state{timer = TRef}); - #state{} -> - C - end. - -stop_stats_timer(C, P) -> - case element(P, C) of - #state{timer = TRef} = State when TRef =/= undefined -> - case erlang:cancel_timer(TRef) of - false -> C; - _ -> setelement(P, C, State#state{timer = undefined}) - end; - #state{} -> - C - end. - -reset_stats_timer(C, P) -> - case element(P, C) of - #state{timer = TRef} = State when TRef =/= undefined -> - setelement(P, C, State#state{timer = undefined}); - #state{} -> - C - end. - -stats_level(C, P) -> - #state{level = Level} = element(P, C), - Level. - -if_enabled(C, P, Fun) -> - case element(P, C) of - #state{level = none} -> ok; - #state{} -> Fun(), ok - end. - -notify_if(true, Type, Props) -> notify(Type, Props); -notify_if(false, _Type, _Props) -> ok. - -notify(Type, Props) -> notify(Type, Props, none). - -notify(Type, Props, Ref) -> - gen_event:notify(?MODULE, event_cons(Type, Props, Ref)). - -sync_notify(Type, Props) -> sync_notify(Type, Props, none). - -sync_notify(Type, Props, Ref) -> - gen_event:sync_notify(?MODULE, event_cons(Type, Props, Ref)). - -event_cons(Type, Props, Ref) -> - #event{type = Type, - props = Props, - reference = Ref, - timestamp = os:timestamp()}. - diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl deleted file mode 100644 index 5448cb01..00000000 --- a/src/rabbit_exchange.erl +++ /dev/null @@ -1,497 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_exchange). --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - --export([recover/0, policy_changed/2, callback/4, declare/6, - assert_equivalence/6, assert_args_equivalence/2, check_type/1, - lookup/1, lookup_or_die/1, list/0, list/1, lookup_scratch/2, - update_scratch/3, update_decorators/1, immutable/1, - info_keys/0, info/1, info/2, info_all/1, info_all/2, - route/2, delete/2, validate_binding/2]). -%% these must be run inside a mnesia tx --export([maybe_auto_delete/2, serial/1, peek_serial/1, update/2]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([name/0, type/0]). - --type(name() :: rabbit_types:r('exchange')). --type(type() :: atom()). --type(fun_name() :: atom()). - --spec(recover/0 :: () -> [name()]). --spec(callback/4:: - (rabbit_types:exchange(), fun_name(), - fun((boolean()) -> non_neg_integer()) | atom(), [any()]) -> 'ok'). --spec(policy_changed/2 :: - (rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'). --spec(declare/6 :: - (name(), type(), boolean(), boolean(), boolean(), - rabbit_framing:amqp_table()) - -> rabbit_types:exchange()). --spec(check_type/1 :: - (binary()) -> atom() | rabbit_types:connection_exit()). --spec(assert_equivalence/6 :: - (rabbit_types:exchange(), atom(), boolean(), boolean(), boolean(), - rabbit_framing:amqp_table()) - -> 'ok' | rabbit_types:connection_exit()). --spec(assert_args_equivalence/2 :: - (rabbit_types:exchange(), rabbit_framing:amqp_table()) - -> 'ok' | rabbit_types:connection_exit()). --spec(lookup/1 :: - (name()) -> rabbit_types:ok(rabbit_types:exchange()) | - rabbit_types:error('not_found')). --spec(lookup_or_die/1 :: - (name()) -> rabbit_types:exchange() | - rabbit_types:channel_exit()). --spec(list/0 :: () -> [rabbit_types:exchange()]). --spec(list/1 :: (rabbit_types:vhost()) -> [rabbit_types:exchange()]). --spec(lookup_scratch/2 :: (name(), atom()) -> - rabbit_types:ok(term()) | - rabbit_types:error('not_found')). --spec(update_scratch/3 :: (name(), atom(), fun((any()) -> any())) -> 'ok'). --spec(update/2 :: - (name(), - fun((rabbit_types:exchange()) -> rabbit_types:exchange())) - -> not_found | rabbit_types:exchange()). --spec(update_decorators/1 :: (name()) -> 'ok'). --spec(immutable/1 :: (rabbit_types:exchange()) -> rabbit_types:exchange()). --spec(info_keys/0 :: () -> rabbit_types:info_keys()). --spec(info/1 :: (rabbit_types:exchange()) -> rabbit_types:infos()). --spec(info/2 :: - (rabbit_types:exchange(), rabbit_types:info_keys()) - -> rabbit_types:infos()). --spec(info_all/1 :: (rabbit_types:vhost()) -> [rabbit_types:infos()]). --spec(info_all/2 ::(rabbit_types:vhost(), rabbit_types:info_keys()) - -> [rabbit_types:infos()]). --spec(route/2 :: (rabbit_types:exchange(), rabbit_types:delivery()) - -> [rabbit_amqqueue:name()]). --spec(delete/2 :: - (name(), 'true') -> 'ok' | rabbit_types:error('not_found' | 'in_use'); - (name(), 'false') -> 'ok' | rabbit_types:error('not_found')). --spec(validate_binding/2 :: - (rabbit_types:exchange(), rabbit_types:binding()) - -> rabbit_types:ok_or_error({'binding_invalid', string(), [any()]})). --spec(maybe_auto_delete/2:: - (rabbit_types:exchange(), boolean()) - -> 'not_deleted' | {'deleted', rabbit_binding:deletions()}). --spec(serial/1 :: (rabbit_types:exchange()) -> - fun((boolean()) -> 'none' | pos_integer())). --spec(peek_serial/1 :: (name()) -> pos_integer() | 'undefined'). - --endif. - -%%---------------------------------------------------------------------------- - --define(INFO_KEYS, [name, type, durable, auto_delete, internal, arguments, - policy]). - -recover() -> - Xs = rabbit_misc:table_filter( - fun (#exchange{name = XName}) -> - mnesia:read({rabbit_exchange, XName}) =:= [] - end, - fun (X, Tx) -> - X1 = case Tx of - true -> store_ram(X); - false -> rabbit_exchange_decorator:set(X) - end, - callback(X1, create, map_create_tx(Tx), [X1]) - end, - rabbit_durable_exchange), - [XName || #exchange{name = XName} <- Xs]. - -callback(X = #exchange{type = XType, - decorators = Decorators}, Fun, Serial0, Args) -> - Serial = if is_function(Serial0) -> Serial0; - is_atom(Serial0) -> fun (_Bool) -> Serial0 end - end, - [ok = apply(M, Fun, [Serial(M:serialise_events(X)) | Args]) || - M <- rabbit_exchange_decorator:select(all, Decorators)], - Module = type_to_module(XType), - apply(Module, Fun, [Serial(Module:serialise_events()) | Args]). - -policy_changed(X = #exchange{type = XType, - decorators = Decorators}, - X1 = #exchange{decorators = Decorators1}) -> - D = rabbit_exchange_decorator:select(all, Decorators), - D1 = rabbit_exchange_decorator:select(all, Decorators1), - DAll = lists:usort(D ++ D1), - [ok = M:policy_changed(X, X1) || M <- [type_to_module(XType) | DAll]], - ok. - -serialise_events(X = #exchange{type = Type, decorators = Decorators}) -> - lists:any(fun (M) -> M:serialise_events(X) end, - rabbit_exchange_decorator:select(all, Decorators)) - orelse (type_to_module(Type)):serialise_events(). - -serial(#exchange{name = XName} = X) -> - Serial = case serialise_events(X) of - true -> next_serial(XName); - false -> none - end, - fun (true) -> Serial; - (false) -> none - end. - -declare(XName, Type, Durable, AutoDelete, Internal, Args) -> - X = rabbit_exchange_decorator:set( - rabbit_policy:set(#exchange{name = XName, - type = Type, - durable = Durable, - auto_delete = AutoDelete, - internal = Internal, - arguments = Args})), - XT = type_to_module(Type), - %% We want to upset things if it isn't ok - ok = XT:validate(X), - rabbit_misc:execute_mnesia_transaction( - fun () -> - case mnesia:wread({rabbit_exchange, XName}) of - [] -> - {new, store(X)}; - [ExistingX] -> - {existing, ExistingX} - end - end, - fun ({new, Exchange}, Tx) -> - ok = callback(X, create, map_create_tx(Tx), [Exchange]), - rabbit_event:notify_if(not Tx, exchange_created, info(Exchange)), - Exchange; - ({existing, Exchange}, _Tx) -> - Exchange; - (Err, _Tx) -> - Err - end). - -map_create_tx(true) -> transaction; -map_create_tx(false) -> none. - - -store(X = #exchange{durable = true}) -> - mnesia:write(rabbit_durable_exchange, X#exchange{decorators = undefined}, - write), - store_ram(X); -store(X = #exchange{durable = false}) -> - store_ram(X). - -store_ram(X) -> - X1 = rabbit_exchange_decorator:set(X), - ok = mnesia:write(rabbit_exchange, rabbit_exchange_decorator:set(X1), - write), - X1. - -%% Used with binaries sent over the wire; the type may not exist. -check_type(TypeBin) -> - case rabbit_registry:binary_to_type(TypeBin) of - {error, not_found} -> - rabbit_misc:protocol_error( - command_invalid, "unknown exchange type '~s'", [TypeBin]); - T -> - case rabbit_registry:lookup_module(exchange, T) of - {error, not_found} -> rabbit_misc:protocol_error( - command_invalid, - "invalid exchange type '~s'", [T]); - {ok, _Module} -> T - end - end. - -assert_equivalence(X = #exchange{ name = XName, - durable = Durable, - auto_delete = AutoDelete, - internal = Internal, - type = Type}, - ReqType, ReqDurable, ReqAutoDelete, ReqInternal, ReqArgs) -> - AFE = fun rabbit_misc:assert_field_equivalence/4, - AFE(Type, ReqType, XName, type), - AFE(Durable, ReqDurable, XName, durable), - AFE(AutoDelete, ReqAutoDelete, XName, auto_delete), - AFE(Internal, ReqInternal, XName, internal), - (type_to_module(Type)):assert_args_equivalence(X, ReqArgs). - -assert_args_equivalence(#exchange{ name = Name, arguments = Args }, - RequiredArgs) -> - %% The spec says "Arguments are compared for semantic - %% equivalence". The only arg we care about is - %% "alternate-exchange". - rabbit_misc:assert_args_equivalence(Args, RequiredArgs, Name, - [<<"alternate-exchange">>]). - -lookup(Name) -> - rabbit_misc:dirty_read({rabbit_exchange, Name}). - -lookup_or_die(Name) -> - case lookup(Name) of - {ok, X} -> X; - {error, not_found} -> rabbit_misc:not_found(Name) - end. - -list() -> mnesia:dirty_match_object(rabbit_exchange, #exchange{_ = '_'}). - -%% Not dirty_match_object since that would not be transactional when used in a -%% tx context -list(VHostPath) -> - mnesia:async_dirty( - fun () -> - mnesia:match_object( - rabbit_exchange, - #exchange{name = rabbit_misc:r(VHostPath, exchange), _ = '_'}, - read) - end). - -lookup_scratch(Name, App) -> - case lookup(Name) of - {ok, #exchange{scratches = undefined}} -> - {error, not_found}; - {ok, #exchange{scratches = Scratches}} -> - case orddict:find(App, Scratches) of - {ok, Value} -> {ok, Value}; - error -> {error, not_found} - end; - {error, not_found} -> - {error, not_found} - end. - -update_scratch(Name, App, Fun) -> - rabbit_misc:execute_mnesia_transaction( - fun() -> - update(Name, - fun(X = #exchange{scratches = Scratches0}) -> - Scratches1 = case Scratches0 of - undefined -> orddict:new(); - _ -> Scratches0 - end, - Scratch = case orddict:find(App, Scratches1) of - {ok, S} -> S; - error -> undefined - end, - Scratches2 = orddict:store( - App, Fun(Scratch), Scratches1), - X#exchange{scratches = Scratches2} - end), - ok - end). - -update_decorators(Name) -> - rabbit_misc:execute_mnesia_transaction( - fun() -> - case mnesia:wread({rabbit_exchange, Name}) of - [X] -> store_ram(X), - ok; - [] -> ok - end - end). - -update(Name, Fun) -> - case mnesia:wread({rabbit_exchange, Name}) of - [X] -> X1 = Fun(X), - store(X1); - [] -> not_found - end. - -immutable(X) -> X#exchange{scratches = none, - policy = none, - decorators = none}. - -info_keys() -> ?INFO_KEYS. - -map(VHostPath, F) -> - %% TODO: there is scope for optimisation here, e.g. using a - %% cursor, parallelising the function invocation - lists:map(F, list(VHostPath)). - -infos(Items, X) -> [{Item, i(Item, X)} || Item <- Items]. - -i(name, #exchange{name = Name}) -> Name; -i(type, #exchange{type = Type}) -> Type; -i(durable, #exchange{durable = Durable}) -> Durable; -i(auto_delete, #exchange{auto_delete = AutoDelete}) -> AutoDelete; -i(internal, #exchange{internal = Internal}) -> Internal; -i(arguments, #exchange{arguments = Arguments}) -> Arguments; -i(policy, X) -> case rabbit_policy:name(X) of - none -> ''; - Policy -> Policy - end; -i(Item, _) -> throw({bad_argument, Item}). - -info(X = #exchange{}) -> infos(?INFO_KEYS, X). - -info(X = #exchange{}, Items) -> infos(Items, X). - -info_all(VHostPath) -> map(VHostPath, fun (X) -> info(X) end). - -info_all(VHostPath, Items) -> map(VHostPath, fun (X) -> info(X, Items) end). - -route(#exchange{name = #resource{virtual_host = VHost, name = RName} = XName, - decorators = Decorators} = X, - #delivery{message = #basic_message{routing_keys = RKs}} = Delivery) -> - case RName of - <<>> -> - RKsSorted = lists:usort(RKs), - [rabbit_channel:deliver_reply(RK, Delivery) || - RK <- RKsSorted, virtual_reply_queue(RK)], - [rabbit_misc:r(VHost, queue, RK) || RK <- RKsSorted, - not virtual_reply_queue(RK)]; - _ -> - Decs = rabbit_exchange_decorator:select(route, Decorators), - lists:usort(route1(Delivery, Decs, {[X], XName, []})) - end. - -virtual_reply_queue(<<"amq.rabbitmq.reply-to.", _/binary>>) -> true; -virtual_reply_queue(_) -> false. - -route1(_, _, {[], _, QNames}) -> - QNames; -route1(Delivery, Decorators, - {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> - ExchangeDests = (type_to_module(Type)):route(X, Delivery), - DecorateDests = process_decorators(X, Decorators, Delivery), - AlternateDests = process_alternate(X, ExchangeDests), - route1(Delivery, Decorators, - lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, - AlternateDests ++ DecorateDests ++ ExchangeDests)). - -process_alternate(X = #exchange{name = XName}, []) -> - case rabbit_policy:get_arg( - <<"alternate-exchange">>, <<"alternate-exchange">>, X) of - undefined -> []; - AName -> [rabbit_misc:r(XName, exchange, AName)] - end; -process_alternate(_X, _Results) -> - []. - -process_decorators(_, [], _) -> %% optimisation - []; -process_decorators(X, Decorators, Delivery) -> - lists:append([Decorator:route(X, Delivery) || Decorator <- Decorators]). - -process_route(#resource{kind = exchange} = XName, - {_WorkList, XName, _QNames} = Acc) -> - Acc; -process_route(#resource{kind = exchange} = XName, - {WorkList, #resource{kind = exchange} = SeenX, QNames}) -> - {cons_if_present(XName, WorkList), - gb_sets:from_list([SeenX, XName]), QNames}; -process_route(#resource{kind = exchange} = XName, - {WorkList, SeenXs, QNames} = Acc) -> - case gb_sets:is_element(XName, SeenXs) of - true -> Acc; - false -> {cons_if_present(XName, WorkList), - gb_sets:add_element(XName, SeenXs), QNames} - end; -process_route(#resource{kind = queue} = QName, - {WorkList, SeenXs, QNames}) -> - {WorkList, SeenXs, [QName | QNames]}. - -cons_if_present(XName, L) -> - case lookup(XName) of - {ok, X} -> [X | L]; - {error, not_found} -> L - end. - -call_with_exchange(XName, Fun) -> - rabbit_misc:execute_mnesia_tx_with_tail( - fun () -> case mnesia:read({rabbit_exchange, XName}) of - [] -> rabbit_misc:const({error, not_found}); - [X] -> Fun(X) - end - end). - -delete(XName, IfUnused) -> - Fun = case IfUnused of - true -> fun conditional_delete/2; - false -> fun unconditional_delete/2 - end, - call_with_exchange( - XName, - fun (X) -> - case Fun(X, false) of - {deleted, X, Bs, Deletions} -> - rabbit_binding:process_deletions( - rabbit_binding:add_deletion( - XName, {X, deleted, Bs}, Deletions)); - {error, _InUseOrNotFound} = E -> - rabbit_misc:const(E) - end - end). - -validate_binding(X = #exchange{type = XType}, Binding) -> - Module = type_to_module(XType), - Module:validate_binding(X, Binding). - -maybe_auto_delete(#exchange{auto_delete = false}, _OnlyDurable) -> - not_deleted; -maybe_auto_delete(#exchange{auto_delete = true} = X, OnlyDurable) -> - case conditional_delete(X, OnlyDurable) of - {error, in_use} -> not_deleted; - {deleted, X, [], Deletions} -> {deleted, Deletions} - end. - -conditional_delete(X = #exchange{name = XName}, OnlyDurable) -> - case rabbit_binding:has_for_source(XName) of - false -> unconditional_delete(X, OnlyDurable); - true -> {error, in_use} - end. - -unconditional_delete(X = #exchange{name = XName}, OnlyDurable) -> - %% this 'guarded' delete prevents unnecessary writes to the mnesia - %% disk log - case mnesia:wread({rabbit_durable_exchange, XName}) of - [] -> ok; - [_] -> ok = mnesia:delete({rabbit_durable_exchange, XName}) - end, - ok = mnesia:delete({rabbit_exchange, XName}), - ok = mnesia:delete({rabbit_exchange_serial, XName}), - Bindings = rabbit_binding:remove_for_source(XName), - {deleted, X, Bindings, rabbit_binding:remove_for_destination( - XName, OnlyDurable)}. - -next_serial(XName) -> - Serial = peek_serial(XName, write), - ok = mnesia:write(rabbit_exchange_serial, - #exchange_serial{name = XName, next = Serial + 1}, write), - Serial. - -peek_serial(XName) -> peek_serial(XName, read). - -peek_serial(XName, LockType) -> - case mnesia:read(rabbit_exchange_serial, XName, LockType) of - [#exchange_serial{next = Serial}] -> Serial; - _ -> 1 - end. - -invalid_module(T) -> - rabbit_log:warning("Could not find exchange type ~s.~n", [T]), - put({xtype_to_module, T}, rabbit_exchange_type_invalid), - rabbit_exchange_type_invalid. - -%% Used with atoms from records; e.g., the type is expected to exist. -type_to_module(T) -> - case get({xtype_to_module, T}) of - undefined -> - case rabbit_registry:lookup_module(exchange, T) of - {ok, Module} -> put({xtype_to_module, T}, Module), - Module; - {error, not_found} -> invalid_module(T) - end; - Module -> - Module - end. diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl deleted file mode 100644 index 900f9c32..00000000 --- a/src/rabbit_exchange_decorator.erl +++ /dev/null @@ -1,128 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_exchange_decorator). - --include("rabbit.hrl"). - --export([select/2, set/1, register/2, unregister/1]). - -%% This is like an exchange type except that: -%% -%% 1) It applies to all exchanges as soon as it is installed, therefore -%% 2) It is not allowed to affect validation, so no validate/1 or -%% assert_args_equivalence/2 -%% -%% It's possible in the future we might make decorators -%% able to manipulate messages as they are published. - --ifdef(use_specs). - --type(tx() :: 'transaction' | 'none'). --type(serial() :: pos_integer() | tx()). - --callback description() -> [proplists:property()]. - -%% Should Rabbit ensure that all binding events that are -%% delivered to an individual exchange can be serialised? (they -%% might still be delivered out of order, but there'll be a -%% serial number). --callback serialise_events(rabbit_types:exchange()) -> boolean(). - -%% called after declaration and recovery --callback create(tx(), rabbit_types:exchange()) -> 'ok'. - -%% called after exchange (auto)deletion. --callback delete(tx(), rabbit_types:exchange(), [rabbit_types:binding()]) -> - 'ok'. - -%% called when the policy attached to this exchange changes. --callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> - 'ok'. - -%% called after a binding has been added or recovered --callback add_binding(serial(), rabbit_types:exchange(), - rabbit_types:binding()) -> 'ok'. - -%% called after bindings have been deleted. --callback remove_bindings(serial(), rabbit_types:exchange(), - [rabbit_types:binding()]) -> 'ok'. - -%% Allows additional destinations to be added to the routing decision. --callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> - [rabbit_amqqueue:name() | rabbit_exchange:name()]. - -%% Whether the decorator wishes to receive callbacks for the exchange -%% none:no callbacks, noroute:all callbacks except route, all:all callbacks --callback active_for(rabbit_types:exchange()) -> 'none' | 'noroute' | 'all'. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{description, 0}, {serialise_events, 1}, {create, 2}, {delete, 3}, - {policy_changed, 2}, {add_binding, 3}, {remove_bindings, 3}, - {route, 2}, {active_for, 1}]; -behaviour_info(_Other) -> - undefined. - --endif. - -%%---------------------------------------------------------------------------- - -%% select a subset of active decorators -select(all, {Route, NoRoute}) -> filter(Route ++ NoRoute); -select(route, {Route, _NoRoute}) -> filter(Route); -select(raw, {Route, NoRoute}) -> Route ++ NoRoute. - -filter(Modules) -> - [M || M <- Modules, code:which(M) =/= non_existing]. - -set(X) -> - Decs = lists:foldl(fun (D, {Route, NoRoute}) -> - ActiveFor = D:active_for(X), - {cons_if_eq(all, ActiveFor, D, Route), - cons_if_eq(noroute, ActiveFor, D, NoRoute)} - end, {[], []}, list()), - X#exchange{decorators = Decs}. - -list() -> [M || {_, M} <- rabbit_registry:lookup_all(exchange_decorator)]. - -cons_if_eq(Select, Select, Item, List) -> [Item | List]; -cons_if_eq(_Select, _Other, _Item, List) -> List. - -register(TypeName, ModuleName) -> - rabbit_registry:register(exchange_decorator, TypeName, ModuleName), - [maybe_recover(X) || X <- rabbit_exchange:list()], - ok. - -unregister(TypeName) -> - rabbit_registry:unregister(exchange_decorator, TypeName), - [maybe_recover(X) || X <- rabbit_exchange:list()], - ok. - -maybe_recover(X = #exchange{name = Name, - decorators = Decs}) -> - #exchange{decorators = Decs1} = set(X), - Old = lists:sort(select(all, Decs)), - New = lists:sort(select(all, Decs1)), - case New of - Old -> ok; - _ -> %% TODO create a tx here for non-federation decorators - [M:create(none, X) || M <- New -- Old], - rabbit_exchange:update_decorators(Name) - end. diff --git a/src/rabbit_exchange_type.erl b/src/rabbit_exchange_type.erl deleted file mode 100644 index 4dd34428..00000000 --- a/src/rabbit_exchange_type.erl +++ /dev/null @@ -1,81 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_exchange_type). - --ifdef(use_specs). - --type(tx() :: 'transaction' | 'none'). --type(serial() :: pos_integer() | tx()). - --callback description() -> [proplists:property()]. - -%% Should Rabbit ensure that all binding events that are -%% delivered to an individual exchange can be serialised? (they -%% might still be delivered out of order, but there'll be a -%% serial number). --callback serialise_events() -> boolean(). - -%% The no_return is there so that we can have an "invalid" exchange -%% type (see rabbit_exchange_type_invalid). --callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> - rabbit_router:match_result(). - -%% called BEFORE declaration, to check args etc; may exit with #amqp_error{} --callback validate(rabbit_types:exchange()) -> 'ok'. - -%% called BEFORE declaration, to check args etc --callback validate_binding(rabbit_types:exchange(), rabbit_types:binding()) -> - rabbit_types:ok_or_error({'binding_invalid', string(), [any()]}). - -%% called after declaration and recovery --callback create(tx(), rabbit_types:exchange()) -> 'ok'. - -%% called after exchange (auto)deletion. --callback delete(tx(), rabbit_types:exchange(), [rabbit_types:binding()]) -> - 'ok'. - -%% called when the policy attached to this exchange changes. --callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> - 'ok'. - -%% called after a binding has been added or recovered --callback add_binding(serial(), rabbit_types:exchange(), - rabbit_types:binding()) -> 'ok'. - -%% called after bindings have been deleted. --callback remove_bindings(serial(), rabbit_types:exchange(), - [rabbit_types:binding()]) -> 'ok'. - -%% called when comparing exchanges for equivalence - should return ok or -%% exit with #amqp_error{} --callback assert_args_equivalence(rabbit_types:exchange(), - rabbit_framing:amqp_table()) -> - 'ok' | rabbit_types:connection_exit(). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{description, 0}, {serialise_events, 0}, {route, 2}, - {validate, 1}, {validate_binding, 2}, {policy_changed, 2}, - {create, 2}, {delete, 3}, {add_binding, 3}, {remove_bindings, 3}, - {assert_args_equivalence, 2}]; -behaviour_info(_Other) -> - undefined. - --endif. diff --git a/src/rabbit_exchange_type_direct.erl b/src/rabbit_exchange_type_direct.erl deleted file mode 100644 index 8a240a8b..00000000 --- a/src/rabbit_exchange_type_direct.erl +++ /dev/null @@ -1,51 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_exchange_type_direct). --include("rabbit.hrl"). - --behaviour(rabbit_exchange_type). - --export([description/0, serialise_events/0, route/2]). --export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/2, add_binding/3, - remove_bindings/3, assert_args_equivalence/2]). - --rabbit_boot_step({?MODULE, - [{description, "exchange type direct"}, - {mfa, {rabbit_registry, register, - [exchange, <<"direct">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - -description() -> - [{description, <<"AMQP direct exchange, as per the AMQP specification">>}]. - -serialise_events() -> false. - -route(#exchange{name = Name}, - #delivery{message = #basic_message{routing_keys = Routes}}) -> - rabbit_router:match_routing_key(Name, Routes). - -validate(_X) -> ok. -validate_binding(_X, _B) -> ok. -create(_Tx, _X) -> ok. -delete(_Tx, _X, _Bs) -> ok. -policy_changed(_X1, _X2) -> ok. -add_binding(_Tx, _X, _B) -> ok. -remove_bindings(_Tx, _X, _Bs) -> ok. -assert_args_equivalence(X, Args) -> - rabbit_exchange:assert_args_equivalence(X, Args). diff --git a/src/rabbit_exchange_type_fanout.erl b/src/rabbit_exchange_type_fanout.erl deleted file mode 100644 index 3a1f0717..00000000 --- a/src/rabbit_exchange_type_fanout.erl +++ /dev/null @@ -1,50 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_exchange_type_fanout). --include("rabbit.hrl"). - --behaviour(rabbit_exchange_type). - --export([description/0, serialise_events/0, route/2]). --export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/2, add_binding/3, - remove_bindings/3, assert_args_equivalence/2]). - --rabbit_boot_step({?MODULE, - [{description, "exchange type fanout"}, - {mfa, {rabbit_registry, register, - [exchange, <<"fanout">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - -description() -> - [{description, <<"AMQP fanout exchange, as per the AMQP specification">>}]. - -serialise_events() -> false. - -route(#exchange{name = Name}, _Delivery) -> - rabbit_router:match_routing_key(Name, ['_']). - -validate(_X) -> ok. -validate_binding(_X, _B) -> ok. -create(_Tx, _X) -> ok. -delete(_Tx, _X, _Bs) -> ok. -policy_changed(_X1, _X2) -> ok. -add_binding(_Tx, _X, _B) -> ok. -remove_bindings(_Tx, _X, _Bs) -> ok. -assert_args_equivalence(X, Args) -> - rabbit_exchange:assert_args_equivalence(X, Args). diff --git a/src/rabbit_exchange_type_headers.erl b/src/rabbit_exchange_type_headers.erl deleted file mode 100644 index afce57d9..00000000 --- a/src/rabbit_exchange_type_headers.erl +++ /dev/null @@ -1,125 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_exchange_type_headers). --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - --behaviour(rabbit_exchange_type). - --export([description/0, serialise_events/0, route/2]). --export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/2, add_binding/3, - remove_bindings/3, assert_args_equivalence/2]). - --rabbit_boot_step({?MODULE, - [{description, "exchange type headers"}, - {mfa, {rabbit_registry, register, - [exchange, <<"headers">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - --ifdef(use_specs). --spec(headers_match/2 :: (rabbit_framing:amqp_table(), - rabbit_framing:amqp_table()) -> boolean()). --endif. - -description() -> - [{description, <<"AMQP headers exchange, as per the AMQP specification">>}]. - -serialise_events() -> false. - -route(#exchange{name = Name}, - #delivery{message = #basic_message{content = Content}}) -> - Headers = case (Content#content.properties)#'P_basic'.headers of - undefined -> []; - H -> rabbit_misc:sort_field_table(H) - end, - rabbit_router:match_bindings( - Name, fun (#binding{args = Spec}) -> headers_match(Spec, Headers) end). - -validate_binding(_X, #binding{args = Args}) -> - case rabbit_misc:table_lookup(Args, <<"x-match">>) of - {longstr, <<"all">>} -> ok; - {longstr, <<"any">>} -> ok; - {longstr, Other} -> {error, - {binding_invalid, - "Invalid x-match field value ~p; " - "expected all or any", [Other]}}; - {Type, Other} -> {error, - {binding_invalid, - "Invalid x-match field type ~p (value ~p); " - "expected longstr", [Type, Other]}}; - undefined -> ok %% [0] - end. -%% [0] spec is vague on whether it can be omitted but in practice it's -%% useful to allow people to do this - -parse_x_match({longstr, <<"all">>}) -> all; -parse_x_match({longstr, <<"any">>}) -> any; -parse_x_match(_) -> all. %% legacy; we didn't validate - -%% Horrendous matching algorithm. Depends for its merge-like -%% (linear-time) behaviour on the lists:keysort -%% (rabbit_misc:sort_field_table) that route/1 and -%% rabbit_binding:{add,remove}/2 do. -%% -%% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -%% In other words: REQUIRES BOTH PATTERN AND DATA TO BE SORTED ASCENDING BY KEY. -%% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -%% -headers_match(Args, Data) -> - MK = parse_x_match(rabbit_misc:table_lookup(Args, <<"x-match">>)), - headers_match(Args, Data, true, false, MK). - -headers_match([], _Data, AllMatch, _AnyMatch, all) -> - AllMatch; -headers_match([], _Data, _AllMatch, AnyMatch, any) -> - AnyMatch; -headers_match([{<<"x-", _/binary>>, _PT, _PV} | PRest], Data, - AllMatch, AnyMatch, MatchKind) -> - headers_match(PRest, Data, AllMatch, AnyMatch, MatchKind); -headers_match(_Pattern, [], _AllMatch, AnyMatch, MatchKind) -> - headers_match([], [], false, AnyMatch, MatchKind); -headers_match(Pattern = [{PK, _PT, _PV} | _], [{DK, _DT, _DV} | DRest], - AllMatch, AnyMatch, MatchKind) when PK > DK -> - headers_match(Pattern, DRest, AllMatch, AnyMatch, MatchKind); -headers_match([{PK, _PT, _PV} | PRest], Data = [{DK, _DT, _DV} | _], - _AllMatch, AnyMatch, MatchKind) when PK < DK -> - headers_match(PRest, Data, false, AnyMatch, MatchKind); -headers_match([{PK, PT, PV} | PRest], [{DK, DT, DV} | DRest], - AllMatch, AnyMatch, MatchKind) when PK == DK -> - {AllMatch1, AnyMatch1} = - case rabbit_misc:type_class(PT) == rabbit_misc:type_class(DT) of - %% It's not properly specified, but a "no value" in a - %% pattern field is supposed to mean simple presence of - %% the corresponding data field. I've interpreted that to - %% mean a type of "void" for the pattern field. - _ when PT == void -> {AllMatch, true}; - false -> {false, AnyMatch}; - _ when PV == DV -> {AllMatch, true}; - _ -> {false, AnyMatch} - end, - headers_match(PRest, DRest, AllMatch1, AnyMatch1, MatchKind). - -validate(_X) -> ok. -create(_Tx, _X) -> ok. -delete(_Tx, _X, _Bs) -> ok. -policy_changed(_X1, _X2) -> ok. -add_binding(_Tx, _X, _B) -> ok. -remove_bindings(_Tx, _X, _Bs) -> ok. -assert_args_equivalence(X, Args) -> - rabbit_exchange:assert_args_equivalence(X, Args). diff --git a/src/rabbit_exchange_type_invalid.erl b/src/rabbit_exchange_type_invalid.erl deleted file mode 100644 index 457f184a..00000000 --- a/src/rabbit_exchange_type_invalid.erl +++ /dev/null @@ -1,52 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_exchange_type_invalid). --include("rabbit.hrl"). - --behaviour(rabbit_exchange_type). - --export([description/0, serialise_events/0, route/2]). --export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/2, add_binding/3, - remove_bindings/3, assert_args_equivalence/2]). - -description() -> - [{description, - <<"Dummy exchange type, to be used when the intended one is not found.">> - }]. - -serialise_events() -> false. - --ifdef(use_specs). --spec(route/2 :: (rabbit_types:exchange(), rabbit_types:delivery()) - -> no_return()). --endif. -route(#exchange{name = Name, type = Type}, _) -> - rabbit_misc:protocol_error( - precondition_failed, - "Cannot route message through ~s: exchange type ~s not found", - [rabbit_misc:rs(Name), Type]). - -validate(_X) -> ok. -validate_binding(_X, _B) -> ok. -create(_Tx, _X) -> ok. -delete(_Tx, _X, _Bs) -> ok. -policy_changed(_X1, _X2) -> ok. -add_binding(_Tx, _X, _B) -> ok. -remove_bindings(_Tx, _X, _Bs) -> ok. -assert_args_equivalence(X, Args) -> - rabbit_exchange:assert_args_equivalence(X, Args). diff --git a/src/rabbit_exchange_type_topic.erl b/src/rabbit_exchange_type_topic.erl deleted file mode 100644 index af00fe88..00000000 --- a/src/rabbit_exchange_type_topic.erl +++ /dev/null @@ -1,270 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_exchange_type_topic). - --include("rabbit.hrl"). - --behaviour(rabbit_exchange_type). - --export([description/0, serialise_events/0, route/2]). --export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/2, add_binding/3, - remove_bindings/3, assert_args_equivalence/2]). - --rabbit_boot_step({?MODULE, - [{description, "exchange type topic"}, - {mfa, {rabbit_registry, register, - [exchange, <<"topic">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - -%%---------------------------------------------------------------------------- - -description() -> - [{description, <<"AMQP topic exchange, as per the AMQP specification">>}]. - -serialise_events() -> false. - -%% NB: This may return duplicate results in some situations (that's ok) -route(#exchange{name = X}, - #delivery{message = #basic_message{routing_keys = Routes}}) -> - lists:append([begin - Words = split_topic_key(RKey), - mnesia:async_dirty(fun trie_match/2, [X, Words]) - end || RKey <- Routes]). - -validate(_X) -> ok. -validate_binding(_X, _B) -> ok. -create(_Tx, _X) -> ok. - -delete(transaction, #exchange{name = X}, _Bs) -> - trie_remove_all_nodes(X), - trie_remove_all_edges(X), - trie_remove_all_bindings(X), - ok; -delete(none, _Exchange, _Bs) -> - ok. - -policy_changed(_X1, _X2) -> ok. - -add_binding(transaction, _Exchange, Binding) -> - internal_add_binding(Binding); -add_binding(none, _Exchange, _Binding) -> - ok. - -remove_bindings(transaction, _X, Bs) -> - %% See rabbit_binding:lock_route_tables for the rationale for - %% taking table locks. - case Bs of - [_] -> ok; - _ -> [mnesia:lock({table, T}, write) || - T <- [rabbit_topic_trie_node, - rabbit_topic_trie_edge, - rabbit_topic_trie_binding]] - end, - [begin - Path = [{FinalNode, _} | _] = - follow_down_get_path(X, split_topic_key(K)), - trie_remove_binding(X, FinalNode, D, Args), - remove_path_if_empty(X, Path) - end || #binding{source = X, key = K, destination = D, args = Args} <- Bs], - ok; -remove_bindings(none, _X, _Bs) -> - ok. - -assert_args_equivalence(X, Args) -> - rabbit_exchange:assert_args_equivalence(X, Args). - -%%---------------------------------------------------------------------------- - -internal_add_binding(#binding{source = X, key = K, destination = D, - args = Args}) -> - FinalNode = follow_down_create(X, split_topic_key(K)), - trie_add_binding(X, FinalNode, D, Args), - ok. - -trie_match(X, Words) -> - trie_match(X, root, Words, []). - -trie_match(X, Node, [], ResAcc) -> - trie_match_part(X, Node, "#", fun trie_match_skip_any/4, [], - trie_bindings(X, Node) ++ ResAcc); -trie_match(X, Node, [W | RestW] = Words, ResAcc) -> - lists:foldl(fun ({WArg, MatchFun, RestWArg}, Acc) -> - trie_match_part(X, Node, WArg, MatchFun, RestWArg, Acc) - end, ResAcc, [{W, fun trie_match/4, RestW}, - {"*", fun trie_match/4, RestW}, - {"#", fun trie_match_skip_any/4, Words}]). - -trie_match_part(X, Node, Search, MatchFun, RestW, ResAcc) -> - case trie_child(X, Node, Search) of - {ok, NextNode} -> MatchFun(X, NextNode, RestW, ResAcc); - error -> ResAcc - end. - -trie_match_skip_any(X, Node, [], ResAcc) -> - trie_match(X, Node, [], ResAcc); -trie_match_skip_any(X, Node, [_ | RestW] = Words, ResAcc) -> - trie_match_skip_any(X, Node, RestW, - trie_match(X, Node, Words, ResAcc)). - -follow_down_create(X, Words) -> - case follow_down_last_node(X, Words) of - {ok, FinalNode} -> FinalNode; - {error, Node, RestW} -> lists:foldl( - fun (W, CurNode) -> - NewNode = new_node_id(), - trie_add_edge(X, CurNode, NewNode, W), - NewNode - end, Node, RestW) - end. - -follow_down_last_node(X, Words) -> - follow_down(X, fun (_, Node, _) -> Node end, root, Words). - -follow_down_get_path(X, Words) -> - {ok, Path} = - follow_down(X, fun (W, Node, PathAcc) -> [{Node, W} | PathAcc] end, - [{root, none}], Words), - Path. - -follow_down(X, AccFun, Acc0, Words) -> - follow_down(X, root, AccFun, Acc0, Words). - -follow_down(_X, _CurNode, _AccFun, Acc, []) -> - {ok, Acc}; -follow_down(X, CurNode, AccFun, Acc, Words = [W | RestW]) -> - case trie_child(X, CurNode, W) of - {ok, NextNode} -> follow_down(X, NextNode, AccFun, - AccFun(W, NextNode, Acc), RestW); - error -> {error, Acc, Words} - end. - -remove_path_if_empty(_, [{root, none}]) -> - ok; -remove_path_if_empty(X, [{Node, W} | [{Parent, _} | _] = RestPath]) -> - case mnesia:read(rabbit_topic_trie_node, - #trie_node{exchange_name = X, node_id = Node}, write) of - [] -> trie_remove_edge(X, Parent, Node, W), - remove_path_if_empty(X, RestPath); - _ -> ok - end. - -trie_child(X, Node, Word) -> - case mnesia:read({rabbit_topic_trie_edge, - #trie_edge{exchange_name = X, - node_id = Node, - word = Word}}) of - [#topic_trie_edge{node_id = NextNode}] -> {ok, NextNode}; - [] -> error - end. - -trie_bindings(X, Node) -> - MatchHead = #topic_trie_binding{ - trie_binding = #trie_binding{exchange_name = X, - node_id = Node, - destination = '$1', - arguments = '_'}}, - mnesia:select(rabbit_topic_trie_binding, [{MatchHead, [], ['$1']}]). - -trie_update_node_counts(X, Node, Field, Delta) -> - E = case mnesia:read(rabbit_topic_trie_node, - #trie_node{exchange_name = X, - node_id = Node}, write) of - [] -> #topic_trie_node{trie_node = #trie_node{ - exchange_name = X, - node_id = Node}, - edge_count = 0, - binding_count = 0}; - [E0] -> E0 - end, - case setelement(Field, E, element(Field, E) + Delta) of - #topic_trie_node{edge_count = 0, binding_count = 0} -> - ok = mnesia:delete_object(rabbit_topic_trie_node, E, write); - EN -> - ok = mnesia:write(rabbit_topic_trie_node, EN, write) - end. - -trie_add_edge(X, FromNode, ToNode, W) -> - trie_update_node_counts(X, FromNode, #topic_trie_node.edge_count, +1), - trie_edge_op(X, FromNode, ToNode, W, fun mnesia:write/3). - -trie_remove_edge(X, FromNode, ToNode, W) -> - trie_update_node_counts(X, FromNode, #topic_trie_node.edge_count, -1), - trie_edge_op(X, FromNode, ToNode, W, fun mnesia:delete_object/3). - -trie_edge_op(X, FromNode, ToNode, W, Op) -> - ok = Op(rabbit_topic_trie_edge, - #topic_trie_edge{trie_edge = #trie_edge{exchange_name = X, - node_id = FromNode, - word = W}, - node_id = ToNode}, - write). - -trie_add_binding(X, Node, D, Args) -> - trie_update_node_counts(X, Node, #topic_trie_node.binding_count, +1), - trie_binding_op(X, Node, D, Args, fun mnesia:write/3). - -trie_remove_binding(X, Node, D, Args) -> - trie_update_node_counts(X, Node, #topic_trie_node.binding_count, -1), - trie_binding_op(X, Node, D, Args, fun mnesia:delete_object/3). - -trie_binding_op(X, Node, D, Args, Op) -> - ok = Op(rabbit_topic_trie_binding, - #topic_trie_binding{ - trie_binding = #trie_binding{exchange_name = X, - node_id = Node, - destination = D, - arguments = Args}}, - write). - -trie_remove_all_nodes(X) -> - remove_all(rabbit_topic_trie_node, - #topic_trie_node{trie_node = #trie_node{exchange_name = X, - _ = '_'}, - _ = '_'}). - -trie_remove_all_edges(X) -> - remove_all(rabbit_topic_trie_edge, - #topic_trie_edge{trie_edge = #trie_edge{exchange_name = X, - _ = '_'}, - _ = '_'}). - -trie_remove_all_bindings(X) -> - remove_all(rabbit_topic_trie_binding, - #topic_trie_binding{ - trie_binding = #trie_binding{exchange_name = X, _ = '_'}, - _ = '_'}). - -remove_all(Table, Pattern) -> - lists:foreach(fun (R) -> mnesia:delete_object(Table, R, write) end, - mnesia:match_object(Table, Pattern, write)). - -new_node_id() -> - rabbit_guid:gen(). - -split_topic_key(Key) -> - split_topic_key(Key, [], []). - -split_topic_key(<<>>, [], []) -> - []; -split_topic_key(<<>>, RevWordAcc, RevResAcc) -> - lists:reverse([lists:reverse(RevWordAcc) | RevResAcc]); -split_topic_key(<<$., Rest/binary>>, RevWordAcc, RevResAcc) -> - split_topic_key(Rest, [], [lists:reverse(RevWordAcc) | RevResAcc]); -split_topic_key(<<C:8, Rest/binary>>, RevWordAcc, RevResAcc) -> - split_topic_key(Rest, [C | RevWordAcc], RevResAcc). diff --git a/src/rabbit_file.erl b/src/rabbit_file.erl deleted file mode 100644 index 81a617a8..00000000 --- a/src/rabbit_file.erl +++ /dev/null @@ -1,307 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_file). - --include_lib("kernel/include/file.hrl"). - --export([is_file/1, is_dir/1, file_size/1, ensure_dir/1, wildcard/2, list_dir/1]). --export([read_term_file/1, write_term_file/2, write_file/2, write_file/3]). --export([append_file/2, ensure_parent_dirs_exist/1]). --export([rename/2, delete/1, recursive_delete/1, recursive_copy/2]). --export([lock_file/1]). - --import(file_handle_cache, [with_handle/1, with_handle/2]). - --define(TMP_EXT, ".tmp"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(ok_or_error() :: rabbit_types:ok_or_error(any())). - --spec(is_file/1 :: ((file:filename())) -> boolean()). --spec(is_dir/1 :: ((file:filename())) -> boolean()). --spec(file_size/1 :: ((file:filename())) -> non_neg_integer()). --spec(ensure_dir/1 :: ((file:filename())) -> ok_or_error()). --spec(wildcard/2 :: (string(), file:filename()) -> [file:filename()]). --spec(list_dir/1 :: (file:filename()) -> rabbit_types:ok_or_error2( - [file:filename()], any())). --spec(read_term_file/1 :: - (file:filename()) -> {'ok', [any()]} | rabbit_types:error(any())). --spec(write_term_file/2 :: (file:filename(), [any()]) -> ok_or_error()). --spec(write_file/2 :: (file:filename(), iodata()) -> ok_or_error()). --spec(write_file/3 :: (file:filename(), iodata(), [any()]) -> ok_or_error()). --spec(append_file/2 :: (file:filename(), string()) -> ok_or_error()). --spec(ensure_parent_dirs_exist/1 :: (string()) -> 'ok'). --spec(rename/2 :: - (file:filename(), file:filename()) -> ok_or_error()). --spec(delete/1 :: ([file:filename()]) -> ok_or_error()). --spec(recursive_delete/1 :: - ([file:filename()]) - -> rabbit_types:ok_or_error({file:filename(), any()})). --spec(recursive_copy/2 :: - (file:filename(), file:filename()) - -> rabbit_types:ok_or_error({file:filename(), file:filename(), any()})). --spec(lock_file/1 :: (file:filename()) -> rabbit_types:ok_or_error('eexist')). - --endif. - -%%---------------------------------------------------------------------------- - -is_file(File) -> - case read_file_info(File) of - {ok, #file_info{type=regular}} -> true; - {ok, #file_info{type=directory}} -> true; - _ -> false - end. - -is_dir(Dir) -> is_dir_internal(read_file_info(Dir)). - -is_dir_no_handle(Dir) -> is_dir_internal(prim_file:read_file_info(Dir)). - -is_dir_internal({ok, #file_info{type=directory}}) -> true; -is_dir_internal(_) -> false. - -file_size(File) -> - case read_file_info(File) of - {ok, #file_info{size=Size}} -> Size; - _ -> 0 - end. - -ensure_dir(File) -> with_handle(fun () -> ensure_dir_internal(File) end). - -ensure_dir_internal("/") -> - ok; -ensure_dir_internal(File) -> - Dir = filename:dirname(File), - case is_dir_no_handle(Dir) of - true -> ok; - false -> ensure_dir_internal(Dir), - prim_file:make_dir(Dir) - end. - -wildcard(Pattern, Dir) -> - case list_dir(Dir) of - {ok, Files} -> {ok, RE} = re:compile(Pattern, [anchored]), - [File || File <- Files, - match =:= re:run(File, RE, [{capture, none}])]; - {error, _} -> [] - end. - -list_dir(Dir) -> with_handle(fun () -> prim_file:list_dir(Dir) end). - -read_file_info(File) -> - with_handle(fun () -> prim_file:read_file_info(File) end). - -read_term_file(File) -> - try - {ok, Data} = with_handle(fun () -> prim_file:read_file(File) end), - {ok, Tokens, _} = erl_scan:string(binary_to_list(Data)), - TokenGroups = group_tokens(Tokens), - {ok, [begin - {ok, Term} = erl_parse:parse_term(Tokens1), - Term - end || Tokens1 <- TokenGroups]} - catch - error:{badmatch, Error} -> Error - end. - -group_tokens(Ts) -> [lists:reverse(G) || G <- group_tokens([], Ts)]. - -group_tokens([], []) -> []; -group_tokens(Cur, []) -> [Cur]; -group_tokens(Cur, [T = {dot, _} | Ts]) -> [[T | Cur] | group_tokens([], Ts)]; -group_tokens(Cur, [T | Ts]) -> group_tokens([T | Cur], Ts). - -write_term_file(File, Terms) -> - write_file(File, list_to_binary([io_lib:format("~w.~n", [Term]) || - Term <- Terms])). - -write_file(Path, Data) -> write_file(Path, Data, []). - -write_file(Path, Data, Modes) -> - Modes1 = [binary, write | (Modes -- [binary, write])], - case make_binary(Data) of - Bin when is_binary(Bin) -> write_file1(Path, Bin, Modes1); - {error, _} = E -> E - end. - -%% make_binary/1 is based on the corresponding function in the -%% kernel/file.erl module of the Erlang R14B02 release, which is -%% licensed under the EPL. - -make_binary(Bin) when is_binary(Bin) -> - Bin; -make_binary(List) -> - try - iolist_to_binary(List) - catch error:Reason -> - {error, Reason} - end. - -write_file1(Path, Bin, Modes) -> - try - with_synced_copy(Path, Modes, - fun (Hdl) -> - ok = prim_file:write(Hdl, Bin) - end) - catch - error:{badmatch, Error} -> Error; - _:{error, Error} -> {error, Error} - end. - -with_synced_copy(Path, Modes, Fun) -> - case lists:member(append, Modes) of - true -> - {error, append_not_supported, Path}; - false -> - with_handle( - fun () -> - Bak = Path ++ ?TMP_EXT, - case prim_file:open(Bak, Modes) of - {ok, Hdl} -> - try - Result = Fun(Hdl), - ok = prim_file:sync(Hdl), - ok = prim_file:rename(Bak, Path), - Result - after - prim_file:close(Hdl) - end; - {error, _} = E -> E - end - end) - end. - -%% TODO the semantics of this function are rather odd. But see bug 25021. -append_file(File, Suffix) -> - case read_file_info(File) of - {ok, FInfo} -> append_file(File, FInfo#file_info.size, Suffix); - {error, enoent} -> append_file(File, 0, Suffix); - Error -> Error - end. - -append_file(_, _, "") -> - ok; -append_file(File, 0, Suffix) -> - with_handle(fun () -> - case prim_file:open([File, Suffix], [append]) of - {ok, Fd} -> prim_file:close(Fd); - Error -> Error - end - end); -append_file(File, _, Suffix) -> - case with_handle(2, fun () -> - file:copy(File, {[File, Suffix], [append]}) - end) of - {ok, _BytesCopied} -> ok; - Error -> Error - end. - -ensure_parent_dirs_exist(Filename) -> - case ensure_dir(Filename) of - ok -> ok; - {error, Reason} -> - throw({error, {cannot_create_parent_dirs, Filename, Reason}}) - end. - -rename(Old, New) -> with_handle(fun () -> prim_file:rename(Old, New) end). - -delete(File) -> with_handle(fun () -> prim_file:delete(File) end). - -recursive_delete(Files) -> - with_handle( - fun () -> lists:foldl(fun (Path, ok) -> recursive_delete1(Path); - (_Path, {error, _Err} = Error) -> Error - end, ok, Files) - end). - -recursive_delete1(Path) -> - case is_dir_no_handle(Path) and not(is_symlink_no_handle(Path)) of - false -> case prim_file:delete(Path) of - ok -> ok; - {error, enoent} -> ok; %% Path doesn't exist anyway - {error, Err} -> {error, {Path, Err}} - end; - true -> case prim_file:list_dir(Path) of - {ok, FileNames} -> - case lists:foldl( - fun (FileName, ok) -> - recursive_delete1( - filename:join(Path, FileName)); - (_FileName, Error) -> - Error - end, ok, FileNames) of - ok -> - case prim_file:del_dir(Path) of - ok -> ok; - {error, Err} -> {error, {Path, Err}} - end; - {error, _Err} = Error -> - Error - end; - {error, Err} -> - {error, {Path, Err}} - end - end. - -is_symlink_no_handle(File) -> - case prim_file:read_link(File) of - {ok, _} -> true; - _ -> false - end. - -recursive_copy(Src, Dest) -> - %% Note that this uses the 'file' module and, hence, shouldn't be - %% run on many processes at once. - case is_dir(Src) of - false -> case file:copy(Src, Dest) of - {ok, _Bytes} -> ok; - {error, enoent} -> ok; %% Path doesn't exist anyway - {error, Err} -> {error, {Src, Dest, Err}} - end; - true -> case file:list_dir(Src) of - {ok, FileNames} -> - case file:make_dir(Dest) of - ok -> - lists:foldl( - fun (FileName, ok) -> - recursive_copy( - filename:join(Src, FileName), - filename:join(Dest, FileName)); - (_FileName, Error) -> - Error - end, ok, FileNames); - {error, Err} -> - {error, {Src, Dest, Err}} - end; - {error, Err} -> - {error, {Src, Dest, Err}} - end - end. - -%% TODO: When we stop supporting Erlang prior to R14, this should be -%% replaced with file:open [write, exclusive] -lock_file(Path) -> - case is_file(Path) of - true -> {error, eexist}; - false -> with_handle( - fun () -> {ok, Lock} = prim_file:open(Path, [write]), - ok = prim_file:close(Lock) - end) - end. diff --git a/src/rabbit_framing.erl b/src/rabbit_framing.erl deleted file mode 100644 index 7f6989d4..00000000 --- a/src/rabbit_framing.erl +++ /dev/null @@ -1,49 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - -%% TODO auto-generate - --module(rabbit_framing). - --ifdef(use_specs). - --export_type([protocol/0, - amqp_field_type/0, amqp_property_type/0, - amqp_table/0, amqp_array/0, amqp_value/0, - amqp_method_name/0, amqp_method/0, amqp_method_record/0, - amqp_method_field_name/0, amqp_property_record/0, - amqp_exception/0, amqp_exception_code/0, amqp_class_id/0]). - --type(protocol() :: 'rabbit_framing_amqp_0_8' | 'rabbit_framing_amqp_0_9_1'). - --define(protocol_type(T), type(T :: rabbit_framing_amqp_0_8:T | - rabbit_framing_amqp_0_9_1:T)). - --?protocol_type(amqp_field_type()). --?protocol_type(amqp_property_type()). --?protocol_type(amqp_table()). --?protocol_type(amqp_array()). --?protocol_type(amqp_value()). --?protocol_type(amqp_method_name()). --?protocol_type(amqp_method()). --?protocol_type(amqp_method_record()). --?protocol_type(amqp_method_field_name()). --?protocol_type(amqp_property_record()). --?protocol_type(amqp_exception()). --?protocol_type(amqp_exception_code()). --?protocol_type(amqp_class_id()). - --endif. diff --git a/src/rabbit_guid.erl b/src/rabbit_guid.erl deleted file mode 100644 index 5307d7e2..00000000 --- a/src/rabbit_guid.erl +++ /dev/null @@ -1,177 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_guid). - --behaviour(gen_server). - --export([start_link/0]). --export([filename/0]). --export([gen/0, gen_secure/0, string/2, binary/2]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3]). - --define(SERVER, ?MODULE). --define(SERIAL_FILENAME, "rabbit_serial"). - --record(state, {serial}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([guid/0]). - --type(guid() :: binary()). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(filename/0 :: () -> string()). --spec(gen/0 :: () -> guid()). --spec(gen_secure/0 :: () -> guid()). --spec(string/2 :: (guid(), any()) -> string()). --spec(binary/2 :: (guid(), any()) -> binary()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, - [update_disk_serial()], []). - -%% We use this to detect a (possibly rather old) Mnesia directory, -%% since it has existed since at least 1.7.0 (as far back as I cared -%% to go). -filename() -> - filename:join(rabbit_mnesia:dir(), ?SERIAL_FILENAME). - -update_disk_serial() -> - Filename = filename(), - Serial = case rabbit_file:read_term_file(Filename) of - {ok, [Num]} -> Num; - {ok, []} -> 0; %% [1] - {error, enoent} -> 0; - {error, Reason} -> - throw({error, {cannot_read_serial_file, Filename, Reason}}) - end, - case rabbit_file:write_term_file(Filename, [Serial + 1]) of - ok -> ok; - {error, Reason1} -> - throw({error, {cannot_write_serial_file, Filename, Reason1}}) - end, - Serial. -%% [1] a couple of users have reported startup failures due to the -%% file being empty, presumably as a result of filesystem -%% corruption. While rabbit doesn't cope with that in general, in this -%% specific case we can be more accommodating. - -%% Generate an un-hashed guid. -fresh() -> - %% We don't use erlang:now() here because a) it may return - %% duplicates when the system clock has been rewound prior to a - %% restart, or ids were generated at a high rate (which causes - %% now() to move ahead of the system time), and b) it is really - %% slow since it takes a global lock and makes a system call. - %% - %% A persisted serial number, the node, and a unique reference - %% (per node incarnation) uniquely identifies a process in space - %% and time. - Serial = gen_server:call(?SERVER, serial, infinity), - {Serial, node(), make_ref()}. - -advance_blocks({B1, B2, B3, B4}, I) -> - %% To produce a new set of blocks, we create a new 32bit block - %% hashing {B5, I}. The new hash is used as last block, and the - %% other three blocks are XORed with it. - %% - %% Doing this is convenient because it avoids cascading conflits, - %% while being very fast. The conflicts are avoided by propagating - %% the changes through all the blocks at each round by XORing, so - %% the only occasion in which a collision will take place is when - %% all 4 blocks are the same and the counter is the same. - %% - %% The range (2^32) is provided explicitly since phash uses 2^27 - %% by default. - B5 = erlang:phash2({B1, I}, 4294967296), - {{(B2 bxor B5), (B3 bxor B5), (B4 bxor B5), B5}, I+1}. - -%% generate a GUID. This function should be used when performance is a -%% priority and predictability is not an issue. Otherwise use -%% gen_secure/0. -gen() -> - %% We hash a fresh GUID with md5, split it in 4 blocks, and each - %% time we need a new guid we rotate them producing a new hash - %% with the aid of the counter. Look at the comments in - %% advance_blocks/2 for details. - case get(guid) of - undefined -> <<B1:32, B2:32, B3:32, B4:32>> = Res = - erlang:md5(term_to_binary(fresh())), - put(guid, {{B1, B2, B3, B4}, 0}), - Res; - {BS, I} -> {{B1, B2, B3, B4}, _} = S = advance_blocks(BS, I), - put(guid, S), - <<B1:32, B2:32, B3:32, B4:32>> - end. - -%% generate a non-predictable GUID. -%% -%% The id is only unique within a single cluster and as long as the -%% serial store hasn't been deleted. -%% -%% If you are not concerned with predictability, gen/0 is faster. -gen_secure() -> - %% Here instead of hashing once we hash the GUID and the counter - %% each time, so that the GUID is not predictable. - G = case get(guid_secure) of - undefined -> {fresh(), 0}; - {S, I} -> {S, I+1} - end, - put(guid_secure, G), - erlang:md5(term_to_binary(G)). - -%% generate a readable string representation of a GUID. -%% -%% employs base64url encoding, which is safer in more contexts than -%% plain base64. -string(G, Prefix) -> - Prefix ++ "-" ++ rabbit_misc:base64url(G). - -binary(G, Prefix) -> - list_to_binary(string(G, Prefix)). - -%%---------------------------------------------------------------------------- - -init([Serial]) -> - {ok, #state{serial = Serial}}. - -handle_call(serial, _From, State = #state{serial = Serial}) -> - {reply, Serial, State}; - -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. diff --git a/src/rabbit_heartbeat.erl b/src/rabbit_heartbeat.erl deleted file mode 100644 index 36b0baa5..00000000 --- a/src/rabbit_heartbeat.erl +++ /dev/null @@ -1,166 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_heartbeat). - --export([start/6, start/7]). --export([start_heartbeat_sender/4, start_heartbeat_receiver/4, - pause_monitor/1, resume_monitor/1]). - --export([system_continue/3, system_terminate/4, system_code_change/4]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([heartbeaters/0]). - --type(heartbeaters() :: {rabbit_types:maybe(pid()), rabbit_types:maybe(pid())}). - --type(heartbeat_callback() :: fun (() -> any())). - --spec(start/6 :: - (pid(), rabbit_net:socket(), - non_neg_integer(), heartbeat_callback(), - non_neg_integer(), heartbeat_callback()) -> heartbeaters()). - --spec(start/7 :: - (pid(), rabbit_net:socket(), rabbit_types:proc_name(), - non_neg_integer(), heartbeat_callback(), - non_neg_integer(), heartbeat_callback()) -> heartbeaters()). - --spec(start_heartbeat_sender/4 :: - (rabbit_net:socket(), non_neg_integer(), heartbeat_callback(), - rabbit_types:proc_type_and_name()) -> rabbit_types:ok(pid())). --spec(start_heartbeat_receiver/4 :: - (rabbit_net:socket(), non_neg_integer(), heartbeat_callback(), - rabbit_types:proc_type_and_name()) -> rabbit_types:ok(pid())). - --spec(pause_monitor/1 :: (heartbeaters()) -> 'ok'). --spec(resume_monitor/1 :: (heartbeaters()) -> 'ok'). - --spec(system_code_change/4 :: (_,_,_,_) -> {'ok',_}). --spec(system_continue/3 :: (_,_,{_, _}) -> any()). --spec(system_terminate/4 :: (_,_,_,_) -> none()). - --endif. - -%%---------------------------------------------------------------------------- -start(SupPid, Sock, SendTimeoutSec, SendFun, ReceiveTimeoutSec, ReceiveFun) -> - start(SupPid, Sock, unknown, - SendTimeoutSec, SendFun, ReceiveTimeoutSec, ReceiveFun). - -start(SupPid, Sock, Identity, - SendTimeoutSec, SendFun, ReceiveTimeoutSec, ReceiveFun) -> - {ok, Sender} = - start_heartbeater(SendTimeoutSec, SupPid, Sock, - SendFun, heartbeat_sender, - start_heartbeat_sender, Identity), - {ok, Receiver} = - start_heartbeater(ReceiveTimeoutSec, SupPid, Sock, - ReceiveFun, heartbeat_receiver, - start_heartbeat_receiver, Identity), - {Sender, Receiver}. - -start_heartbeat_sender(Sock, TimeoutSec, SendFun, Identity) -> - %% the 'div 2' is there so that we don't end up waiting for nearly - %% 2 * TimeoutSec before sending a heartbeat in the boundary case - %% where the last message was sent just after a heartbeat. - heartbeater({Sock, TimeoutSec * 1000 div 2, send_oct, 0, - fun () -> SendFun(), continue end}, Identity). - -start_heartbeat_receiver(Sock, TimeoutSec, ReceiveFun, Identity) -> - %% we check for incoming data every interval, and time out after - %% two checks with no change. As a result we will time out between - %% 2 and 3 intervals after the last data has been received. - heartbeater({Sock, TimeoutSec * 1000, recv_oct, 1, - fun () -> ReceiveFun(), stop end}, Identity). - -pause_monitor({_Sender, none}) -> ok; -pause_monitor({_Sender, Receiver}) -> Receiver ! pause, ok. - -resume_monitor({_Sender, none}) -> ok; -resume_monitor({_Sender, Receiver}) -> Receiver ! resume, ok. - -system_continue(_Parent, Deb, {Params, State}) -> - heartbeater(Params, Deb, State). - -system_terminate(Reason, _Parent, _Deb, _State) -> - exit(Reason). - -system_code_change(Misc, _Module, _OldVsn, _Extra) -> - {ok, Misc}. - -%%---------------------------------------------------------------------------- -start_heartbeater(0, _SupPid, _Sock, _TimeoutFun, _Name, _Callback, - _Identity) -> - {ok, none}; -start_heartbeater(TimeoutSec, SupPid, Sock, TimeoutFun, Name, Callback, - Identity) -> - supervisor2:start_child( - SupPid, {Name, - {rabbit_heartbeat, Callback, - [Sock, TimeoutSec, TimeoutFun, {Name, Identity}]}, - transient, ?MAX_WAIT, worker, [rabbit_heartbeat]}). - -heartbeater(Params, Identity) -> - Deb = sys:debug_options([]), - {ok, proc_lib:spawn_link(fun () -> - rabbit_misc:store_proc_name(Identity), - heartbeater(Params, Deb, {0, 0}) - end)}. - -heartbeater({Sock, TimeoutMillisec, StatName, Threshold, Handler} = Params, - Deb, {StatVal, SameCount} = State) -> - Recurse = fun (State1) -> heartbeater(Params, Deb, State1) end, - System = fun (From, Req) -> - sys:handle_system_msg( - Req, From, self(), ?MODULE, Deb, {Params, State}) - end, - receive - pause -> - receive - resume -> Recurse({0, 0}); - {system, From, Req} -> System(From, Req); - Other -> exit({unexpected_message, Other}) - end; - {system, From, Req} -> - System(From, Req); - Other -> - exit({unexpected_message, Other}) - after TimeoutMillisec -> - case rabbit_net:getstat(Sock, [StatName]) of - {ok, [{StatName, NewStatVal}]} -> - if NewStatVal =/= StatVal -> - Recurse({NewStatVal, 0}); - SameCount < Threshold -> - Recurse({NewStatVal, SameCount + 1}); - true -> - case Handler() of - stop -> ok; - continue -> Recurse({NewStatVal, 0}) - end - end; - {error, einval} -> - %% the socket is dead, most likely because the - %% connection is being shut down -> terminate - ok; - {error, Reason} -> - exit({cannot_get_socket_stats, Reason}) - end - end. diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl deleted file mode 100644 index f32a187d..00000000 --- a/src/rabbit_limiter.erl +++ /dev/null @@ -1,442 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - -%% The purpose of the limiter is to stem the flow of messages from -%% queues to channels, in order to act upon various protocol-level -%% flow control mechanisms, specifically AMQP 0-9-1's basic.qos -%% prefetch_count, our consumer prefetch extension, and AMQP 1.0's -%% link (aka consumer) credit mechanism. -%% -%% Each channel has an associated limiter process, created with -%% start_link/1, which it passes to queues on consumer creation with -%% rabbit_amqqueue:basic_consume/9, and rabbit_amqqueue:basic_get/4. -%% The latter isn't strictly necessary, since basic.get is not -%% subject to limiting, but it means that whenever a queue knows about -%% a channel, it also knows about its limiter, which is less fiddly. -%% -%% The limiter process holds state that is, in effect, shared between -%% the channel and all queues from which the channel is -%% consuming. Essentially all these queues are competing for access to -%% a single, limited resource - the ability to deliver messages via -%% the channel - and it is the job of the limiter process to mediate -%% that access. -%% -%% The limiter process is separate from the channel process for two -%% reasons: separation of concerns, and efficiency. Channels can get -%% very busy, particularly if they are also dealing with publishes. -%% With a separate limiter process all the aforementioned access -%% mediation can take place without touching the channel. -%% -%% For efficiency, both the channel and the queues keep some local -%% state, initialised from the limiter pid with new/1 and client/1, -%% respectively. In particular this allows them to avoid any -%% interaction with the limiter process when it is 'inactive', i.e. no -%% protocol-level flow control is taking place. -%% -%% This optimisation does come at the cost of some complexity though: -%% when a limiter becomes active, the channel needs to inform all its -%% consumer queues of this change in status. It does this by invoking -%% rabbit_amqqueue:activate_limit_all/2. Note that there is no inverse -%% transition, i.e. once a queue has been told about an active -%% limiter, it is not subsequently told when that limiter becomes -%% inactive. In practice it is rare for that to happen, though we -%% could optimise this case in the future. -%% -%% Consumer credit (for AMQP 1.0) and per-consumer prefetch (for AMQP -%% 0-9-1) are treated as essentially the same thing, but with the -%% exception that per-consumer prefetch gets an auto-topup when -%% acknowledgments come in. -%% -%% The bookkeeping for this is local to queues, so it is not necessary -%% to store information about it in the limiter process. But for -%% abstraction we hide it from the queue behind the limiter API, and -%% it therefore becomes part of the queue local state. -%% -%% The interactions with the limiter are as follows: -%% -%% 1. Channels tell the limiter about basic.qos prefetch counts - -%% that's what the limit_prefetch/3, unlimit_prefetch/1, -%% get_prefetch_limit/1 API functions are about. They also tell the -%% limiter queue state (via the queue) about consumer credit -%% changes and message acknowledgement - that's what credit/5 and -%% ack_from_queue/3 are for. -%% -%% 2. Queues also tell the limiter queue state about the queue -%% becoming empty (via drained/1) and consumers leaving (via -%% forget_consumer/2). -%% -%% 3. Queues register with the limiter - this happens as part of -%% activate/1. -%% -%% 4. The limiter process maintains an internal counter of 'messages -%% sent but not yet acknowledged', called the 'volume'. -%% -%% 5. Queues ask the limiter for permission (with can_send/3) whenever -%% they want to deliver a message to a channel. The limiter checks -%% whether a) the volume has not yet reached the prefetch limit, -%% and b) whether the consumer has enough credit. If so it -%% increments the volume and tells the queue to proceed. Otherwise -%% it marks the queue as requiring notification (see below) and -%% tells the queue not to proceed. -%% -%% 6. A queue that has been told to proceed (by the return value of -%% can_send/3) sends the message to the channel. Conversely, a -%% queue that has been told not to proceed, will not attempt to -%% deliver that message, or any future messages, to the -%% channel. This is accomplished by can_send/3 capturing the -%% outcome in the local state, where it can be accessed with -%% is_suspended/1. -%% -%% 7. When a channel receives an ack it tells the limiter (via ack/2) -%% how many messages were ack'ed. The limiter process decrements -%% the volume and if it falls below the prefetch_count then it -%% notifies (through rabbit_amqqueue:resume/2) all the queues -%% requiring notification, i.e. all those that had a can_send/3 -%% request denied. -%% -%% 8. Upon receipt of such a notification, queues resume delivery to -%% the channel, i.e. they will once again start asking limiter, as -%% described in (5). -%% -%% 9. When a queue has no more consumers associated with a particular -%% channel, it deactivates use of the limiter with deactivate/1, -%% which alters the local state such that no further interactions -%% with the limiter process take place until a subsequent -%% activate/1. - --module(rabbit_limiter). - --include("rabbit.hrl"). - --behaviour(gen_server2). - --export([start_link/1]). -%% channel API --export([new/1, limit_prefetch/3, unlimit_prefetch/1, is_active/1, - get_prefetch_limit/1, ack/2, pid/1]). -%% queue API --export([client/1, activate/1, can_send/3, resume/1, deactivate/1, - is_suspended/1, is_consumer_blocked/2, credit/5, ack_from_queue/3, - drained/1, forget_consumer/2]). -%% callbacks --export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, - handle_info/2, prioritise_call/4]). - -%%---------------------------------------------------------------------------- - --record(lstate, {pid, prefetch_limited}). --record(qstate, {pid, state, credits}). - --ifdef(use_specs). - --type(lstate() :: #lstate{pid :: pid(), - prefetch_limited :: boolean()}). --type(qstate() :: #qstate{pid :: pid(), - state :: 'dormant' | 'active' | 'suspended'}). - --type(credit_mode() :: 'manual' | 'drain' | 'auto'). - --spec(start_link/1 :: (rabbit_types:proc_name()) -> - rabbit_types:ok_pid_or_error()). --spec(new/1 :: (pid()) -> lstate()). - --spec(limit_prefetch/3 :: (lstate(), non_neg_integer(), non_neg_integer()) - -> lstate()). --spec(unlimit_prefetch/1 :: (lstate()) -> lstate()). --spec(is_active/1 :: (lstate()) -> boolean()). --spec(get_prefetch_limit/1 :: (lstate()) -> non_neg_integer()). --spec(ack/2 :: (lstate(), non_neg_integer()) -> 'ok'). --spec(pid/1 :: (lstate()) -> pid()). - --spec(client/1 :: (pid()) -> qstate()). --spec(activate/1 :: (qstate()) -> qstate()). --spec(can_send/3 :: (qstate(), boolean(), rabbit_types:ctag()) -> - {'continue' | 'suspend', qstate()}). --spec(resume/1 :: (qstate()) -> qstate()). --spec(deactivate/1 :: (qstate()) -> qstate()). --spec(is_suspended/1 :: (qstate()) -> boolean()). --spec(is_consumer_blocked/2 :: (qstate(), rabbit_types:ctag()) -> boolean()). --spec(credit/5 :: (qstate(), rabbit_types:ctag(), non_neg_integer(), - credit_mode(), boolean()) -> {boolean(), qstate()}). --spec(ack_from_queue/3 :: (qstate(), rabbit_types:ctag(), non_neg_integer()) - -> {boolean(), qstate()}). --spec(drained/1 :: (qstate()) - -> {[{rabbit_types:ctag(), non_neg_integer()}], qstate()}). --spec(forget_consumer/2 :: (qstate(), rabbit_types:ctag()) -> qstate()). - --endif. - -%%---------------------------------------------------------------------------- - --record(lim, {prefetch_count = 0, - ch_pid, - %% 'Notify' is a boolean that indicates whether a queue should be - %% notified of a change in the limit or volume that may allow it to - %% deliver more messages via the limiter's channel. - queues = orddict:new(), % QPid -> {MonitorRef, Notify} - volume = 0}). - -%% mode is of type credit_mode() --record(credit, {credit = 0, mode}). - -%%---------------------------------------------------------------------------- -%% API -%%---------------------------------------------------------------------------- - -start_link(ProcName) -> gen_server2:start_link(?MODULE, [ProcName], []). - -new(Pid) -> - %% this a 'call' to ensure that it is invoked at most once. - ok = gen_server:call(Pid, {new, self()}, infinity), - #lstate{pid = Pid, prefetch_limited = false}. - -limit_prefetch(L, PrefetchCount, UnackedCount) when PrefetchCount > 0 -> - ok = gen_server:call( - L#lstate.pid, - {limit_prefetch, PrefetchCount, UnackedCount}, infinity), - L#lstate{prefetch_limited = true}. - -unlimit_prefetch(L) -> - ok = gen_server:call(L#lstate.pid, unlimit_prefetch, infinity), - L#lstate{prefetch_limited = false}. - -is_active(#lstate{prefetch_limited = Limited}) -> Limited. - -get_prefetch_limit(#lstate{prefetch_limited = false}) -> 0; -get_prefetch_limit(L) -> - gen_server:call(L#lstate.pid, get_prefetch_limit, infinity). - -ack(#lstate{prefetch_limited = false}, _AckCount) -> ok; -ack(L, AckCount) -> gen_server:cast(L#lstate.pid, {ack, AckCount}). - -pid(#lstate{pid = Pid}) -> Pid. - -client(Pid) -> #qstate{pid = Pid, state = dormant, credits = gb_trees:empty()}. - -activate(L = #qstate{state = dormant}) -> - ok = gen_server:cast(L#qstate.pid, {register, self()}), - L#qstate{state = active}; -activate(L) -> L. - -can_send(L = #qstate{pid = Pid, state = State, credits = Credits}, - AckRequired, CTag) -> - case is_consumer_blocked(L, CTag) of - false -> case (State =/= active orelse - safe_call(Pid, {can_send, self(), AckRequired}, true)) of - true -> Credits1 = decrement_credit(CTag, Credits), - {continue, L#qstate{credits = Credits1}}; - false -> {suspend, L#qstate{state = suspended}} - end; - true -> {suspend, L} - end. - -safe_call(Pid, Msg, ExitValue) -> - rabbit_misc:with_exit_handler( - fun () -> ExitValue end, - fun () -> gen_server2:call(Pid, Msg, infinity) end). - -resume(L = #qstate{state = suspended}) -> - L#qstate{state = active}; -resume(L) -> L. - -deactivate(L = #qstate{state = dormant}) -> L; -deactivate(L) -> - ok = gen_server:cast(L#qstate.pid, {unregister, self()}), - L#qstate{state = dormant}. - -is_suspended(#qstate{state = suspended}) -> true; -is_suspended(#qstate{}) -> false. - -is_consumer_blocked(#qstate{credits = Credits}, CTag) -> - case gb_trees:lookup(CTag, Credits) of - none -> false; - {value, #credit{credit = C}} when C > 0 -> false; - {value, #credit{}} -> true - end. - -credit(Limiter = #qstate{credits = Credits}, CTag, Crd, Mode, IsEmpty) -> - {Res, Cr} = - case IsEmpty andalso Mode =:= drain of - true -> {true, #credit{credit = 0, mode = manual}}; - false -> {false, #credit{credit = Crd, mode = Mode}} - end, - {Res, Limiter#qstate{credits = enter_credit(CTag, Cr, Credits)}}. - -ack_from_queue(Limiter = #qstate{credits = Credits}, CTag, Credit) -> - {Credits1, Unblocked} = - case gb_trees:lookup(CTag, Credits) of - {value, C = #credit{mode = auto, credit = C0}} -> - {update_credit(CTag, C#credit{credit = C0 + Credit}, Credits), - C0 =:= 0 andalso Credit =/= 0}; - _ -> - {Credits, false} - end, - {Unblocked, Limiter#qstate{credits = Credits1}}. - -drained(Limiter = #qstate{credits = Credits}) -> - Drain = fun(C) -> C#credit{credit = 0, mode = manual} end, - {CTagCredits, Credits2} = - rabbit_misc:gb_trees_fold( - fun (CTag, C = #credit{credit = Crd, mode = drain}, {Acc, Creds0}) -> - {[{CTag, Crd} | Acc], update_credit(CTag, Drain(C), Creds0)}; - (_CTag, #credit{credit = _Crd, mode = _Mode}, {Acc, Creds0}) -> - {Acc, Creds0} - end, {[], Credits}, Credits), - {CTagCredits, Limiter#qstate{credits = Credits2}}. - -forget_consumer(Limiter = #qstate{credits = Credits}, CTag) -> - Limiter#qstate{credits = gb_trees:delete_any(CTag, Credits)}. - -%%---------------------------------------------------------------------------- -%% Queue-local code -%%---------------------------------------------------------------------------- - -%% We want to do all the AMQP 1.0-ish link level credit calculations -%% in the queue (to do them elsewhere introduces a ton of -%% races). However, it's a big chunk of code that is conceptually very -%% linked to the limiter concept. So we get the queue to hold a bit of -%% state for us (#qstate.credits), and maintain a fiction that the -%% limiter is making the decisions... - -decrement_credit(CTag, Credits) -> - case gb_trees:lookup(CTag, Credits) of - {value, C = #credit{credit = Credit}} -> - update_credit(CTag, C#credit{credit = Credit - 1}, Credits); - none -> - Credits - end. - -enter_credit(CTag, C, Credits) -> - gb_trees:enter(CTag, ensure_credit_invariant(C), Credits). - -update_credit(CTag, C, Credits) -> - gb_trees:update(CTag, ensure_credit_invariant(C), Credits). - -ensure_credit_invariant(C = #credit{credit = 0, mode = drain}) -> - %% Using up all credit implies no need to send a 'drained' event - C#credit{mode = manual}; -ensure_credit_invariant(C) -> - C. - -%%---------------------------------------------------------------------------- -%% gen_server callbacks -%%---------------------------------------------------------------------------- - -init([ProcName]) -> ?store_proc_name(ProcName), - {ok, #lim{}}. - -prioritise_call(get_prefetch_limit, _From, _Len, _State) -> 9; -prioritise_call(_Msg, _From, _Len, _State) -> 0. - -handle_call({new, ChPid}, _From, State = #lim{ch_pid = undefined}) -> - {reply, ok, State#lim{ch_pid = ChPid}}; - -handle_call({limit_prefetch, PrefetchCount, UnackedCount}, _From, - State = #lim{prefetch_count = 0}) -> - {reply, ok, maybe_notify(State, State#lim{prefetch_count = PrefetchCount, - volume = UnackedCount})}; -handle_call({limit_prefetch, PrefetchCount, _UnackedCount}, _From, State) -> - {reply, ok, maybe_notify(State, State#lim{prefetch_count = PrefetchCount})}; - -handle_call(unlimit_prefetch, _From, State) -> - {reply, ok, maybe_notify(State, State#lim{prefetch_count = 0, - volume = 0})}; - -handle_call(get_prefetch_limit, _From, - State = #lim{prefetch_count = PrefetchCount}) -> - {reply, PrefetchCount, State}; - -handle_call({can_send, QPid, AckRequired}, _From, - State = #lim{volume = Volume}) -> - case prefetch_limit_reached(State) of - true -> {reply, false, limit_queue(QPid, State)}; - false -> {reply, true, State#lim{volume = if AckRequired -> Volume + 1; - true -> Volume - end}} - end. - -handle_cast({ack, Count}, State = #lim{volume = Volume}) -> - NewVolume = if Volume == 0 -> 0; - true -> Volume - Count - end, - {noreply, maybe_notify(State, State#lim{volume = NewVolume})}; - -handle_cast({register, QPid}, State) -> - {noreply, remember_queue(QPid, State)}; - -handle_cast({unregister, QPid}, State) -> - {noreply, forget_queue(QPid, State)}. - -handle_info({'DOWN', _MonitorRef, _Type, QPid, _Info}, State) -> - {noreply, forget_queue(QPid, State)}. - -terminate(_, _) -> - ok. - -code_change(_, State, _) -> - {ok, State}. - -%%---------------------------------------------------------------------------- -%% Internal plumbing -%%---------------------------------------------------------------------------- - -maybe_notify(OldState, NewState) -> - case prefetch_limit_reached(OldState) andalso - not prefetch_limit_reached(NewState) of - true -> notify_queues(NewState); - false -> NewState - end. - -prefetch_limit_reached(#lim{prefetch_count = Limit, volume = Volume}) -> - Limit =/= 0 andalso Volume >= Limit. - -remember_queue(QPid, State = #lim{queues = Queues}) -> - case orddict:is_key(QPid, Queues) of - false -> MRef = erlang:monitor(process, QPid), - State#lim{queues = orddict:store(QPid, {MRef, false}, Queues)}; - true -> State - end. - -forget_queue(QPid, State = #lim{queues = Queues}) -> - case orddict:find(QPid, Queues) of - {ok, {MRef, _}} -> true = erlang:demonitor(MRef), - State#lim{queues = orddict:erase(QPid, Queues)}; - error -> State - end. - -limit_queue(QPid, State = #lim{queues = Queues}) -> - UpdateFun = fun ({MRef, _}) -> {MRef, true} end, - State#lim{queues = orddict:update(QPid, UpdateFun, Queues)}. - -notify_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> - {QList, NewQueues} = - orddict:fold(fun (_QPid, {_, false}, Acc) -> Acc; - (QPid, {MRef, true}, {L, D}) -> - {[QPid | L], orddict:store(QPid, {MRef, false}, D)} - end, {[], Queues}, Queues), - case length(QList) of - 0 -> ok; - 1 -> ok = rabbit_amqqueue:resume(hd(QList), ChPid); %% common case - L -> - %% We randomly vary the position of queues in the list, - %% thus ensuring that each queue has an equal chance of - %% being notified first. - {L1, L2} = lists:split(random:uniform(L), QList), - [[ok = rabbit_amqqueue:resume(Q, ChPid) || Q <- L3] - || L3 <- [L2, L1]], - ok - end, - State#lim{queues = NewQueues}. diff --git a/src/rabbit_log.erl b/src/rabbit_log.erl deleted file mode 100644 index e05ef05a..00000000 --- a/src/rabbit_log.erl +++ /dev/null @@ -1,97 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_log). - --export([log/3, log/4, info/1, info/2, warning/1, warning/2, error/1, error/2]). --export([with_local_io/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([level/0]). - --type(category() :: atom()). --type(level() :: 'info' | 'warning' | 'error'). - --spec(log/3 :: (category(), level(), string()) -> 'ok'). --spec(log/4 :: (category(), level(), string(), [any()]) -> 'ok'). - --spec(info/1 :: (string()) -> 'ok'). --spec(info/2 :: (string(), [any()]) -> 'ok'). --spec(warning/1 :: (string()) -> 'ok'). --spec(warning/2 :: (string(), [any()]) -> 'ok'). --spec(error/1 :: (string()) -> 'ok'). --spec(error/2 :: (string(), [any()]) -> 'ok'). - --spec(with_local_io/1 :: (fun (() -> A)) -> A). - --endif. - -%%---------------------------------------------------------------------------- - -log(Category, Level, Fmt) -> log(Category, Level, Fmt, []). - -log(Category, Level, Fmt, Args) when is_list(Args) -> - case level(Level) =< catlevel(Category) of - false -> ok; - true -> F = case Level of - info -> fun error_logger:info_msg/2; - warning -> fun error_logger:warning_msg/2; - error -> fun error_logger:error_msg/2 - end, - with_local_io(fun () -> F(Fmt, Args) end) - end. - -info(Fmt) -> log(default, info, Fmt). -info(Fmt, Args) -> log(default, info, Fmt, Args). -warning(Fmt) -> log(default, warning, Fmt). -warning(Fmt, Args) -> log(default, warning, Fmt, Args). -error(Fmt) -> log(default, error, Fmt). -error(Fmt, Args) -> log(default, error, Fmt, Args). - -catlevel(Category) -> - %% We can get here as part of rabbitmqctl when it is impersonating - %% a node; in which case the env will not be defined. - CatLevelList = case application:get_env(rabbit, log_levels) of - {ok, L} -> L; - undefined -> [] - end, - level(proplists:get_value(Category, CatLevelList, info)). - -%%-------------------------------------------------------------------- - -level(info) -> 3; -level(warning) -> 2; -level(error) -> 1; -level(none) -> 0. - -%% Execute Fun using the IO system of the local node (i.e. the node on -%% which the code is executing). Since this is invoked for every log -%% message, we try to avoid unnecessarily churning group_leader/1. -with_local_io(Fun) -> - GL = group_leader(), - Node = node(), - case node(GL) of - Node -> Fun(); - _ -> group_leader(whereis(user), self()), - try - Fun() - after - group_leader(GL, self()) - end - end. diff --git a/src/rabbit_memory_monitor.erl b/src/rabbit_memory_monitor.erl deleted file mode 100644 index 451ee1f4..00000000 --- a/src/rabbit_memory_monitor.erl +++ /dev/null @@ -1,269 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - - -%% This module handles the node-wide memory statistics. -%% It receives statistics from all queues, counts the desired -%% queue length (in seconds), and sends this information back to -%% queues. - --module(rabbit_memory_monitor). - --behaviour(gen_server2). - --export([start_link/0, register/2, deregister/1, - report_ram_duration/2, stop/0, conserve_resources/3]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --record(process, {pid, reported, sent, callback, monitor}). - --record(state, {timer, %% 'internal_update' timer - queue_durations, %% ets #process - queue_duration_sum, %% sum of all queue_durations - queue_duration_count, %% number of elements in sum - desired_duration, %% the desired queue duration - disk_alarm %% disable paging, disk alarm has fired - }). - --define(SERVER, ?MODULE). --define(DEFAULT_UPDATE_INTERVAL, 2500). --define(TABLE_NAME, ?MODULE). - -%% If all queues are pushed to disk (duration 0), then the sum of -%% their reported lengths will be 0. If memory then becomes available, -%% unless we manually intervene, the sum will remain 0, and the queues -%% will never get a non-zero duration. Thus when the mem use is < -%% SUM_INC_THRESHOLD, increase the sum artificially by SUM_INC_AMOUNT. --define(SUM_INC_THRESHOLD, 0.95). --define(SUM_INC_AMOUNT, 1.0). - --define(EPSILON, 0.000001). %% less than this and we clamp to 0 - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(register/2 :: (pid(), {atom(),atom(),[any()]}) -> 'ok'). --spec(deregister/1 :: (pid()) -> 'ok'). --spec(report_ram_duration/2 :: - (pid(), float() | 'infinity') -> number() | 'infinity'). --spec(stop/0 :: () -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- -%% Public API -%%---------------------------------------------------------------------------- - -start_link() -> - gen_server2:start_link({local, ?SERVER}, ?MODULE, [], []). - -register(Pid, MFA = {_M, _F, _A}) -> - gen_server2:call(?SERVER, {register, Pid, MFA}, infinity). - -deregister(Pid) -> - gen_server2:cast(?SERVER, {deregister, Pid}). - -report_ram_duration(Pid, QueueDuration) -> - gen_server2:call(?SERVER, - {report_ram_duration, Pid, QueueDuration}, infinity). - -stop() -> - gen_server2:cast(?SERVER, stop). - -conserve_resources(Pid, disk, Conserve) -> - gen_server2:cast(Pid, {disk_alarm, Conserve}); -conserve_resources(_Pid, _Source, _Conserve) -> - ok. - -%%---------------------------------------------------------------------------- -%% Gen_server callbacks -%%---------------------------------------------------------------------------- - -init([]) -> - {ok, TRef} = timer:send_interval(?DEFAULT_UPDATE_INTERVAL, update), - - Ets = ets:new(?TABLE_NAME, [set, private, {keypos, #process.pid}]), - Alarms = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}), - {ok, internal_update( - #state { timer = TRef, - queue_durations = Ets, - queue_duration_sum = 0.0, - queue_duration_count = 0, - desired_duration = infinity, - disk_alarm = lists:member(disk, Alarms)})}. - -handle_call({report_ram_duration, Pid, QueueDuration}, From, - State = #state { queue_duration_sum = Sum, - queue_duration_count = Count, - queue_durations = Durations, - desired_duration = SendDuration }) -> - - [Proc = #process { reported = PrevQueueDuration }] = - ets:lookup(Durations, Pid), - - gen_server2:reply(From, SendDuration), - - {Sum1, Count1} = - case {PrevQueueDuration, QueueDuration} of - {infinity, infinity} -> {Sum, Count}; - {infinity, _} -> {Sum + QueueDuration, Count + 1}; - {_, infinity} -> {Sum - PrevQueueDuration, Count - 1}; - {_, _} -> {Sum - PrevQueueDuration + QueueDuration, - Count} - end, - true = ets:insert(Durations, Proc #process { reported = QueueDuration, - sent = SendDuration }), - {noreply, State #state { queue_duration_sum = zero_clamp(Sum1), - queue_duration_count = Count1 }}; - -handle_call({register, Pid, MFA}, _From, - State = #state { queue_durations = Durations }) -> - MRef = erlang:monitor(process, Pid), - true = ets:insert(Durations, #process { pid = Pid, reported = infinity, - sent = infinity, callback = MFA, - monitor = MRef }), - {reply, ok, State}; - -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_cast({disk_alarm, Alarm}, State = #state{disk_alarm = Alarm}) -> - {noreply, State}; - -handle_cast({disk_alarm, Alarm}, State) -> - {noreply, internal_update(State#state{disk_alarm = Alarm})}; - -handle_cast({deregister, Pid}, State) -> - {noreply, internal_deregister(Pid, true, State)}; - -handle_cast(stop, State) -> - {stop, normal, State}; - -handle_cast(_Request, State) -> - {noreply, State}. - -handle_info(update, State) -> - {noreply, internal_update(State)}; - -handle_info({'DOWN', _MRef, process, Pid, _Reason}, State) -> - {noreply, internal_deregister(Pid, false, State)}; - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, #state { timer = TRef }) -> - timer:cancel(TRef), - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - - -%%---------------------------------------------------------------------------- -%% Internal functions -%%---------------------------------------------------------------------------- - -zero_clamp(Sum) when Sum < ?EPSILON -> 0.0; -zero_clamp(Sum) -> Sum. - -internal_deregister(Pid, Demonitor, - State = #state { queue_duration_sum = Sum, - queue_duration_count = Count, - queue_durations = Durations }) -> - case ets:lookup(Durations, Pid) of - [] -> State; - [#process { reported = PrevQueueDuration, monitor = MRef }] -> - true = case Demonitor of - true -> erlang:demonitor(MRef); - false -> true - end, - {Sum1, Count1} = - case PrevQueueDuration of - infinity -> {Sum, Count}; - _ -> {zero_clamp(Sum - PrevQueueDuration), - Count - 1} - end, - true = ets:delete(Durations, Pid), - State #state { queue_duration_sum = Sum1, - queue_duration_count = Count1 } - end. - -internal_update(State = #state{queue_durations = Durations, - desired_duration = DesiredDurationAvg, - disk_alarm = DiskAlarm}) -> - DesiredDurationAvg1 = desired_duration_average(State), - ShouldInform = should_inform_predicate(DiskAlarm), - case ShouldInform(DesiredDurationAvg, DesiredDurationAvg1) of - true -> inform_queues(ShouldInform, DesiredDurationAvg1, Durations); - false -> ok - end, - State#state{desired_duration = DesiredDurationAvg1}. - -desired_duration_average(#state{disk_alarm = true}) -> - infinity; -desired_duration_average(#state{disk_alarm = false, - queue_duration_sum = Sum, - queue_duration_count = Count}) -> - {ok, LimitThreshold} = - application:get_env(rabbit, vm_memory_high_watermark_paging_ratio), - MemoryLimit = vm_memory_monitor:get_memory_limit(), - MemoryRatio = case MemoryLimit > 0.0 of - true -> erlang:memory(total) / MemoryLimit; - false -> infinity - end, - if MemoryRatio =:= infinity -> - 0.0; - MemoryRatio < LimitThreshold orelse Count == 0 -> - infinity; - MemoryRatio < ?SUM_INC_THRESHOLD -> - ((Sum + ?SUM_INC_AMOUNT) / Count) / MemoryRatio; - true -> - (Sum / Count) / MemoryRatio - end. - -inform_queues(ShouldInform, DesiredDurationAvg, Durations) -> - true = - ets:foldl( - fun (Proc = #process{reported = QueueDuration, - sent = PrevSendDuration, - callback = {M, F, A}}, true) -> - case ShouldInform(PrevSendDuration, DesiredDurationAvg) - andalso ShouldInform(QueueDuration, DesiredDurationAvg) of - true -> ok = erlang:apply( - M, F, A ++ [DesiredDurationAvg]), - ets:insert( - Durations, - Proc#process{sent = DesiredDurationAvg}); - false -> true - end - end, true, Durations). - -%% In normal use, we only inform queues immediately if the desired -%% duration has decreased, we want to ensure timely paging. -should_inform_predicate(false) -> fun greater_than/2; -%% When the disk alarm has gone off though, we want to inform queues -%% immediately if the desired duration has *increased* - we want to -%% ensure timely stopping paging. -should_inform_predicate(true) -> fun (D1, D2) -> greater_than(D2, D1) end. - -greater_than(infinity, infinity) -> false; -greater_than(infinity, _D2) -> true; -greater_than(_D1, infinity) -> false; -greater_than(D1, D2) -> D1 > D2. diff --git a/src/rabbit_mirror_queue_coordinator.erl b/src/rabbit_mirror_queue_coordinator.erl deleted file mode 100644 index 3d460528..00000000 --- a/src/rabbit_mirror_queue_coordinator.erl +++ /dev/null @@ -1,426 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_coordinator). - --export([start_link/4, get_gm/1, ensure_monitoring/2]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3]). - --export([joined/2, members_changed/3, handle_msg/3, handle_terminate/2]). - --behaviour(gen_server2). --behaviour(gm). - --include("rabbit.hrl"). --include("gm_specs.hrl"). - --record(state, { q, - gm, - monitors, - death_fun, - depth_fun - }). - --ifdef(use_specs). - --spec(start_link/4 :: (rabbit_types:amqqueue(), pid() | 'undefined', - rabbit_mirror_queue_master:death_fun(), - rabbit_mirror_queue_master:depth_fun()) -> - rabbit_types:ok_pid_or_error()). --spec(get_gm/1 :: (pid()) -> pid()). --spec(ensure_monitoring/2 :: (pid(), [pid()]) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- -%% -%% Mirror Queues -%% -%% A queue with mirrors consists of the following: -%% -%% #amqqueue{ pid, slave_pids } -%% | | -%% +----------+ +-------+--------------+-----------...etc... -%% | | | -%% V V V -%% amqqueue_process---+ slave-----+ slave-----+ ...etc... -%% | BQ = master----+ | | BQ = vq | | BQ = vq | -%% | | BQ = vq | | +-+-------+ +-+-------+ -%% | +-+-------+ | | | -%% +-++-----|---------+ | | (some details elided) -%% || | | | -%% || coordinator-+ | | -%% || +-+---------+ | | -%% || | | | -%% || gm-+ -- -- -- -- gm-+- -- -- -- gm-+- -- --...etc... -%% || +--+ +--+ +--+ -%% || -%% consumers -%% -%% The master is merely an implementation of bq, and thus is invoked -%% through the normal bq interface by the amqqueue_process. The slaves -%% meanwhile are processes in their own right (as is the -%% coordinator). The coordinator and all slaves belong to the same gm -%% group. Every member of a gm group receives messages sent to the gm -%% group. Because the master is the bq of amqqueue_process, it doesn't -%% have sole control over its mailbox, and as a result, the master -%% itself cannot be passed messages directly (well, it could by via -%% the amqqueue:run_backing_queue callback but that would induce -%% additional unnecessary loading on the master queue process), yet it -%% needs to react to gm events, such as the death of slaves. Thus the -%% master creates the coordinator, and it is the coordinator that is -%% the gm callback module and event handler for the master. -%% -%% Consumers are only attached to the master. Thus the master is -%% responsible for informing all slaves when messages are fetched from -%% the bq, when they're acked, and when they're requeued. -%% -%% The basic goal is to ensure that all slaves performs actions on -%% their bqs in the same order as the master. Thus the master -%% intercepts all events going to its bq, and suitably broadcasts -%% these events on the gm. The slaves thus receive two streams of -%% events: one stream is via the gm, and one stream is from channels -%% directly. Whilst the stream via gm is guaranteed to be consistently -%% seen by all slaves, the same is not true of the stream via -%% channels. For example, in the event of an unexpected death of a -%% channel during a publish, only some of the mirrors may receive that -%% publish. As a result of this problem, the messages broadcast over -%% the gm contain published content, and thus slaves can operate -%% successfully on messages that they only receive via the gm. -%% -%% The key purpose of also sending messages directly from the channels -%% to the slaves is that without this, in the event of the death of -%% the master, messages could be lost until a suitable slave is -%% promoted. However, that is not the only reason. A slave cannot send -%% confirms for a message until it has seen it from the -%% channel. Otherwise, it might send a confirm to a channel for a -%% message that it might *never* receive from that channel. This can -%% happen because new slaves join the gm ring (and thus receive -%% messages from the master) before inserting themselves in the -%% queue's mnesia record (which is what channels look at for routing). -%% As it turns out, channels will simply ignore such bogus confirms, -%% but relying on that would introduce a dangerously tight coupling. -%% -%% Hence the slaves have to wait until they've seen both the publish -%% via gm, and the publish via the channel before they issue the -%% confirm. Either form of publish can arrive first, and a slave can -%% be upgraded to the master at any point during this -%% process. Confirms continue to be issued correctly, however. -%% -%% Because the slave is a full process, it impersonates parts of the -%% amqqueue API. However, it does not need to implement all parts: for -%% example, no ack or consumer-related message can arrive directly at -%% a slave from a channel: it is only publishes that pass both -%% directly to the slaves and go via gm. -%% -%% Slaves can be added dynamically. When this occurs, there is no -%% attempt made to sync the current contents of the master with the -%% new slave, thus the slave will start empty, regardless of the state -%% of the master. Thus the slave needs to be able to detect and ignore -%% operations which are for messages it has not received: because of -%% the strict FIFO nature of queues in general, this is -%% straightforward - all new publishes that the new slave receives via -%% gm should be processed as normal, but fetches which are for -%% messages the slave has never seen should be ignored. Similarly, -%% acks for messages the slave never fetched should be -%% ignored. Similarly, we don't republish rejected messages that we -%% haven't seen. Eventually, as the master is consumed from, the -%% messages at the head of the queue which were there before the slave -%% joined will disappear, and the slave will become fully synced with -%% the state of the master. -%% -%% The detection of the sync-status is based on the depth of the BQs, -%% where the depth is defined as the sum of the length of the BQ (as -%% per BQ:len) and the messages pending an acknowledgement. When the -%% depth of the slave is equal to the master's, then the slave is -%% synchronised. We only store the difference between the two for -%% simplicity. Comparing the length is not enough since we need to -%% take into account rejected messages which will make it back into -%% the master queue but can't go back in the slave, since we don't -%% want "holes" in the slave queue. Note that the depth, and the -%% length likewise, must always be shorter on the slave - we assert -%% that in various places. In case slaves are joined to an empty queue -%% which only goes on to receive publishes, they start by asking the -%% master to broadcast its depth. This is enough for slaves to always -%% be able to work out when their head does not differ from the master -%% (and is much simpler and cheaper than getting the master to hang on -%% to the guid of the msg at the head of its queue). When a slave is -%% promoted to a master, it unilaterally broadcasts its depth, in -%% order to solve the problem of depth requests from new slaves being -%% unanswered by a dead master. -%% -%% Obviously, due to the async nature of communication across gm, the -%% slaves can fall behind. This does not matter from a sync pov: if -%% they fall behind and the master dies then a) no publishes are lost -%% because all publishes go to all mirrors anyway; b) the worst that -%% happens is that acks get lost and so messages come back to -%% life. This is no worse than normal given you never get confirmation -%% that an ack has been received (not quite true with QoS-prefetch, -%% but close enough for jazz). -%% -%% Because acktags are issued by the bq independently, and because -%% there is no requirement for the master and all slaves to use the -%% same bq, all references to msgs going over gm is by msg_id. Thus -%% upon acking, the master must convert the acktags back to msg_ids -%% (which happens to be what bq:ack returns), then sends the msg_ids -%% over gm, the slaves must convert the msg_ids to acktags (a mapping -%% the slaves themselves must maintain). -%% -%% When the master dies, a slave gets promoted. This will be the -%% eldest slave, and thus the hope is that that slave is most likely -%% to be sync'd with the master. The design of gm is that the -%% notification of the death of the master will only appear once all -%% messages in-flight from the master have been fully delivered to all -%% members of the gm group. Thus at this point, the slave that gets -%% promoted cannot broadcast different events in a different order -%% than the master for the same msgs: there is no possibility for the -%% same msg to be processed by the old master and the new master - if -%% it was processed by the old master then it will have been processed -%% by the slave before the slave was promoted, and vice versa. -%% -%% Upon promotion, all msgs pending acks are requeued as normal, the -%% slave constructs state suitable for use in the master module, and -%% then dynamically changes into an amqqueue_process with the master -%% as the bq, and the slave's bq as the master's bq. Thus the very -%% same process that was the slave is now a full amqqueue_process. -%% -%% It is important that we avoid memory leaks due to the death of -%% senders (i.e. channels) and partial publications. A sender -%% publishing a message may fail mid way through the publish and thus -%% only some of the mirrors will receive the message. We need the -%% mirrors to be able to detect this and tidy up as necessary to avoid -%% leaks. If we just had the master monitoring all senders then we -%% would have the possibility that a sender appears and only sends the -%% message to a few of the slaves before dying. Those slaves would -%% then hold on to the message, assuming they'll receive some -%% instruction eventually from the master. Thus we have both slaves -%% and the master monitor all senders they become aware of. But there -%% is a race: if the slave receives a DOWN of a sender, how does it -%% know whether or not the master is going to send it instructions -%% regarding those messages? -%% -%% Whilst the master monitors senders, it can't access its mailbox -%% directly, so it delegates monitoring to the coordinator. When the -%% coordinator receives a DOWN message from a sender, it informs the -%% master via a callback. This allows the master to do any tidying -%% necessary, but more importantly allows the master to broadcast a -%% sender_death message to all the slaves, saying the sender has -%% died. Once the slaves receive the sender_death message, they know -%% that they're not going to receive any more instructions from the gm -%% regarding that sender. However, it is possible that the coordinator -%% receives the DOWN and communicates that to the master before the -%% master has finished receiving and processing publishes from the -%% sender. This turns out not to be a problem: the sender has actually -%% died, and so will not need to receive confirms or other feedback, -%% and should further messages be "received" from the sender, the -%% master will ask the coordinator to set up a new monitor, and -%% will continue to process the messages normally. Slaves may thus -%% receive publishes via gm from previously declared "dead" senders, -%% but again, this is fine: should the slave have just thrown out the -%% message it had received directly from the sender (due to receiving -%% a sender_death message via gm), it will be able to cope with the -%% publication purely from the master via gm. -%% -%% When a slave receives a DOWN message for a sender, if it has not -%% received the sender_death message from the master via gm already, -%% then it will wait 20 seconds before broadcasting a request for -%% confirmation from the master that the sender really has died. -%% Should a sender have only sent a publish to slaves, this allows -%% slaves to inform the master of the previous existence of the -%% sender. The master will thus monitor the sender, receive the DOWN, -%% and subsequently broadcast the sender_death message, allowing the -%% slaves to tidy up. This process can repeat for the same sender: -%% consider one slave receives the publication, then the DOWN, then -%% asks for confirmation of death, then the master broadcasts the -%% sender_death message. Only then does another slave receive the -%% publication and thus set up its monitoring. Eventually that slave -%% too will receive the DOWN, ask for confirmation and the master will -%% monitor the sender again, receive another DOWN, and send out -%% another sender_death message. Given the 20 second delay before -%% requesting death confirmation, this is highly unlikely, but it is a -%% possibility. -%% -%% When the 20 second timer expires, the slave first checks to see -%% whether it still needs confirmation of the death before requesting -%% it. This prevents unnecessary traffic on gm as it allows one -%% broadcast of the sender_death message to satisfy many slaves. -%% -%% If we consider the promotion of a slave at this point, we have two -%% possibilities: that of the slave that has received the DOWN and is -%% thus waiting for confirmation from the master that the sender -%% really is down; and that of the slave that has not received the -%% DOWN. In the first case, in the act of promotion to master, the new -%% master will monitor again the dead sender, and after it has -%% finished promoting itself, it should find another DOWN waiting, -%% which it will then broadcast. This will allow slaves to tidy up as -%% normal. In the second case, we have the possibility that -%% confirmation-of-sender-death request has been broadcast, but that -%% it was broadcast before the master failed, and that the slave being -%% promoted does not know anything about that sender, and so will not -%% monitor it on promotion. Thus a slave that broadcasts such a -%% request, at the point of broadcasting it, recurses, setting another -%% 20 second timer. As before, on expiry of the timer, the slaves -%% checks to see whether it still has not received a sender_death -%% message for the dead sender, and if not, broadcasts a death -%% confirmation request. Thus this ensures that even when a master -%% dies and the new slave has no knowledge of the dead sender, it will -%% eventually receive a death confirmation request, shall monitor the -%% dead sender, receive the DOWN and broadcast the sender_death -%% message. -%% -%% The preceding commentary deals with the possibility of slaves -%% receiving publications from senders which the master does not, and -%% the need to prevent memory leaks in such scenarios. The inverse is -%% also possible: a partial publication may cause only the master to -%% receive a publication. It will then publish the message via gm. The -%% slaves will receive it via gm, will publish it to their BQ and will -%% set up monitoring on the sender. They will then receive the DOWN -%% message and the master will eventually publish the corresponding -%% sender_death message. The slave will then be able to tidy up its -%% state as normal. -%% -%% Recovery of mirrored queues is straightforward: as nodes die, the -%% remaining nodes record this, and eventually a situation is reached -%% in which only one node is alive, which is the master. This is the -%% only node which, upon recovery, will resurrect a mirrored queue: -%% nodes which die and then rejoin as a slave will start off empty as -%% if they have no mirrored content at all. This is not surprising: to -%% achieve anything more sophisticated would require the master and -%% recovering slave to be able to check to see whether they agree on -%% the last seen state of the queue: checking depth alone is not -%% sufficient in this case. -%% -%% For more documentation see the comments in bug 23554. -%% -%%---------------------------------------------------------------------------- - -start_link(Queue, GM, DeathFun, DepthFun) -> - gen_server2:start_link(?MODULE, [Queue, GM, DeathFun, DepthFun], []). - -get_gm(CPid) -> - gen_server2:call(CPid, get_gm, infinity). - -ensure_monitoring(CPid, Pids) -> - gen_server2:cast(CPid, {ensure_monitoring, Pids}). - -%% --------------------------------------------------------------------------- -%% gen_server -%% --------------------------------------------------------------------------- - -init([#amqqueue { name = QueueName } = Q, GM, DeathFun, DepthFun]) -> - ?store_proc_name(QueueName), - GM1 = case GM of - undefined -> - {ok, GM2} = gm:start_link( - QueueName, ?MODULE, [self()], - fun rabbit_misc:execute_mnesia_transaction/1), - receive {joined, GM2, _Members} -> - ok - end, - GM2; - _ -> - true = link(GM), - GM - end, - {ok, #state { q = Q, - gm = GM1, - monitors = pmon:new(), - death_fun = DeathFun, - depth_fun = DepthFun }, - hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. - -handle_call(get_gm, _From, State = #state { gm = GM }) -> - reply(GM, State). - -handle_cast({gm_deaths, DeadGMPids}, - State = #state { q = #amqqueue { name = QueueName, pid = MPid } }) - when node(MPid) =:= node() -> - case rabbit_mirror_queue_misc:remove_from_queue( - QueueName, MPid, DeadGMPids) of - {ok, MPid, DeadPids} -> - rabbit_mirror_queue_misc:report_deaths(MPid, true, QueueName, - DeadPids), - noreply(State); - {error, not_found} -> - {stop, normal, State} - end; - -handle_cast(request_depth, State = #state { depth_fun = DepthFun }) -> - ok = DepthFun(), - noreply(State); - -handle_cast({ensure_monitoring, Pids}, State = #state { monitors = Mons }) -> - noreply(State #state { monitors = pmon:monitor_all(Pids, Mons) }); - -handle_cast({delete_and_terminate, Reason}, State) -> - {stop, Reason, State}. - -handle_info({'DOWN', _MonitorRef, process, Pid, _Reason}, - State = #state { monitors = Mons, - death_fun = DeathFun }) -> - noreply(case pmon:is_monitored(Pid, Mons) of - false -> State; - true -> ok = DeathFun(Pid), - State #state { monitors = pmon:erase(Pid, Mons) } - end); - -handle_info(Msg, State) -> - {stop, {unexpected_info, Msg}, State}. - -terminate(_Reason, #state{}) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%% --------------------------------------------------------------------------- -%% GM -%% --------------------------------------------------------------------------- - -joined([CPid], Members) -> - CPid ! {joined, self(), Members}, - ok. - -members_changed([_CPid], _Births, []) -> - ok; -members_changed([CPid], _Births, Deaths) -> - ok = gen_server2:cast(CPid, {gm_deaths, Deaths}). - -handle_msg([CPid], _From, request_depth = Msg) -> - ok = gen_server2:cast(CPid, Msg); -handle_msg([CPid], _From, {ensure_monitoring, _Pids} = Msg) -> - ok = gen_server2:cast(CPid, Msg); -handle_msg([CPid], _From, {delete_and_terminate, _Reason} = Msg) -> - ok = gen_server2:cast(CPid, Msg), - {stop, {shutdown, ring_shutdown}}; -handle_msg([_CPid], _From, _Msg) -> - ok. - -handle_terminate([_CPid], _Reason) -> - ok. - -%% --------------------------------------------------------------------------- -%% Others -%% --------------------------------------------------------------------------- - -noreply(State) -> - {noreply, State, hibernate}. - -reply(Reply, State) -> - {reply, Reply, State, hibernate}. diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl deleted file mode 100644 index aa1e1ab9..00000000 --- a/src/rabbit_mirror_queue_master.erl +++ /dev/null @@ -1,517 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_master). - --export([init/3, terminate/2, delete_and_terminate/2, - purge/1, purge_acks/1, publish/5, publish_delivered/4, - discard/3, fetch/2, drop/2, ack/2, requeue/2, ackfold/4, fold/3, - len/1, is_empty/1, depth/1, drain_confirmed/1, - dropwhile/2, fetchwhile/4, set_ram_duration_target/2, ram_duration/1, - needs_timeout/1, timeout/1, handle_pre_hibernate/1, resume/1, - msg_rates/1, info/2, invoke/3, is_duplicate/2]). - --export([start/1, stop/0, delete_crashed/1]). - --export([promote_backing_queue_state/8, sender_death_fun/0, depth_fun/0]). - --export([init_with_existing_bq/3, stop_mirroring/1, sync_mirrors/3]). - --behaviour(rabbit_backing_queue). - --include("rabbit.hrl"). - --record(state, { name, - gm, - coordinator, - backing_queue, - backing_queue_state, - seen_status, - confirmed, - known_senders - }). - --ifdef(use_specs). - --export_type([death_fun/0, depth_fun/0, stats_fun/0]). - --type(death_fun() :: fun ((pid()) -> 'ok')). --type(depth_fun() :: fun (() -> 'ok')). --type(stats_fun() :: fun ((any()) -> 'ok')). --type(master_state() :: #state { name :: rabbit_amqqueue:name(), - gm :: pid(), - coordinator :: pid(), - backing_queue :: atom(), - backing_queue_state :: any(), - seen_status :: dict:dict(), - confirmed :: [rabbit_guid:guid()], - known_senders :: sets:set() - }). - --spec(promote_backing_queue_state/8 :: - (rabbit_amqqueue:name(), pid(), atom(), any(), pid(), [any()], - dict:dict(), [pid()]) -> master_state()). --spec(sender_death_fun/0 :: () -> death_fun()). --spec(depth_fun/0 :: () -> depth_fun()). --spec(init_with_existing_bq/3 :: (rabbit_types:amqqueue(), atom(), any()) -> - master_state()). --spec(stop_mirroring/1 :: (master_state()) -> {atom(), any()}). --spec(sync_mirrors/3 :: (stats_fun(), stats_fun(), master_state()) -> - {'ok', master_state()} | {stop, any(), master_state()}). - --endif. - -%% For general documentation of HA design, see -%% rabbit_mirror_queue_coordinator - -%% --------------------------------------------------------------------------- -%% Backing queue -%% --------------------------------------------------------------------------- - -start(_DurableQueues) -> - %% This will never get called as this module will never be - %% installed as the default BQ implementation. - exit({not_valid_for_generic_backing_queue, ?MODULE}). - -stop() -> - %% Same as start/1. - exit({not_valid_for_generic_backing_queue, ?MODULE}). - -delete_crashed(_QName) -> - exit({not_valid_for_generic_backing_queue, ?MODULE}). - -init(Q, Recover, AsyncCallback) -> - {ok, BQ} = application:get_env(backing_queue_module), - BQS = BQ:init(Q, Recover, AsyncCallback), - State = #state{gm = GM} = init_with_existing_bq(Q, BQ, BQS), - ok = gm:broadcast(GM, {depth, BQ:depth(BQS)}), - State. - -init_with_existing_bq(Q = #amqqueue{name = QName}, BQ, BQS) -> - {ok, CPid} = rabbit_mirror_queue_coordinator:start_link( - Q, undefined, sender_death_fun(), depth_fun()), - GM = rabbit_mirror_queue_coordinator:get_gm(CPid), - Self = self(), - ok = rabbit_misc:execute_mnesia_transaction( - fun () -> - [Q1 = #amqqueue{gm_pids = GMPids}] - = mnesia:read({rabbit_queue, QName}), - ok = rabbit_amqqueue:store_queue( - Q1#amqqueue{gm_pids = [{GM, Self} | GMPids], - state = live}) - end), - {_MNode, SNodes} = rabbit_mirror_queue_misc:suggested_queue_nodes(Q), - %% We need synchronous add here (i.e. do not return until the - %% slave is running) so that when queue declaration is finished - %% all slaves are up; we don't want to end up with unsynced slaves - %% just by declaring a new queue. But add can't be synchronous all - %% the time as it can be called by slaves and that's - %% deadlock-prone. - rabbit_mirror_queue_misc:add_mirrors(QName, SNodes, sync), - #state { name = QName, - gm = GM, - coordinator = CPid, - backing_queue = BQ, - backing_queue_state = BQS, - seen_status = dict:new(), - confirmed = [], - known_senders = sets:new() }. - -stop_mirroring(State = #state { coordinator = CPid, - backing_queue = BQ, - backing_queue_state = BQS }) -> - unlink(CPid), - stop_all_slaves(shutdown, State), - {BQ, BQS}. - -sync_mirrors(HandleInfo, EmitStats, - State = #state { name = QName, - gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> - Log = fun (Fmt, Params) -> - rabbit_mirror_queue_misc:log_info( - QName, "Synchronising: " ++ Fmt ++ "~n", Params) - end, - Log("~p messages to synchronise", [BQ:len(BQS)]), - {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(QName), - Ref = make_ref(), - Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, QName, Log, SPids), - gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), - S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, - case rabbit_mirror_queue_sync:master_go( - Syncer, Ref, Log, HandleInfo, EmitStats, BQ, BQS) of - {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; - {sync_died, R, BQS1} -> Log("~p", [R]), - {ok, S(BQS1)}; - {already_synced, BQS1} -> {ok, S(BQS1)}; - {ok, BQS1} -> Log("complete", []), - {ok, S(BQS1)} - end. - -terminate({shutdown, dropped} = Reason, - State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - %% Backing queue termination - this node has been explicitly - %% dropped. Normally, non-durable queues would be tidied up on - %% startup, but there's a possibility that we will be added back - %% in without this node being restarted. Thus we must do the full - %% blown delete_and_terminate now, but only locally: we do not - %% broadcast delete_and_terminate. - State#state{backing_queue_state = BQ:delete_and_terminate(Reason, BQS)}; - -terminate(Reason, - State = #state { name = QName, - backing_queue = BQ, - backing_queue_state = BQS }) -> - %% Backing queue termination. The queue is going down but - %% shouldn't be deleted. Most likely safe shutdown of this - %% node. - {ok, Q = #amqqueue{sync_slave_pids = SSPids}} = - rabbit_amqqueue:lookup(QName), - case SSPids =:= [] andalso - rabbit_policy:get(<<"ha-promote-on-shutdown">>, Q) =/= <<"always">> of - true -> %% Remove the whole queue to avoid data loss - rabbit_mirror_queue_misc:log_warning( - QName, "Stopping all nodes on master shutdown since no " - "synchronised slave is available~n", []), - stop_all_slaves(Reason, State); - false -> %% Just let some other slave take over. - ok - end, - State #state { backing_queue_state = BQ:terminate(Reason, BQS) }. - -delete_and_terminate(Reason, State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - stop_all_slaves(Reason, State), - State#state{backing_queue_state = BQ:delete_and_terminate(Reason, BQS)}. - -stop_all_slaves(Reason, #state{name = QName, gm = GM}) -> - {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(QName), - PidsMRefs = [{Pid, erlang:monitor(process, Pid)} || Pid <- [GM | SPids]], - ok = gm:broadcast(GM, {delete_and_terminate, Reason}), - %% It's possible that we could be partitioned from some slaves - %% between the lookup and the broadcast, in which case we could - %% monitor them but they would not have received the GM - %% message. So only wait for slaves which are still - %% not-partitioned. - [receive {'DOWN', MRef, process, _Pid, _Info} -> ok end - || {Pid, MRef} <- PidsMRefs, rabbit_mnesia:on_running_node(Pid)], - %% Normally when we remove a slave another slave or master will - %% notice and update Mnesia. But we just removed them all, and - %% have stopped listening ourselves. So manually clean up. - rabbit_misc:execute_mnesia_transaction( - fun () -> - [Q] = mnesia:read({rabbit_queue, QName}), - rabbit_mirror_queue_misc:store_updated_slaves( - Q #amqqueue { gm_pids = [], slave_pids = [] }) - end), - ok = gm:forget_group(QName). - -purge(State = #state { gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> - ok = gm:broadcast(GM, {drop, 0, BQ:len(BQS), false}), - {Count, BQS1} = BQ:purge(BQS), - {Count, State #state { backing_queue_state = BQS1 }}. - -purge_acks(_State) -> exit({not_implemented, {?MODULE, purge_acks}}). - -publish(Msg = #basic_message { id = MsgId }, MsgProps, IsDelivered, ChPid, - State = #state { gm = GM, - seen_status = SS, - backing_queue = BQ, - backing_queue_state = BQS }) -> - false = dict:is_key(MsgId, SS), %% ASSERTION - ok = gm:broadcast(GM, {publish, ChPid, MsgProps, Msg}, - rabbit_basic:msg_size(Msg)), - BQS1 = BQ:publish(Msg, MsgProps, IsDelivered, ChPid, BQS), - ensure_monitoring(ChPid, State #state { backing_queue_state = BQS1 }). - -publish_delivered(Msg = #basic_message { id = MsgId }, MsgProps, - ChPid, State = #state { gm = GM, - seen_status = SS, - backing_queue = BQ, - backing_queue_state = BQS }) -> - false = dict:is_key(MsgId, SS), %% ASSERTION - ok = gm:broadcast(GM, {publish_delivered, ChPid, MsgProps, Msg}, - rabbit_basic:msg_size(Msg)), - {AckTag, BQS1} = BQ:publish_delivered(Msg, MsgProps, ChPid, BQS), - State1 = State #state { backing_queue_state = BQS1 }, - {AckTag, ensure_monitoring(ChPid, State1)}. - -discard(MsgId, ChPid, State = #state { gm = GM, - backing_queue = BQ, - backing_queue_state = BQS, - seen_status = SS }) -> - false = dict:is_key(MsgId, SS), %% ASSERTION - ok = gm:broadcast(GM, {discard, ChPid, MsgId}), - ensure_monitoring(ChPid, State #state { backing_queue_state = - BQ:discard(MsgId, ChPid, BQS) }). - -dropwhile(Pred, State = #state{backing_queue = BQ, - backing_queue_state = BQS }) -> - Len = BQ:len(BQS), - {Next, BQS1} = BQ:dropwhile(Pred, BQS), - {Next, drop(Len, false, State #state { backing_queue_state = BQS1 })}. - -fetchwhile(Pred, Fun, Acc, State = #state{backing_queue = BQ, - backing_queue_state = BQS }) -> - Len = BQ:len(BQS), - {Next, Acc1, BQS1} = BQ:fetchwhile(Pred, Fun, Acc, BQS), - {Next, Acc1, drop(Len, true, State #state { backing_queue_state = BQS1 })}. - -drain_confirmed(State = #state { backing_queue = BQ, - backing_queue_state = BQS, - seen_status = SS, - confirmed = Confirmed }) -> - {MsgIds, BQS1} = BQ:drain_confirmed(BQS), - {MsgIds1, SS1} = - lists:foldl( - fun (MsgId, {MsgIdsN, SSN}) -> - %% We will never see 'discarded' here - case dict:find(MsgId, SSN) of - error -> - {[MsgId | MsgIdsN], SSN}; - {ok, published} -> - %% It was published when we were a slave, - %% and we were promoted before we saw the - %% publish from the channel. We still - %% haven't seen the channel publish, and - %% consequently we need to filter out the - %% confirm here. We will issue the confirm - %% when we see the publish from the channel. - {MsgIdsN, dict:store(MsgId, confirmed, SSN)}; - {ok, confirmed} -> - %% Well, confirms are racy by definition. - {[MsgId | MsgIdsN], SSN} - end - end, {[], SS}, MsgIds), - {Confirmed ++ MsgIds1, State #state { backing_queue_state = BQS1, - seen_status = SS1, - confirmed = [] }}. - -fetch(AckRequired, State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - {Result, BQS1} = BQ:fetch(AckRequired, BQS), - State1 = State #state { backing_queue_state = BQS1 }, - {Result, case Result of - empty -> State1; - {_MsgId, _IsDelivered, AckTag} -> drop_one(AckTag, State1) - end}. - -drop(AckRequired, State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - {Result, BQS1} = BQ:drop(AckRequired, BQS), - State1 = State #state { backing_queue_state = BQS1 }, - {Result, case Result of - empty -> State1; - {_MsgId, AckTag} -> drop_one(AckTag, State1) - end}. - -ack(AckTags, State = #state { gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> - {MsgIds, BQS1} = BQ:ack(AckTags, BQS), - case MsgIds of - [] -> ok; - _ -> ok = gm:broadcast(GM, {ack, MsgIds}) - end, - {MsgIds, State #state { backing_queue_state = BQS1 }}. - -requeue(AckTags, State = #state { gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> - {MsgIds, BQS1} = BQ:requeue(AckTags, BQS), - ok = gm:broadcast(GM, {requeue, MsgIds}), - {MsgIds, State #state { backing_queue_state = BQS1 }}. - -ackfold(MsgFun, Acc, State = #state { backing_queue = BQ, - backing_queue_state = BQS }, AckTags) -> - {Acc1, BQS1} = BQ:ackfold(MsgFun, Acc, BQS, AckTags), - {Acc1, State #state { backing_queue_state = BQS1 }}. - -fold(Fun, Acc, State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - {Result, BQS1} = BQ:fold(Fun, Acc, BQS), - {Result, State #state { backing_queue_state = BQS1 }}. - -len(#state { backing_queue = BQ, backing_queue_state = BQS }) -> - BQ:len(BQS). - -is_empty(#state { backing_queue = BQ, backing_queue_state = BQS }) -> - BQ:is_empty(BQS). - -depth(#state { backing_queue = BQ, backing_queue_state = BQS }) -> - BQ:depth(BQS). - -set_ram_duration_target(Target, State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - State #state { backing_queue_state = - BQ:set_ram_duration_target(Target, BQS) }. - -ram_duration(State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> - {Result, BQS1} = BQ:ram_duration(BQS), - {Result, State #state { backing_queue_state = BQS1 }}. - -needs_timeout(#state { backing_queue = BQ, backing_queue_state = BQS }) -> - BQ:needs_timeout(BQS). - -timeout(State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> - State #state { backing_queue_state = BQ:timeout(BQS) }. - -handle_pre_hibernate(State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - State #state { backing_queue_state = BQ:handle_pre_hibernate(BQS) }. - -resume(State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - State #state { backing_queue_state = BQ:resume(BQS) }. - -msg_rates(#state { backing_queue = BQ, backing_queue_state = BQS }) -> - BQ:msg_rates(BQS). - -info(backing_queue_status, - State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> - BQ:info(backing_queue_status, BQS) ++ - [ {mirror_seen, dict:size(State #state.seen_status)}, - {mirror_senders, sets:size(State #state.known_senders)} ]; -info(Item, #state { backing_queue = BQ, backing_queue_state = BQS }) -> - BQ:info(Item, BQS). - -invoke(?MODULE, Fun, State) -> - Fun(?MODULE, State); -invoke(Mod, Fun, State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - State #state { backing_queue_state = BQ:invoke(Mod, Fun, BQS) }. - -is_duplicate(Message = #basic_message { id = MsgId }, - State = #state { seen_status = SS, - backing_queue = BQ, - backing_queue_state = BQS, - confirmed = Confirmed }) -> - %% Here, we need to deal with the possibility that we're about to - %% receive a message that we've already seen when we were a slave - %% (we received it via gm). Thus if we do receive such message now - %% via the channel, there may be a confirm waiting to issue for - %% it. - - %% We will never see {published, ChPid, MsgSeqNo} here. - case dict:find(MsgId, SS) of - error -> - %% We permit the underlying BQ to have a peek at it, but - %% only if we ourselves are not filtering out the msg. - {Result, BQS1} = BQ:is_duplicate(Message, BQS), - {Result, State #state { backing_queue_state = BQS1 }}; - {ok, published} -> - %% It already got published when we were a slave and no - %% confirmation is waiting. amqqueue_process will have, in - %% its msg_id_to_channel mapping, the entry for dealing - %% with the confirm when that comes back in (it's added - %% immediately after calling is_duplicate). The msg is - %% invalid. We will not see this again, nor will we be - %% further involved in confirming this message, so erase. - {true, State #state { seen_status = dict:erase(MsgId, SS) }}; - {ok, Disposition} - when Disposition =:= confirmed - %% It got published when we were a slave via gm, and - %% confirmed some time after that (maybe even after - %% promotion), but before we received the publish from the - %% channel, so couldn't previously know what the - %% msg_seq_no was (and thus confirm as a slave). So we - %% need to confirm now. As above, amqqueue_process will - %% have the entry for the msg_id_to_channel mapping added - %% immediately after calling is_duplicate/2. - orelse Disposition =:= discarded -> - %% Message was discarded while we were a slave. Confirm now. - %% As above, amqqueue_process will have the entry for the - %% msg_id_to_channel mapping. - {true, State #state { seen_status = dict:erase(MsgId, SS), - confirmed = [MsgId | Confirmed] }} - end. - -%% --------------------------------------------------------------------------- -%% Other exported functions -%% --------------------------------------------------------------------------- - -promote_backing_queue_state(QName, CPid, BQ, BQS, GM, AckTags, Seen, KS) -> - {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), - Len = BQ:len(BQS1), - Depth = BQ:depth(BQS1), - true = Len == Depth, %% ASSERTION: everything must have been requeued - ok = gm:broadcast(GM, {depth, Depth}), - #state { name = QName, - gm = GM, - coordinator = CPid, - backing_queue = BQ, - backing_queue_state = BQS1, - seen_status = Seen, - confirmed = [], - known_senders = sets:from_list(KS) }. - -sender_death_fun() -> - Self = self(), - fun (DeadPid) -> - rabbit_amqqueue:run_backing_queue( - Self, ?MODULE, - fun (?MODULE, State = #state { gm = GM, known_senders = KS }) -> - ok = gm:broadcast(GM, {sender_death, DeadPid}), - KS1 = sets:del_element(DeadPid, KS), - State #state { known_senders = KS1 } - end) - end. - -depth_fun() -> - Self = self(), - fun () -> - rabbit_amqqueue:run_backing_queue( - Self, ?MODULE, - fun (?MODULE, State = #state { gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> - ok = gm:broadcast(GM, {depth, BQ:depth(BQS)}), - State - end) - end. - -%% --------------------------------------------------------------------------- -%% Helpers -%% --------------------------------------------------------------------------- - -drop_one(AckTag, State = #state { gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> - ok = gm:broadcast(GM, {drop, BQ:len(BQS), 1, AckTag =/= undefined}), - State. - -drop(PrevLen, AckRequired, State = #state { gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> - Len = BQ:len(BQS), - case PrevLen - Len of - 0 -> State; - Dropped -> ok = gm:broadcast(GM, {drop, Len, Dropped, AckRequired}), - State - end. - -ensure_monitoring(ChPid, State = #state { coordinator = CPid, - known_senders = KS }) -> - case sets:is_element(ChPid, KS) of - true -> State; - false -> ok = rabbit_mirror_queue_coordinator:ensure_monitoring( - CPid, [ChPid]), - State #state { known_senders = sets:add_element(ChPid, KS) } - end. diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl deleted file mode 100644 index 826b6927..00000000 --- a/src/rabbit_mirror_queue_misc.erl +++ /dev/null @@ -1,417 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_misc). --behaviour(rabbit_policy_validator). - --export([remove_from_queue/3, on_node_up/0, add_mirrors/3, - report_deaths/4, store_updated_slaves/1, - initial_queue_node/2, suggested_queue_nodes/1, - is_mirrored/1, update_mirrors/2, validate_policy/1, - maybe_auto_sync/1, maybe_drop_master_after_sync/1, - log_info/3, log_warning/3]). - -%% for testing only --export([module/1]). - --include("rabbit.hrl"). - --rabbit_boot_step( - {?MODULE, - [{description, "HA policy validation"}, - {mfa, {rabbit_registry, register, - [policy_validator, <<"ha-mode">>, ?MODULE]}}, - {mfa, {rabbit_registry, register, - [policy_validator, <<"ha-params">>, ?MODULE]}}, - {mfa, {rabbit_registry, register, - [policy_validator, <<"ha-sync-mode">>, ?MODULE]}}, - {mfa, {rabbit_registry, register, - [policy_validator, <<"ha-promote-on-shutdown">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, recovery}]}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(remove_from_queue/3 :: - (rabbit_amqqueue:name(), pid(), [pid()]) - -> {'ok', pid(), [pid()]} | {'error', 'not_found'}). --spec(on_node_up/0 :: () -> 'ok'). --spec(add_mirrors/3 :: (rabbit_amqqueue:name(), [node()], 'sync' | 'async') - -> 'ok'). --spec(store_updated_slaves/1 :: (rabbit_types:amqqueue()) -> - rabbit_types:amqqueue()). --spec(initial_queue_node/2 :: (rabbit_types:amqqueue(), node()) -> node()). --spec(suggested_queue_nodes/1 :: (rabbit_types:amqqueue()) -> - {node(), [node()]}). --spec(is_mirrored/1 :: (rabbit_types:amqqueue()) -> boolean()). --spec(update_mirrors/2 :: - (rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'). --spec(maybe_drop_master_after_sync/1 :: (rabbit_types:amqqueue()) -> 'ok'). --spec(maybe_auto_sync/1 :: (rabbit_types:amqqueue()) -> 'ok'). --spec(log_info/3 :: (rabbit_amqqueue:name(), string(), [any()]) -> 'ok'). --spec(log_warning/3 :: (rabbit_amqqueue:name(), string(), [any()]) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -%% Returns {ok, NewMPid, DeadPids} -remove_from_queue(QueueName, Self, DeadGMPids) -> - rabbit_misc:execute_mnesia_transaction( - fun () -> - %% Someone else could have deleted the queue before we - %% get here. - case mnesia:read({rabbit_queue, QueueName}) of - [] -> {error, not_found}; - [Q = #amqqueue { pid = QPid, - slave_pids = SPids, - gm_pids = GMPids, - down_slave_nodes = DSNs}] -> - {DeadGM, AliveGM} = lists:partition( - fun ({GM, _}) -> - lists:member(GM, DeadGMPids) - end, GMPids), - DeadPids = [Pid || {_GM, Pid} <- DeadGM], - AlivePids = [Pid || {_GM, Pid} <- AliveGM], - Alive = [Pid || Pid <- [QPid | SPids], - lists:member(Pid, AlivePids)], - DSNs1 = [node(Pid) || - Pid <- SPids, - not lists:member(Pid, AlivePids)] ++ DSNs, - {QPid1, SPids1} = promote_slave(Alive), - case {{QPid, SPids}, {QPid1, SPids1}} of - {Same, Same} -> - ok; - _ when QPid =:= QPid1 orelse QPid1 =:= Self -> - %% Either master hasn't changed, so - %% we're ok to update mnesia; or we have - %% become the master. - Q1 = Q#amqqueue{pid = QPid1, - slave_pids = SPids1, - gm_pids = AliveGM, - down_slave_nodes = DSNs1}, - store_updated_slaves(Q1), - %% If we add and remove nodes at the same time we - %% might tell the old master we need to sync and - %% then shut it down. So let's check if the new - %% master needs to sync. - maybe_auto_sync(Q1); - _ -> - %% Master has changed, and we're not it. - %% [1]. - Q1 = Q#amqqueue{slave_pids = Alive, - gm_pids = AliveGM, - down_slave_nodes = DSNs1}, - store_updated_slaves(Q1) - end, - {ok, QPid1, DeadPids} - end - end). -%% [1] We still update mnesia here in case the slave that is supposed -%% to become master dies before it does do so, in which case the dead -%% old master might otherwise never get removed, which in turn might -%% prevent promotion of another slave (e.g. us). -%% -%% Note however that we do not update the master pid. Otherwise we can -%% have the situation where a slave updates the mnesia record for a -%% queue, promoting another slave before that slave realises it has -%% become the new master, which is bad because it could then mean the -%% slave (now master) receives messages it's not ready for (for -%% example, new consumers). -%% -%% We set slave_pids to Alive rather than SPids1 since otherwise we'd -%% be removing the pid of the candidate master, which in turn would -%% prevent it from promoting itself. -%% -%% We maintain gm_pids as our source of truth, i.e. it contains the -%% most up-to-date information about which GMs and associated -%% {M,S}Pids are alive. And all pids in slave_pids always have a -%% corresponding entry in gm_pids. By contrast, due to the -%% aforementioned restriction on updating the master pid, that pid may -%% not be present in gm_pids, but only if said master has died. - -on_node_up() -> - QNames = - rabbit_misc:execute_mnesia_transaction( - fun () -> - mnesia:foldl( - fun (Q = #amqqueue{name = QName, - pid = Pid, - slave_pids = SPids}, QNames0) -> - %% We don't want to pass in the whole - %% cluster - we don't want a situation - %% where starting one node causes us to - %% decide to start a mirror on another - PossibleNodes0 = [node(P) || P <- [Pid | SPids]], - PossibleNodes = - case lists:member(node(), PossibleNodes0) of - true -> PossibleNodes0; - false -> [node() | PossibleNodes0] - end, - {_MNode, SNodes} = suggested_queue_nodes( - Q, PossibleNodes), - case lists:member(node(), SNodes) of - true -> [QName | QNames0]; - false -> QNames0 - end - end, [], rabbit_queue) - end), - [add_mirror(QName, node(), async) || QName <- QNames], - ok. - -drop_mirrors(QName, Nodes) -> - [drop_mirror(QName, Node) || Node <- Nodes], - ok. - -drop_mirror(QName, MirrorNode) -> - case rabbit_amqqueue:lookup(QName) of - {ok, #amqqueue { name = Name, pid = QPid, slave_pids = SPids }} -> - case [Pid || Pid <- [QPid | SPids], node(Pid) =:= MirrorNode] of - [] -> - {error, {queue_not_mirrored_on_node, MirrorNode}}; - [QPid] when SPids =:= [] -> - {error, cannot_drop_only_mirror}; - [Pid] -> - log_info(Name, "Dropping queue mirror on node ~p~n", - [MirrorNode]), - exit(Pid, {shutdown, dropped}), - {ok, dropped} - end; - {error, not_found} = E -> - E - end. - -add_mirrors(QName, Nodes, SyncMode) -> - [add_mirror(QName, Node, SyncMode) || Node <- Nodes], - ok. - -add_mirror(QName, MirrorNode, SyncMode) -> - case rabbit_amqqueue:lookup(QName) of - {ok, Q} -> - rabbit_misc:with_exit_handler( - rabbit_misc:const(ok), - fun () -> - SPid = rabbit_amqqueue_sup_sup:start_queue_process( - MirrorNode, Q, slave), - log_info(QName, "Adding mirror on node ~p: ~p~n", - [MirrorNode, SPid]), - rabbit_mirror_queue_slave:go(SPid, SyncMode) - end); - {error, not_found} = E -> - E - end. - -report_deaths(_MirrorPid, _IsMaster, _QueueName, []) -> - ok; -report_deaths(MirrorPid, IsMaster, QueueName, DeadPids) -> - log_info(QueueName, "~s ~s saw deaths of mirrors~s~n", - [case IsMaster of - true -> "Master"; - false -> "Slave" - end, - rabbit_misc:pid_to_string(MirrorPid), - [[$ , rabbit_misc:pid_to_string(P)] || P <- DeadPids]]). - -log_info (QName, Fmt, Args) -> log(info, QName, Fmt, Args). -log_warning(QName, Fmt, Args) -> log(warning, QName, Fmt, Args). - -log(Level, QName, Fmt, Args) -> - rabbit_log:log(mirroring, Level, "Mirrored ~s: " ++ Fmt, - [rabbit_misc:rs(QName) | Args]). - -store_updated_slaves(Q = #amqqueue{pid = MPid, - slave_pids = SPids, - sync_slave_pids = SSPids, - down_slave_nodes = DSNs}) -> - %% TODO now that we clear sync_slave_pids in rabbit_durable_queue, - %% do we still need this filtering? - SSPids1 = [SSPid || SSPid <- SSPids, lists:member(SSPid, SPids)], - DSNs1 = DSNs -- [node(P) || P <- [MPid | SPids]], - Q1 = Q#amqqueue{sync_slave_pids = SSPids1, - down_slave_nodes = DSNs1, - state = live}, - ok = rabbit_amqqueue:store_queue(Q1), - %% Wake it up so that we emit a stats event - rabbit_amqqueue:notify_policy_changed(Q1), - Q1. - -%%---------------------------------------------------------------------------- - -promote_slave([SPid | SPids]) -> - %% The slave pids are maintained in descending order of age, so - %% the one to promote is the oldest. - {SPid, SPids}. - -initial_queue_node(Q, DefNode) -> - {MNode, _SNodes} = suggested_queue_nodes(Q, DefNode, all_nodes()), - MNode. - -suggested_queue_nodes(Q) -> suggested_queue_nodes(Q, all_nodes()). -suggested_queue_nodes(Q, All) -> suggested_queue_nodes(Q, node(), All). - -%% The third argument exists so we can pull a call to -%% rabbit_mnesia:cluster_nodes(running) out of a loop or transaction -%% or both. -suggested_queue_nodes(Q = #amqqueue{exclusive_owner = Owner}, DefNode, All) -> - {MNode0, SNodes, SSNodes} = actual_queue_nodes(Q), - MNode = case MNode0 of - none -> DefNode; - _ -> MNode0 - end, - case Owner of - none -> Params = policy(<<"ha-params">>, Q), - case module(Q) of - {ok, M} -> M:suggested_queue_nodes( - Params, MNode, SNodes, SSNodes, All); - _ -> {MNode, []} - end; - _ -> {MNode, []} - end. - -all_nodes() -> rabbit_mnesia:cluster_nodes(running). - -policy(Policy, Q) -> - case rabbit_policy:get(Policy, Q) of - undefined -> none; - P -> P - end. - -module(#amqqueue{} = Q) -> - case rabbit_policy:get(<<"ha-mode">>, Q) of - undefined -> not_mirrored; - Mode -> module(Mode) - end; - -module(Mode) when is_binary(Mode) -> - case rabbit_registry:binary_to_type(Mode) of - {error, not_found} -> not_mirrored; - T -> case rabbit_registry:lookup_module(ha_mode, T) of - {ok, Module} -> {ok, Module}; - _ -> not_mirrored - end - end. - -is_mirrored(Q) -> - case module(Q) of - {ok, _} -> true; - _ -> false - end. - -actual_queue_nodes(#amqqueue{pid = MPid, - slave_pids = SPids, - sync_slave_pids = SSPids}) -> - Nodes = fun (L) -> [node(Pid) || Pid <- L] end, - {case MPid of - none -> none; - _ -> node(MPid) - end, Nodes(SPids), Nodes(SSPids)}. - -maybe_auto_sync(Q = #amqqueue{pid = QPid}) -> - case policy(<<"ha-sync-mode">>, Q) of - <<"automatic">> -> - spawn(fun() -> rabbit_amqqueue:sync_mirrors(QPid) end); - _ -> - ok - end. - -update_mirrors(OldQ = #amqqueue{pid = QPid}, - NewQ = #amqqueue{pid = QPid}) -> - case {is_mirrored(OldQ), is_mirrored(NewQ)} of - {false, false} -> ok; - {true, false} -> rabbit_amqqueue:stop_mirroring(QPid); - {false, true} -> rabbit_amqqueue:start_mirroring(QPid); - {true, true} -> update_mirrors0(OldQ, NewQ) - end. - -update_mirrors0(OldQ = #amqqueue{name = QName}, - NewQ = #amqqueue{name = QName}) -> - {OldMNode, OldSNodes, _} = actual_queue_nodes(OldQ), - {NewMNode, NewSNodes} = suggested_queue_nodes(NewQ), - OldNodes = [OldMNode | OldSNodes], - NewNodes = [NewMNode | NewSNodes], - add_mirrors (QName, NewNodes -- OldNodes, async), - drop_mirrors(QName, OldNodes -- NewNodes), - %% This is for the case where no extra nodes were added but we changed to - %% a policy requiring auto-sync. - maybe_auto_sync(NewQ), - ok. - -%% The arrival of a newly synced slave may cause the master to die if -%% the policy does not want the master but it has been kept alive -%% because there were no synced slaves. -%% -%% We don't just call update_mirrors/2 here since that could decide to -%% start a slave for some other reason, and since we are the slave ATM -%% that allows complicated deadlocks. -maybe_drop_master_after_sync(Q = #amqqueue{name = QName, - pid = MPid}) -> - {DesiredMNode, DesiredSNodes} = suggested_queue_nodes(Q), - case node(MPid) of - DesiredMNode -> ok; - OldMNode -> false = lists:member(OldMNode, DesiredSNodes), %% [0] - drop_mirror(QName, OldMNode) - end, - ok. -%% [0] ASSERTION - if the policy wants the master to change, it has -%% not just shuffled it into the slaves. All our modes ensure this -%% does not happen, but we should guard against a misbehaving plugin. - -%%---------------------------------------------------------------------------- - -validate_policy(KeyList) -> - Mode = proplists:get_value(<<"ha-mode">>, KeyList, none), - Params = proplists:get_value(<<"ha-params">>, KeyList, none), - SyncMode = proplists:get_value(<<"ha-sync-mode">>, KeyList, none), - PromoteOnShutdown = proplists:get_value( - <<"ha-promote-on-shutdown">>, KeyList, none), - case {Mode, Params, SyncMode, PromoteOnShutdown} of - {none, none, none, none} -> - ok; - {none, _, _, _} -> - {error, "ha-mode must be specified to specify ha-params, " - "ha-sync-mode or ha-promote-on-shutdown", []}; - _ -> - case module(Mode) of - {ok, M} -> case M:validate_policy(Params) of - ok -> case validate_sync_mode(SyncMode) of - ok -> validate_pos(PromoteOnShutdown); - E -> E - end; - E -> E - end; - _ -> {error, "~p is not a valid ha-mode value", [Mode]} - end - end. - -validate_sync_mode(SyncMode) -> - case SyncMode of - <<"automatic">> -> ok; - <<"manual">> -> ok; - none -> ok; - Mode -> {error, "ha-sync-mode must be \"manual\" " - "or \"automatic\", got ~p", [Mode]} - end. - -validate_pos(PromoteOnShutdown) -> - case PromoteOnShutdown of - <<"always">> -> ok; - <<"when-synced">> -> ok; - none -> ok; - Mode -> {error, "ha-promote-on-shutdown must be " - "\"always\" or \"when-synced\", got ~p", [Mode]} - end. diff --git a/src/rabbit_mirror_queue_mode.erl b/src/rabbit_mirror_queue_mode.erl deleted file mode 100644 index 1724be66..00000000 --- a/src/rabbit_mirror_queue_mode.erl +++ /dev/null @@ -1,57 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_mode). - --ifdef(use_specs). - --type(master() :: node()). --type(slave() :: node()). --type(params() :: any()). - --callback description() -> [proplists:property()]. - -%% Called whenever we think we might need to change nodes for a -%% mirrored queue. Note that this is called from a variety of -%% contexts, both inside and outside Mnesia transactions. Ideally it -%% will be pure-functional. -%% -%% Takes: parameters set in the policy, -%% current master, -%% current slaves, -%% current synchronised slaves, -%% all nodes to consider -%% -%% Returns: tuple of new master, new slaves -%% --callback suggested_queue_nodes( - params(), master(), [slave()], [slave()], [node()]) -> - {master(), [slave()]}. - -%% Are the parameters valid for this mode? --callback validate_policy(params()) -> - rabbit_policy_validator:validate_results(). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{description, 0}, {suggested_queue_nodes, 5}, {validate_policy, 1}]; -behaviour_info(_Other) -> - undefined. - --endif. diff --git a/src/rabbit_mirror_queue_mode_all.erl b/src/rabbit_mirror_queue_mode_all.erl deleted file mode 100644 index ab5fccc8..00000000 --- a/src/rabbit_mirror_queue_mode_all.erl +++ /dev/null @@ -1,41 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_mode_all). - --include("rabbit.hrl"). - --behaviour(rabbit_mirror_queue_mode). - --export([description/0, suggested_queue_nodes/5, validate_policy/1]). - --rabbit_boot_step({?MODULE, - [{description, "mirror mode all"}, - {mfa, {rabbit_registry, register, - [ha_mode, <<"all">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - -description() -> - [{description, <<"Mirror queue to all nodes">>}]. - -suggested_queue_nodes(_Params, MNode, _SNodes, _SSNodes, Poss) -> - {MNode, Poss -- [MNode]}. - -validate_policy(none) -> - ok; -validate_policy(_Params) -> - {error, "ha-mode=\"all\" does not take parameters", []}. diff --git a/src/rabbit_mirror_queue_mode_exactly.erl b/src/rabbit_mirror_queue_mode_exactly.erl deleted file mode 100644 index bdbc4801..00000000 --- a/src/rabbit_mirror_queue_mode_exactly.erl +++ /dev/null @@ -1,56 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_mode_exactly). - --include("rabbit.hrl"). - --behaviour(rabbit_mirror_queue_mode). - --export([description/0, suggested_queue_nodes/5, validate_policy/1]). - --rabbit_boot_step({?MODULE, - [{description, "mirror mode exactly"}, - {mfa, {rabbit_registry, register, - [ha_mode, <<"exactly">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - -description() -> - [{description, <<"Mirror queue to a specified number of nodes">>}]. - -%% When we need to add nodes, we randomise our candidate list as a -%% crude form of load-balancing. TODO it would also be nice to -%% randomise the list of ones to remove when we have too many - we -%% would have to take account of synchronisation though. -suggested_queue_nodes(Count, MNode, SNodes, _SSNodes, Poss) -> - SCount = Count - 1, - {MNode, case SCount > length(SNodes) of - true -> Cand = shuffle((Poss -- [MNode]) -- SNodes), - SNodes ++ lists:sublist(Cand, SCount - length(SNodes)); - false -> lists:sublist(SNodes, SCount) - end}. - -shuffle(L) -> - {A1,A2,A3} = now(), - random:seed(A1, A2, A3), - {_, L1} = lists:unzip(lists:keysort(1, [{random:uniform(), N} || N <- L])), - L1. - -validate_policy(N) when is_integer(N) andalso N > 0 -> - ok; -validate_policy(Params) -> - {error, "ha-mode=\"exactly\" takes an integer, ~p given", [Params]}. diff --git a/src/rabbit_mirror_queue_mode_nodes.erl b/src/rabbit_mirror_queue_mode_nodes.erl deleted file mode 100644 index 1b32f3b3..00000000 --- a/src/rabbit_mirror_queue_mode_nodes.erl +++ /dev/null @@ -1,70 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_mode_nodes). - --include("rabbit.hrl"). - --behaviour(rabbit_mirror_queue_mode). - --export([description/0, suggested_queue_nodes/5, validate_policy/1]). - --rabbit_boot_step({?MODULE, - [{description, "mirror mode nodes"}, - {mfa, {rabbit_registry, register, - [ha_mode, <<"nodes">>, ?MODULE]}}, - {requires, rabbit_registry}, - {enables, kernel_ready}]}). - -description() -> - [{description, <<"Mirror queue to specified nodes">>}]. - -suggested_queue_nodes(Nodes0, MNode, _SNodes, SSNodes, Poss) -> - Nodes1 = [list_to_atom(binary_to_list(Node)) || Node <- Nodes0], - %% If the current master is not in the nodes specified, then what we want - %% to do depends on whether there are any synchronised slaves. If there - %% are then we can just kill the current master - the admin has asked for - %% a migration and we should give it to them. If there are not however - %% then we must keep the master around so as not to lose messages. - Nodes = case SSNodes of - [] -> lists:usort([MNode | Nodes1]); - _ -> Nodes1 - end, - Unavailable = Nodes -- Poss, - Available = Nodes -- Unavailable, - case Available of - [] -> %% We have never heard of anything? Not much we can do but - %% keep the master alive. - {MNode, []}; - _ -> case lists:member(MNode, Available) of - true -> {MNode, Available -- [MNode]}; - false -> %% Make sure the new master is synced! In order to - %% get here SSNodes must not be empty. - [NewMNode | _] = SSNodes, - {NewMNode, Available -- [NewMNode]} - end - end. - -validate_policy([]) -> - {error, "ha-mode=\"nodes\" list must be non-empty", []}; -validate_policy(Nodes) when is_list(Nodes) -> - case [I || I <- Nodes, not is_binary(I)] of - [] -> ok; - Invalid -> {error, "ha-mode=\"nodes\" takes a list of strings, " - "~p was not a string", [Invalid]} - end; -validate_policy(Params) -> - {error, "ha-mode=\"nodes\" takes a list, ~p given", [Params]}. diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl deleted file mode 100644 index 92e1cc27..00000000 --- a/src/rabbit_mirror_queue_slave.erl +++ /dev/null @@ -1,957 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_slave). - -%% For general documentation of HA design, see -%% rabbit_mirror_queue_coordinator -%% -%% We receive messages from GM and from publishers, and the gm -%% messages can arrive either before or after the 'actual' message. -%% All instructions from the GM group must be processed in the order -%% in which they're received. - --export([set_maximum_since_use/2, info/1, go/2]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3, handle_pre_hibernate/1, prioritise_call/4, - prioritise_cast/3, prioritise_info/3, format_message_queue/2]). - --export([joined/2, members_changed/3, handle_msg/3, handle_terminate/2]). - --behaviour(gen_server2). --behaviour(gm). - --include("rabbit.hrl"). - --include("gm_specs.hrl"). - -%%---------------------------------------------------------------------------- - --define(INFO_KEYS, - [pid, - name, - master_pid, - is_synchronised - ]). - --define(SYNC_INTERVAL, 25). %% milliseconds --define(RAM_DURATION_UPDATE_INTERVAL, 5000). --define(DEATH_TIMEOUT, 20000). %% 20 seconds - --record(state, { q, - gm, - backing_queue, - backing_queue_state, - sync_timer_ref, - rate_timer_ref, - - sender_queues, %% :: Pid -> {Q Msg, Set MsgId, ChState} - msg_id_ack, %% :: MsgId -> AckTag - - msg_id_status, - known_senders, - - %% Master depth - local depth - depth_delta - }). - -%%---------------------------------------------------------------------------- - -set_maximum_since_use(QPid, Age) -> - gen_server2:cast(QPid, {set_maximum_since_use, Age}). - -info(QPid) -> gen_server2:call(QPid, info, infinity). - -init(Q) -> - ?store_proc_name(Q#amqqueue.name), - {ok, {not_started, Q}, hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, - ?DESIRED_HIBERNATE}, ?MODULE}. - -go(SPid, sync) -> gen_server2:call(SPid, go, infinity); -go(SPid, async) -> gen_server2:cast(SPid, go). - -handle_go(Q = #amqqueue{name = QName}) -> - %% We join the GM group before we add ourselves to the amqqueue - %% record. As a result: - %% 1. We can receive msgs from GM that correspond to messages we will - %% never receive from publishers. - %% 2. When we receive a message from publishers, we must receive a - %% message from the GM group for it. - %% 3. However, that instruction from the GM group can arrive either - %% before or after the actual message. We need to be able to - %% distinguish between GM instructions arriving early, and case (1) - %% above. - %% - process_flag(trap_exit, true), %% amqqueue_process traps exits too. - {ok, GM} = gm:start_link(QName, ?MODULE, [self()], - fun rabbit_misc:execute_mnesia_transaction/1), - MRef = erlang:monitor(process, GM), - %% We ignore the DOWN message because we are also linked and - %% trapping exits, we just want to not get stuck and we will exit - %% later. - receive - {joined, GM} -> erlang:demonitor(MRef, [flush]), - ok; - {'DOWN', MRef, _, _, _} -> ok - end, - Self = self(), - Node = node(), - case rabbit_misc:execute_mnesia_transaction( - fun() -> init_it(Self, GM, Node, QName) end) of - {new, QPid, GMPids} -> - ok = file_handle_cache:register_callback( - rabbit_amqqueue, set_maximum_since_use, [Self]), - ok = rabbit_memory_monitor:register( - Self, {rabbit_amqqueue, set_ram_duration_target, [Self]}), - {ok, BQ} = application:get_env(backing_queue_module), - Q1 = Q #amqqueue { pid = QPid }, - ok = rabbit_queue_index:erase(QName), %% For crash recovery - BQS = bq_init(BQ, Q1, new), - State = #state { q = Q1, - gm = GM, - backing_queue = BQ, - backing_queue_state = BQS, - rate_timer_ref = undefined, - sync_timer_ref = undefined, - - sender_queues = dict:new(), - msg_id_ack = dict:new(), - - msg_id_status = dict:new(), - known_senders = pmon:new(delegate), - - depth_delta = undefined - }, - ok = gm:broadcast(GM, request_depth), - ok = gm:validate_members(GM, [GM | [G || {G, _} <- GMPids]]), - rabbit_mirror_queue_misc:maybe_auto_sync(Q1), - {ok, State}; - {stale, StalePid} -> - rabbit_mirror_queue_misc:log_warning( - QName, "Detected stale HA master: ~p~n", [StalePid]), - gm:leave(GM), - {error, {stale_master_pid, StalePid}}; - duplicate_live_master -> - gm:leave(GM), - {error, {duplicate_live_master, Node}}; - existing -> - gm:leave(GM), - {error, normal}; - master_in_recovery -> - gm:leave(GM), - %% The queue record vanished - we must have a master starting - %% concurrently with us. In that case we can safely decide to do - %% nothing here, and the master will start us in - %% master:init_with_existing_bq/3 - {error, normal} - end. - -init_it(Self, GM, Node, QName) -> - case mnesia:read({rabbit_queue, QName}) of - [Q = #amqqueue { pid = QPid, slave_pids = SPids, gm_pids = GMPids }] -> - case [Pid || Pid <- [QPid | SPids], node(Pid) =:= Node] of - [] -> add_slave(Q, Self, GM), - {new, QPid, GMPids}; - [QPid] -> case rabbit_mnesia:is_process_alive(QPid) of - true -> duplicate_live_master; - false -> {stale, QPid} - end; - [SPid] -> case rabbit_mnesia:is_process_alive(SPid) of - true -> existing; - false -> GMPids1 = [T || T = {_, S} <- GMPids, - S =/= SPid], - Q1 = Q#amqqueue{ - slave_pids = SPids -- [SPid], - gm_pids = GMPids1}, - add_slave(Q1, Self, GM), - {new, QPid, GMPids1} - end - end; - [] -> - master_in_recovery - end. - -%% Add to the end, so they are in descending order of age, see -%% rabbit_mirror_queue_misc:promote_slave/1 -add_slave(Q = #amqqueue { slave_pids = SPids, gm_pids = GMPids }, New, GM) -> - rabbit_mirror_queue_misc:store_updated_slaves( - Q#amqqueue{slave_pids = SPids ++ [New], gm_pids = [{GM, New} | GMPids]}). - -handle_call(go, _From, {not_started, Q} = NotStarted) -> - case handle_go(Q) of - {ok, State} -> {reply, ok, State}; - {error, Error} -> {stop, Error, NotStarted} - end; - -handle_call({gm_deaths, DeadGMPids}, From, - State = #state { gm = GM, q = Q = #amqqueue { - name = QName, pid = MPid }}) -> - Self = self(), - case rabbit_mirror_queue_misc:remove_from_queue(QName, Self, DeadGMPids) of - {error, not_found} -> - gen_server2:reply(From, ok), - {stop, normal, State}; - {ok, Pid, DeadPids} -> - rabbit_mirror_queue_misc:report_deaths(Self, false, QName, - DeadPids), - case Pid of - MPid -> - %% master hasn't changed - gen_server2:reply(From, ok), - noreply(State); - Self -> - %% we've become master - QueueState = promote_me(From, State), - {become, rabbit_amqqueue_process, QueueState, hibernate}; - _ -> - %% master has changed to not us - gen_server2:reply(From, ok), - %% Since GM is by nature lazy we need to make sure - %% there is some traffic when a master dies, to - %% make sure all slaves get informed of the - %% death. That is all process_death does, create - %% some traffic. - ok = gm:broadcast(GM, process_death), - noreply(State #state { q = Q #amqqueue { pid = Pid } }) - end - end; - -handle_call(info, _From, State) -> - reply(infos(?INFO_KEYS, State), State). - -handle_cast(go, {not_started, Q} = NotStarted) -> - case handle_go(Q) of - {ok, State} -> {noreply, State}; - {error, Error} -> {stop, Error, NotStarted} - end; - -handle_cast({run_backing_queue, Mod, Fun}, State) -> - noreply(run_backing_queue(Mod, Fun, State)); - -handle_cast({gm, Instruction}, State) -> - handle_process_result(process_instruction(Instruction, State)); - -handle_cast({deliver, Delivery = #delivery{sender = Sender}, true, Flow}, - State) -> - %% Asynchronous, non-"mandatory", deliver mode. - case Flow of - flow -> credit_flow:ack(Sender); - noflow -> ok - end, - noreply(maybe_enqueue_message(Delivery, State)); - -handle_cast({sync_start, Ref, Syncer}, - State = #state { depth_delta = DD, - backing_queue = BQ, - backing_queue_state = BQS }) -> - State1 = #state{rate_timer_ref = TRef} = ensure_rate_timer(State), - S = fun({MA, TRefN, BQSN}) -> - State1#state{depth_delta = undefined, - msg_id_ack = dict:from_list(MA), - rate_timer_ref = TRefN, - backing_queue_state = BQSN} - end, - case rabbit_mirror_queue_sync:slave( - DD, Ref, TRef, Syncer, BQ, BQS, - fun (BQN, BQSN) -> - BQSN1 = update_ram_duration(BQN, BQSN), - TRefN = rabbit_misc:send_after(?RAM_DURATION_UPDATE_INTERVAL, - self(), update_ram_duration), - {TRefN, BQSN1} - end) of - denied -> noreply(State1); - {ok, Res} -> noreply(set_delta(0, S(Res))); - {failed, Res} -> noreply(S(Res)); - {stop, Reason, Res} -> {stop, Reason, S(Res)} - end; - -handle_cast({set_maximum_since_use, Age}, State) -> - ok = file_handle_cache:set_maximum_since_use(Age), - noreply(State); - -handle_cast({set_ram_duration_target, Duration}, - State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - BQS1 = BQ:set_ram_duration_target(Duration, BQS), - noreply(State #state { backing_queue_state = BQS1 }). - -handle_info(update_ram_duration, State = #state{backing_queue = BQ, - backing_queue_state = BQS}) -> - BQS1 = update_ram_duration(BQ, BQS), - %% Don't call noreply/1, we don't want to set timers - {State1, Timeout} = next_state(State #state { - rate_timer_ref = undefined, - backing_queue_state = BQS1 }), - {noreply, State1, Timeout}; - -handle_info(sync_timeout, State) -> - noreply(backing_queue_timeout( - State #state { sync_timer_ref = undefined })); - -handle_info(timeout, State) -> - noreply(backing_queue_timeout(State)); - -handle_info({'DOWN', _MonitorRef, process, ChPid, _Reason}, State) -> - local_sender_death(ChPid, State), - noreply(maybe_forget_sender(ChPid, down_from_ch, State)); - -handle_info({'EXIT', _Pid, Reason}, State) -> - {stop, Reason, State}; - -handle_info({bump_credit, Msg}, State) -> - credit_flow:handle_bump_msg(Msg), - noreply(State); - -%% In the event of a short partition during sync we can detect the -%% master's 'death', drop out of sync, and then receive sync messages -%% which were still in flight. Ignore them. -handle_info({sync_msg, _Ref, _Msg, _Props, _Unacked}, State) -> - noreply(State); - -handle_info({sync_complete, _Ref}, State) -> - noreply(State); - -handle_info(Msg, State) -> - {stop, {unexpected_info, Msg}, State}. - -terminate(_Reason, {not_started, _Q}) -> - ok; -terminate(_Reason, #state { backing_queue_state = undefined }) -> - %% We've received a delete_and_terminate from gm, thus nothing to - %% do here. - ok; -terminate({shutdown, dropped} = R, State = #state{backing_queue = BQ, - backing_queue_state = BQS}) -> - %% See rabbit_mirror_queue_master:terminate/2 - terminate_common(State), - BQ:delete_and_terminate(R, BQS); -terminate(shutdown, State) -> - terminate_shutdown(shutdown, State); -terminate({shutdown, _} = R, State) -> - terminate_shutdown(R, State); -terminate(Reason, State = #state{backing_queue = BQ, - backing_queue_state = BQS}) -> - terminate_common(State), - BQ:delete_and_terminate(Reason, BQS). - -%% If the Reason is shutdown, or {shutdown, _}, it is not the queue -%% being deleted: it's just the node going down. Even though we're a -%% slave, we have no idea whether or not we'll be the only copy coming -%% back up. Thus we must assume we will be, and preserve anything we -%% have on disk. -terminate_shutdown(Reason, State = #state{backing_queue = BQ, - backing_queue_state = BQS}) -> - terminate_common(State), - BQ:terminate(Reason, BQS). - -terminate_common(State) -> - ok = rabbit_memory_monitor:deregister(self()), - stop_rate_timer(stop_sync_timer(State)). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -handle_pre_hibernate({not_started, _Q} = State) -> - {hibernate, State}; - -handle_pre_hibernate(State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - {RamDuration, BQS1} = BQ:ram_duration(BQS), - DesiredDuration = - rabbit_memory_monitor:report_ram_duration(self(), RamDuration), - BQS2 = BQ:set_ram_duration_target(DesiredDuration, BQS1), - BQS3 = BQ:handle_pre_hibernate(BQS2), - {hibernate, stop_rate_timer(State #state { backing_queue_state = BQS3 })}. - -prioritise_call(Msg, _From, _Len, _State) -> - case Msg of - info -> 9; - {gm_deaths, _Dead} -> 5; - _ -> 0 - end. - -prioritise_cast(Msg, _Len, _State) -> - case Msg of - {set_ram_duration_target, _Duration} -> 8; - {set_maximum_since_use, _Age} -> 8; - {run_backing_queue, _Mod, _Fun} -> 6; - {gm, _Msg} -> 5; - _ -> 0 - end. - -prioritise_info(Msg, _Len, _State) -> - case Msg of - update_ram_duration -> 8; - sync_timeout -> 6; - _ -> 0 - end. - -format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ). - -%% --------------------------------------------------------------------------- -%% GM -%% --------------------------------------------------------------------------- - -joined([SPid], _Members) -> SPid ! {joined, self()}, ok. - -members_changed([_SPid], _Births, []) -> - ok; -members_changed([ SPid], _Births, Deaths) -> - case rabbit_misc:with_exit_handler( - rabbit_misc:const(ok), - fun() -> - gen_server2:call(SPid, {gm_deaths, Deaths}, infinity) - end) of - ok -> ok; - {promote, CPid} -> {become, rabbit_mirror_queue_coordinator, [CPid]} - end. - -handle_msg([_SPid], _From, request_depth) -> - %% This is only of value to the master - ok; -handle_msg([_SPid], _From, {ensure_monitoring, _Pid}) -> - %% This is only of value to the master - ok; -handle_msg([_SPid], _From, process_death) -> - %% We must not take any notice of the master death here since it - %% comes without ordering guarantees - there could still be - %% messages from the master we have yet to receive. When we get - %% members_changed, then there will be no more messages. - ok; -handle_msg([CPid], _From, {delete_and_terminate, _Reason} = Msg) -> - ok = gen_server2:cast(CPid, {gm, Msg}), - {stop, {shutdown, ring_shutdown}}; -handle_msg([SPid], _From, {sync_start, Ref, Syncer, SPids}) -> - case lists:member(SPid, SPids) of - true -> gen_server2:cast(SPid, {sync_start, Ref, Syncer}); - false -> ok - end; -handle_msg([SPid], _From, Msg) -> - ok = gen_server2:cast(SPid, {gm, Msg}). - -handle_terminate([_SPid], _Reason) -> - ok. - -%% --------------------------------------------------------------------------- -%% Others -%% --------------------------------------------------------------------------- - -infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. - -i(pid, _State) -> self(); -i(name, #state { q = #amqqueue { name = Name } }) -> Name; -i(master_pid, #state { q = #amqqueue { pid = MPid } }) -> MPid; -i(is_synchronised, #state { depth_delta = DD }) -> DD =:= 0; -i(Item, _State) -> throw({bad_argument, Item}). - -bq_init(BQ, Q, Recover) -> - Self = self(), - BQ:init(Q, Recover, - fun (Mod, Fun) -> - rabbit_amqqueue:run_backing_queue(Self, Mod, Fun) - end). - -run_backing_queue(rabbit_mirror_queue_master, Fun, State) -> - %% Yes, this might look a little crazy, but see comments in - %% confirm_sender_death/1 - Fun(?MODULE, State); -run_backing_queue(Mod, Fun, State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - State #state { backing_queue_state = BQ:invoke(Mod, Fun, BQS) }. - -send_mandatory(#delivery{mandatory = false}) -> - ok; -send_mandatory(#delivery{mandatory = true, - sender = SenderPid, - msg_seq_no = MsgSeqNo}) -> - gen_server2:cast(SenderPid, {mandatory_received, MsgSeqNo}). - -send_or_record_confirm(_, #delivery{ confirm = false }, MS, _State) -> - MS; -send_or_record_confirm(published, #delivery { sender = ChPid, - confirm = true, - msg_seq_no = MsgSeqNo, - message = #basic_message { - id = MsgId, - is_persistent = true } }, - MS, #state { q = #amqqueue { durable = true } }) -> - dict:store(MsgId, {published, ChPid, MsgSeqNo} , MS); -send_or_record_confirm(_Status, #delivery { sender = ChPid, - confirm = true, - msg_seq_no = MsgSeqNo }, - MS, _State) -> - ok = rabbit_misc:confirm_to_sender(ChPid, [MsgSeqNo]), - MS. - -confirm_messages(MsgIds, State = #state { msg_id_status = MS }) -> - {CMs, MS1} = - lists:foldl( - fun (MsgId, {CMsN, MSN} = Acc) -> - %% We will never see 'discarded' here - case dict:find(MsgId, MSN) of - error -> - %% If it needed confirming, it'll have - %% already been done. - Acc; - {ok, published} -> - %% Still not seen it from the channel, just - %% record that it's been confirmed. - {CMsN, dict:store(MsgId, confirmed, MSN)}; - {ok, {published, ChPid, MsgSeqNo}} -> - %% Seen from both GM and Channel. Can now - %% confirm. - {rabbit_misc:gb_trees_cons(ChPid, MsgSeqNo, CMsN), - dict:erase(MsgId, MSN)}; - {ok, confirmed} -> - %% It's already been confirmed. This is - %% probably it's been both sync'd to disk - %% and then delivered and ack'd before we've - %% seen the publish from the - %% channel. Nothing to do here. - Acc - end - end, {gb_trees:empty(), MS}, MsgIds), - rabbit_misc:gb_trees_foreach(fun rabbit_misc:confirm_to_sender/2, CMs), - State #state { msg_id_status = MS1 }. - -handle_process_result({ok, State}) -> noreply(State); -handle_process_result({stop, State}) -> {stop, normal, State}. - --ifdef(use_specs). --spec(promote_me/2 :: ({pid(), term()}, #state{}) -> no_return()). --endif. -promote_me(From, #state { q = Q = #amqqueue { name = QName }, - gm = GM, - backing_queue = BQ, - backing_queue_state = BQS, - rate_timer_ref = RateTRef, - sender_queues = SQ, - msg_id_ack = MA, - msg_id_status = MS, - known_senders = KS }) -> - rabbit_mirror_queue_misc:log_info(QName, "Promoting slave ~s to master~n", - [rabbit_misc:pid_to_string(self())]), - Q1 = Q #amqqueue { pid = self() }, - {ok, CPid} = rabbit_mirror_queue_coordinator:start_link( - Q1, GM, rabbit_mirror_queue_master:sender_death_fun(), - rabbit_mirror_queue_master:depth_fun()), - true = unlink(GM), - gen_server2:reply(From, {promote, CPid}), - - %% Everything that we're monitoring, we need to ensure our new - %% coordinator is monitoring. - MPids = pmon:monitored(KS), - ok = rabbit_mirror_queue_coordinator:ensure_monitoring(CPid, MPids), - - %% We find all the messages that we've received from channels but - %% not from gm, and pass them to the - %% queue_process:init_with_backing_queue_state to be enqueued. - %% - %% We also have to requeue messages which are pending acks: the - %% consumers from the master queue have been lost and so these - %% messages need requeuing. They might also be pending - %% confirmation, and indeed they might also be pending arrival of - %% the publication from the channel itself, if we received both - %% the publication and the fetch via gm first! Requeuing doesn't - %% affect confirmations: if the message was previously pending a - %% confirmation then it still will be, under the same msg_id. So - %% as a master, we need to be prepared to filter out the - %% publication of said messages from the channel (is_duplicate - %% (thus such requeued messages must remain in the msg_id_status - %% (MS) which becomes seen_status (SS) in the master)). - %% - %% Then there are messages we already have in the queue, which are - %% not currently pending acknowledgement: - %% 1. Messages we've only received via gm: - %% Filter out subsequent publication from channel through - %% validate_message. Might have to issue confirms then or - %% later, thus queue_process state will have to know that - %% there's a pending confirm. - %% 2. Messages received via both gm and channel: - %% Queue will have to deal with issuing confirms if necessary. - %% - %% MS contains the following three entry types: - %% - %% a) published: - %% published via gm only; pending arrival of publication from - %% channel, maybe pending confirm. - %% - %% b) {published, ChPid, MsgSeqNo}: - %% published via gm and channel; pending confirm. - %% - %% c) confirmed: - %% published via gm only, and confirmed; pending publication - %% from channel. - %% - %% d) discarded: - %% seen via gm only as discarded. Pending publication from - %% channel - %% - %% The forms a, c and d only, need to go to the master state - %% seen_status (SS). - %% - %% The form b only, needs to go through to the queue_process - %% state to form the msg_id_to_channel mapping (MTC). - %% - %% No messages that are enqueued from SQ at this point will have - %% entries in MS. - %% - %% Messages that are extracted from MA may have entries in MS, and - %% those messages are then requeued. However, as discussed above, - %% this does not affect MS, nor which bits go through to SS in - %% Master, or MTC in queue_process. - - St = [published, confirmed, discarded], - SS = dict:filter(fun (_MsgId, Status) -> lists:member(Status, St) end, MS), - AckTags = [AckTag || {_MsgId, AckTag} <- dict:to_list(MA)], - - MasterState = rabbit_mirror_queue_master:promote_backing_queue_state( - QName, CPid, BQ, BQS, GM, AckTags, SS, MPids), - - MTC = dict:fold(fun (MsgId, {published, ChPid, MsgSeqNo}, MTC0) -> - gb_trees:insert(MsgId, {ChPid, MsgSeqNo}, MTC0); - (_Msgid, _Status, MTC0) -> - MTC0 - end, gb_trees:empty(), MS), - Deliveries = [Delivery#delivery{mandatory = false} || %% [0] - {_ChPid, {PubQ, _PendCh, _ChState}} <- dict:to_list(SQ), - Delivery <- queue:to_list(PubQ)], - AwaitGmDown = [ChPid || {ChPid, {_, _, down_from_ch}} <- dict:to_list(SQ)], - KS1 = lists:foldl(fun (ChPid0, KS0) -> - pmon:demonitor(ChPid0, KS0) - end, KS, AwaitGmDown), - rabbit_misc:store_proc_name(rabbit_amqqueue_process, QName), - rabbit_amqqueue_process:init_with_backing_queue_state( - Q1, rabbit_mirror_queue_master, MasterState, RateTRef, Deliveries, KS1, - MTC). - -%% [0] We reset mandatory to false here because we will have sent the -%% mandatory_received already as soon as we got the message - -noreply(State) -> - {NewState, Timeout} = next_state(State), - {noreply, ensure_rate_timer(NewState), Timeout}. - -reply(Reply, State) -> - {NewState, Timeout} = next_state(State), - {reply, Reply, ensure_rate_timer(NewState), Timeout}. - -next_state(State = #state{backing_queue = BQ, backing_queue_state = BQS}) -> - {MsgIds, BQS1} = BQ:drain_confirmed(BQS), - State1 = confirm_messages(MsgIds, - State #state { backing_queue_state = BQS1 }), - case BQ:needs_timeout(BQS1) of - false -> {stop_sync_timer(State1), hibernate }; - idle -> {stop_sync_timer(State1), ?SYNC_INTERVAL}; - timed -> {ensure_sync_timer(State1), 0 } - end. - -backing_queue_timeout(State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - State#state{backing_queue_state = BQ:timeout(BQS)}. - -ensure_sync_timer(State) -> - rabbit_misc:ensure_timer(State, #state.sync_timer_ref, - ?SYNC_INTERVAL, sync_timeout). - -stop_sync_timer(State) -> rabbit_misc:stop_timer(State, #state.sync_timer_ref). - -ensure_rate_timer(State) -> - rabbit_misc:ensure_timer(State, #state.rate_timer_ref, - ?RAM_DURATION_UPDATE_INTERVAL, - update_ram_duration). - -stop_rate_timer(State) -> rabbit_misc:stop_timer(State, #state.rate_timer_ref). - -ensure_monitoring(ChPid, State = #state { known_senders = KS }) -> - State #state { known_senders = pmon:monitor(ChPid, KS) }. - -local_sender_death(ChPid, #state { known_senders = KS }) -> - %% The channel will be monitored iff we have received a delivery - %% from it but not heard about its death from the master. So if it - %% is monitored we need to point the death out to the master (see - %% essay). - ok = case pmon:is_monitored(ChPid, KS) of - false -> ok; - true -> confirm_sender_death(ChPid) - end. - -confirm_sender_death(Pid) -> - %% We have to deal with the possibility that we'll be promoted to - %% master before this thing gets run. Consequently we set the - %% module to rabbit_mirror_queue_master so that if we do become a - %% rabbit_amqqueue_process before then, sane things will happen. - Fun = - fun (?MODULE, State = #state { known_senders = KS, - gm = GM }) -> - %% We're running still as a slave - %% - %% See comment in local_sender_death/2; we might have - %% received a sender_death in the meanwhile so check - %% again. - ok = case pmon:is_monitored(Pid, KS) of - false -> ok; - true -> gm:broadcast(GM, {ensure_monitoring, [Pid]}), - confirm_sender_death(Pid) - end, - State; - (rabbit_mirror_queue_master, State) -> - %% We've become a master. State is now opaque to - %% us. When we became master, if Pid was still known - %% to us then we'd have set up monitoring of it then, - %% so this is now a noop. - State - end, - %% Note that we do not remove our knowledge of this ChPid until we - %% get the sender_death from GM as well as a DOWN notification. - {ok, _TRef} = timer:apply_after( - ?DEATH_TIMEOUT, rabbit_amqqueue, run_backing_queue, - [self(), rabbit_mirror_queue_master, Fun]), - ok. - -forget_sender(_, running) -> false; -forget_sender(down_from_gm, down_from_gm) -> false; %% [1] -forget_sender(Down1, Down2) when Down1 =/= Down2 -> true. - -%% [1] If another slave goes through confirm_sender_death/1 before we -%% do we can get two GM sender_death messages in a row for the same -%% channel - don't treat that as anything special. - -%% Record and process lifetime events from channels. Forget all about a channel -%% only when down notifications are received from both the channel and from gm. -maybe_forget_sender(ChPid, ChState, State = #state { sender_queues = SQ, - msg_id_status = MS, - known_senders = KS }) -> - case dict:find(ChPid, SQ) of - error -> - State; - {ok, {MQ, PendCh, ChStateRecord}} -> - case forget_sender(ChState, ChStateRecord) of - true -> - credit_flow:peer_down(ChPid), - State #state { sender_queues = dict:erase(ChPid, SQ), - msg_id_status = lists:foldl( - fun dict:erase/2, - MS, sets:to_list(PendCh)), - known_senders = pmon:demonitor(ChPid, KS) }; - false -> - SQ1 = dict:store(ChPid, {MQ, PendCh, ChState}, SQ), - State #state { sender_queues = SQ1 } - end - end. - -maybe_enqueue_message( - Delivery = #delivery { message = #basic_message { id = MsgId }, - sender = ChPid }, - State = #state { sender_queues = SQ, msg_id_status = MS }) -> - send_mandatory(Delivery), %% must do this before confirms - State1 = ensure_monitoring(ChPid, State), - %% We will never see {published, ChPid, MsgSeqNo} here. - case dict:find(MsgId, MS) of - error -> - {MQ, PendingCh, ChState} = get_sender_queue(ChPid, SQ), - MQ1 = queue:in(Delivery, MQ), - SQ1 = dict:store(ChPid, {MQ1, PendingCh, ChState}, SQ), - State1 #state { sender_queues = SQ1 }; - {ok, Status} -> - MS1 = send_or_record_confirm( - Status, Delivery, dict:erase(MsgId, MS), State1), - SQ1 = remove_from_pending_ch(MsgId, ChPid, SQ), - State1 #state { msg_id_status = MS1, - sender_queues = SQ1 } - end. - -get_sender_queue(ChPid, SQ) -> - case dict:find(ChPid, SQ) of - error -> {queue:new(), sets:new(), running}; - {ok, Val} -> Val - end. - -remove_from_pending_ch(MsgId, ChPid, SQ) -> - case dict:find(ChPid, SQ) of - error -> - SQ; - {ok, {MQ, PendingCh, ChState}} -> - dict:store(ChPid, {MQ, sets:del_element(MsgId, PendingCh), ChState}, - SQ) - end. - -publish_or_discard(Status, ChPid, MsgId, - State = #state { sender_queues = SQ, msg_id_status = MS }) -> - %% We really are going to do the publish/discard right now, even - %% though we may not have seen it directly from the channel. But - %% we cannot issue confirms until the latter has happened. So we - %% need to keep track of the MsgId and its confirmation status in - %% the meantime. - State1 = ensure_monitoring(ChPid, State), - {MQ, PendingCh, ChState} = get_sender_queue(ChPid, SQ), - {MQ1, PendingCh1, MS1} = - case queue:out(MQ) of - {empty, _MQ2} -> - {MQ, sets:add_element(MsgId, PendingCh), - dict:store(MsgId, Status, MS)}; - {{value, Delivery = #delivery { - message = #basic_message { id = MsgId } }}, MQ2} -> - {MQ2, PendingCh, - %% We received the msg from the channel first. Thus - %% we need to deal with confirms here. - send_or_record_confirm(Status, Delivery, MS, State1)}; - {{value, #delivery {}}, _MQ2} -> - %% The instruction was sent to us before we were - %% within the slave_pids within the #amqqueue{} - %% record. We'll never receive the message directly - %% from the channel. And the channel will not be - %% expecting any confirms from us. - {MQ, PendingCh, MS} - end, - SQ1 = dict:store(ChPid, {MQ1, PendingCh1, ChState}, SQ), - State1 #state { sender_queues = SQ1, msg_id_status = MS1 }. - - -process_instruction({publish, ChPid, MsgProps, - Msg = #basic_message { id = MsgId }}, State) -> - State1 = #state { backing_queue = BQ, backing_queue_state = BQS } = - publish_or_discard(published, ChPid, MsgId, State), - BQS1 = BQ:publish(Msg, MsgProps, true, ChPid, BQS), - {ok, State1 #state { backing_queue_state = BQS1 }}; -process_instruction({publish_delivered, ChPid, MsgProps, - Msg = #basic_message { id = MsgId }}, State) -> - State1 = #state { backing_queue = BQ, backing_queue_state = BQS } = - publish_or_discard(published, ChPid, MsgId, State), - true = BQ:is_empty(BQS), - {AckTag, BQS1} = BQ:publish_delivered(Msg, MsgProps, ChPid, BQS), - {ok, maybe_store_ack(true, MsgId, AckTag, - State1 #state { backing_queue_state = BQS1 })}; -process_instruction({discard, ChPid, MsgId}, State) -> - State1 = #state { backing_queue = BQ, backing_queue_state = BQS } = - publish_or_discard(discarded, ChPid, MsgId, State), - BQS1 = BQ:discard(MsgId, ChPid, BQS), - {ok, State1 #state { backing_queue_state = BQS1 }}; -process_instruction({drop, Length, Dropped, AckRequired}, - State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - QLen = BQ:len(BQS), - ToDrop = case QLen - Length of - N when N > 0 -> N; - _ -> 0 - end, - State1 = lists:foldl( - fun (const, StateN = #state{backing_queue_state = BQSN}) -> - {{MsgId, AckTag}, BQSN1} = BQ:drop(AckRequired, BQSN), - maybe_store_ack( - AckRequired, MsgId, AckTag, - StateN #state { backing_queue_state = BQSN1 }) - end, State, lists:duplicate(ToDrop, const)), - {ok, case AckRequired of - true -> State1; - false -> update_delta(ToDrop - Dropped, State1) - end}; -process_instruction({ack, MsgIds}, - State = #state { backing_queue = BQ, - backing_queue_state = BQS, - msg_id_ack = MA }) -> - {AckTags, MA1} = msg_ids_to_acktags(MsgIds, MA), - {MsgIds1, BQS1} = BQ:ack(AckTags, BQS), - [] = MsgIds1 -- MsgIds, %% ASSERTION - {ok, update_delta(length(MsgIds1) - length(MsgIds), - State #state { msg_id_ack = MA1, - backing_queue_state = BQS1 })}; -process_instruction({requeue, MsgIds}, - State = #state { backing_queue = BQ, - backing_queue_state = BQS, - msg_id_ack = MA }) -> - {AckTags, MA1} = msg_ids_to_acktags(MsgIds, MA), - {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), - {ok, State #state { msg_id_ack = MA1, - backing_queue_state = BQS1 }}; -process_instruction({sender_death, ChPid}, - State = #state { known_senders = KS }) -> - %% The channel will be monitored iff we have received a message - %% from it. In this case we just want to avoid doing work if we - %% never got any messages. - {ok, case pmon:is_monitored(ChPid, KS) of - false -> State; - true -> maybe_forget_sender(ChPid, down_from_gm, State) - end}; -process_instruction({depth, Depth}, - State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - {ok, set_delta(Depth - BQ:depth(BQS), State)}; - -process_instruction({delete_and_terminate, Reason}, - State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - BQ:delete_and_terminate(Reason, BQS), - {stop, State #state { backing_queue_state = undefined }}. - -msg_ids_to_acktags(MsgIds, MA) -> - {AckTags, MA1} = - lists:foldl( - fun (MsgId, {Acc, MAN}) -> - case dict:find(MsgId, MA) of - error -> {Acc, MAN}; - {ok, AckTag} -> {[AckTag | Acc], dict:erase(MsgId, MAN)} - end - end, {[], MA}, MsgIds), - {lists:reverse(AckTags), MA1}. - -maybe_store_ack(false, _MsgId, _AckTag, State) -> - State; -maybe_store_ack(true, MsgId, AckTag, State = #state { msg_id_ack = MA }) -> - State #state { msg_id_ack = dict:store(MsgId, AckTag, MA) }. - -set_delta(0, State = #state { depth_delta = undefined }) -> - ok = record_synchronised(State#state.q), - State #state { depth_delta = 0 }; -set_delta(NewDelta, State = #state { depth_delta = undefined }) -> - true = NewDelta > 0, %% assertion - State #state { depth_delta = NewDelta }; -set_delta(NewDelta, State = #state { depth_delta = Delta }) -> - update_delta(NewDelta - Delta, State). - -update_delta(_DeltaChange, State = #state { depth_delta = undefined }) -> - State; -update_delta( DeltaChange, State = #state { depth_delta = 0 }) -> - 0 = DeltaChange, %% assertion: we cannot become unsync'ed - State; -update_delta( DeltaChange, State = #state { depth_delta = Delta }) -> - true = DeltaChange =< 0, %% assertion: we cannot become 'less' sync'ed - set_delta(Delta + DeltaChange, State #state { depth_delta = undefined }). - -update_ram_duration(BQ, BQS) -> - {RamDuration, BQS1} = BQ:ram_duration(BQS), - DesiredDuration = - rabbit_memory_monitor:report_ram_duration(self(), RamDuration), - BQ:set_ram_duration_target(DesiredDuration, BQS1). - -record_synchronised(#amqqueue { name = QName }) -> - Self = self(), - case rabbit_misc:execute_mnesia_transaction( - fun () -> - case mnesia:read({rabbit_queue, QName}) of - [] -> - ok; - [Q1 = #amqqueue { sync_slave_pids = SSPids }] -> - Q2 = Q1#amqqueue{sync_slave_pids = [Self | SSPids]}, - rabbit_mirror_queue_misc:store_updated_slaves(Q2), - {ok, Q2} - end - end) of - ok -> ok; - {ok, Q} -> rabbit_mirror_queue_misc:maybe_drop_master_after_sync(Q) - end. diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl deleted file mode 100644 index ee1d2105..00000000 --- a/src/rabbit_mirror_queue_sync.erl +++ /dev/null @@ -1,278 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2012 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mirror_queue_sync). - --include("rabbit.hrl"). - --export([master_prepare/4, master_go/7, slave/7]). - --define(SYNC_PROGRESS_INTERVAL, 1000000). - -%% There are three processes around, the master, the syncer and the -%% slave(s). The syncer is an intermediary, linked to the master in -%% order to make sure we do not mess with the master's credit flow or -%% set of monitors. -%% -%% Interactions -%% ------------ -%% -%% '*' indicates repeating messages. All are standard Erlang messages -%% except sync_start which is sent over GM to flush out any other -%% messages that we might have sent that way already. (credit) is the -%% usual credit_flow bump message every so often. -%% -%% Master Syncer Slave(s) -%% sync_mirrors -> || || -%% (from channel) || -- (spawns) --> || || -%% || --------- sync_start (over GM) -------> || -%% || || <--- sync_ready ---- || -%% || || (or) || -%% || || <--- sync_deny ----- || -%% || <--- ready ---- || || -%% || <--- next* ---- || || } -%% || ---- msg* ----> || || } loop -%% || || ---- sync_msg* ----> || } -%% || || <--- (credit)* ----- || } -%% || <--- next ---- || || -%% || ---- done ----> || || -%% || || -- sync_complete --> || -%% || (Dies) || - --ifdef(use_specs). - --type(log_fun() :: fun ((string(), [any()]) -> 'ok')). --type(bq() :: atom()). --type(bqs() :: any()). --type(ack() :: any()). --type(slave_sync_state() :: {[{rabbit_types:msg_id(), ack()}], timer:tref(), - bqs()}). - --spec(master_prepare/4 :: (reference(), rabbit_amqqueue:name(), - log_fun(), [pid()]) -> pid()). --spec(master_go/7 :: (pid(), reference(), log_fun(), - rabbit_mirror_queue_master:stats_fun(), - rabbit_mirror_queue_master:stats_fun(), - bq(), bqs()) -> - {'already_synced', bqs()} | {'ok', bqs()} | - {'shutdown', any(), bqs()} | - {'sync_died', any(), bqs()}). --spec(slave/7 :: (non_neg_integer(), reference(), timer:tref(), pid(), - bq(), bqs(), fun((bq(), bqs()) -> {timer:tref(), bqs()})) -> - 'denied' | - {'ok' | 'failed', slave_sync_state()} | - {'stop', any(), slave_sync_state()}). - --endif. - -%% --------------------------------------------------------------------------- -%% Master - -master_prepare(Ref, QName, Log, SPids) -> - MPid = self(), - spawn_link(fun () -> - ?store_proc_name(QName), - syncer(Ref, Log, MPid, SPids) - end). - -master_go(Syncer, Ref, Log, HandleInfo, EmitStats, BQ, BQS) -> - Args = {Syncer, Ref, Log, HandleInfo, EmitStats, rabbit_misc:get_parent()}, - receive - {'EXIT', Syncer, normal} -> {already_synced, BQS}; - {'EXIT', Syncer, Reason} -> {sync_died, Reason, BQS}; - {ready, Syncer} -> EmitStats({syncing, 0}), - master_go0(Args, BQ, BQS) - end. - -master_go0(Args, BQ, BQS) -> - case BQ:fold(fun (Msg, MsgProps, Unacked, Acc) -> - master_send(Msg, MsgProps, Unacked, Args, Acc) - end, {0, erlang:now()}, BQS) of - {{shutdown, Reason}, BQS1} -> {shutdown, Reason, BQS1}; - {{sync_died, Reason}, BQS1} -> {sync_died, Reason, BQS1}; - {_, BQS1} -> master_done(Args, BQS1) - end. - -master_send(Msg, MsgProps, Unacked, - {Syncer, Ref, Log, HandleInfo, EmitStats, Parent}, {I, Last}) -> - T = case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of - true -> EmitStats({syncing, I}), - Log("~p messages", [I]), - erlang:now(); - false -> Last - end, - HandleInfo({syncing, I}), - receive - {'$gen_cast', {set_maximum_since_use, Age}} -> - ok = file_handle_cache:set_maximum_since_use(Age) - after 0 -> - ok - end, - receive - {'$gen_call', From, - cancel_sync_mirrors} -> stop_syncer(Syncer, {cancel, Ref}), - gen_server2:reply(From, ok), - {stop, cancelled}; - {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps, Unacked}, - {cont, {I + 1, T}}; - {'EXIT', Parent, Reason} -> {stop, {shutdown, Reason}}; - {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}} - end. - -master_done({Syncer, Ref, _Log, _HandleInfo, _EmitStats, Parent}, BQS) -> - receive - {next, Ref} -> stop_syncer(Syncer, {done, Ref}), - {ok, BQS}; - {'EXIT', Parent, Reason} -> {shutdown, Reason, BQS}; - {'EXIT', Syncer, Reason} -> {sync_died, Reason, BQS} - end. - -stop_syncer(Syncer, Msg) -> - unlink(Syncer), - Syncer ! Msg, - receive {'EXIT', Syncer, _} -> ok - after 0 -> ok - end. - -%% Master -%% --------------------------------------------------------------------------- -%% Syncer - -syncer(Ref, Log, MPid, SPids) -> - [erlang:monitor(process, SPid) || SPid <- SPids], - %% We wait for a reply from the slaves so that we know they are in - %% a receive block and will thus receive messages we send to them - %% *without* those messages ending up in their gen_server2 pqueue. - case await_slaves(Ref, SPids) of - [] -> Log("all slaves already synced", []); - SPids1 -> MPid ! {ready, self()}, - Log("mirrors ~p to sync", [[node(SPid) || SPid <- SPids1]]), - syncer_loop(Ref, MPid, SPids1) - end. - -await_slaves(Ref, SPids) -> - [SPid || SPid <- SPids, - rabbit_mnesia:on_running_node(SPid) andalso %% [0] - receive - {sync_ready, Ref, SPid} -> true; - {sync_deny, Ref, SPid} -> false; - {'DOWN', _, process, SPid, _} -> false - end]. -%% [0] This check is in case there's been a partition which has then -%% healed in between the master retrieving the slave pids from Mnesia -%% and sending 'sync_start' over GM. If so there might be slaves on the -%% other side of the partition which we can monitor (since they have -%% rejoined the distributed system with us) but which did not get the -%% 'sync_start' and so will not reply. We need to act as though they are -%% down. - -syncer_loop(Ref, MPid, SPids) -> - MPid ! {next, Ref}, - receive - {msg, Ref, Msg, MsgProps, Unacked} -> - SPids1 = wait_for_credit(SPids), - [begin - credit_flow:send(SPid), - SPid ! {sync_msg, Ref, Msg, MsgProps, Unacked} - end || SPid <- SPids1], - syncer_loop(Ref, MPid, SPids1); - {cancel, Ref} -> - %% We don't tell the slaves we will die - so when we do - %% they interpret that as a failure, which is what we - %% want. - ok; - {done, Ref} -> - [SPid ! {sync_complete, Ref} || SPid <- SPids] - end. - -wait_for_credit(SPids) -> - case credit_flow:blocked() of - true -> receive - {bump_credit, Msg} -> - credit_flow:handle_bump_msg(Msg), - wait_for_credit(SPids); - {'DOWN', _, process, SPid, _} -> - credit_flow:peer_down(SPid), - wait_for_credit(lists:delete(SPid, SPids)) - end; - false -> SPids - end. - -%% Syncer -%% --------------------------------------------------------------------------- -%% Slave - -slave(0, Ref, _TRef, Syncer, _BQ, _BQS, _UpdateRamDuration) -> - Syncer ! {sync_deny, Ref, self()}, - denied; - -slave(_DD, Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> - MRef = erlang:monitor(process, Syncer), - Syncer ! {sync_ready, Ref, self()}, - {_MsgCount, BQS1} = BQ:purge(BQ:purge_acks(BQS)), - slave_sync_loop({Ref, MRef, Syncer, BQ, UpdateRamDuration, - rabbit_misc:get_parent()}, {[], TRef, BQS1}). - -slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, - State = {MA, TRef, BQS}) -> - receive - {'DOWN', MRef, process, Syncer, _Reason} -> - %% If the master dies half way we are not in the usual - %% half-synced state (with messages nearer the tail of the - %% queue); instead we have ones nearer the head. If we then - %% sync with a newly promoted master, or even just receive - %% messages from it, we have a hole in the middle. So the - %% only thing to do here is purge. - {_MsgCount, BQS1} = BQ:purge(BQ:purge_acks(BQS)), - credit_flow:peer_down(Syncer), - {failed, {[], TRef, BQS1}}; - {bump_credit, Msg} -> - credit_flow:handle_bump_msg(Msg), - slave_sync_loop(Args, State); - {sync_complete, Ref} -> - erlang:demonitor(MRef, [flush]), - credit_flow:peer_down(Syncer), - {ok, State}; - {'$gen_cast', {set_maximum_since_use, Age}} -> - ok = file_handle_cache:set_maximum_since_use(Age), - slave_sync_loop(Args, State); - {'$gen_cast', {set_ram_duration_target, Duration}} -> - BQS1 = BQ:set_ram_duration_target(Duration, BQS), - slave_sync_loop(Args, {MA, TRef, BQS1}); - {'$gen_cast', {run_backing_queue, Mod, Fun}} -> - BQS1 = BQ:invoke(Mod, Fun, BQS), - slave_sync_loop(Args, {MA, TRef, BQS1}); - update_ram_duration -> - {TRef1, BQS1} = UpdateRamDuration(BQ, BQS), - slave_sync_loop(Args, {MA, TRef1, BQS1}); - {sync_msg, Ref, Msg, Props, Unacked} -> - credit_flow:ack(Syncer), - Props1 = Props#message_properties{needs_confirming = false}, - {MA1, BQS1} = - case Unacked of - false -> {MA, BQ:publish(Msg, Props1, true, none, BQS)}; - true -> {AckTag, BQS2} = BQ:publish_delivered( - Msg, Props1, none, BQS), - {[{Msg#basic_message.id, AckTag} | MA], BQS2} - end, - slave_sync_loop(Args, {MA1, TRef, BQS1}); - {'EXIT', Parent, Reason} -> - {stop, Reason, State}; - %% If the master throws an exception - {'$gen_cast', {gm, {delete_and_terminate, Reason}}} -> - BQ:delete_and_terminate(Reason, BQS), - {stop, Reason, {[], TRef, undefined}} - end. diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl deleted file mode 100644 index 3e2c88ee..00000000 --- a/src/rabbit_misc.erl +++ /dev/null @@ -1,1099 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_misc). --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - --export([method_record_type/1, polite_pause/0, polite_pause/1]). --export([die/1, frame_error/2, amqp_error/4, quit/1, - protocol_error/3, protocol_error/4, protocol_error/1]). --export([not_found/1, absent/2]). --export([type_class/1, assert_args_equivalence/4, assert_field_equivalence/4]). --export([dirty_read/1]). --export([table_lookup/2, set_table_value/4]). --export([r/3, r/2, r_arg/4, rs/1]). --export([enable_cover/0, report_cover/0]). --export([enable_cover/1, report_cover/1]). --export([start_cover/1]). --export([confirm_to_sender/2]). --export([throw_on_error/2, with_exit_handler/2, is_abnormal_exit/1, - filter_exit_map/2]). --export([with_user/2, with_user_and_vhost/3]). --export([execute_mnesia_transaction/1]). --export([execute_mnesia_transaction/2]). --export([execute_mnesia_tx_with_tail/1]). --export([ensure_ok/2]). --export([tcp_name/3, format_inet_error/1]). --export([upmap/2, map_in_order/2]). --export([table_filter/3]). --export([dirty_read_all/1, dirty_foreach_key/2, dirty_dump_log/1]). --export([format/2, format_many/1, format_stderr/2]). --export([unfold/2, ceil/1, queue_fold/3]). --export([sort_field_table/1]). --export([pid_to_string/1, string_to_pid/1, node_to_fake_pid/1]). --export([version_compare/2, version_compare/3]). --export([version_minor_equivalent/2]). --export([dict_cons/3, orddict_cons/3, gb_trees_cons/3]). --export([gb_trees_fold/3, gb_trees_foreach/2]). --export([all_module_attributes/1, build_acyclic_graph/3]). --export([now_ms/0]). --export([const/1]). --export([ntoa/1, ntoab/1]). --export([is_process_alive/1]). --export([pget/2, pget/3, pget_or_die/2, pset/3]). --export([format_message_queue/2]). --export([append_rpc_all_nodes/4]). --export([os_cmd/1]). --export([gb_sets_difference/2]). --export([version/0, otp_release/0, which_applications/0]). --export([sequence_error/1]). --export([json_encode/1, json_decode/1, json_to_term/1, term_to_json/1]). --export([check_expiry/1]). --export([base64url/1]). --export([interval_operation/4]). --export([ensure_timer/4, stop_timer/2, send_after/3, cancel_timer/1]). --export([get_parent/0]). --export([store_proc_name/1, store_proc_name/2]). --export([moving_average/4]). --export([now_to_ms/1]). - -%% Horrible macro to use in guards --define(IS_BENIGN_EXIT(R), - R =:= noproc; R =:= noconnection; R =:= nodedown; R =:= normal; - R =:= shutdown). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([resource_name/0, thunk/1, channel_or_connection_exit/0]). - --type(ok_or_error() :: rabbit_types:ok_or_error(any())). --type(thunk(T) :: fun(() -> T)). --type(resource_name() :: binary()). --type(channel_or_connection_exit() - :: rabbit_types:channel_exit() | rabbit_types:connection_exit()). --type(digraph_label() :: term()). --type(graph_vertex_fun() :: - fun (({atom(), [term()]}) -> [{digraph:vertex(), digraph_label()}])). --type(graph_edge_fun() :: - fun (({atom(), [term()]}) -> [{digraph:vertex(), digraph:vertex()}])). --type(tref() :: {'erlang', reference()} | {timer, timer:tref()}). - --spec(method_record_type/1 :: (rabbit_framing:amqp_method_record()) - -> rabbit_framing:amqp_method_name()). --spec(polite_pause/0 :: () -> 'done'). --spec(polite_pause/1 :: (non_neg_integer()) -> 'done'). --spec(die/1 :: - (rabbit_framing:amqp_exception()) -> channel_or_connection_exit()). - --spec(quit/1 :: (integer()) -> no_return()). - --spec(frame_error/2 :: (rabbit_framing:amqp_method_name(), binary()) - -> rabbit_types:connection_exit()). --spec(amqp_error/4 :: - (rabbit_framing:amqp_exception(), string(), [any()], - rabbit_framing:amqp_method_name()) - -> rabbit_types:amqp_error()). --spec(protocol_error/3 :: (rabbit_framing:amqp_exception(), string(), [any()]) - -> channel_or_connection_exit()). --spec(protocol_error/4 :: - (rabbit_framing:amqp_exception(), string(), [any()], - rabbit_framing:amqp_method_name()) -> channel_or_connection_exit()). --spec(protocol_error/1 :: - (rabbit_types:amqp_error()) -> channel_or_connection_exit()). --spec(not_found/1 :: (rabbit_types:r(atom())) -> rabbit_types:channel_exit()). --spec(absent/2 :: (rabbit_types:amqqueue(), rabbit_amqqueue:absent_reason()) - -> rabbit_types:channel_exit()). --spec(type_class/1 :: (rabbit_framing:amqp_field_type()) -> atom()). --spec(assert_args_equivalence/4 :: (rabbit_framing:amqp_table(), - rabbit_framing:amqp_table(), - rabbit_types:r(any()), [binary()]) -> - 'ok' | rabbit_types:connection_exit()). --spec(assert_field_equivalence/4 :: - (any(), any(), rabbit_types:r(any()), atom() | binary()) -> - 'ok' | rabbit_types:connection_exit()). --spec(equivalence_fail/4 :: - (any(), any(), rabbit_types:r(any()), atom() | binary()) -> - rabbit_types:connection_exit()). --spec(dirty_read/1 :: - ({atom(), any()}) -> rabbit_types:ok_or_error2(any(), 'not_found')). --spec(table_lookup/2 :: - (rabbit_framing:amqp_table(), binary()) - -> 'undefined' | {rabbit_framing:amqp_field_type(), any()}). --spec(set_table_value/4 :: - (rabbit_framing:amqp_table(), binary(), - rabbit_framing:amqp_field_type(), rabbit_framing:amqp_value()) - -> rabbit_framing:amqp_table()). --spec(r/2 :: (rabbit_types:vhost(), K) - -> rabbit_types:r3(rabbit_types:vhost(), K, '_') - when is_subtype(K, atom())). --spec(r/3 :: - (rabbit_types:vhost() | rabbit_types:r(atom()), K, resource_name()) - -> rabbit_types:r3(rabbit_types:vhost(), K, resource_name()) - when is_subtype(K, atom())). --spec(r_arg/4 :: - (rabbit_types:vhost() | rabbit_types:r(atom()), K, - rabbit_framing:amqp_table(), binary()) -> - undefined | - rabbit_types:error( - {invalid_type, rabbit_framing:amqp_field_type()}) | - rabbit_types:r(K) when is_subtype(K, atom())). --spec(rs/1 :: (rabbit_types:r(atom())) -> string()). --spec(enable_cover/0 :: () -> ok_or_error()). --spec(start_cover/1 :: ([{string(), string()} | string()]) -> 'ok'). --spec(report_cover/0 :: () -> 'ok'). --spec(enable_cover/1 :: ([file:filename() | atom()]) -> ok_or_error()). --spec(report_cover/1 :: ([file:filename() | atom()]) -> 'ok'). --spec(throw_on_error/2 :: - (atom(), thunk(rabbit_types:error(any()) | {ok, A} | A)) -> A). --spec(with_exit_handler/2 :: (thunk(A), thunk(A)) -> A). --spec(is_abnormal_exit/1 :: (any()) -> boolean()). --spec(filter_exit_map/2 :: (fun ((A) -> B), [A]) -> [B]). --spec(with_user/2 :: (rabbit_types:username(), thunk(A)) -> A). --spec(with_user_and_vhost/3 :: - (rabbit_types:username(), rabbit_types:vhost(), thunk(A)) - -> A). --spec(execute_mnesia_transaction/1 :: (thunk(A)) -> A). --spec(execute_mnesia_transaction/2 :: - (thunk(A), fun ((A, boolean()) -> B)) -> B). --spec(execute_mnesia_tx_with_tail/1 :: - (thunk(fun ((boolean()) -> B))) -> B | (fun ((boolean()) -> B))). --spec(ensure_ok/2 :: (ok_or_error(), atom()) -> 'ok'). --spec(tcp_name/3 :: - (atom(), inet:ip_address(), rabbit_networking:ip_port()) - -> atom()). --spec(format_inet_error/1 :: (atom()) -> string()). --spec(upmap/2 :: (fun ((A) -> B), [A]) -> [B]). --spec(map_in_order/2 :: (fun ((A) -> B), [A]) -> [B]). --spec(table_filter/3:: (fun ((A) -> boolean()), fun ((A, boolean()) -> 'ok'), - atom()) -> [A]). --spec(dirty_read_all/1 :: (atom()) -> [any()]). --spec(dirty_foreach_key/2 :: (fun ((any()) -> any()), atom()) - -> 'ok' | 'aborted'). --spec(dirty_dump_log/1 :: (file:filename()) -> ok_or_error()). --spec(format/2 :: (string(), [any()]) -> string()). --spec(format_many/1 :: ([{string(), [any()]}]) -> string()). --spec(format_stderr/2 :: (string(), [any()]) -> 'ok'). --spec(unfold/2 :: (fun ((A) -> ({'true', B, A} | 'false')), A) -> {[B], A}). --spec(ceil/1 :: (number()) -> integer()). --spec(queue_fold/3 :: (fun ((any(), B) -> B), B, queue:queue()) -> B). --spec(sort_field_table/1 :: - (rabbit_framing:amqp_table()) -> rabbit_framing:amqp_table()). --spec(pid_to_string/1 :: (pid()) -> string()). --spec(string_to_pid/1 :: (string()) -> pid()). --spec(node_to_fake_pid/1 :: (atom()) -> pid()). --spec(version_compare/2 :: (string(), string()) -> 'lt' | 'eq' | 'gt'). --spec(version_compare/3 :: - (string(), string(), ('lt' | 'lte' | 'eq' | 'gte' | 'gt')) - -> boolean()). --spec(version_minor_equivalent/2 :: (string(), string()) -> boolean()). --spec(dict_cons/3 :: (any(), any(), dict:dict()) -> dict:dict()). --spec(orddict_cons/3 :: (any(), any(), orddict:orddict()) -> orddict:orddict()). --spec(gb_trees_cons/3 :: (any(), any(), gb_trees:tree()) -> gb_trees:tree()). --spec(gb_trees_fold/3 :: (fun ((any(), any(), A) -> A), A, gb_trees:tree()) - -> A). --spec(gb_trees_foreach/2 :: - (fun ((any(), any()) -> any()), gb_trees:tree()) -> 'ok'). --spec(all_module_attributes/1 :: - (atom()) -> [{atom(), atom(), [term()]}]). --spec(build_acyclic_graph/3 :: - (graph_vertex_fun(), graph_edge_fun(), [{atom(), [term()]}]) - -> rabbit_types:ok_or_error2(digraph:digraph(), - {'vertex', 'duplicate', digraph:vertex()} | - {'edge', ({bad_vertex, digraph:vertex()} | - {bad_edge, [digraph:vertex()]}), - digraph:vertex(), digraph:vertex()})). --spec(now_ms/0 :: () -> non_neg_integer()). --spec(const/1 :: (A) -> thunk(A)). --spec(ntoa/1 :: (inet:ip_address()) -> string()). --spec(ntoab/1 :: (inet:ip_address()) -> string()). --spec(is_process_alive/1 :: (pid()) -> boolean()). --spec(pget/2 :: (term(), [term()]) -> term()). --spec(pget/3 :: (term(), [term()], term()) -> term()). --spec(pget_or_die/2 :: (term(), [term()]) -> term() | no_return()). --spec(pset/3 :: (term(), term(), [term()]) -> term()). --spec(format_message_queue/2 :: (any(), priority_queue:q()) -> term()). --spec(append_rpc_all_nodes/4 :: ([node()], atom(), atom(), [any()]) -> [any()]). --spec(os_cmd/1 :: (string()) -> string()). --spec(gb_sets_difference/2 :: (gb_sets:set(), gb_sets:set()) -> gb_sets:set()). --spec(version/0 :: () -> string()). --spec(otp_release/0 :: () -> string()). --spec(which_applications/0 :: () -> [{atom(), string(), string()}]). --spec(sequence_error/1 :: ([({'error', any()} | any())]) - -> {'error', any()} | any()). --spec(json_encode/1 :: (any()) -> {'ok', string()} | {'error', any()}). --spec(json_decode/1 :: (string()) -> {'ok', any()} | 'error'). --spec(json_to_term/1 :: (any()) -> any()). --spec(term_to_json/1 :: (any()) -> any()). --spec(check_expiry/1 :: (integer()) -> rabbit_types:ok_or_error(any())). --spec(base64url/1 :: (binary()) -> string()). --spec(interval_operation/4 :: - ({atom(), atom(), any()}, float(), non_neg_integer(), non_neg_integer()) - -> {any(), non_neg_integer()}). --spec(ensure_timer/4 :: (A, non_neg_integer(), non_neg_integer(), any()) -> A). --spec(stop_timer/2 :: (A, non_neg_integer()) -> A). --spec(send_after/3 :: (non_neg_integer(), pid(), any()) -> tref()). --spec(cancel_timer/1 :: (tref()) -> 'ok'). --spec(get_parent/0 :: () -> pid()). --spec(store_proc_name/2 :: (atom(), rabbit_types:proc_name()) -> ok). --spec(store_proc_name/1 :: (rabbit_types:proc_type_and_name()) -> ok). --spec(moving_average/4 :: (float(), float(), float(), float() | 'undefined') - -> float()). --spec(now_to_ms/1 :: ({non_neg_integer(), - non_neg_integer(), - non_neg_integer()}) -> pos_integer()). --endif. - -%%---------------------------------------------------------------------------- - -method_record_type(Record) -> - element(1, Record). - -polite_pause() -> - polite_pause(3000). - -polite_pause(N) -> - receive - after N -> done - end. - -die(Error) -> - protocol_error(Error, "~w", [Error]). - -frame_error(MethodName, BinaryFields) -> - protocol_error(frame_error, "cannot decode ~w", [BinaryFields], MethodName). - -amqp_error(Name, ExplanationFormat, Params, Method) -> - Explanation = format(ExplanationFormat, Params), - #amqp_error{name = Name, explanation = Explanation, method = Method}. - -protocol_error(Name, ExplanationFormat, Params) -> - protocol_error(Name, ExplanationFormat, Params, none). - -protocol_error(Name, ExplanationFormat, Params, Method) -> - protocol_error(amqp_error(Name, ExplanationFormat, Params, Method)). - -protocol_error(#amqp_error{} = Error) -> - exit(Error). - -not_found(R) -> protocol_error(not_found, "no ~s", [rs(R)]). - -absent(#amqqueue{name = QueueName, pid = QPid, durable = true}, nodedown) -> - %% The assertion of durability is mainly there because we mention - %% durability in the error message. That way we will hopefully - %% notice if at some future point our logic changes s.t. we get - %% here with non-durable queues. - protocol_error(not_found, - "home node '~s' of durable ~s is down or inaccessible", - [node(QPid), rs(QueueName)]); - -absent(#amqqueue{name = QueueName}, crashed) -> - protocol_error(not_found, - "~s has crashed and failed to restart", [rs(QueueName)]). - -type_class(byte) -> int; -type_class(short) -> int; -type_class(signedint) -> int; -type_class(long) -> int; -type_class(decimal) -> int; -type_class(float) -> float; -type_class(double) -> float; -type_class(Other) -> Other. - -assert_args_equivalence(Orig, New, Name, Keys) -> - [assert_args_equivalence1(Orig, New, Name, Key) || Key <- Keys], - ok. - -assert_args_equivalence1(Orig, New, Name, Key) -> - {Orig1, New1} = {table_lookup(Orig, Key), table_lookup(New, Key)}, - case {Orig1, New1} of - {Same, Same} -> - ok; - {{OrigType, OrigVal}, {NewType, NewVal}} -> - case type_class(OrigType) == type_class(NewType) andalso - OrigVal == NewVal of - true -> ok; - false -> assert_field_equivalence(OrigVal, NewVal, Name, Key) - end; - {OrigTypeVal, NewTypeVal} -> - assert_field_equivalence(OrigTypeVal, NewTypeVal, Name, Key) - end. - -assert_field_equivalence(_Orig, _Orig, _Name, _Key) -> - ok; -assert_field_equivalence(Orig, New, Name, Key) -> - equivalence_fail(Orig, New, Name, Key). - -equivalence_fail(Orig, New, Name, Key) -> - protocol_error(precondition_failed, "inequivalent arg '~s' " - "for ~s: received ~s but current is ~s", - [Key, rs(Name), val(New), val(Orig)]). - -val(undefined) -> - "none"; -val({Type, Value}) -> - ValFmt = case is_binary(Value) of - true -> "~s"; - false -> "~p" - end, - format("the value '" ++ ValFmt ++ "' of type '~s'", [Value, Type]); -val(Value) -> - format(case is_binary(Value) of - true -> "'~s'"; - false -> "'~p'" - end, [Value]). - -%% Normally we'd call mnesia:dirty_read/1 here, but that is quite -%% expensive due to general mnesia overheads (figuring out table types -%% and locations, etc). We get away with bypassing these because we -%% know that the tables we are looking at here -%% - are not the schema table -%% - have a local ram copy -%% - do not have any indices -dirty_read({Table, Key}) -> - case ets:lookup(Table, Key) of - [Result] -> {ok, Result}; - [] -> {error, not_found} - end. - -table_lookup(Table, Key) -> - case lists:keysearch(Key, 1, Table) of - {value, {_, TypeBin, ValueBin}} -> {TypeBin, ValueBin}; - false -> undefined - end. - -set_table_value(Table, Key, Type, Value) -> - sort_field_table( - lists:keystore(Key, 1, Table, {Key, Type, Value})). - -r(#resource{virtual_host = VHostPath}, Kind, Name) -> - #resource{virtual_host = VHostPath, kind = Kind, name = Name}; -r(VHostPath, Kind, Name) -> - #resource{virtual_host = VHostPath, kind = Kind, name = Name}. - -r(VHostPath, Kind) -> - #resource{virtual_host = VHostPath, kind = Kind, name = '_'}. - -r_arg(#resource{virtual_host = VHostPath}, Kind, Table, Key) -> - r_arg(VHostPath, Kind, Table, Key); -r_arg(VHostPath, Kind, Table, Key) -> - case table_lookup(Table, Key) of - {longstr, NameBin} -> r(VHostPath, Kind, NameBin); - undefined -> undefined; - {Type, _} -> {error, {invalid_type, Type}} - end. - -rs(#resource{virtual_host = VHostPath, kind = Kind, name = Name}) -> - format("~s '~s' in vhost '~s'", [Kind, Name, VHostPath]). - -enable_cover() -> enable_cover(["."]). - -enable_cover(Dirs) -> - lists:foldl(fun (Dir, ok) -> - case cover:compile_beam_directory( - filename:join(lists:concat([Dir]),"ebin")) of - {error, _} = Err -> Err; - _ -> ok - end; - (_Dir, Err) -> - Err - end, ok, Dirs). - -start_cover(NodesS) -> - {ok, _} = cover:start([rabbit_nodes:make(N) || N <- NodesS]), - ok. - -report_cover() -> report_cover(["."]). - -report_cover(Dirs) -> [report_cover1(lists:concat([Dir])) || Dir <- Dirs], ok. - -report_cover1(Root) -> - Dir = filename:join(Root, "cover"), - ok = filelib:ensure_dir(filename:join(Dir, "junk")), - lists:foreach(fun (F) -> file:delete(F) end, - filelib:wildcard(filename:join(Dir, "*.html"))), - {ok, SummaryFile} = file:open(filename:join(Dir, "summary.txt"), [write]), - {CT, NCT} = - lists:foldl( - fun (M,{CovTot, NotCovTot}) -> - {ok, {M, {Cov, NotCov}}} = cover:analyze(M, module), - ok = report_coverage_percentage(SummaryFile, - Cov, NotCov, M), - {ok,_} = cover:analyze_to_file( - M, - filename:join(Dir, atom_to_list(M) ++ ".html"), - [html]), - {CovTot+Cov, NotCovTot+NotCov} - end, - {0, 0}, - lists:sort(cover:modules())), - ok = report_coverage_percentage(SummaryFile, CT, NCT, 'TOTAL'), - ok = file:close(SummaryFile), - ok. - -report_coverage_percentage(File, Cov, NotCov, Mod) -> - io:fwrite(File, "~6.2f ~p~n", - [if - Cov+NotCov > 0 -> 100.0*Cov/(Cov+NotCov); - true -> 100.0 - end, - Mod]). - -confirm_to_sender(Pid, MsgSeqNos) -> - gen_server2:cast(Pid, {confirm, MsgSeqNos, self()}). - -%% @doc Halts the emulator returning the given status code to the os. -%% On Windows this function will block indefinitely so as to give the io -%% subsystem time to flush stdout completely. -quit(Status) -> - case os:type() of - {unix, _} -> halt(Status); - {win32, _} -> init:stop(Status), - receive - after infinity -> ok - end - end. - -throw_on_error(E, Thunk) -> - case Thunk() of - {error, Reason} -> throw({E, Reason}); - {ok, Res} -> Res; - Res -> Res - end. - -with_exit_handler(Handler, Thunk) -> - try - Thunk() - catch - exit:{R, _} when ?IS_BENIGN_EXIT(R) -> Handler(); - exit:{{R, _}, _} when ?IS_BENIGN_EXIT(R) -> Handler() - end. - -is_abnormal_exit(R) when ?IS_BENIGN_EXIT(R) -> false; -is_abnormal_exit({R, _}) when ?IS_BENIGN_EXIT(R) -> false; -is_abnormal_exit(_) -> true. - -filter_exit_map(F, L) -> - Ref = make_ref(), - lists:filter(fun (R) -> R =/= Ref end, - [with_exit_handler( - fun () -> Ref end, - fun () -> F(I) end) || I <- L]). - - -with_user(Username, Thunk) -> - fun () -> - case mnesia:read({rabbit_user, Username}) of - [] -> - mnesia:abort({no_such_user, Username}); - [_U] -> - Thunk() - end - end. - -with_user_and_vhost(Username, VHostPath, Thunk) -> - with_user(Username, rabbit_vhost:with(VHostPath, Thunk)). - -execute_mnesia_transaction(TxFun) -> - %% Making this a sync_transaction allows us to use dirty_read - %% elsewhere and get a consistent result even when that read - %% executes on a different node. - case worker_pool:submit( - fun () -> - case mnesia:is_transaction() of - false -> DiskLogBefore = mnesia_dumper:get_log_writes(), - Res = mnesia:sync_transaction(TxFun), - DiskLogAfter = mnesia_dumper:get_log_writes(), - case DiskLogAfter == DiskLogBefore of - true -> Res; - false -> {sync, Res} - end; - true -> mnesia:sync_transaction(TxFun) - end - end, single) of - {sync, {atomic, Result}} -> mnesia_sync:sync(), Result; - {sync, {aborted, Reason}} -> throw({error, Reason}); - {atomic, Result} -> Result; - {aborted, Reason} -> throw({error, Reason}) - end. - -%% Like execute_mnesia_transaction/1 with additional Pre- and Post- -%% commit function -execute_mnesia_transaction(TxFun, PrePostCommitFun) -> - case mnesia:is_transaction() of - true -> throw(unexpected_transaction); - false -> ok - end, - PrePostCommitFun(execute_mnesia_transaction( - fun () -> - Result = TxFun(), - PrePostCommitFun(Result, true), - Result - end), false). - -%% Like execute_mnesia_transaction/2, but TxFun is expected to return a -%% TailFun which gets called (only) immediately after the tx commit -execute_mnesia_tx_with_tail(TxFun) -> - case mnesia:is_transaction() of - true -> execute_mnesia_transaction(TxFun); - false -> TailFun = execute_mnesia_transaction(TxFun), - TailFun() - end. - -ensure_ok(ok, _) -> ok; -ensure_ok({error, Reason}, ErrorTag) -> throw({error, {ErrorTag, Reason}}). - -tcp_name(Prefix, IPAddress, Port) - when is_atom(Prefix) andalso is_number(Port) -> - list_to_atom( - format("~w_~s:~w", [Prefix, inet_parse:ntoa(IPAddress), Port])). - -format_inet_error(E) -> format("~w (~s)", [E, format_inet_error0(E)]). - -format_inet_error0(address) -> "cannot connect to host/port"; -format_inet_error0(timeout) -> "timed out"; -format_inet_error0(Error) -> inet:format_error(Error). - -%% This is a modified version of Luke Gorrie's pmap - -%% http://lukego.livejournal.com/6753.html - that doesn't care about -%% the order in which results are received. -%% -%% WARNING: This is is deliberately lightweight rather than robust -- if F -%% throws, upmap will hang forever, so make sure F doesn't throw! -upmap(F, L) -> - Parent = self(), - Ref = make_ref(), - [receive {Ref, Result} -> Result end - || _ <- [spawn(fun () -> Parent ! {Ref, F(X)} end) || X <- L]]. - -map_in_order(F, L) -> - lists:reverse( - lists:foldl(fun (E, Acc) -> [F(E) | Acc] end, [], L)). - -%% Apply a pre-post-commit function to all entries in a table that -%% satisfy a predicate, and return those entries. -%% -%% We ignore entries that have been modified or removed. -table_filter(Pred, PrePostCommitFun, TableName) -> - lists:foldl( - fun (E, Acc) -> - case execute_mnesia_transaction( - fun () -> mnesia:match_object(TableName, E, read) =/= [] - andalso Pred(E) end, - fun (false, _Tx) -> false; - (true, Tx) -> PrePostCommitFun(E, Tx), true - end) of - false -> Acc; - true -> [E | Acc] - end - end, [], dirty_read_all(TableName)). - -dirty_read_all(TableName) -> - mnesia:dirty_select(TableName, [{'$1',[],['$1']}]). - -dirty_foreach_key(F, TableName) -> - dirty_foreach_key1(F, TableName, mnesia:dirty_first(TableName)). - -dirty_foreach_key1(_F, _TableName, '$end_of_table') -> - ok; -dirty_foreach_key1(F, TableName, K) -> - case catch mnesia:dirty_next(TableName, K) of - {'EXIT', _} -> - aborted; - NextKey -> - F(K), - dirty_foreach_key1(F, TableName, NextKey) - end. - -dirty_dump_log(FileName) -> - {ok, LH} = disk_log:open([{name, dirty_dump_log}, - {mode, read_only}, - {file, FileName}]), - dirty_dump_log1(LH, disk_log:chunk(LH, start)), - disk_log:close(LH). - -dirty_dump_log1(_LH, eof) -> - io:format("Done.~n"); -dirty_dump_log1(LH, {K, Terms}) -> - io:format("Chunk: ~p~n", [Terms]), - dirty_dump_log1(LH, disk_log:chunk(LH, K)); -dirty_dump_log1(LH, {K, Terms, BadBytes}) -> - io:format("Bad Chunk, ~p: ~p~n", [BadBytes, Terms]), - dirty_dump_log1(LH, disk_log:chunk(LH, K)). - -format(Fmt, Args) -> lists:flatten(io_lib:format(Fmt, Args)). - -format_many(List) -> - lists:flatten([io_lib:format(F ++ "~n", A) || {F, A} <- List]). - -format_stderr(Fmt, Args) -> - case os:type() of - {unix, _} -> - Port = open_port({fd, 0, 2}, [out]), - port_command(Port, io_lib:format(Fmt, Args)), - port_close(Port); - {win32, _} -> - %% stderr on Windows is buffered and I can't figure out a - %% way to trigger a fflush(stderr) in Erlang. So rather - %% than risk losing output we write to stdout instead, - %% which appears to be unbuffered. - io:format(Fmt, Args) - end, - ok. - -unfold(Fun, Init) -> - unfold(Fun, [], Init). - -unfold(Fun, Acc, Init) -> - case Fun(Init) of - {true, E, I} -> unfold(Fun, [E|Acc], I); - false -> {Acc, Init} - end. - -ceil(N) -> - T = trunc(N), - case N == T of - true -> T; - false -> 1 + T - end. - -queue_fold(Fun, Init, Q) -> - case queue:out(Q) of - {empty, _Q} -> Init; - {{value, V}, Q1} -> queue_fold(Fun, Fun(V, Init), Q1) - end. - -%% Sorts a list of AMQP table fields as per the AMQP spec -sort_field_table(Arguments) -> - lists:keysort(1, Arguments). - -%% This provides a string representation of a pid that is the same -%% regardless of what node we are running on. The representation also -%% permits easy identification of the pid's node. -pid_to_string(Pid) when is_pid(Pid) -> - %% see http://erlang.org/doc/apps/erts/erl_ext_dist.html (8.10 and - %% 8.7) - <<131,103,100,NodeLen:16,NodeBin:NodeLen/binary,Id:32,Ser:32,Cre:8>> - = term_to_binary(Pid), - Node = binary_to_term(<<131,100,NodeLen:16,NodeBin:NodeLen/binary>>), - format("<~s.~B.~B.~B>", [Node, Cre, Id, Ser]). - -%% inverse of above -string_to_pid(Str) -> - Err = {error, {invalid_pid_syntax, Str}}, - %% The \ before the trailing $ is only there to keep emacs - %% font-lock from getting confused. - case re:run(Str, "^<(.*)\\.(\\d+)\\.(\\d+)\\.(\\d+)>\$", - [{capture,all_but_first,list}]) of - {match, [NodeStr, CreStr, IdStr, SerStr]} -> - <<131,NodeEnc/binary>> = term_to_binary(list_to_atom(NodeStr)), - [Cre, Id, Ser] = lists:map(fun list_to_integer/1, - [CreStr, IdStr, SerStr]), - binary_to_term(<<131,103,NodeEnc/binary,Id:32,Ser:32,Cre:8>>); - nomatch -> - throw(Err) - end. - -%% node(node_to_fake_pid(Node)) =:= Node. -node_to_fake_pid(Node) -> - string_to_pid(format("<~s.0.0.0>", [Node])). - -version_compare(A, B, lte) -> - case version_compare(A, B) of - eq -> true; - lt -> true; - gt -> false - end; -version_compare(A, B, gte) -> - case version_compare(A, B) of - eq -> true; - gt -> true; - lt -> false - end; -version_compare(A, B, Result) -> - Result =:= version_compare(A, B). - -version_compare(A, A) -> - eq; -version_compare([], [$0 | B]) -> - version_compare([], dropdot(B)); -version_compare([], _) -> - lt; %% 2.3 < 2.3.1 -version_compare([$0 | A], []) -> - version_compare(dropdot(A), []); -version_compare(_, []) -> - gt; %% 2.3.1 > 2.3 -version_compare(A, B) -> - {AStr, ATl} = lists:splitwith(fun (X) -> X =/= $. end, A), - {BStr, BTl} = lists:splitwith(fun (X) -> X =/= $. end, B), - ANum = list_to_integer(AStr), - BNum = list_to_integer(BStr), - if ANum =:= BNum -> version_compare(dropdot(ATl), dropdot(BTl)); - ANum < BNum -> lt; - ANum > BNum -> gt - end. - -%% a.b.c and a.b.d match, but a.b.c and a.d.e don't. If -%% versions do not match that pattern, just compare them. -version_minor_equivalent(A, B) -> - {ok, RE} = re:compile("^(\\d+\\.\\d+)(\\.\\d+)\$"), - Opts = [{capture, all_but_first, list}], - case {re:run(A, RE, Opts), re:run(B, RE, Opts)} of - {{match, [A1|_]}, {match, [B1|_]}} -> A1 =:= B1; - _ -> A =:= B - end. - -dropdot(A) -> lists:dropwhile(fun (X) -> X =:= $. end, A). - -dict_cons(Key, Value, Dict) -> - dict:update(Key, fun (List) -> [Value | List] end, [Value], Dict). - -orddict_cons(Key, Value, Dict) -> - orddict:update(Key, fun (List) -> [Value | List] end, [Value], Dict). - -gb_trees_cons(Key, Value, Tree) -> - case gb_trees:lookup(Key, Tree) of - {value, Values} -> gb_trees:update(Key, [Value | Values], Tree); - none -> gb_trees:insert(Key, [Value], Tree) - end. - -gb_trees_fold(Fun, Acc, Tree) -> - gb_trees_fold1(Fun, Acc, gb_trees:next(gb_trees:iterator(Tree))). - -gb_trees_fold1(_Fun, Acc, none) -> - Acc; -gb_trees_fold1(Fun, Acc, {Key, Val, It}) -> - gb_trees_fold1(Fun, Fun(Key, Val, Acc), gb_trees:next(It)). - -gb_trees_foreach(Fun, Tree) -> - gb_trees_fold(fun (Key, Val, Acc) -> Fun(Key, Val), Acc end, ok, Tree). - -now_ms() -> - timer:now_diff(now(), {0,0,0}) div 1000. - -module_attributes(Module) -> - case catch Module:module_info(attributes) of - {'EXIT', {undef, [{Module, module_info, _} | _]}} -> - io:format("WARNING: module ~p not found, so not scanned for boot steps.~n", - [Module]), - []; - {'EXIT', Reason} -> - exit(Reason); - V -> - V - end. - -all_module_attributes(Name) -> - Targets = - lists:usort( - lists:append( - [[{App, Module} || Module <- Modules] || - {App, _, _} <- application:loaded_applications(), - {ok, Modules} <- [application:get_key(App, modules)]])), - lists:foldl( - fun ({App, Module}, Acc) -> - case lists:append([Atts || {N, Atts} <- module_attributes(Module), - N =:= Name]) of - [] -> Acc; - Atts -> [{App, Module, Atts} | Acc] - end - end, [], Targets). - -build_acyclic_graph(VertexFun, EdgeFun, Graph) -> - G = digraph:new([acyclic]), - try - [case digraph:vertex(G, Vertex) of - false -> digraph:add_vertex(G, Vertex, Label); - _ -> ok = throw({graph_error, {vertex, duplicate, Vertex}}) - end || GraphElem <- Graph, - {Vertex, Label} <- VertexFun(GraphElem)], - [case digraph:add_edge(G, From, To) of - {error, E} -> throw({graph_error, {edge, E, From, To}}); - _ -> ok - end || GraphElem <- Graph, - {From, To} <- EdgeFun(GraphElem)], - {ok, G} - catch {graph_error, Reason} -> - true = digraph:delete(G), - {error, Reason} - end. - -const(X) -> fun () -> X end. - -%% Format IPv4-mapped IPv6 addresses as IPv4, since they're what we see -%% when IPv6 is enabled but not used (i.e. 99% of the time). -ntoa({0,0,0,0,0,16#ffff,AB,CD}) -> - inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256}); -ntoa(IP) -> - inet_parse:ntoa(IP). - -ntoab(IP) -> - Str = ntoa(IP), - case string:str(Str, ":") of - 0 -> Str; - _ -> "[" ++ Str ++ "]" - end. - -%% We try to avoid reconnecting to down nodes here; this is used in a -%% loop in rabbit_amqqueue:on_node_down/1 and any delays we incur -%% would be bad news. -%% -%% See also rabbit_mnesia:is_process_alive/1 which also requires the -%% process be in the same running cluster as us (i.e. not partitioned -%% or some random node). -is_process_alive(Pid) -> - Node = node(Pid), - lists:member(Node, [node() | nodes()]) andalso - rpc:call(Node, erlang, is_process_alive, [Pid]) =:= true. - -pget(K, P) -> proplists:get_value(K, P). -pget(K, P, D) -> proplists:get_value(K, P, D). - -pget_or_die(K, P) -> - case proplists:get_value(K, P) of - undefined -> exit({error, key_missing, K}); - V -> V - end. - -pset(Key, Value, List) -> [{Key, Value} | proplists:delete(Key, List)]. - -format_message_queue(_Opt, MQ) -> - Len = priority_queue:len(MQ), - {Len, - case Len > 100 of - false -> priority_queue:to_list(MQ); - true -> {summary, - orddict:to_list( - lists:foldl( - fun ({P, V}, Counts) -> - orddict:update_counter( - {P, format_message_queue_entry(V)}, 1, Counts) - end, orddict:new(), priority_queue:to_list(MQ)))} - end}. - -format_message_queue_entry(V) when is_atom(V) -> - V; -format_message_queue_entry(V) when is_tuple(V) -> - list_to_tuple([format_message_queue_entry(E) || E <- tuple_to_list(V)]); -format_message_queue_entry(_V) -> - '_'. - -append_rpc_all_nodes(Nodes, M, F, A) -> - {ResL, _} = rpc:multicall(Nodes, M, F, A), - lists:append([case Res of - {badrpc, _} -> []; - _ -> Res - end || Res <- ResL]). - -os_cmd(Command) -> - case os:type() of - {win32, _} -> - %% Clink workaround; see - %% http://code.google.com/p/clink/issues/detail?id=141 - os:cmd(" " ++ Command); - _ -> - %% Don't just return "/bin/sh: <cmd>: not found" if not found - Exec = hd(string:tokens(Command, " ")), - case os:find_executable(Exec) of - false -> throw({command_not_found, Exec}); - _ -> os:cmd(Command) - end - end. - -gb_sets_difference(S1, S2) -> - gb_sets:fold(fun gb_sets:delete_any/2, S1, S2). - -version() -> - {ok, VSN} = application:get_key(rabbit, vsn), - VSN. - -%% See http://www.erlang.org/doc/system_principles/versions.html -otp_release() -> - File = filename:join([code:root_dir(), "releases", - erlang:system_info(otp_release), "OTP_VERSION"]), - case file:read_file(File) of - {ok, VerBin} -> - %% 17.0 or later, we need the file for the minor version - string:strip(binary_to_list(VerBin), both, $\n); - {error, _} -> - %% R16B03 or earlier (no file, otp_release is correct) - %% or we couldn't read the file (so this is best we can do) - erlang:system_info(otp_release) - end. - -%% application:which_applications(infinity) is dangerous, since it can -%% cause deadlocks on shutdown. So we have to use a timeout variant, -%% but w/o creating spurious timeout errors. -which_applications() -> - try - application:which_applications() - catch - exit:{timeout, _} -> [] - end. - -sequence_error([T]) -> T; -sequence_error([{error, _} = Error | _]) -> Error; -sequence_error([_ | Rest]) -> sequence_error(Rest). - -json_encode(Term) -> - try - {ok, mochijson2:encode(Term)} - catch - exit:{json_encode, E} -> - {error, E} - end. - -json_decode(Term) -> - try - {ok, mochijson2:decode(Term)} - catch - %% Sadly `mochijson2:decode/1' does not offer a nice way to catch - %% decoding errors... - error:_ -> error - end. - -json_to_term({struct, L}) -> - [{K, json_to_term(V)} || {K, V} <- L]; -json_to_term(L) when is_list(L) -> - [json_to_term(I) || I <- L]; -json_to_term(V) when is_binary(V) orelse is_number(V) orelse V =:= null orelse - V =:= true orelse V =:= false -> - V. - -%% This has the flaw that empty lists will never be JSON objects, so use with -%% care. -term_to_json([{_, _}|_] = L) -> - {struct, [{K, term_to_json(V)} || {K, V} <- L]}; -term_to_json(L) when is_list(L) -> - [term_to_json(I) || I <- L]; -term_to_json(V) when is_binary(V) orelse is_number(V) orelse V =:= null orelse - V =:= true orelse V =:= false -> - V. - -now_to_ms({Mega, Sec, Micro}) -> - (Mega * 1000000 * 1000000 + Sec * 1000000 + Micro) div 1000. - -check_expiry(N) when N < 0 -> {error, {value_negative, N}}; -check_expiry(_N) -> ok. - -base64url(In) -> - lists:reverse(lists:foldl(fun ($\+, Acc) -> [$\- | Acc]; - ($\/, Acc) -> [$\_ | Acc]; - ($\=, Acc) -> Acc; - (Chr, Acc) -> [Chr | Acc] - end, [], base64:encode_to_string(In))). - -%% Ideally, you'd want Fun to run every IdealInterval. but you don't -%% want it to take more than MaxRatio of IdealInterval. So if it takes -%% more then you want to run it less often. So we time how long it -%% takes to run, and then suggest how long you should wait before -%% running it again. Times are in millis. -interval_operation({M, F, A}, MaxRatio, IdealInterval, LastInterval) -> - {Micros, Res} = timer:tc(M, F, A), - {Res, case {Micros > 1000 * (MaxRatio * IdealInterval), - Micros > 1000 * (MaxRatio * LastInterval)} of - {true, true} -> round(LastInterval * 1.5); - {true, false} -> LastInterval; - {false, false} -> lists:max([IdealInterval, - round(LastInterval / 1.5)]) - end}. - -ensure_timer(State, Idx, After, Msg) -> - case element(Idx, State) of - undefined -> TRef = send_after(After, self(), Msg), - setelement(Idx, State, TRef); - _ -> State - end. - -stop_timer(State, Idx) -> - case element(Idx, State) of - undefined -> State; - TRef -> cancel_timer(TRef), - setelement(Idx, State, undefined) - end. - -%% timer:send_after/3 goes through a single timer process but allows -%% long delays. erlang:send_after/3 does not have a bottleneck but -%% only allows max 2^32-1 millis. --define(MAX_ERLANG_SEND_AFTER, 4294967295). -send_after(Millis, Pid, Msg) when Millis > ?MAX_ERLANG_SEND_AFTER -> - {ok, Ref} = timer:send_after(Millis, Pid, Msg), - {timer, Ref}; -send_after(Millis, Pid, Msg) -> - {erlang, erlang:send_after(Millis, Pid, Msg)}. - -cancel_timer({erlang, Ref}) -> erlang:cancel_timer(Ref), - ok; -cancel_timer({timer, Ref}) -> {ok, cancel} = timer:cancel(Ref), - ok. - -store_proc_name(Type, ProcName) -> store_proc_name({Type, ProcName}). -store_proc_name(TypeProcName) -> put(process_name, TypeProcName). - -moving_average(_Time, _HalfLife, Next, undefined) -> - Next; -%% We want the Weight to decrease as Time goes up (since Weight is the -%% weight for the current sample, not the new one), so that the moving -%% average decays at the same speed regardless of how long the time is -%% between samplings. So we want Weight = math:exp(Something), where -%% Something turns out to be negative. -%% -%% We want to determine Something here in terms of the Time taken -%% since the last measurement, and a HalfLife. So we want Weight = -%% math:exp(Time * Constant / HalfLife). What should Constant be? We -%% want Weight to be 0.5 when Time = HalfLife. -%% -%% Plug those numbers in and you get 0.5 = math:exp(Constant). Take -%% the log of each side and you get math:log(0.5) = Constant. -moving_average(Time, HalfLife, Next, Current) -> - Weight = math:exp(Time * math:log(0.5) / HalfLife), - Next * (1 - Weight) + Current * Weight. - -%% ------------------------------------------------------------------------- -%% Begin copypasta from gen_server2.erl - -get_parent() -> - case get('$ancestors') of - [Parent | _] when is_pid (Parent) -> Parent; - [Parent | _] when is_atom(Parent) -> name_to_pid(Parent); - _ -> exit(process_was_not_started_by_proc_lib) - end. - -name_to_pid(Name) -> - case whereis(Name) of - undefined -> case whereis_name(Name) of - undefined -> exit(could_not_find_registerd_name); - Pid -> Pid - end; - Pid -> Pid - end. - -whereis_name(Name) -> - case ets:lookup(global_names, Name) of - [{_Name, Pid, _Method, _RPid, _Ref}] -> - if node(Pid) == node() -> case erlang:is_process_alive(Pid) of - true -> Pid; - false -> undefined - end; - true -> Pid - end; - [] -> undefined - end. - -%% End copypasta from gen_server2.erl -%% ------------------------------------------------------------------------- diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl deleted file mode 100644 index f9110e58..00000000 --- a/src/rabbit_mnesia.erl +++ /dev/null @@ -1,900 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_mnesia). - --export([init/0, - join_cluster/2, - reset/0, - force_reset/0, - update_cluster_nodes/1, - change_cluster_node_type/1, - forget_cluster_node/2, - force_load_next_boot/0, - - status/0, - is_clustered/0, - on_running_node/1, - is_process_alive/1, - cluster_nodes/1, - node_type/0, - dir/0, - cluster_status_from_mnesia/0, - - init_db_unchecked/2, - copy_db/1, - check_cluster_consistency/0, - ensure_mnesia_dir/0, - - on_node_up/1, - on_node_down/1 - ]). - -%% Used internally in rpc calls --export([node_info/0, remove_node_if_mnesia_running/1]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([node_type/0, cluster_status/0]). - --type(node_type() :: disc | ram). --type(cluster_status() :: {[node()], [node()], [node()]}). - -%% Main interface --spec(init/0 :: () -> 'ok'). --spec(join_cluster/2 :: (node(), node_type()) - -> 'ok' | {'ok', 'already_member'}). --spec(reset/0 :: () -> 'ok'). --spec(force_reset/0 :: () -> 'ok'). --spec(update_cluster_nodes/1 :: (node()) -> 'ok'). --spec(change_cluster_node_type/1 :: (node_type()) -> 'ok'). --spec(forget_cluster_node/2 :: (node(), boolean()) -> 'ok'). --spec(force_load_next_boot/0 :: () -> 'ok'). - -%% Various queries to get the status of the db --spec(status/0 :: () -> [{'nodes', [{node_type(), [node()]}]} | - {'running_nodes', [node()]} | - {'partitions', [{node(), [node()]}]}]). --spec(is_clustered/0 :: () -> boolean()). --spec(on_running_node/1 :: (pid()) -> boolean()). --spec(is_process_alive/1 :: (pid()) -> boolean()). --spec(cluster_nodes/1 :: ('all' | 'disc' | 'ram' | 'running') -> [node()]). --spec(node_type/0 :: () -> node_type()). --spec(dir/0 :: () -> file:filename()). --spec(cluster_status_from_mnesia/0 :: () -> rabbit_types:ok_or_error2( - cluster_status(), any())). - -%% Operations on the db and utils, mainly used in `rabbit_upgrade' and `rabbit' --spec(init_db_unchecked/2 :: ([node()], node_type()) -> 'ok'). --spec(copy_db/1 :: (file:filename()) -> rabbit_types:ok_or_error(any())). --spec(check_cluster_consistency/0 :: () -> 'ok'). --spec(ensure_mnesia_dir/0 :: () -> 'ok'). - -%% Hooks used in `rabbit_node_monitor' --spec(on_node_up/1 :: (node()) -> 'ok'). --spec(on_node_down/1 :: (node()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- -%% Main interface -%%---------------------------------------------------------------------------- - -init() -> - ensure_mnesia_running(), - ensure_mnesia_dir(), - case is_virgin_node() of - true -> init_from_config(); - false -> NodeType = node_type(), - init_db_and_upgrade(cluster_nodes(all), NodeType, - NodeType =:= ram) - end, - %% We intuitively expect the global name server to be synced when - %% Mnesia is up. In fact that's not guaranteed to be the case - - %% let's make it so. - ok = global:sync(), - ok. - -init_from_config() -> - FindBadNodeNames = fun - (Name, BadNames) when is_atom(Name) -> BadNames; - (Name, BadNames) -> [Name | BadNames] - end, - {TryNodes, NodeType} = - case application:get_env(rabbit, cluster_nodes) of - {ok, {Nodes, Type} = Config} - when is_list(Nodes) andalso (Type == disc orelse Type == ram) -> - case lists:foldr(FindBadNodeNames, [], Nodes) of - [] -> Config; - BadNames -> e({invalid_cluster_node_names, BadNames}) - end; - {ok, {_, BadType}} when BadType /= disc andalso BadType /= ram -> - e({invalid_cluster_node_type, BadType}); - {ok, Nodes} when is_list(Nodes) -> - %% The legacy syntax (a nodes list without the node - %% type) is unsupported. - case lists:foldr(FindBadNodeNames, [], Nodes) of - [] -> e(cluster_node_type_mandatory); - _ -> e(invalid_cluster_nodes_conf) - end; - {ok, _} -> - e(invalid_cluster_nodes_conf) - end, - case TryNodes of - [] -> init_db_and_upgrade([node()], disc, false); - _ -> auto_cluster(TryNodes, NodeType) - end. - -auto_cluster(TryNodes, NodeType) -> - case find_auto_cluster_node(nodes_excl_me(TryNodes)) of - {ok, Node} -> - rabbit_log:info("Node '~p' selected for auto-clustering~n", [Node]), - {ok, {_, DiscNodes, _}} = discover_cluster0(Node), - init_db_and_upgrade(DiscNodes, NodeType, true), - rabbit_node_monitor:notify_joined_cluster(); - none -> - rabbit_log:warning( - "Could not find any node for auto-clustering from: ~p~n" - "Starting blank node...~n", [TryNodes]), - init_db_and_upgrade([node()], disc, false) - end. - -%% Make the node join a cluster. The node will be reset automatically -%% before we actually cluster it. The nodes provided will be used to -%% find out about the nodes in the cluster. -%% -%% This function will fail if: -%% -%% * The node is currently the only disc node of its cluster -%% * We can't connect to any of the nodes provided -%% * The node is currently already clustered with the cluster of the nodes -%% provided -%% -%% Note that we make no attempt to verify that the nodes provided are -%% all in the same cluster, we simply pick the first online node and -%% we cluster to its cluster. -join_cluster(DiscoveryNode, NodeType) -> - ensure_mnesia_not_running(), - ensure_mnesia_dir(), - case is_only_clustered_disc_node() of - true -> e(clustering_only_disc_node); - false -> ok - end, - {ClusterNodes, _, _} = discover_cluster([DiscoveryNode]), - case me_in_nodes(ClusterNodes) of - false -> - case check_cluster_consistency(DiscoveryNode, false) of - {ok, _} -> - %% reset the node. this simplifies things and it - %% will be needed in this case - we're joining a new - %% cluster with new nodes which are not in synch - %% with the current node. It also lifts the burden - %% of resetting the node from the user. - reset_gracefully(), - - %% Join the cluster - rabbit_log:info("Clustering with ~p as ~p node~n", - [ClusterNodes, NodeType]), - ok = init_db_with_mnesia(ClusterNodes, NodeType, - true, true), - rabbit_node_monitor:notify_joined_cluster(), - ok; - {error, Reason} -> - {error, Reason} - end; - true -> - rabbit_log:info("Already member of cluster: ~p~n", [ClusterNodes]), - {ok, already_member} - end. - -%% return node to its virgin state, where it is not member of any -%% cluster, has no cluster configuration, no local database, and no -%% persisted messages -reset() -> - ensure_mnesia_not_running(), - rabbit_log:info("Resetting Rabbit~n", []), - reset_gracefully(). - -force_reset() -> - ensure_mnesia_not_running(), - rabbit_log:info("Resetting Rabbit forcefully~n", []), - wipe(). - -reset_gracefully() -> - AllNodes = cluster_nodes(all), - %% Reconnecting so that we will get an up to date nodes. We don't - %% need to check for consistency because we are resetting. - %% Force=true here so that reset still works when clustered with a - %% node which is down. - init_db_with_mnesia(AllNodes, node_type(), false, false), - case is_only_clustered_disc_node() of - true -> e(resetting_only_disc_node); - false -> ok - end, - leave_cluster(), - rabbit_misc:ensure_ok(mnesia:delete_schema([node()]), cannot_delete_schema), - wipe(). - -wipe() -> - %% We need to make sure that we don't end up in a distributed - %% Erlang system with nodes while not being in an Mnesia cluster - %% with them. We don't handle that well. - [erlang:disconnect_node(N) || N <- cluster_nodes(all)], - %% remove persisted messages and any other garbage we find - ok = rabbit_file:recursive_delete(filelib:wildcard(dir() ++ "/*")), - ok = rabbit_node_monitor:reset_cluster_status(), - ok. - -change_cluster_node_type(Type) -> - ensure_mnesia_not_running(), - ensure_mnesia_dir(), - case is_clustered() of - false -> e(not_clustered); - true -> ok - end, - {_, _, RunningNodes} = discover_cluster(cluster_nodes(all)), - %% We might still be marked as running by a remote node since the - %% information of us going down might not have propagated yet. - Node = case RunningNodes -- [node()] of - [] -> e(no_online_cluster_nodes); - [Node0|_] -> Node0 - end, - ok = reset(), - ok = join_cluster(Node, Type). - -update_cluster_nodes(DiscoveryNode) -> - ensure_mnesia_not_running(), - ensure_mnesia_dir(), - Status = {AllNodes, _, _} = discover_cluster([DiscoveryNode]), - case me_in_nodes(AllNodes) of - true -> - %% As in `check_consistency/0', we can safely delete the - %% schema here, since it'll be replicated from the other - %% nodes - mnesia:delete_schema([node()]), - rabbit_node_monitor:write_cluster_status(Status), - rabbit_log:info("Updating cluster nodes from ~p~n", - [DiscoveryNode]), - init_db_with_mnesia(AllNodes, node_type(), true, true); - false -> - e(inconsistent_cluster) - end, - ok. - -%% We proceed like this: try to remove the node locally. If the node -%% is offline, we remove the node if: -%% * This node is a disc node -%% * All other nodes are offline -%% * This node was, at the best of our knowledge (see comment below) -%% the last or second to last after the node we're removing to go -%% down -forget_cluster_node(Node, RemoveWhenOffline) -> - case lists:member(Node, cluster_nodes(all)) of - true -> ok; - false -> e(not_a_cluster_node) - end, - case {RemoveWhenOffline, is_running()} of - {true, false} -> remove_node_offline_node(Node); - {true, true} -> e(online_node_offline_flag); - {false, false} -> e(offline_node_no_offline_flag); - {false, true} -> rabbit_log:info( - "Removing node ~p from cluster~n", [Node]), - case remove_node_if_mnesia_running(Node) of - ok -> ok; - {error, _} = Err -> throw(Err) - end - end. - -remove_node_offline_node(Node) -> - %% Here `mnesia:system_info(running_db_nodes)' will RPC, but that's what we - %% want - we need to know the running nodes *now*. If the current node is a - %% RAM node it will return bogus results, but we don't care since we only do - %% this operation from disc nodes. - case {mnesia:system_info(running_db_nodes) -- [Node], node_type()} of - {[], disc} -> - start_mnesia(), - try - %% What we want to do here is replace the last node to - %% go down with the current node. The way we do this - %% is by force loading the table, and making sure that - %% they are loaded. - rabbit_table:force_load(), - rabbit_table:wait_for_replicated(), - forget_cluster_node(Node, false), - force_load_next_boot() - after - stop_mnesia() - end; - {_, _} -> - e(removing_node_from_offline_node) - end. - -%%---------------------------------------------------------------------------- -%% Queries -%%---------------------------------------------------------------------------- - -status() -> - IfNonEmpty = fun (_, []) -> []; - (Type, Nodes) -> [{Type, Nodes}] - end, - [{nodes, (IfNonEmpty(disc, cluster_nodes(disc)) ++ - IfNonEmpty(ram, cluster_nodes(ram)))}] ++ - case is_running() of - true -> RunningNodes = cluster_nodes(running), - [{running_nodes, RunningNodes}, - {cluster_name, rabbit_nodes:cluster_name()}, - {partitions, mnesia_partitions(RunningNodes)}]; - false -> [] - end. - -mnesia_partitions(Nodes) -> - Replies = rabbit_node_monitor:partitions(Nodes), - [Reply || Reply = {_, R} <- Replies, R =/= []]. - -is_running() -> mnesia:system_info(is_running) =:= yes. - -is_clustered() -> AllNodes = cluster_nodes(all), - AllNodes =/= [] andalso AllNodes =/= [node()]. - -on_running_node(Pid) -> lists:member(node(Pid), cluster_nodes(running)). - -%% This requires the process be in the same running cluster as us -%% (i.e. not partitioned or some random node). -%% -%% See also rabbit_misc:is_process_alive/1 which does not. -is_process_alive(Pid) -> - on_running_node(Pid) andalso - rpc:call(node(Pid), erlang, is_process_alive, [Pid]) =:= true. - -cluster_nodes(WhichNodes) -> cluster_status(WhichNodes). - -%% This function is the actual source of information, since it gets -%% the data from mnesia. Obviously it'll work only when mnesia is -%% running. -cluster_status_from_mnesia() -> - case is_running() of - false -> - {error, mnesia_not_running}; - true -> - %% If the tables are not present, it means that - %% `init_db/3' hasn't been run yet. In other words, either - %% we are a virgin node or a restarted RAM node. In both - %% cases we're not interested in what mnesia has to say. - NodeType = case mnesia:system_info(use_dir) of - true -> disc; - false -> ram - end, - case rabbit_table:is_present() of - true -> AllNodes = mnesia:system_info(db_nodes), - DiscCopies = mnesia:table_info(schema, disc_copies), - DiscNodes = case NodeType of - disc -> nodes_incl_me(DiscCopies); - ram -> DiscCopies - end, - %% `mnesia:system_info(running_db_nodes)' is safe since - %% we know that mnesia is running - RunningNodes = mnesia:system_info(running_db_nodes), - {ok, {AllNodes, DiscNodes, RunningNodes}}; - false -> {error, tables_not_present} - end - end. - -cluster_status(WhichNodes) -> - {AllNodes, DiscNodes, RunningNodes} = Nodes = - case cluster_status_from_mnesia() of - {ok, Nodes0} -> - Nodes0; - {error, _Reason} -> - {AllNodes0, DiscNodes0, RunningNodes0} = - rabbit_node_monitor:read_cluster_status(), - %% The cluster status file records the status when the node is - %% online, but we know for sure that the node is offline now, so - %% we can remove it from the list of running nodes. - {AllNodes0, DiscNodes0, nodes_excl_me(RunningNodes0)} - end, - case WhichNodes of - status -> Nodes; - all -> AllNodes; - disc -> DiscNodes; - ram -> AllNodes -- DiscNodes; - running -> RunningNodes - end. - -node_info() -> - {rabbit_misc:otp_release(), rabbit_misc:version(), - cluster_status_from_mnesia()}. - -node_type() -> - {_AllNodes, DiscNodes, _RunningNodes} = - rabbit_node_monitor:read_cluster_status(), - case DiscNodes =:= [] orelse me_in_nodes(DiscNodes) of - true -> disc; - false -> ram - end. - -dir() -> mnesia:system_info(directory). - -%%---------------------------------------------------------------------------- -%% Operations on the db -%%---------------------------------------------------------------------------- - -%% Adds the provided nodes to the mnesia cluster, creating a new -%% schema if there is the need to and catching up if there are other -%% nodes in the cluster already. It also updates the cluster status -%% file. -init_db(ClusterNodes, NodeType, CheckOtherNodes) -> - Nodes = change_extra_db_nodes(ClusterNodes, CheckOtherNodes), - %% Note that we use `system_info' here and not the cluster status - %% since when we start rabbit for the first time the cluster - %% status will say we are a disc node but the tables won't be - %% present yet. - WasDiscNode = mnesia:system_info(use_dir), - case {Nodes, WasDiscNode, NodeType} of - {[], _, ram} -> - %% Standalone ram node, we don't want that - throw({error, cannot_create_standalone_ram_node}); - {[], false, disc} -> - %% RAM -> disc, starting from scratch - ok = create_schema(); - {[], true, disc} -> - %% First disc node up - maybe_force_load(), - ok; - {[_ | _], _, _} -> - %% Subsequent node in cluster, catch up - maybe_force_load(), - ok = rabbit_table:wait_for_replicated(), - ok = rabbit_table:create_local_copy(NodeType) - end, - ensure_schema_integrity(), - rabbit_node_monitor:update_cluster_status(), - ok. - -init_db_unchecked(ClusterNodes, NodeType) -> - init_db(ClusterNodes, NodeType, false). - -init_db_and_upgrade(ClusterNodes, NodeType, CheckOtherNodes) -> - ok = init_db(ClusterNodes, NodeType, CheckOtherNodes), - ok = case rabbit_upgrade:maybe_upgrade_local() of - ok -> ok; - starting_from_scratch -> rabbit_version:record_desired(); - version_not_available -> schema_ok_or_move() - end, - %% `maybe_upgrade_local' restarts mnesia, so ram nodes will forget - %% about the cluster - case NodeType of - ram -> start_mnesia(), - change_extra_db_nodes(ClusterNodes, false); - disc -> ok - end, - %% ...and all nodes will need to wait for tables - rabbit_table:wait_for_replicated(), - ok. - -init_db_with_mnesia(ClusterNodes, NodeType, - CheckOtherNodes, CheckConsistency) -> - start_mnesia(CheckConsistency), - try - init_db_and_upgrade(ClusterNodes, NodeType, CheckOtherNodes) - after - stop_mnesia() - end. - -ensure_mnesia_dir() -> - MnesiaDir = dir() ++ "/", - case filelib:ensure_dir(MnesiaDir) of - {error, Reason} -> - throw({error, {cannot_create_mnesia_dir, MnesiaDir, Reason}}); - ok -> - ok - end. - -ensure_mnesia_running() -> - case mnesia:system_info(is_running) of - yes -> - ok; - starting -> - wait_for(mnesia_running), - ensure_mnesia_running(); - Reason when Reason =:= no; Reason =:= stopping -> - throw({error, mnesia_not_running}) - end. - -ensure_mnesia_not_running() -> - case mnesia:system_info(is_running) of - no -> - ok; - stopping -> - wait_for(mnesia_not_running), - ensure_mnesia_not_running(); - Reason when Reason =:= yes; Reason =:= starting -> - throw({error, mnesia_unexpectedly_running}) - end. - -ensure_schema_integrity() -> - case rabbit_table:check_schema_integrity() of - ok -> - ok; - {error, Reason} -> - throw({error, {schema_integrity_check_failed, Reason}}) - end. - -copy_db(Destination) -> - ok = ensure_mnesia_not_running(), - rabbit_file:recursive_copy(dir(), Destination). - -force_load_filename() -> - filename:join(dir(), "force_load"). - -force_load_next_boot() -> - rabbit_file:write_file(force_load_filename(), <<"">>). - -maybe_force_load() -> - case rabbit_file:is_file(force_load_filename()) of - true -> rabbit_table:force_load(), - rabbit_file:delete(force_load_filename()); - false -> ok - end. - -%% This does not guarantee us much, but it avoids some situations that -%% will definitely end up badly -check_cluster_consistency() -> - %% We want to find 0 or 1 consistent nodes. - case lists:foldl( - fun (Node, {error, _}) -> check_cluster_consistency(Node, true); - (_Node, {ok, Status}) -> {ok, Status} - end, {error, not_found}, nodes_excl_me(cluster_nodes(all))) - of - {ok, Status = {RemoteAllNodes, _, _}} -> - case ordsets:is_subset(ordsets:from_list(cluster_nodes(all)), - ordsets:from_list(RemoteAllNodes)) of - true -> - ok; - false -> - %% We delete the schema here since we think we are - %% clustered with nodes that are no longer in the - %% cluster and there is no other way to remove - %% them from our schema. On the other hand, we are - %% sure that there is another online node that we - %% can use to sync the tables with. There is a - %% race here: if between this check and the - %% `init_db' invocation the cluster gets - %% disbanded, we're left with a node with no - %% mnesia data that will try to connect to offline - %% nodes. - mnesia:delete_schema([node()]) - end, - rabbit_node_monitor:write_cluster_status(Status); - {error, not_found} -> - ok; - {error, _} = E -> - throw(E) - end. - -check_cluster_consistency(Node, CheckNodesConsistency) -> - case rpc:call(Node, rabbit_mnesia, node_info, []) of - {badrpc, _Reason} -> - {error, not_found}; - {_OTP, _Rabbit, {error, _}} -> - {error, not_found}; - {OTP, Rabbit, {ok, Status}} when CheckNodesConsistency -> - case check_consistency(OTP, Rabbit, Node, Status) of - {error, _} = E -> E; - {ok, Res} -> {ok, Res} - end; - {OTP, Rabbit, {ok, Status}} -> - case check_consistency(OTP, Rabbit) of - {error, _} = E -> E; - ok -> {ok, Status} - end; - {_OTP, Rabbit, _Hash, _Status} -> - %% delegate hash checking implies version mismatch - version_error("Rabbit", rabbit_misc:version(), Rabbit) - end. - -%%-------------------------------------------------------------------- -%% Hooks for `rabbit_node_monitor' -%%-------------------------------------------------------------------- - -on_node_up(Node) -> - case running_disc_nodes() of - [Node] -> rabbit_log:info("cluster contains disc nodes again~n"); - _ -> ok - end. - -on_node_down(_Node) -> - case running_disc_nodes() of - [] -> rabbit_log:info("only running disc node went down~n"); - _ -> ok - end. - -running_disc_nodes() -> - {_AllNodes, DiscNodes, RunningNodes} = cluster_status(status), - ordsets:to_list(ordsets:intersection(ordsets:from_list(DiscNodes), - ordsets:from_list(RunningNodes))). - -%%-------------------------------------------------------------------- -%% Internal helpers -%%-------------------------------------------------------------------- - -discover_cluster(Nodes) -> - case lists:foldl(fun (_, {ok, Res}) -> {ok, Res}; - (Node, _) -> discover_cluster0(Node) - end, {error, no_nodes_provided}, Nodes) of - {ok, Res} -> Res; - {error, E} -> throw({error, E}); - {badrpc, Reason} -> throw({badrpc_multi, Reason, Nodes}) - end. - -discover_cluster0(Node) when Node == node() -> - {error, cannot_cluster_node_with_itself}; -discover_cluster0(Node) -> - rpc:call(Node, rabbit_mnesia, cluster_status_from_mnesia, []). - -schema_ok_or_move() -> - case rabbit_table:check_schema_integrity() of - ok -> - ok; - {error, Reason} -> - %% NB: we cannot use rabbit_log here since it may not have been - %% started yet - rabbit_log:warning("schema integrity check failed: ~p~n" - "moving database to backup location " - "and recreating schema from scratch~n", - [Reason]), - ok = move_db(), - ok = create_schema() - end. - -%% We only care about disc nodes since ram nodes are supposed to catch -%% up only -create_schema() -> - stop_mnesia(), - rabbit_misc:ensure_ok(mnesia:create_schema([node()]), cannot_create_schema), - start_mnesia(), - ok = rabbit_table:create(), - ensure_schema_integrity(), - ok = rabbit_version:record_desired(). - -move_db() -> - stop_mnesia(), - MnesiaDir = filename:dirname(dir() ++ "/"), - {{Year, Month, Day}, {Hour, Minute, Second}} = erlang:universaltime(), - BackupDir = rabbit_misc:format( - "~s_~w~2..0w~2..0w~2..0w~2..0w~2..0w", - [MnesiaDir, Year, Month, Day, Hour, Minute, Second]), - case file:rename(MnesiaDir, BackupDir) of - ok -> - %% NB: we cannot use rabbit_log here since it may not have - %% been started yet - rabbit_log:warning("moved database from ~s to ~s~n", - [MnesiaDir, BackupDir]), - ok; - {error, Reason} -> throw({error, {cannot_backup_mnesia, - MnesiaDir, BackupDir, Reason}}) - end, - ensure_mnesia_dir(), - start_mnesia(), - ok. - -remove_node_if_mnesia_running(Node) -> - case is_running() of - false -> - {error, mnesia_not_running}; - true -> - %% Deleting the the schema copy of the node will result in - %% the node being removed from the cluster, with that - %% change being propagated to all nodes - case mnesia:del_table_copy(schema, Node) of - {atomic, ok} -> - rabbit_amqqueue:forget_all_durable(Node), - rabbit_node_monitor:notify_left_cluster(Node), - ok; - {aborted, Reason} -> - {error, {failed_to_remove_node, Node, Reason}} - end - end. - -leave_cluster() -> - case nodes_excl_me(cluster_nodes(all)) of - [] -> ok; - AllNodes -> case lists:any(fun leave_cluster/1, AllNodes) of - true -> ok; - false -> e(no_running_cluster_nodes) - end - end. - -leave_cluster(Node) -> - case rpc:call(Node, - rabbit_mnesia, remove_node_if_mnesia_running, [node()]) of - ok -> true; - {error, mnesia_not_running} -> false; - {error, Reason} -> throw({error, Reason}); - {badrpc, nodedown} -> false - end. - -wait_for(Condition) -> - rabbit_log:info("Waiting for ~p...~n", [Condition]), - timer:sleep(1000). - -start_mnesia(CheckConsistency) -> - case CheckConsistency of - true -> check_cluster_consistency(); - false -> ok - end, - rabbit_misc:ensure_ok(mnesia:start(), cannot_start_mnesia), - ensure_mnesia_running(). - -start_mnesia() -> - start_mnesia(true). - -stop_mnesia() -> - stopped = mnesia:stop(), - ensure_mnesia_not_running(). - -change_extra_db_nodes(ClusterNodes0, CheckOtherNodes) -> - ClusterNodes = nodes_excl_me(ClusterNodes0), - case {mnesia:change_config(extra_db_nodes, ClusterNodes), ClusterNodes} of - {{ok, []}, [_|_]} when CheckOtherNodes -> - throw({error, {failed_to_cluster_with, ClusterNodes, - "Mnesia could not connect to any nodes."}}); - {{ok, Nodes}, _} -> - Nodes - end. - -check_consistency(OTP, Rabbit) -> - rabbit_misc:sequence_error( - [check_otp_consistency(OTP), - check_rabbit_consistency(Rabbit)]). - -check_consistency(OTP, Rabbit, Node, Status) -> - rabbit_misc:sequence_error( - [check_otp_consistency(OTP), - check_rabbit_consistency(Rabbit), - check_nodes_consistency(Node, Status)]). - -check_nodes_consistency(Node, RemoteStatus = {RemoteAllNodes, _, _}) -> - case me_in_nodes(RemoteAllNodes) of - true -> - {ok, RemoteStatus}; - false -> - {error, {inconsistent_cluster, - rabbit_misc:format("Node ~p thinks it's clustered " - "with node ~p, but ~p disagrees", - [node(), Node, Node])}} - end. - -check_version_consistency(This, Remote, Name) -> - check_version_consistency(This, Remote, Name, fun (A, B) -> A =:= B end). - -check_version_consistency(This, Remote, Name, Comp) -> - case Comp(This, Remote) of - true -> ok; - false -> version_error(Name, This, Remote) - end. - -version_error(Name, This, Remote) -> - {error, {inconsistent_cluster, - rabbit_misc:format("~s version mismatch: local node is ~s, " - "remote node ~s", [Name, This, Remote])}}. - -check_otp_consistency(Remote) -> - check_version_consistency(rabbit_misc:otp_release(), Remote, "OTP"). - -check_rabbit_consistency(Remote) -> - check_version_consistency( - rabbit_misc:version(), Remote, "Rabbit", - fun rabbit_misc:version_minor_equivalent/2). - -%% This is fairly tricky. We want to know if the node is in the state -%% that a `reset' would leave it in. We cannot simply check if the -%% mnesia tables aren't there because restarted RAM nodes won't have -%% tables while still being non-virgin. What we do instead is to -%% check if the mnesia directory is non existant or empty, with the -%% exception of the cluster status files, which will be there thanks to -%% `rabbit_node_monitor:prepare_cluster_status_file/0'. -is_virgin_node() -> - case rabbit_file:list_dir(dir()) of - {error, enoent} -> - true; - {ok, []} -> - true; - {ok, [File1, File2]} -> - lists:usort([dir() ++ "/" ++ File1, dir() ++ "/" ++ File2]) =:= - lists:usort([rabbit_node_monitor:cluster_status_filename(), - rabbit_node_monitor:running_nodes_filename()]); - {ok, _} -> - false - end. - -find_auto_cluster_node([]) -> - none; -find_auto_cluster_node([Node | Nodes]) -> - Fail = fun (Fmt, Args) -> - rabbit_log:warning( - "Could not auto-cluster with ~s: " ++ Fmt, [Node | Args]), - find_auto_cluster_node(Nodes) - end, - case rpc:call(Node, rabbit_mnesia, node_info, []) of - {badrpc, _} = Reason -> Fail("~p~n", [Reason]); - %% old delegate hash check - {_OTP, RMQ, _Hash, _} -> Fail("version ~s~n", [RMQ]); - {_OTP, _RMQ, {error, _} = E} -> Fail("~p~n", [E]); - {OTP, RMQ, _} -> case check_consistency(OTP, RMQ) of - {error, _} -> Fail("versions ~p~n", - [{OTP, RMQ}]); - ok -> {ok, Node} - end - end. - -is_only_clustered_disc_node() -> - node_type() =:= disc andalso is_clustered() andalso - cluster_nodes(disc) =:= [node()]. - -me_in_nodes(Nodes) -> lists:member(node(), Nodes). - -nodes_incl_me(Nodes) -> lists:usort([node()|Nodes]). - -nodes_excl_me(Nodes) -> Nodes -- [node()]. - -e(Tag) -> throw({error, {Tag, error_description(Tag)}}). - -error_description({invalid_cluster_node_names, BadNames}) -> - "In the 'cluster_nodes' configuration key, the following node names " - "are invalid: " ++ lists:flatten(io_lib:format("~p", [BadNames])); -error_description({invalid_cluster_node_type, BadType}) -> - "In the 'cluster_nodes' configuration key, the node type is invalid " - "(expected 'disc' or 'ram'): " ++ - lists:flatten(io_lib:format("~p", [BadType])); -error_description(cluster_node_type_mandatory) -> - "The 'cluster_nodes' configuration key must indicate the node type: " - "either {[...], disc} or {[...], ram}"; -error_description(invalid_cluster_nodes_conf) -> - "The 'cluster_nodes' configuration key is invalid, it must be of the " - "form {[Nodes], Type}, where Nodes is a list of node names and " - "Type is either 'disc' or 'ram'"; -error_description(clustering_only_disc_node) -> - "You cannot cluster a node if it is the only disc node in its existing " - " cluster. If new nodes joined while this node was offline, use " - "'update_cluster_nodes' to add them manually."; -error_description(resetting_only_disc_node) -> - "You cannot reset a node when it is the only disc node in a cluster. " - "Please convert another node of the cluster to a disc node first."; -error_description(not_clustered) -> - "Non-clustered nodes can only be disc nodes."; -error_description(no_online_cluster_nodes) -> - "Could not find any online cluster nodes. If the cluster has changed, " - "you can use the 'update_cluster_nodes' command."; -error_description(inconsistent_cluster) -> - "The nodes provided do not have this node as part of the cluster."; -error_description(not_a_cluster_node) -> - "The node selected is not in the cluster."; -error_description(online_node_offline_flag) -> - "You set the --offline flag, which is used to remove nodes remotely from " - "offline nodes, but this node is online."; -error_description(offline_node_no_offline_flag) -> - "You are trying to remove a node from an offline node. That is dangerous, " - "but can be done with the --offline flag. Please consult the manual " - "for rabbitmqctl for more information."; -error_description(removing_node_from_offline_node) -> - "To remove a node remotely from an offline node, the node you are removing " - "from must be a disc node and all the other nodes must be offline."; -error_description(no_running_cluster_nodes) -> - "You cannot leave a cluster if no online nodes are present.". diff --git a/src/rabbit_msg_file.erl b/src/rabbit_msg_file.erl deleted file mode 100644 index 2f3ccc35..00000000 --- a/src/rabbit_msg_file.erl +++ /dev/null @@ -1,125 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_msg_file). - --export([append/3, read/2, scan/4]). - -%%---------------------------------------------------------------------------- - --include("rabbit_msg_store.hrl"). - --define(INTEGER_SIZE_BYTES, 8). --define(INTEGER_SIZE_BITS, (8 * ?INTEGER_SIZE_BYTES)). --define(WRITE_OK_SIZE_BITS, 8). --define(WRITE_OK_MARKER, 255). --define(FILE_PACKING_ADJUSTMENT, (1 + ?INTEGER_SIZE_BYTES)). --define(MSG_ID_SIZE_BYTES, 16). --define(MSG_ID_SIZE_BITS, (8 * ?MSG_ID_SIZE_BYTES)). --define(SCAN_BLOCK_SIZE, 4194304). %% 4MB - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(io_device() :: any()). --type(position() :: non_neg_integer()). --type(msg_size() :: non_neg_integer()). --type(file_size() :: non_neg_integer()). --type(message_accumulator(A) :: - fun (({rabbit_types:msg_id(), msg_size(), position(), binary()}, A) -> - A)). - --spec(append/3 :: (io_device(), rabbit_types:msg_id(), msg()) -> - rabbit_types:ok_or_error2(msg_size(), any())). --spec(read/2 :: (io_device(), msg_size()) -> - rabbit_types:ok_or_error2({rabbit_types:msg_id(), msg()}, - any())). --spec(scan/4 :: (io_device(), file_size(), message_accumulator(A), A) -> - {'ok', A, position()}). - --endif. - -%%---------------------------------------------------------------------------- - -append(FileHdl, MsgId, MsgBody) - when is_binary(MsgId) andalso size(MsgId) =:= ?MSG_ID_SIZE_BYTES -> - MsgBodyBin = term_to_binary(MsgBody), - MsgBodyBinSize = size(MsgBodyBin), - Size = MsgBodyBinSize + ?MSG_ID_SIZE_BYTES, - case file_handle_cache:append(FileHdl, - <<Size:?INTEGER_SIZE_BITS, - MsgId:?MSG_ID_SIZE_BYTES/binary, - MsgBodyBin:MsgBodyBinSize/binary, - ?WRITE_OK_MARKER:?WRITE_OK_SIZE_BITS>>) of - ok -> {ok, Size + ?FILE_PACKING_ADJUSTMENT}; - KO -> KO - end. - -read(FileHdl, TotalSize) -> - Size = TotalSize - ?FILE_PACKING_ADJUSTMENT, - BodyBinSize = Size - ?MSG_ID_SIZE_BYTES, - case file_handle_cache:read(FileHdl, TotalSize) of - {ok, <<Size:?INTEGER_SIZE_BITS, - MsgId:?MSG_ID_SIZE_BYTES/binary, - MsgBodyBin:BodyBinSize/binary, - ?WRITE_OK_MARKER:?WRITE_OK_SIZE_BITS>>} -> - {ok, {MsgId, binary_to_term(MsgBodyBin)}}; - KO -> KO - end. - -scan(FileHdl, FileSize, Fun, Acc) when FileSize >= 0 -> - scan(FileHdl, FileSize, <<>>, 0, 0, Fun, Acc). - -scan(_FileHdl, FileSize, _Data, FileSize, ScanOffset, _Fun, Acc) -> - {ok, Acc, ScanOffset}; -scan(FileHdl, FileSize, Data, ReadOffset, ScanOffset, Fun, Acc) -> - Read = lists:min([?SCAN_BLOCK_SIZE, (FileSize - ReadOffset)]), - case file_handle_cache:read(FileHdl, Read) of - {ok, Data1} -> - {Data2, Acc1, ScanOffset1} = - scanner(<<Data/binary, Data1/binary>>, ScanOffset, Fun, Acc), - ReadOffset1 = ReadOffset + size(Data1), - scan(FileHdl, FileSize, Data2, ReadOffset1, ScanOffset1, Fun, Acc1); - _KO -> - {ok, Acc, ScanOffset} - end. - -scanner(<<>>, Offset, _Fun, Acc) -> - {<<>>, Acc, Offset}; -scanner(<<0:?INTEGER_SIZE_BITS, _Rest/binary>>, Offset, _Fun, Acc) -> - {<<>>, Acc, Offset}; %% Nothing to do other than stop. -scanner(<<Size:?INTEGER_SIZE_BITS, MsgIdAndMsg:Size/binary, - WriteMarker:?WRITE_OK_SIZE_BITS, Rest/binary>>, Offset, Fun, Acc) -> - TotalSize = Size + ?FILE_PACKING_ADJUSTMENT, - case WriteMarker of - ?WRITE_OK_MARKER -> - %% Here we take option 5 from - %% http://www.erlang.org/cgi-bin/ezmlm-cgi?2:mss:1569 in - %% which we read the MsgId as a number, and then convert it - %% back to a binary in order to work around bugs in - %% Erlang's GC. - <<MsgIdNum:?MSG_ID_SIZE_BITS, Msg/binary>> = - <<MsgIdAndMsg:Size/binary>>, - <<MsgId:?MSG_ID_SIZE_BYTES/binary>> = - <<MsgIdNum:?MSG_ID_SIZE_BITS>>, - scanner(Rest, Offset + TotalSize, Fun, - Fun({MsgId, TotalSize, Offset, Msg}, Acc)); - _ -> - scanner(Rest, Offset + TotalSize, Fun, Acc) - end; -scanner(Data, Offset, _Fun, Acc) -> - {Data, Acc, Offset}. diff --git a/src/rabbit_msg_store.erl b/src/rabbit_msg_store.erl deleted file mode 100644 index 6c80ddcd..00000000 --- a/src/rabbit_msg_store.erl +++ /dev/null @@ -1,2070 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_msg_store). - --behaviour(gen_server2). - --export([start_link/4, successfully_recovered_state/1, - client_init/4, client_terminate/1, client_delete_and_terminate/1, - client_ref/1, close_all_indicated/1, - write/3, write_flow/3, read/2, contains/2, remove/2]). - --export([set_maximum_since_use/2, has_readers/2, combine_files/3, - delete_file/2]). %% internal - --export([transform_dir/3, force_recovery/2]). %% upgrade - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3, prioritise_call/4, prioritise_cast/3, - prioritise_info/3, format_message_queue/2]). - -%%---------------------------------------------------------------------------- - --include("rabbit_msg_store.hrl"). - --define(SYNC_INTERVAL, 25). %% milliseconds --define(CLEAN_FILENAME, "clean.dot"). --define(FILE_SUMMARY_FILENAME, "file_summary.ets"). --define(TRANSFORM_TMP, "transform_tmp"). - --define(BINARY_MODE, [raw, binary]). --define(READ_MODE, [read]). --define(READ_AHEAD_MODE, [read_ahead | ?READ_MODE]). --define(WRITE_MODE, [write]). - --define(FILE_EXTENSION, ".rdq"). --define(FILE_EXTENSION_TMP, ".rdt"). - --define(HANDLE_CACHE_BUFFER_SIZE, 1048576). %% 1MB - - %% i.e. two pairs, so GC does not go idle when busy --define(MAXIMUM_SIMULTANEOUS_GC_FILES, 4). - -%%---------------------------------------------------------------------------- - --record(msstate, - { dir, %% store directory - index_module, %% the module for index ops - index_state, %% where are messages? - current_file, %% current file name as number - current_file_handle, %% current file handle since the last fsync? - file_handle_cache, %% file handle cache - sync_timer_ref, %% TRef for our interval timer - sum_valid_data, %% sum of valid data in all files - sum_file_size, %% sum of file sizes - pending_gc_completion, %% things to do once GC completes - gc_pid, %% pid of our GC - file_handles_ets, %% tid of the shared file handles table - file_summary_ets, %% tid of the file summary table - cur_file_cache_ets, %% tid of current file cache table - flying_ets, %% tid of writes/removes in flight - dying_clients, %% set of dying clients - clients, %% map of references of all registered clients - %% to callbacks - successfully_recovered, %% boolean: did we recover state? - file_size_limit, %% how big are our files allowed to get? - cref_to_msg_ids %% client ref to synced messages mapping - }). - --record(client_msstate, - { server, - client_ref, - file_handle_cache, - index_state, - index_module, - dir, - gc_pid, - file_handles_ets, - file_summary_ets, - cur_file_cache_ets, - flying_ets - }). - --record(file_summary, - {file, valid_total_size, left, right, file_size, locked, readers}). - --record(gc_state, - { dir, - index_module, - index_state, - file_summary_ets, - file_handles_ets, - msg_store - }). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([gc_state/0, file_num/0]). - --type(gc_state() :: #gc_state { dir :: file:filename(), - index_module :: atom(), - index_state :: any(), - file_summary_ets :: ets:tid(), - file_handles_ets :: ets:tid(), - msg_store :: server() - }). - --type(server() :: pid() | atom()). --type(client_ref() :: binary()). --type(file_num() :: non_neg_integer()). --type(client_msstate() :: #client_msstate { - server :: server(), - client_ref :: client_ref(), - file_handle_cache :: dict:dict(), - index_state :: any(), - index_module :: atom(), - dir :: file:filename(), - gc_pid :: pid(), - file_handles_ets :: ets:tid(), - file_summary_ets :: ets:tid(), - cur_file_cache_ets :: ets:tid(), - flying_ets :: ets:tid()}). --type(msg_ref_delta_gen(A) :: - fun ((A) -> 'finished' | - {rabbit_types:msg_id(), non_neg_integer(), A})). --type(maybe_msg_id_fun() :: - 'undefined' | fun ((gb_sets:set(), 'written' | 'ignored') -> any())). --type(maybe_close_fds_fun() :: 'undefined' | fun (() -> 'ok')). --type(deletion_thunk() :: fun (() -> boolean())). - --spec(start_link/4 :: - (atom(), file:filename(), [binary()] | 'undefined', - {msg_ref_delta_gen(A), A}) -> rabbit_types:ok_pid_or_error()). --spec(successfully_recovered_state/1 :: (server()) -> boolean()). --spec(client_init/4 :: (server(), client_ref(), maybe_msg_id_fun(), - maybe_close_fds_fun()) -> client_msstate()). --spec(client_terminate/1 :: (client_msstate()) -> 'ok'). --spec(client_delete_and_terminate/1 :: (client_msstate()) -> 'ok'). --spec(client_ref/1 :: (client_msstate()) -> client_ref()). --spec(close_all_indicated/1 :: - (client_msstate()) -> rabbit_types:ok(client_msstate())). --spec(write/3 :: (rabbit_types:msg_id(), msg(), client_msstate()) -> 'ok'). --spec(write_flow/3 :: (rabbit_types:msg_id(), msg(), client_msstate()) -> 'ok'). --spec(read/2 :: (rabbit_types:msg_id(), client_msstate()) -> - {rabbit_types:ok(msg()) | 'not_found', client_msstate()}). --spec(contains/2 :: (rabbit_types:msg_id(), client_msstate()) -> boolean()). --spec(remove/2 :: ([rabbit_types:msg_id()], client_msstate()) -> 'ok'). - --spec(set_maximum_since_use/2 :: (server(), non_neg_integer()) -> 'ok'). --spec(has_readers/2 :: (non_neg_integer(), gc_state()) -> boolean()). --spec(combine_files/3 :: (non_neg_integer(), non_neg_integer(), gc_state()) -> - deletion_thunk()). --spec(delete_file/2 :: (non_neg_integer(), gc_state()) -> deletion_thunk()). --spec(force_recovery/2 :: (file:filename(), server()) -> 'ok'). --spec(transform_dir/3 :: (file:filename(), server(), - fun ((any()) -> (rabbit_types:ok_or_error2(msg(), any())))) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -%% We run GC whenever (garbage / sum_file_size) > ?GARBAGE_FRACTION -%% It is not recommended to set this to < 0.5 --define(GARBAGE_FRACTION, 0.5). - -%% The components: -%% -%% Index: this is a mapping from MsgId to #msg_location{}: -%% {MsgId, RefCount, File, Offset, TotalSize} -%% By default, it's in ets, but it's also pluggable. -%% FileSummary: this is an ets table which maps File to #file_summary{}: -%% {File, ValidTotalSize, Left, Right, FileSize, Locked, Readers} -%% -%% The basic idea is that messages are appended to the current file up -%% until that file becomes too big (> file_size_limit). At that point, -%% the file is closed and a new file is created on the _right_ of the -%% old file which is used for new messages. Files are named -%% numerically ascending, thus the file with the lowest name is the -%% eldest file. -%% -%% We need to keep track of which messages are in which files (this is -%% the Index); how much useful data is in each file and which files -%% are on the left and right of each other. This is the purpose of the -%% FileSummary ets table. -%% -%% As messages are removed from files, holes appear in these -%% files. The field ValidTotalSize contains the total amount of useful -%% data left in the file. This is needed for garbage collection. -%% -%% When we discover that a file is now empty, we delete it. When we -%% discover that it can be combined with the useful data in either its -%% left or right neighbour, and overall, across all the files, we have -%% ((the amount of garbage) / (the sum of all file sizes)) > -%% ?GARBAGE_FRACTION, we start a garbage collection run concurrently, -%% which will compact the two files together. This keeps disk -%% utilisation high and aids performance. We deliberately do this -%% lazily in order to prevent doing GC on files which are soon to be -%% emptied (and hence deleted) soon. -%% -%% Given the compaction between two files, the left file (i.e. elder -%% file) is considered the ultimate destination for the good data in -%% the right file. If necessary, the good data in the left file which -%% is fragmented throughout the file is written out to a temporary -%% file, then read back in to form a contiguous chunk of good data at -%% the start of the left file. Thus the left file is garbage collected -%% and compacted. Then the good data from the right file is copied -%% onto the end of the left file. Index and FileSummary tables are -%% updated. -%% -%% On non-clean startup, we scan the files we discover, dealing with -%% the possibilites of a crash having occured during a compaction -%% (this consists of tidyup - the compaction is deliberately designed -%% such that data is duplicated on disk rather than risking it being -%% lost), and rebuild the FileSummary ets table and Index. -%% -%% So, with this design, messages move to the left. Eventually, they -%% should end up in a contiguous block on the left and are then never -%% rewritten. But this isn't quite the case. If in a file there is one -%% message that is being ignored, for some reason, and messages in the -%% file to the right and in the current block are being read all the -%% time then it will repeatedly be the case that the good data from -%% both files can be combined and will be written out to a new -%% file. Whenever this happens, our shunned message will be rewritten. -%% -%% So, provided that we combine messages in the right order, -%% (i.e. left file, bottom to top, right file, bottom to top), -%% eventually our shunned message will end up at the bottom of the -%% left file. The compaction/combining algorithm is smart enough to -%% read in good data from the left file that is scattered throughout -%% (i.e. C and D in the below diagram), then truncate the file to just -%% above B (i.e. truncate to the limit of the good contiguous region -%% at the start of the file), then write C and D on top and then write -%% E, F and G from the right file on top. Thus contiguous blocks of -%% good data at the bottom of files are not rewritten. -%% -%% +-------+ +-------+ +-------+ -%% | X | | G | | G | -%% +-------+ +-------+ +-------+ -%% | D | | X | | F | -%% +-------+ +-------+ +-------+ -%% | X | | X | | E | -%% +-------+ +-------+ +-------+ -%% | C | | F | ===> | D | -%% +-------+ +-------+ +-------+ -%% | X | | X | | C | -%% +-------+ +-------+ +-------+ -%% | B | | X | | B | -%% +-------+ +-------+ +-------+ -%% | A | | E | | A | -%% +-------+ +-------+ +-------+ -%% left right left -%% -%% From this reasoning, we do have a bound on the number of times the -%% message is rewritten. From when it is inserted, there can be no -%% files inserted between it and the head of the queue, and the worst -%% case is that everytime it is rewritten, it moves one position lower -%% in the file (for it to stay at the same position requires that -%% there are no holes beneath it, which means truncate would be used -%% and so it would not be rewritten at all). Thus this seems to -%% suggest the limit is the number of messages ahead of it in the -%% queue, though it's likely that that's pessimistic, given the -%% requirements for compaction/combination of files. -%% -%% The other property is that we have is the bound on the lowest -%% utilisation, which should be 50% - worst case is that all files are -%% fractionally over half full and can't be combined (equivalent is -%% alternating full files and files with only one tiny message in -%% them). -%% -%% Messages are reference-counted. When a message with the same msg id -%% is written several times we only store it once, and only remove it -%% from the store when it has been removed the same number of times. -%% -%% The reference counts do not persist. Therefore the initialisation -%% function must be provided with a generator that produces ref count -%% deltas for all recovered messages. This is only used on startup -%% when the shutdown was non-clean. -%% -%% Read messages with a reference count greater than one are entered -%% into a message cache. The purpose of the cache is not especially -%% performance, though it can help there too, but prevention of memory -%% explosion. It ensures that as messages with a high reference count -%% are read from several processes they are read back as the same -%% binary object rather than multiples of identical binary -%% objects. -%% -%% Reads can be performed directly by clients without calling to the -%% server. This is safe because multiple file handles can be used to -%% read files. However, locking is used by the concurrent GC to make -%% sure that reads are not attempted from files which are in the -%% process of being garbage collected. -%% -%% When a message is removed, its reference count is decremented. Even -%% if the reference count becomes 0, its entry is not removed. This is -%% because in the event of the same message being sent to several -%% different queues, there is the possibility of one queue writing and -%% removing the message before other queues write it at all. Thus -%% accomodating 0-reference counts allows us to avoid unnecessary -%% writes here. Of course, there are complications: the file to which -%% the message has already been written could be locked pending -%% deletion or GC, which means we have to rewrite the message as the -%% original copy will now be lost. -%% -%% The server automatically defers reads, removes and contains calls -%% that occur which refer to files which are currently being -%% GC'd. Contains calls are only deferred in order to ensure they do -%% not overtake removes. -%% -%% The current file to which messages are being written has a -%% write-back cache. This is written to immediately by clients and can -%% be read from by clients too. This means that there are only ever -%% writes made to the current file, thus eliminating delays due to -%% flushing write buffers in order to be able to safely read from the -%% current file. The one exception to this is that on start up, the -%% cache is not populated with msgs found in the current file, and -%% thus in this case only, reads may have to come from the file -%% itself. The effect of this is that even if the msg_store process is -%% heavily overloaded, clients can still write and read messages with -%% very low latency and not block at all. -%% -%% Clients of the msg_store are required to register before using the -%% msg_store. This provides them with the necessary client-side state -%% to allow them to directly access the various caches and files. When -%% they terminate, they should deregister. They can do this by calling -%% either client_terminate/1 or client_delete_and_terminate/1. The -%% differences are: (a) client_terminate is synchronous. As a result, -%% if the msg_store is badly overloaded and has lots of in-flight -%% writes and removes to process, this will take some time to -%% return. However, once it does return, you can be sure that all the -%% actions you've issued to the msg_store have been processed. (b) Not -%% only is client_delete_and_terminate/1 asynchronous, but it also -%% permits writes and subsequent removes from the current -%% (terminating) client which are still in flight to be safely -%% ignored. Thus from the point of view of the msg_store itself, and -%% all from the same client: -%% -%% (T) = termination; (WN) = write of msg N; (RN) = remove of msg N -%% --> W1, W2, W1, R1, T, W3, R2, W2, R1, R2, R3, W4 --> -%% -%% The client obviously sent T after all the other messages (up to -%% W4), but because the msg_store prioritises messages, the T can be -%% promoted and thus received early. -%% -%% Thus at the point of the msg_store receiving T, we have messages 1 -%% and 2 with a refcount of 1. After T, W3 will be ignored because -%% it's an unknown message, as will R3, and W4. W2, R1 and R2 won't be -%% ignored because the messages that they refer to were already known -%% to the msg_store prior to T. However, it can be a little more -%% complex: after the first R2, the refcount of msg 2 is 0. At that -%% point, if a GC occurs or file deletion, msg 2 could vanish, which -%% would then mean that the subsequent W2 and R2 are then ignored. -%% -%% The use case then for client_delete_and_terminate/1 is if the -%% client wishes to remove everything it's written to the msg_store: -%% it issues removes for all messages it's written and not removed, -%% and then calls client_delete_and_terminate/1. At that point, any -%% in-flight writes (and subsequent removes) can be ignored, but -%% removes and writes for messages the msg_store already knows about -%% will continue to be processed normally (which will normally just -%% involve modifying the reference count, which is fast). Thus we save -%% disk bandwidth for writes which are going to be immediately removed -%% again by the the terminating client. -%% -%% We use a separate set to keep track of the dying clients in order -%% to keep that set, which is inspected on every write and remove, as -%% small as possible. Inspecting the set of all clients would degrade -%% performance with many healthy clients and few, if any, dying -%% clients, which is the typical case. -%% -%% When the msg_store has a backlog (i.e. it has unprocessed messages -%% in its mailbox / gen_server priority queue), a further optimisation -%% opportunity arises: we can eliminate pairs of 'write' and 'remove' -%% from the same client for the same message. A typical occurrence of -%% these is when an empty durable queue delivers persistent messages -%% to ack'ing consumers. The queue will asynchronously ask the -%% msg_store to 'write' such messages, and when they are acknowledged -%% it will issue a 'remove'. That 'remove' may be issued before the -%% msg_store has processed the 'write'. There is then no point going -%% ahead with the processing of that 'write'. -%% -%% To detect this situation a 'flying_ets' table is shared between the -%% clients and the server. The table is keyed on the combination of -%% client (reference) and msg id, and the value represents an -%% integration of all the writes and removes currently "in flight" for -%% that message between the client and server - '+1' means all the -%% writes/removes add up to a single 'write', '-1' to a 'remove', and -%% '0' to nothing. (NB: the integration can never add up to more than -%% one 'write' or 'read' since clients must not write/remove a message -%% more than once without first removing/writing it). -%% -%% Maintaining this table poses two challenges: 1) both the clients -%% and the server access and update the table, which causes -%% concurrency issues, 2) we must ensure that entries do not stay in -%% the table forever, since that would constitute a memory leak. We -%% address the former by carefully modelling all operations as -%% sequences of atomic actions that produce valid results in all -%% possible interleavings. We address the latter by deleting table -%% entries whenever the server finds a 0-valued entry during the -%% processing of a write/remove. 0 is essentially equivalent to "no -%% entry". If, OTOH, the value is non-zero we know there is at least -%% one other 'write' or 'remove' in flight, so we get an opportunity -%% later to delete the table entry when processing these. -%% -%% There are two further complications. We need to ensure that 1) -%% eliminated writes still get confirmed, and 2) the write-back cache -%% doesn't grow unbounded. These are quite straightforward to -%% address. See the comments in the code. -%% -%% For notes on Clean Shutdown and startup, see documentation in -%% variable_queue. - -%%---------------------------------------------------------------------------- -%% public API -%%---------------------------------------------------------------------------- - -start_link(Server, Dir, ClientRefs, StartupFunState) -> - gen_server2:start_link({local, Server}, ?MODULE, - [Server, Dir, ClientRefs, StartupFunState], - [{timeout, infinity}]). - -successfully_recovered_state(Server) -> - gen_server2:call(Server, successfully_recovered_state, infinity). - -client_init(Server, Ref, MsgOnDiskFun, CloseFDsFun) -> - {IState, IModule, Dir, GCPid, - FileHandlesEts, FileSummaryEts, CurFileCacheEts, FlyingEts} = - gen_server2:call( - Server, {new_client_state, Ref, self(), MsgOnDiskFun, CloseFDsFun}, - infinity), - #client_msstate { server = Server, - client_ref = Ref, - file_handle_cache = dict:new(), - index_state = IState, - index_module = IModule, - dir = Dir, - gc_pid = GCPid, - file_handles_ets = FileHandlesEts, - file_summary_ets = FileSummaryEts, - cur_file_cache_ets = CurFileCacheEts, - flying_ets = FlyingEts }. - -client_terminate(CState = #client_msstate { client_ref = Ref }) -> - close_all_handles(CState), - ok = server_call(CState, {client_terminate, Ref}). - -client_delete_and_terminate(CState = #client_msstate { client_ref = Ref }) -> - close_all_handles(CState), - ok = server_cast(CState, {client_dying, Ref}), - ok = server_cast(CState, {client_delete, Ref}). - -client_ref(#client_msstate { client_ref = Ref }) -> Ref. - -write_flow(MsgId, Msg, CState = #client_msstate { server = Server }) -> - credit_flow:send(whereis(Server), ?CREDIT_DISC_BOUND), - client_write(MsgId, Msg, flow, CState). - -write(MsgId, Msg, CState) -> client_write(MsgId, Msg, noflow, CState). - -read(MsgId, - CState = #client_msstate { cur_file_cache_ets = CurFileCacheEts }) -> - %% Check the cur file cache - case ets:lookup(CurFileCacheEts, MsgId) of - [] -> - Defer = fun() -> {server_call(CState, {read, MsgId}), CState} end, - case index_lookup_positive_ref_count(MsgId, CState) of - not_found -> Defer(); - MsgLocation -> client_read1(MsgLocation, Defer, CState) - end; - [{MsgId, Msg, _CacheRefCount}] -> - {{ok, Msg}, CState} - end. - -contains(MsgId, CState) -> server_call(CState, {contains, MsgId}). -remove([], _CState) -> ok; -remove(MsgIds, CState = #client_msstate { client_ref = CRef }) -> - [client_update_flying(-1, MsgId, CState) || MsgId <- MsgIds], - server_cast(CState, {remove, CRef, MsgIds}). - -set_maximum_since_use(Server, Age) -> - gen_server2:cast(Server, {set_maximum_since_use, Age}). - -%%---------------------------------------------------------------------------- -%% Client-side-only helpers -%%---------------------------------------------------------------------------- - -server_call(#client_msstate { server = Server }, Msg) -> - gen_server2:call(Server, Msg, infinity). - -server_cast(#client_msstate { server = Server }, Msg) -> - gen_server2:cast(Server, Msg). - -client_write(MsgId, Msg, Flow, - CState = #client_msstate { cur_file_cache_ets = CurFileCacheEts, - client_ref = CRef }) -> - ok = client_update_flying(+1, MsgId, CState), - ok = update_msg_cache(CurFileCacheEts, MsgId, Msg), - ok = server_cast(CState, {write, CRef, MsgId, Flow}). - -client_read1(#msg_location { msg_id = MsgId, file = File } = MsgLocation, Defer, - CState = #client_msstate { file_summary_ets = FileSummaryEts }) -> - case ets:lookup(FileSummaryEts, File) of - [] -> %% File has been GC'd and no longer exists. Go around again. - read(MsgId, CState); - [#file_summary { locked = Locked, right = Right }] -> - client_read2(Locked, Right, MsgLocation, Defer, CState) - end. - -client_read2(false, undefined, _MsgLocation, Defer, _CState) -> - %% Although we've already checked both caches and not found the - %% message there, the message is apparently in the - %% current_file. We can only arrive here if we are trying to read - %% a message which we have not written, which is very odd, so just - %% defer. - %% - %% OR, on startup, the cur_file_cache is not populated with the - %% contents of the current file, thus reads from the current file - %% will end up here and will need to be deferred. - Defer(); -client_read2(true, _Right, _MsgLocation, Defer, _CState) -> - %% Of course, in the mean time, the GC could have run and our msg - %% is actually in a different file, unlocked. However, defering is - %% the safest and simplest thing to do. - Defer(); -client_read2(false, _Right, - MsgLocation = #msg_location { msg_id = MsgId, file = File }, - Defer, - CState = #client_msstate { file_summary_ets = FileSummaryEts }) -> - %% It's entirely possible that everything we're doing from here on - %% is for the wrong file, or a non-existent file, as a GC may have - %% finished. - safe_ets_update_counter( - FileSummaryEts, File, {#file_summary.readers, +1}, - fun (_) -> client_read3(MsgLocation, Defer, CState) end, - fun () -> read(MsgId, CState) end). - -client_read3(#msg_location { msg_id = MsgId, file = File }, Defer, - CState = #client_msstate { file_handles_ets = FileHandlesEts, - file_summary_ets = FileSummaryEts, - gc_pid = GCPid, - client_ref = Ref }) -> - Release = - fun() -> ok = case ets:update_counter(FileSummaryEts, File, - {#file_summary.readers, -1}) of - 0 -> case ets:lookup(FileSummaryEts, File) of - [#file_summary { locked = true }] -> - rabbit_msg_store_gc:no_readers( - GCPid, File); - _ -> ok - end; - _ -> ok - end - end, - %% If a GC involving the file hasn't already started, it won't - %% start now. Need to check again to see if we've been locked in - %% the meantime, between lookup and update_counter (thus GC - %% started before our +1. In fact, it could have finished by now - %% too). - case ets:lookup(FileSummaryEts, File) of - [] -> %% GC has deleted our file, just go round again. - read(MsgId, CState); - [#file_summary { locked = true }] -> - %% If we get a badarg here, then the GC has finished and - %% deleted our file. Try going around again. Otherwise, - %% just defer. - %% - %% badarg scenario: we lookup, msg_store locks, GC starts, - %% GC ends, we +1 readers, msg_store ets:deletes (and - %% unlocks the dest) - try Release(), - Defer() - catch error:badarg -> read(MsgId, CState) - end; - [#file_summary { locked = false }] -> - %% Ok, we're definitely safe to continue - a GC involving - %% the file cannot start up now, and isn't running, so - %% nothing will tell us from now on to close the handle if - %% it's already open. - %% - %% Finally, we need to recheck that the msg is still at - %% the same place - it's possible an entire GC ran between - %% us doing the lookup and the +1 on the readers. (Same as - %% badarg scenario above, but we don't have a missing file - %% - we just have the /wrong/ file). - case index_lookup(MsgId, CState) of - #msg_location { file = File } = MsgLocation -> - %% Still the same file. - {ok, CState1} = close_all_indicated(CState), - %% We are now guaranteed that the mark_handle_open - %% call will either insert_new correctly, or will - %% fail, but find the value is open, not close. - mark_handle_open(FileHandlesEts, File, Ref), - %% Could the msg_store now mark the file to be - %% closed? No: marks for closing are issued only - %% when the msg_store has locked the file. - %% This will never be the current file - {Msg, CState2} = read_from_disk(MsgLocation, CState1), - Release(), %% this MUST NOT fail with badarg - {{ok, Msg}, CState2}; - #msg_location {} = MsgLocation -> %% different file! - Release(), %% this MUST NOT fail with badarg - client_read1(MsgLocation, Defer, CState); - not_found -> %% it seems not to exist. Defer, just to be sure. - try Release() %% this can badarg, same as locked case, above - catch error:badarg -> ok - end, - Defer() - end - end. - -client_update_flying(Diff, MsgId, #client_msstate { flying_ets = FlyingEts, - client_ref = CRef }) -> - Key = {MsgId, CRef}, - case ets:insert_new(FlyingEts, {Key, Diff}) of - true -> ok; - false -> try ets:update_counter(FlyingEts, Key, {2, Diff}) of - 0 -> ok; - Diff -> ok; - Err -> throw({bad_flying_ets_update, Diff, Err, Key}) - catch error:badarg -> - %% this is guaranteed to succeed since the - %% server only removes and updates flying_ets - %% entries; it never inserts them - true = ets:insert_new(FlyingEts, {Key, Diff}) - end, - ok - end. - -clear_client(CRef, State = #msstate { cref_to_msg_ids = CTM, - dying_clients = DyingClients }) -> - State #msstate { cref_to_msg_ids = dict:erase(CRef, CTM), - dying_clients = sets:del_element(CRef, DyingClients) }. - - -%%---------------------------------------------------------------------------- -%% gen_server callbacks -%%---------------------------------------------------------------------------- - -init([Server, BaseDir, ClientRefs, StartupFunState]) -> - process_flag(trap_exit, true), - - ok = file_handle_cache:register_callback(?MODULE, set_maximum_since_use, - [self()]), - - Dir = filename:join(BaseDir, atom_to_list(Server)), - - {ok, IndexModule} = application:get_env(msg_store_index_module), - rabbit_log:info("~w: using ~p to provide index~n", [Server, IndexModule]), - - AttemptFileSummaryRecovery = - case ClientRefs of - undefined -> ok = rabbit_file:recursive_delete([Dir]), - ok = filelib:ensure_dir(filename:join(Dir, "nothing")), - false; - _ -> ok = filelib:ensure_dir(filename:join(Dir, "nothing")), - recover_crashed_compactions(Dir) - end, - - %% if we found crashed compactions we trust neither the - %% file_summary nor the location index. Note the file_summary is - %% left empty here if it can't be recovered. - {FileSummaryRecovered, FileSummaryEts} = - recover_file_summary(AttemptFileSummaryRecovery, Dir), - - {CleanShutdown, IndexState, ClientRefs1} = - recover_index_and_client_refs(IndexModule, FileSummaryRecovered, - ClientRefs, Dir, Server), - Clients = dict:from_list( - [{CRef, {undefined, undefined, undefined}} || - CRef <- ClientRefs1]), - %% CleanShutdown => msg location index and file_summary both - %% recovered correctly. - true = case {FileSummaryRecovered, CleanShutdown} of - {true, false} -> ets:delete_all_objects(FileSummaryEts); - _ -> true - end, - %% CleanShutdown <=> msg location index and file_summary both - %% recovered correctly. - - FileHandlesEts = ets:new(rabbit_msg_store_shared_file_handles, - [ordered_set, public]), - CurFileCacheEts = ets:new(rabbit_msg_store_cur_file, [set, public]), - FlyingEts = ets:new(rabbit_msg_store_flying, [set, public]), - - {ok, FileSizeLimit} = application:get_env(msg_store_file_size_limit), - - {ok, GCPid} = rabbit_msg_store_gc:start_link( - #gc_state { dir = Dir, - index_module = IndexModule, - index_state = IndexState, - file_summary_ets = FileSummaryEts, - file_handles_ets = FileHandlesEts, - msg_store = self() - }), - - State = #msstate { dir = Dir, - index_module = IndexModule, - index_state = IndexState, - current_file = 0, - current_file_handle = undefined, - file_handle_cache = dict:new(), - sync_timer_ref = undefined, - sum_valid_data = 0, - sum_file_size = 0, - pending_gc_completion = orddict:new(), - gc_pid = GCPid, - file_handles_ets = FileHandlesEts, - file_summary_ets = FileSummaryEts, - cur_file_cache_ets = CurFileCacheEts, - flying_ets = FlyingEts, - dying_clients = sets:new(), - clients = Clients, - successfully_recovered = CleanShutdown, - file_size_limit = FileSizeLimit, - cref_to_msg_ids = dict:new() - }, - - %% If we didn't recover the msg location index then we need to - %% rebuild it now. - {Offset, State1 = #msstate { current_file = CurFile }} = - build_index(CleanShutdown, StartupFunState, State), - - %% read is only needed so that we can seek - {ok, CurHdl} = open_file(Dir, filenum_to_name(CurFile), - [read | ?WRITE_MODE]), - {ok, Offset} = file_handle_cache:position(CurHdl, Offset), - ok = file_handle_cache:truncate(CurHdl), - - {ok, maybe_compact(State1 #msstate { current_file_handle = CurHdl }), - hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. - -prioritise_call(Msg, _From, _Len, _State) -> - case Msg of - successfully_recovered_state -> 7; - {new_client_state, _Ref, _Pid, _MODC, _CloseFDsFun} -> 7; - {read, _MsgId} -> 2; - _ -> 0 - end. - -prioritise_cast(Msg, _Len, _State) -> - case Msg of - {combine_files, _Source, _Destination, _Reclaimed} -> 8; - {delete_file, _File, _Reclaimed} -> 8; - {set_maximum_since_use, _Age} -> 8; - {client_dying, _Pid} -> 7; - _ -> 0 - end. - -prioritise_info(Msg, _Len, _State) -> - case Msg of - sync -> 8; - _ -> 0 - end. - -handle_call(successfully_recovered_state, _From, State) -> - reply(State #msstate.successfully_recovered, State); - -handle_call({new_client_state, CRef, CPid, MsgOnDiskFun, CloseFDsFun}, _From, - State = #msstate { dir = Dir, - index_state = IndexState, - index_module = IndexModule, - file_handles_ets = FileHandlesEts, - file_summary_ets = FileSummaryEts, - cur_file_cache_ets = CurFileCacheEts, - flying_ets = FlyingEts, - clients = Clients, - gc_pid = GCPid }) -> - Clients1 = dict:store(CRef, {CPid, MsgOnDiskFun, CloseFDsFun}, Clients), - erlang:monitor(process, CPid), - reply({IndexState, IndexModule, Dir, GCPid, FileHandlesEts, FileSummaryEts, - CurFileCacheEts, FlyingEts}, - State #msstate { clients = Clients1 }); - -handle_call({client_terminate, CRef}, _From, State) -> - reply(ok, clear_client(CRef, State)); - -handle_call({read, MsgId}, From, State) -> - State1 = read_message(MsgId, From, State), - noreply(State1); - -handle_call({contains, MsgId}, From, State) -> - State1 = contains_message(MsgId, From, State), - noreply(State1). - -handle_cast({client_dying, CRef}, - State = #msstate { dying_clients = DyingClients }) -> - DyingClients1 = sets:add_element(CRef, DyingClients), - noreply(write_message(CRef, <<>>, - State #msstate { dying_clients = DyingClients1 })); - -handle_cast({client_delete, CRef}, - State = #msstate { clients = Clients }) -> - State1 = State #msstate { clients = dict:erase(CRef, Clients) }, - noreply(remove_message(CRef, CRef, clear_client(CRef, State1))); - -handle_cast({write, CRef, MsgId, Flow}, - State = #msstate { cur_file_cache_ets = CurFileCacheEts, - clients = Clients }) -> - case Flow of - flow -> {CPid, _, _} = dict:fetch(CRef, Clients), - credit_flow:ack(CPid, ?CREDIT_DISC_BOUND); - noflow -> ok - end, - true = 0 =< ets:update_counter(CurFileCacheEts, MsgId, {3, -1}), - case update_flying(-1, MsgId, CRef, State) of - process -> - [{MsgId, Msg, _PWC}] = ets:lookup(CurFileCacheEts, MsgId), - noreply(write_message(MsgId, Msg, CRef, State)); - ignore -> - %% A 'remove' has already been issued and eliminated the - %% 'write'. - State1 = blind_confirm(CRef, gb_sets:singleton(MsgId), - ignored, State), - %% If all writes get eliminated, cur_file_cache_ets could - %% grow unbounded. To prevent that we delete the cache - %% entry here, but only if the message isn't in the - %% current file. That way reads of the message can - %% continue to be done client side, from either the cache - %% or the non-current files. If the message *is* in the - %% current file then the cache entry will be removed by - %% the normal logic for that in write_message/4 and - %% maybe_roll_to_new_file/2. - case index_lookup(MsgId, State1) of - [#msg_location { file = File }] - when File == State1 #msstate.current_file -> - ok; - _ -> - true = ets:match_delete(CurFileCacheEts, {MsgId, '_', 0}) - end, - noreply(State1) - end; - -handle_cast({remove, CRef, MsgIds}, State) -> - {RemovedMsgIds, State1} = - lists:foldl( - fun (MsgId, {Removed, State2}) -> - case update_flying(+1, MsgId, CRef, State2) of - process -> {[MsgId | Removed], - remove_message(MsgId, CRef, State2)}; - ignore -> {Removed, State2} - end - end, {[], State}, MsgIds), - noreply(maybe_compact(client_confirm(CRef, gb_sets:from_list(RemovedMsgIds), - ignored, State1))); - -handle_cast({combine_files, Source, Destination, Reclaimed}, - State = #msstate { sum_file_size = SumFileSize, - file_handles_ets = FileHandlesEts, - file_summary_ets = FileSummaryEts, - clients = Clients }) -> - ok = cleanup_after_file_deletion(Source, State), - %% see comment in cleanup_after_file_deletion, and client_read3 - true = mark_handle_to_close(Clients, FileHandlesEts, Destination, false), - true = ets:update_element(FileSummaryEts, Destination, - {#file_summary.locked, false}), - State1 = State #msstate { sum_file_size = SumFileSize - Reclaimed }, - noreply(maybe_compact(run_pending([Source, Destination], State1))); - -handle_cast({delete_file, File, Reclaimed}, - State = #msstate { sum_file_size = SumFileSize }) -> - ok = cleanup_after_file_deletion(File, State), - State1 = State #msstate { sum_file_size = SumFileSize - Reclaimed }, - noreply(maybe_compact(run_pending([File], State1))); - -handle_cast({set_maximum_since_use, Age}, State) -> - ok = file_handle_cache:set_maximum_since_use(Age), - noreply(State). - -handle_info(sync, State) -> - noreply(internal_sync(State)); - -handle_info(timeout, State) -> - noreply(internal_sync(State)); - -handle_info({'DOWN', _MRef, process, Pid, _Reason}, State) -> - credit_flow:peer_down(Pid), - noreply(State); - -handle_info({'EXIT', _Pid, Reason}, State) -> - {stop, Reason, State}. - -terminate(_Reason, State = #msstate { index_state = IndexState, - index_module = IndexModule, - current_file_handle = CurHdl, - gc_pid = GCPid, - file_handles_ets = FileHandlesEts, - file_summary_ets = FileSummaryEts, - cur_file_cache_ets = CurFileCacheEts, - flying_ets = FlyingEts, - clients = Clients, - dir = Dir }) -> - %% stop the gc first, otherwise it could be working and we pull - %% out the ets tables from under it. - ok = rabbit_msg_store_gc:stop(GCPid), - State1 = case CurHdl of - undefined -> State; - _ -> State2 = internal_sync(State), - ok = file_handle_cache:close(CurHdl), - State2 - end, - State3 = close_all_handles(State1), - ok = store_file_summary(FileSummaryEts, Dir), - [true = ets:delete(T) || T <- [FileSummaryEts, FileHandlesEts, - CurFileCacheEts, FlyingEts]], - IndexModule:terminate(IndexState), - ok = store_recovery_terms([{client_refs, dict:fetch_keys(Clients)}, - {index_module, IndexModule}], Dir), - State3 #msstate { index_state = undefined, - current_file_handle = undefined }. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ). - -%%---------------------------------------------------------------------------- -%% general helper functions -%%---------------------------------------------------------------------------- - -noreply(State) -> - {State1, Timeout} = next_state(State), - {noreply, State1, Timeout}. - -reply(Reply, State) -> - {State1, Timeout} = next_state(State), - {reply, Reply, State1, Timeout}. - -next_state(State = #msstate { sync_timer_ref = undefined, - cref_to_msg_ids = CTM }) -> - case dict:size(CTM) of - 0 -> {State, hibernate}; - _ -> {start_sync_timer(State), 0} - end; -next_state(State = #msstate { cref_to_msg_ids = CTM }) -> - case dict:size(CTM) of - 0 -> {stop_sync_timer(State), hibernate}; - _ -> {State, 0} - end. - -start_sync_timer(State) -> - rabbit_misc:ensure_timer(State, #msstate.sync_timer_ref, - ?SYNC_INTERVAL, sync). - -stop_sync_timer(State) -> - rabbit_misc:stop_timer(State, #msstate.sync_timer_ref). - -internal_sync(State = #msstate { current_file_handle = CurHdl, - cref_to_msg_ids = CTM }) -> - State1 = stop_sync_timer(State), - CGs = dict:fold(fun (CRef, MsgIds, NS) -> - case gb_sets:is_empty(MsgIds) of - true -> NS; - false -> [{CRef, MsgIds} | NS] - end - end, [], CTM), - ok = case CGs of - [] -> ok; - _ -> file_handle_cache:sync(CurHdl) - end, - lists:foldl(fun ({CRef, MsgIds}, StateN) -> - client_confirm(CRef, MsgIds, written, StateN) - end, State1, CGs). - -update_flying(Diff, MsgId, CRef, #msstate { flying_ets = FlyingEts }) -> - Key = {MsgId, CRef}, - NDiff = -Diff, - case ets:lookup(FlyingEts, Key) of - [] -> ignore; - [{_, Diff}] -> ignore; %% [1] - [{_, NDiff}] -> ets:update_counter(FlyingEts, Key, {2, Diff}), - true = ets:delete_object(FlyingEts, {Key, 0}), - process; - [{_, 0}] -> true = ets:delete_object(FlyingEts, {Key, 0}), - ignore; - [{_, Err}] -> throw({bad_flying_ets_record, Diff, Err, Key}) - end. -%% [1] We can get here, for example, in the following scenario: There -%% is a write followed by a remove in flight. The counter will be 0, -%% so on processing the write the server attempts to delete the -%% entry. If at that point the client injects another write it will -%% either insert a new entry, containing +1, or increment the existing -%% entry to +1, thus preventing its removal. Either way therefore when -%% the server processes the read, the counter will be +1. - -write_action({true, not_found}, _MsgId, State) -> - {ignore, undefined, State}; -write_action({true, #msg_location { file = File }}, _MsgId, State) -> - {ignore, File, State}; -write_action({false, not_found}, _MsgId, State) -> - {write, State}; -write_action({Mask, #msg_location { ref_count = 0, file = File, - total_size = TotalSize }}, - MsgId, State = #msstate { file_summary_ets = FileSummaryEts }) -> - case {Mask, ets:lookup(FileSummaryEts, File)} of - {false, [#file_summary { locked = true }]} -> - ok = index_delete(MsgId, State), - {write, State}; - {false_if_increment, [#file_summary { locked = true }]} -> - %% The msg for MsgId is older than the client death - %% message, but as it is being GC'd currently we'll have - %% to write a new copy, which will then be younger, so - %% ignore this write. - {ignore, File, State}; - {_Mask, [#file_summary {}]} -> - ok = index_update_ref_count(MsgId, 1, State), - State1 = adjust_valid_total_size(File, TotalSize, State), - {confirm, File, State1} - end; -write_action({_Mask, #msg_location { ref_count = RefCount, file = File }}, - MsgId, State) -> - ok = index_update_ref_count(MsgId, RefCount + 1, State), - %% We already know about it, just update counter. Only update - %% field otherwise bad interaction with concurrent GC - {confirm, File, State}. - -write_message(MsgId, Msg, CRef, - State = #msstate { cur_file_cache_ets = CurFileCacheEts }) -> - case write_action(should_mask_action(CRef, MsgId, State), MsgId, State) of - {write, State1} -> - write_message(MsgId, Msg, - record_pending_confirm(CRef, MsgId, State1)); - {ignore, CurFile, State1 = #msstate { current_file = CurFile }} -> - State1; - {ignore, _File, State1} -> - true = ets:delete_object(CurFileCacheEts, {MsgId, Msg, 0}), - State1; - {confirm, CurFile, State1 = #msstate { current_file = CurFile }}-> - record_pending_confirm(CRef, MsgId, State1); - {confirm, _File, State1} -> - true = ets:delete_object(CurFileCacheEts, {MsgId, Msg, 0}), - update_pending_confirms( - fun (MsgOnDiskFun, CTM) -> - MsgOnDiskFun(gb_sets:singleton(MsgId), written), - CTM - end, CRef, State1) - end. - -remove_message(MsgId, CRef, - State = #msstate { file_summary_ets = FileSummaryEts }) -> - case should_mask_action(CRef, MsgId, State) of - {true, _Location} -> - State; - {false_if_increment, #msg_location { ref_count = 0 }} -> - %% CRef has tried to both write and remove this msg whilst - %% it's being GC'd. - %% - %% ASSERTION: [#file_summary { locked = true }] = - %% ets:lookup(FileSummaryEts, File), - State; - {_Mask, #msg_location { ref_count = RefCount, file = File, - total_size = TotalSize }} - when RefCount > 0 -> - %% only update field, otherwise bad interaction with - %% concurrent GC - Dec = fun () -> index_update_ref_count( - MsgId, RefCount - 1, State) end, - case RefCount of - %% don't remove from cur_file_cache_ets here because - %% there may be further writes in the mailbox for the - %% same msg. - 1 -> case ets:lookup(FileSummaryEts, File) of - [#file_summary { locked = true }] -> - add_to_pending_gc_completion( - {remove, MsgId, CRef}, File, State); - [#file_summary {}] -> - ok = Dec(), - delete_file_if_empty( - File, adjust_valid_total_size( - File, -TotalSize, State)) - end; - _ -> ok = Dec(), - State - end - end. - -write_message(MsgId, Msg, - State = #msstate { current_file_handle = CurHdl, - current_file = CurFile, - sum_valid_data = SumValid, - sum_file_size = SumFileSize, - file_summary_ets = FileSummaryEts }) -> - {ok, CurOffset} = file_handle_cache:current_virtual_offset(CurHdl), - {ok, TotalSize} = rabbit_msg_file:append(CurHdl, MsgId, Msg), - ok = index_insert( - #msg_location { msg_id = MsgId, ref_count = 1, file = CurFile, - offset = CurOffset, total_size = TotalSize }, State), - [#file_summary { right = undefined, locked = false }] = - ets:lookup(FileSummaryEts, CurFile), - [_,_] = ets:update_counter(FileSummaryEts, CurFile, - [{#file_summary.valid_total_size, TotalSize}, - {#file_summary.file_size, TotalSize}]), - maybe_roll_to_new_file(CurOffset + TotalSize, - State #msstate { - sum_valid_data = SumValid + TotalSize, - sum_file_size = SumFileSize + TotalSize }). - -read_message(MsgId, From, State) -> - case index_lookup_positive_ref_count(MsgId, State) of - not_found -> gen_server2:reply(From, not_found), - State; - MsgLocation -> read_message1(From, MsgLocation, State) - end. - -read_message1(From, #msg_location { msg_id = MsgId, file = File, - offset = Offset } = MsgLoc, - State = #msstate { current_file = CurFile, - current_file_handle = CurHdl, - file_summary_ets = FileSummaryEts, - cur_file_cache_ets = CurFileCacheEts }) -> - case File =:= CurFile of - true -> {Msg, State1} = - %% can return [] if msg in file existed on startup - case ets:lookup(CurFileCacheEts, MsgId) of - [] -> - {ok, RawOffSet} = - file_handle_cache:current_raw_offset(CurHdl), - ok = case Offset >= RawOffSet of - true -> file_handle_cache:flush(CurHdl); - false -> ok - end, - read_from_disk(MsgLoc, State); - [{MsgId, Msg1, _CacheRefCount}] -> - {Msg1, State} - end, - gen_server2:reply(From, {ok, Msg}), - State1; - false -> [#file_summary { locked = Locked }] = - ets:lookup(FileSummaryEts, File), - case Locked of - true -> add_to_pending_gc_completion({read, MsgId, From}, - File, State); - false -> {Msg, State1} = read_from_disk(MsgLoc, State), - gen_server2:reply(From, {ok, Msg}), - State1 - end - end. - -read_from_disk(#msg_location { msg_id = MsgId, file = File, offset = Offset, - total_size = TotalSize }, State) -> - {Hdl, State1} = get_read_handle(File, State), - {ok, Offset} = file_handle_cache:position(Hdl, Offset), - {ok, {MsgId, Msg}} = - case rabbit_msg_file:read(Hdl, TotalSize) of - {ok, {MsgId, _}} = Obj -> - Obj; - Rest -> - {error, {misread, [{old_state, State}, - {file_num, File}, - {offset, Offset}, - {msg_id, MsgId}, - {read, Rest}, - {proc_dict, get()} - ]}} - end, - {Msg, State1}. - -contains_message(MsgId, From, - State = #msstate { pending_gc_completion = Pending }) -> - case index_lookup_positive_ref_count(MsgId, State) of - not_found -> - gen_server2:reply(From, false), - State; - #msg_location { file = File } -> - case orddict:is_key(File, Pending) of - true -> add_to_pending_gc_completion( - {contains, MsgId, From}, File, State); - false -> gen_server2:reply(From, true), - State - end - end. - -add_to_pending_gc_completion( - Op, File, State = #msstate { pending_gc_completion = Pending }) -> - State #msstate { pending_gc_completion = - rabbit_misc:orddict_cons(File, Op, Pending) }. - -run_pending(Files, State) -> - lists:foldl( - fun (File, State1 = #msstate { pending_gc_completion = Pending }) -> - Pending1 = orddict:erase(File, Pending), - lists:foldl( - fun run_pending_action/2, - State1 #msstate { pending_gc_completion = Pending1 }, - lists:reverse(orddict:fetch(File, Pending))) - end, State, Files). - -run_pending_action({read, MsgId, From}, State) -> - read_message(MsgId, From, State); -run_pending_action({contains, MsgId, From}, State) -> - contains_message(MsgId, From, State); -run_pending_action({remove, MsgId, CRef}, State) -> - remove_message(MsgId, CRef, State). - -safe_ets_update_counter(Tab, Key, UpdateOp, SuccessFun, FailThunk) -> - try - SuccessFun(ets:update_counter(Tab, Key, UpdateOp)) - catch error:badarg -> FailThunk() - end. - -update_msg_cache(CacheEts, MsgId, Msg) -> - case ets:insert_new(CacheEts, {MsgId, Msg, 1}) of - true -> ok; - false -> safe_ets_update_counter( - CacheEts, MsgId, {3, +1}, fun (_) -> ok end, - fun () -> update_msg_cache(CacheEts, MsgId, Msg) end) - end. - -adjust_valid_total_size(File, Delta, State = #msstate { - sum_valid_data = SumValid, - file_summary_ets = FileSummaryEts }) -> - [_] = ets:update_counter(FileSummaryEts, File, - [{#file_summary.valid_total_size, Delta}]), - State #msstate { sum_valid_data = SumValid + Delta }. - -orddict_store(Key, Val, Dict) -> - false = orddict:is_key(Key, Dict), - orddict:store(Key, Val, Dict). - -update_pending_confirms(Fun, CRef, - State = #msstate { clients = Clients, - cref_to_msg_ids = CTM }) -> - case dict:fetch(CRef, Clients) of - {_CPid, undefined, _CloseFDsFun} -> State; - {_CPid, MsgOnDiskFun, _CloseFDsFun} -> CTM1 = Fun(MsgOnDiskFun, CTM), - State #msstate { - cref_to_msg_ids = CTM1 } - end. - -record_pending_confirm(CRef, MsgId, State) -> - update_pending_confirms( - fun (_MsgOnDiskFun, CTM) -> - dict:update(CRef, fun (MsgIds) -> gb_sets:add(MsgId, MsgIds) end, - gb_sets:singleton(MsgId), CTM) - end, CRef, State). - -client_confirm(CRef, MsgIds, ActionTaken, State) -> - update_pending_confirms( - fun (MsgOnDiskFun, CTM) -> - case dict:find(CRef, CTM) of - {ok, Gs} -> MsgOnDiskFun(gb_sets:intersection(Gs, MsgIds), - ActionTaken), - MsgIds1 = rabbit_misc:gb_sets_difference( - Gs, MsgIds), - case gb_sets:is_empty(MsgIds1) of - true -> dict:erase(CRef, CTM); - false -> dict:store(CRef, MsgIds1, CTM) - end; - error -> CTM - end - end, CRef, State). - -blind_confirm(CRef, MsgIds, ActionTaken, State) -> - update_pending_confirms( - fun (MsgOnDiskFun, CTM) -> MsgOnDiskFun(MsgIds, ActionTaken), CTM end, - CRef, State). - -%% Detect whether the MsgId is older or younger than the client's death -%% msg (if there is one). If the msg is older than the client death -%% msg, and it has a 0 ref_count we must only alter the ref_count, not -%% rewrite the msg - rewriting it would make it younger than the death -%% msg and thus should be ignored. Note that this (correctly) returns -%% false when testing to remove the death msg itself. -should_mask_action(CRef, MsgId, - State = #msstate { dying_clients = DyingClients }) -> - case {sets:is_element(CRef, DyingClients), index_lookup(MsgId, State)} of - {false, Location} -> - {false, Location}; - {true, not_found} -> - {true, not_found}; - {true, #msg_location { file = File, offset = Offset, - ref_count = RefCount } = Location} -> - #msg_location { file = DeathFile, offset = DeathOffset } = - index_lookup(CRef, State), - {case {{DeathFile, DeathOffset} < {File, Offset}, RefCount} of - {true, _} -> true; - {false, 0} -> false_if_increment; - {false, _} -> false - end, Location} - end. - -%%---------------------------------------------------------------------------- -%% file helper functions -%%---------------------------------------------------------------------------- - -open_file(Dir, FileName, Mode) -> - file_handle_cache:open(form_filename(Dir, FileName), ?BINARY_MODE ++ Mode, - [{write_buffer, ?HANDLE_CACHE_BUFFER_SIZE}, - {read_buffer, ?HANDLE_CACHE_BUFFER_SIZE}]). - -close_handle(Key, CState = #client_msstate { file_handle_cache = FHC }) -> - CState #client_msstate { file_handle_cache = close_handle(Key, FHC) }; - -close_handle(Key, State = #msstate { file_handle_cache = FHC }) -> - State #msstate { file_handle_cache = close_handle(Key, FHC) }; - -close_handle(Key, FHC) -> - case dict:find(Key, FHC) of - {ok, Hdl} -> ok = file_handle_cache:close(Hdl), - dict:erase(Key, FHC); - error -> FHC - end. - -mark_handle_open(FileHandlesEts, File, Ref) -> - %% This is fine to fail (already exists). Note it could fail with - %% the value being close, and not have it updated to open. - ets:insert_new(FileHandlesEts, {{Ref, File}, open}), - true. - -%% See comment in client_read3 - only call this when the file is locked -mark_handle_to_close(ClientRefs, FileHandlesEts, File, Invoke) -> - [ begin - case (ets:update_element(FileHandlesEts, Key, {2, close}) - andalso Invoke) of - true -> case dict:fetch(Ref, ClientRefs) of - {_CPid, _MsgOnDiskFun, undefined} -> - ok; - {_CPid, _MsgOnDiskFun, CloseFDsFun} -> - ok = CloseFDsFun() - end; - false -> ok - end - end || {{Ref, _File} = Key, open} <- - ets:match_object(FileHandlesEts, {{'_', File}, open}) ], - true. - -safe_file_delete_fun(File, Dir, FileHandlesEts) -> - fun () -> safe_file_delete(File, Dir, FileHandlesEts) end. - -safe_file_delete(File, Dir, FileHandlesEts) -> - %% do not match on any value - it's the absence of the row that - %% indicates the client has really closed the file. - case ets:match_object(FileHandlesEts, {{'_', File}, '_'}, 1) of - {[_|_], _Cont} -> false; - _ -> ok = file:delete( - form_filename(Dir, filenum_to_name(File))), - true - end. - -close_all_indicated(#client_msstate { file_handles_ets = FileHandlesEts, - client_ref = Ref } = - CState) -> - Objs = ets:match_object(FileHandlesEts, {{Ref, '_'}, close}), - {ok, lists:foldl(fun ({Key = {_Ref, File}, close}, CStateM) -> - true = ets:delete(FileHandlesEts, Key), - close_handle(File, CStateM) - end, CState, Objs)}. - -close_all_handles(CState = #client_msstate { file_handles_ets = FileHandlesEts, - file_handle_cache = FHC, - client_ref = Ref }) -> - ok = dict:fold(fun (File, Hdl, ok) -> - true = ets:delete(FileHandlesEts, {Ref, File}), - file_handle_cache:close(Hdl) - end, ok, FHC), - CState #client_msstate { file_handle_cache = dict:new() }; - -close_all_handles(State = #msstate { file_handle_cache = FHC }) -> - ok = dict:fold(fun (_Key, Hdl, ok) -> file_handle_cache:close(Hdl) end, - ok, FHC), - State #msstate { file_handle_cache = dict:new() }. - -get_read_handle(FileNum, CState = #client_msstate { file_handle_cache = FHC, - dir = Dir }) -> - {Hdl, FHC2} = get_read_handle(FileNum, FHC, Dir), - {Hdl, CState #client_msstate { file_handle_cache = FHC2 }}; - -get_read_handle(FileNum, State = #msstate { file_handle_cache = FHC, - dir = Dir }) -> - {Hdl, FHC2} = get_read_handle(FileNum, FHC, Dir), - {Hdl, State #msstate { file_handle_cache = FHC2 }}. - -get_read_handle(FileNum, FHC, Dir) -> - case dict:find(FileNum, FHC) of - {ok, Hdl} -> {Hdl, FHC}; - error -> {ok, Hdl} = open_file(Dir, filenum_to_name(FileNum), - ?READ_MODE), - {Hdl, dict:store(FileNum, Hdl, FHC)} - end. - -preallocate(Hdl, FileSizeLimit, FinalPos) -> - {ok, FileSizeLimit} = file_handle_cache:position(Hdl, FileSizeLimit), - ok = file_handle_cache:truncate(Hdl), - {ok, FinalPos} = file_handle_cache:position(Hdl, FinalPos), - ok. - -truncate_and_extend_file(Hdl, Lowpoint, Highpoint) -> - {ok, Lowpoint} = file_handle_cache:position(Hdl, Lowpoint), - ok = file_handle_cache:truncate(Hdl), - ok = preallocate(Hdl, Highpoint, Lowpoint). - -form_filename(Dir, Name) -> filename:join(Dir, Name). - -filenum_to_name(File) -> integer_to_list(File) ++ ?FILE_EXTENSION. - -filename_to_num(FileName) -> list_to_integer(filename:rootname(FileName)). - -list_sorted_filenames(Dir, Ext) -> - lists:sort(fun (A, B) -> filename_to_num(A) < filename_to_num(B) end, - filelib:wildcard("*" ++ Ext, Dir)). - -%%---------------------------------------------------------------------------- -%% index -%%---------------------------------------------------------------------------- - -index_lookup_positive_ref_count(Key, State) -> - case index_lookup(Key, State) of - not_found -> not_found; - #msg_location { ref_count = 0 } -> not_found; - #msg_location {} = MsgLocation -> MsgLocation - end. - -index_update_ref_count(Key, RefCount, State) -> - index_update_fields(Key, {#msg_location.ref_count, RefCount}, State). - -index_lookup(Key, #client_msstate { index_module = Index, - index_state = State }) -> - Index:lookup(Key, State); - -index_lookup(Key, #msstate { index_module = Index, index_state = State }) -> - Index:lookup(Key, State). - -index_insert(Obj, #msstate { index_module = Index, index_state = State }) -> - Index:insert(Obj, State). - -index_update(Obj, #msstate { index_module = Index, index_state = State }) -> - Index:update(Obj, State). - -index_update_fields(Key, Updates, #msstate { index_module = Index, - index_state = State }) -> - Index:update_fields(Key, Updates, State). - -index_delete(Key, #msstate { index_module = Index, index_state = State }) -> - Index:delete(Key, State). - -index_delete_by_file(File, #msstate { index_module = Index, - index_state = State }) -> - Index:delete_by_file(File, State). - -%%---------------------------------------------------------------------------- -%% shutdown and recovery -%%---------------------------------------------------------------------------- - -recover_index_and_client_refs(IndexModule, _Recover, undefined, Dir, _Server) -> - {false, IndexModule:new(Dir), []}; -recover_index_and_client_refs(IndexModule, false, _ClientRefs, Dir, Server) -> - rabbit_log:warning("~w: rebuilding indices from scratch~n", [Server]), - {false, IndexModule:new(Dir), []}; -recover_index_and_client_refs(IndexModule, true, ClientRefs, Dir, Server) -> - Fresh = fun (ErrorMsg, ErrorArgs) -> - rabbit_log:warning("~w: " ++ ErrorMsg ++ "~n" - "rebuilding indices from scratch~n", - [Server | ErrorArgs]), - {false, IndexModule:new(Dir), []} - end, - case read_recovery_terms(Dir) of - {false, Error} -> - Fresh("failed to read recovery terms: ~p", [Error]); - {true, Terms} -> - RecClientRefs = proplists:get_value(client_refs, Terms, []), - RecIndexModule = proplists:get_value(index_module, Terms), - case (lists:sort(ClientRefs) =:= lists:sort(RecClientRefs) - andalso IndexModule =:= RecIndexModule) of - true -> case IndexModule:recover(Dir) of - {ok, IndexState1} -> - {true, IndexState1, ClientRefs}; - {error, Error} -> - Fresh("failed to recover index: ~p", [Error]) - end; - false -> Fresh("recovery terms differ from present", []) - end - end. - -store_recovery_terms(Terms, Dir) -> - rabbit_file:write_term_file(filename:join(Dir, ?CLEAN_FILENAME), Terms). - -read_recovery_terms(Dir) -> - Path = filename:join(Dir, ?CLEAN_FILENAME), - case rabbit_file:read_term_file(Path) of - {ok, Terms} -> case file:delete(Path) of - ok -> {true, Terms}; - {error, Error} -> {false, Error} - end; - {error, Error} -> {false, Error} - end. - -store_file_summary(Tid, Dir) -> - ok = ets:tab2file(Tid, filename:join(Dir, ?FILE_SUMMARY_FILENAME), - [{extended_info, [object_count]}]). - -recover_file_summary(false, _Dir) -> - %% TODO: the only reason for this to be an *ordered*_set is so - %% that a) maybe_compact can start a traversal from the eldest - %% file, and b) build_index in fast recovery mode can easily - %% identify the current file. It's awkward to have both that - %% odering and the left/right pointers in the entries - replacing - %% the former with some additional bit of state would be easy, but - %% ditching the latter would be neater. - {false, ets:new(rabbit_msg_store_file_summary, - [ordered_set, public, {keypos, #file_summary.file}])}; -recover_file_summary(true, Dir) -> - Path = filename:join(Dir, ?FILE_SUMMARY_FILENAME), - case ets:file2tab(Path) of - {ok, Tid} -> ok = file:delete(Path), - {true, Tid}; - {error, _Error} -> recover_file_summary(false, Dir) - end. - -count_msg_refs(Gen, Seed, State) -> - case Gen(Seed) of - finished -> - ok; - {_MsgId, 0, Next} -> - count_msg_refs(Gen, Next, State); - {MsgId, Delta, Next} -> - ok = case index_lookup(MsgId, State) of - not_found -> - index_insert(#msg_location { msg_id = MsgId, - file = undefined, - ref_count = Delta }, - State); - #msg_location { ref_count = RefCount } = StoreEntry -> - NewRefCount = RefCount + Delta, - case NewRefCount of - 0 -> index_delete(MsgId, State); - _ -> index_update(StoreEntry #msg_location { - ref_count = NewRefCount }, - State) - end - end, - count_msg_refs(Gen, Next, State) - end. - -recover_crashed_compactions(Dir) -> - FileNames = list_sorted_filenames(Dir, ?FILE_EXTENSION), - TmpFileNames = list_sorted_filenames(Dir, ?FILE_EXTENSION_TMP), - lists:foreach( - fun (TmpFileName) -> - NonTmpRelatedFileName = - filename:rootname(TmpFileName) ++ ?FILE_EXTENSION, - true = lists:member(NonTmpRelatedFileName, FileNames), - ok = recover_crashed_compaction( - Dir, TmpFileName, NonTmpRelatedFileName) - end, TmpFileNames), - TmpFileNames == []. - -recover_crashed_compaction(Dir, TmpFileName, NonTmpRelatedFileName) -> - %% Because a msg can legitimately appear multiple times in the - %% same file, identifying the contents of the tmp file and where - %% they came from is non-trivial. If we are recovering a crashed - %% compaction then we will be rebuilding the index, which can cope - %% with duplicates appearing. Thus the simplest and safest thing - %% to do is to append the contents of the tmp file to its main - %% file. - {ok, TmpHdl} = open_file(Dir, TmpFileName, ?READ_MODE), - {ok, MainHdl} = open_file(Dir, NonTmpRelatedFileName, - ?READ_MODE ++ ?WRITE_MODE), - {ok, _End} = file_handle_cache:position(MainHdl, eof), - Size = filelib:file_size(form_filename(Dir, TmpFileName)), - {ok, Size} = file_handle_cache:copy(TmpHdl, MainHdl, Size), - ok = file_handle_cache:close(MainHdl), - ok = file_handle_cache:delete(TmpHdl), - ok. - -scan_file_for_valid_messages(Dir, FileName) -> - case open_file(Dir, FileName, ?READ_MODE) of - {ok, Hdl} -> Valid = rabbit_msg_file:scan( - Hdl, filelib:file_size( - form_filename(Dir, FileName)), - fun scan_fun/2, []), - ok = file_handle_cache:close(Hdl), - Valid; - {error, enoent} -> {ok, [], 0}; - {error, Reason} -> {error, {unable_to_scan_file, FileName, Reason}} - end. - -scan_fun({MsgId, TotalSize, Offset, _Msg}, Acc) -> - [{MsgId, TotalSize, Offset} | Acc]. - -%% Takes the list in *ascending* order (i.e. eldest message -%% first). This is the opposite of what scan_file_for_valid_messages -%% produces. The list of msgs that is produced is youngest first. -drop_contiguous_block_prefix(L) -> drop_contiguous_block_prefix(L, 0). - -drop_contiguous_block_prefix([], ExpectedOffset) -> - {ExpectedOffset, []}; -drop_contiguous_block_prefix([#msg_location { offset = ExpectedOffset, - total_size = TotalSize } | Tail], - ExpectedOffset) -> - ExpectedOffset1 = ExpectedOffset + TotalSize, - drop_contiguous_block_prefix(Tail, ExpectedOffset1); -drop_contiguous_block_prefix(MsgsAfterGap, ExpectedOffset) -> - {ExpectedOffset, MsgsAfterGap}. - -build_index(true, _StartupFunState, - State = #msstate { file_summary_ets = FileSummaryEts }) -> - ets:foldl( - fun (#file_summary { valid_total_size = ValidTotalSize, - file_size = FileSize, - file = File }, - {_Offset, State1 = #msstate { sum_valid_data = SumValid, - sum_file_size = SumFileSize }}) -> - {FileSize, State1 #msstate { - sum_valid_data = SumValid + ValidTotalSize, - sum_file_size = SumFileSize + FileSize, - current_file = File }} - end, {0, State}, FileSummaryEts); -build_index(false, {MsgRefDeltaGen, MsgRefDeltaGenInit}, - State = #msstate { dir = Dir }) -> - ok = count_msg_refs(MsgRefDeltaGen, MsgRefDeltaGenInit, State), - {ok, Pid} = gatherer:start_link(), - case [filename_to_num(FileName) || - FileName <- list_sorted_filenames(Dir, ?FILE_EXTENSION)] of - [] -> build_index(Pid, undefined, [State #msstate.current_file], - State); - Files -> {Offset, State1} = build_index(Pid, undefined, Files, State), - {Offset, lists:foldl(fun delete_file_if_empty/2, - State1, Files)} - end. - -build_index(Gatherer, Left, [], - State = #msstate { file_summary_ets = FileSummaryEts, - sum_valid_data = SumValid, - sum_file_size = SumFileSize }) -> - case gatherer:out(Gatherer) of - empty -> - unlink(Gatherer), - ok = gatherer:stop(Gatherer), - ok = index_delete_by_file(undefined, State), - Offset = case ets:lookup(FileSummaryEts, Left) of - [] -> 0; - [#file_summary { file_size = FileSize }] -> FileSize - end, - {Offset, State #msstate { current_file = Left }}; - {value, #file_summary { valid_total_size = ValidTotalSize, - file_size = FileSize } = FileSummary} -> - true = ets:insert_new(FileSummaryEts, FileSummary), - build_index(Gatherer, Left, [], - State #msstate { - sum_valid_data = SumValid + ValidTotalSize, - sum_file_size = SumFileSize + FileSize }) - end; -build_index(Gatherer, Left, [File|Files], State) -> - ok = gatherer:fork(Gatherer), - ok = worker_pool:submit_async( - fun () -> build_index_worker(Gatherer, State, - Left, File, Files) - end), - build_index(Gatherer, File, Files, State). - -build_index_worker(Gatherer, State = #msstate { dir = Dir }, - Left, File, Files) -> - {ok, Messages, FileSize} = - scan_file_for_valid_messages(Dir, filenum_to_name(File)), - {ValidMessages, ValidTotalSize} = - lists:foldl( - fun (Obj = {MsgId, TotalSize, Offset}, {VMAcc, VTSAcc}) -> - case index_lookup(MsgId, State) of - #msg_location { file = undefined } = StoreEntry -> - ok = index_update(StoreEntry #msg_location { - file = File, offset = Offset, - total_size = TotalSize }, - State), - {[Obj | VMAcc], VTSAcc + TotalSize}; - _ -> - {VMAcc, VTSAcc} - end - end, {[], 0}, Messages), - {Right, FileSize1} = - case Files of - %% if it's the last file, we'll truncate to remove any - %% rubbish above the last valid message. This affects the - %% file size. - [] -> {undefined, case ValidMessages of - [] -> 0; - _ -> {_MsgId, TotalSize, Offset} = - lists:last(ValidMessages), - Offset + TotalSize - end}; - [F|_] -> {F, FileSize} - end, - ok = gatherer:in(Gatherer, #file_summary { - file = File, - valid_total_size = ValidTotalSize, - left = Left, - right = Right, - file_size = FileSize1, - locked = false, - readers = 0 }), - ok = gatherer:finish(Gatherer). - -%%---------------------------------------------------------------------------- -%% garbage collection / compaction / aggregation -- internal -%%---------------------------------------------------------------------------- - -maybe_roll_to_new_file( - Offset, - State = #msstate { dir = Dir, - current_file_handle = CurHdl, - current_file = CurFile, - file_summary_ets = FileSummaryEts, - cur_file_cache_ets = CurFileCacheEts, - file_size_limit = FileSizeLimit }) - when Offset >= FileSizeLimit -> - State1 = internal_sync(State), - ok = file_handle_cache:close(CurHdl), - NextFile = CurFile + 1, - {ok, NextHdl} = open_file(Dir, filenum_to_name(NextFile), ?WRITE_MODE), - true = ets:insert_new(FileSummaryEts, #file_summary { - file = NextFile, - valid_total_size = 0, - left = CurFile, - right = undefined, - file_size = 0, - locked = false, - readers = 0 }), - true = ets:update_element(FileSummaryEts, CurFile, - {#file_summary.right, NextFile}), - true = ets:match_delete(CurFileCacheEts, {'_', '_', 0}), - maybe_compact(State1 #msstate { current_file_handle = NextHdl, - current_file = NextFile }); -maybe_roll_to_new_file(_, State) -> - State. - -maybe_compact(State = #msstate { sum_valid_data = SumValid, - sum_file_size = SumFileSize, - gc_pid = GCPid, - pending_gc_completion = Pending, - file_summary_ets = FileSummaryEts, - file_size_limit = FileSizeLimit }) - when SumFileSize > 2 * FileSizeLimit andalso - (SumFileSize - SumValid) / SumFileSize > ?GARBAGE_FRACTION -> - %% TODO: the algorithm here is sub-optimal - it may result in a - %% complete traversal of FileSummaryEts. - First = ets:first(FileSummaryEts), - case First =:= '$end_of_table' orelse - orddict:size(Pending) >= ?MAXIMUM_SIMULTANEOUS_GC_FILES of - true -> - State; - false -> - case find_files_to_combine(FileSummaryEts, FileSizeLimit, - ets:lookup(FileSummaryEts, First)) of - not_found -> - State; - {Src, Dst} -> - Pending1 = orddict_store(Dst, [], - orddict_store(Src, [], Pending)), - State1 = close_handle(Src, close_handle(Dst, State)), - true = ets:update_element(FileSummaryEts, Src, - {#file_summary.locked, true}), - true = ets:update_element(FileSummaryEts, Dst, - {#file_summary.locked, true}), - ok = rabbit_msg_store_gc:combine(GCPid, Src, Dst), - State1 #msstate { pending_gc_completion = Pending1 } - end - end; -maybe_compact(State) -> - State. - -find_files_to_combine(FileSummaryEts, FileSizeLimit, - [#file_summary { file = Dst, - valid_total_size = DstValid, - right = Src, - locked = DstLocked }]) -> - case Src of - undefined -> - not_found; - _ -> - [#file_summary { file = Src, - valid_total_size = SrcValid, - left = Dst, - right = SrcRight, - locked = SrcLocked }] = Next = - ets:lookup(FileSummaryEts, Src), - case SrcRight of - undefined -> not_found; - _ -> case (DstValid + SrcValid =< FileSizeLimit) andalso - (DstValid > 0) andalso (SrcValid > 0) andalso - not (DstLocked orelse SrcLocked) of - true -> {Src, Dst}; - false -> find_files_to_combine( - FileSummaryEts, FileSizeLimit, Next) - end - end - end. - -delete_file_if_empty(File, State = #msstate { current_file = File }) -> - State; -delete_file_if_empty(File, State = #msstate { - gc_pid = GCPid, - file_summary_ets = FileSummaryEts, - pending_gc_completion = Pending }) -> - [#file_summary { valid_total_size = ValidData, - locked = false }] = - ets:lookup(FileSummaryEts, File), - case ValidData of - %% don't delete the file_summary_ets entry for File here - %% because we could have readers which need to be able to - %% decrement the readers count. - 0 -> true = ets:update_element(FileSummaryEts, File, - {#file_summary.locked, true}), - ok = rabbit_msg_store_gc:delete(GCPid, File), - Pending1 = orddict_store(File, [], Pending), - close_handle(File, - State #msstate { pending_gc_completion = Pending1 }); - _ -> State - end. - -cleanup_after_file_deletion(File, - #msstate { file_handles_ets = FileHandlesEts, - file_summary_ets = FileSummaryEts, - clients = Clients }) -> - %% Ensure that any clients that have open fhs to the file close - %% them before using them again. This has to be done here (given - %% it's done in the msg_store, and not the gc), and not when - %% starting up the GC, because if done when starting up the GC, - %% the client could find the close, and close and reopen the fh, - %% whilst the GC is waiting for readers to disappear, before it's - %% actually done the GC. - true = mark_handle_to_close(Clients, FileHandlesEts, File, true), - [#file_summary { left = Left, - right = Right, - locked = true, - readers = 0 }] = ets:lookup(FileSummaryEts, File), - %% We'll never delete the current file, so right is never undefined - true = Right =/= undefined, %% ASSERTION - true = ets:update_element(FileSummaryEts, Right, - {#file_summary.left, Left}), - %% ensure the double linked list is maintained - true = case Left of - undefined -> true; %% File is the eldest file (left-most) - _ -> ets:update_element(FileSummaryEts, Left, - {#file_summary.right, Right}) - end, - true = ets:delete(FileSummaryEts, File), - ok. - -%%---------------------------------------------------------------------------- -%% garbage collection / compaction / aggregation -- external -%%---------------------------------------------------------------------------- - -has_readers(File, #gc_state { file_summary_ets = FileSummaryEts }) -> - [#file_summary { locked = true, readers = Count }] = - ets:lookup(FileSummaryEts, File), - Count /= 0. - -combine_files(Source, Destination, - State = #gc_state { file_summary_ets = FileSummaryEts, - file_handles_ets = FileHandlesEts, - dir = Dir, - msg_store = Server }) -> - [#file_summary { - readers = 0, - left = Destination, - valid_total_size = SourceValid, - file_size = SourceFileSize, - locked = true }] = ets:lookup(FileSummaryEts, Source), - [#file_summary { - readers = 0, - right = Source, - valid_total_size = DestinationValid, - file_size = DestinationFileSize, - locked = true }] = ets:lookup(FileSummaryEts, Destination), - - SourceName = filenum_to_name(Source), - DestinationName = filenum_to_name(Destination), - {ok, SourceHdl} = open_file(Dir, SourceName, - ?READ_AHEAD_MODE), - {ok, DestinationHdl} = open_file(Dir, DestinationName, - ?READ_AHEAD_MODE ++ ?WRITE_MODE), - TotalValidData = SourceValid + DestinationValid, - %% if DestinationValid =:= DestinationContiguousTop then we don't - %% need a tmp file - %% if they're not equal, then we need to write out everything past - %% the DestinationContiguousTop to a tmp file then truncate, - %% copy back in, and then copy over from Source - %% otherwise we just truncate straight away and copy over from Source - {DestinationWorkList, DestinationValid} = - load_and_vacuum_message_file(Destination, State), - {DestinationContiguousTop, DestinationWorkListTail} = - drop_contiguous_block_prefix(DestinationWorkList), - case DestinationWorkListTail of - [] -> ok = truncate_and_extend_file( - DestinationHdl, DestinationContiguousTop, TotalValidData); - _ -> Tmp = filename:rootname(DestinationName) ++ ?FILE_EXTENSION_TMP, - {ok, TmpHdl} = open_file(Dir, Tmp, ?READ_AHEAD_MODE++?WRITE_MODE), - ok = copy_messages( - DestinationWorkListTail, DestinationContiguousTop, - DestinationValid, DestinationHdl, TmpHdl, Destination, - State), - TmpSize = DestinationValid - DestinationContiguousTop, - %% so now Tmp contains everything we need to salvage - %% from Destination, and index_state has been updated to - %% reflect the compaction of Destination so truncate - %% Destination and copy from Tmp back to the end - {ok, 0} = file_handle_cache:position(TmpHdl, 0), - ok = truncate_and_extend_file( - DestinationHdl, DestinationContiguousTop, TotalValidData), - {ok, TmpSize} = - file_handle_cache:copy(TmpHdl, DestinationHdl, TmpSize), - %% position in DestinationHdl should now be DestinationValid - ok = file_handle_cache:sync(DestinationHdl), - ok = file_handle_cache:delete(TmpHdl) - end, - {SourceWorkList, SourceValid} = load_and_vacuum_message_file(Source, State), - ok = copy_messages(SourceWorkList, DestinationValid, TotalValidData, - SourceHdl, DestinationHdl, Destination, State), - %% tidy up - ok = file_handle_cache:close(DestinationHdl), - ok = file_handle_cache:close(SourceHdl), - - %% don't update dest.right, because it could be changing at the - %% same time - true = ets:update_element( - FileSummaryEts, Destination, - [{#file_summary.valid_total_size, TotalValidData}, - {#file_summary.file_size, TotalValidData}]), - - Reclaimed = SourceFileSize + DestinationFileSize - TotalValidData, - gen_server2:cast(Server, {combine_files, Source, Destination, Reclaimed}), - safe_file_delete_fun(Source, Dir, FileHandlesEts). - -delete_file(File, State = #gc_state { file_summary_ets = FileSummaryEts, - file_handles_ets = FileHandlesEts, - dir = Dir, - msg_store = Server }) -> - [#file_summary { valid_total_size = 0, - locked = true, - file_size = FileSize, - readers = 0 }] = ets:lookup(FileSummaryEts, File), - {[], 0} = load_and_vacuum_message_file(File, State), - gen_server2:cast(Server, {delete_file, File, FileSize}), - safe_file_delete_fun(File, Dir, FileHandlesEts). - -load_and_vacuum_message_file(File, #gc_state { dir = Dir, - index_module = Index, - index_state = IndexState }) -> - %% Messages here will be end-of-file at start-of-list - {ok, Messages, _FileSize} = - scan_file_for_valid_messages(Dir, filenum_to_name(File)), - %% foldl will reverse so will end up with msgs in ascending offset order - lists:foldl( - fun ({MsgId, TotalSize, Offset}, Acc = {List, Size}) -> - case Index:lookup(MsgId, IndexState) of - #msg_location { file = File, total_size = TotalSize, - offset = Offset, ref_count = 0 } = Entry -> - ok = Index:delete_object(Entry, IndexState), - Acc; - #msg_location { file = File, total_size = TotalSize, - offset = Offset } = Entry -> - {[ Entry | List ], TotalSize + Size}; - _ -> - Acc - end - end, {[], 0}, Messages). - -copy_messages(WorkList, InitOffset, FinalOffset, SourceHdl, DestinationHdl, - Destination, #gc_state { index_module = Index, - index_state = IndexState }) -> - Copy = fun ({BlockStart, BlockEnd}) -> - BSize = BlockEnd - BlockStart, - {ok, BlockStart} = - file_handle_cache:position(SourceHdl, BlockStart), - {ok, BSize} = - file_handle_cache:copy(SourceHdl, DestinationHdl, BSize) - end, - case - lists:foldl( - fun (#msg_location { msg_id = MsgId, offset = Offset, - total_size = TotalSize }, - {CurOffset, Block = {BlockStart, BlockEnd}}) -> - %% CurOffset is in the DestinationFile. - %% Offset, BlockStart and BlockEnd are in the SourceFile - %% update MsgLocation to reflect change of file and offset - ok = Index:update_fields(MsgId, - [{#msg_location.file, Destination}, - {#msg_location.offset, CurOffset}], - IndexState), - {CurOffset + TotalSize, - case BlockEnd of - undefined -> - %% base case, called only for the first list elem - {Offset, Offset + TotalSize}; - Offset -> - %% extend the current block because the - %% next msg follows straight on - {BlockStart, BlockEnd + TotalSize}; - _ -> - %% found a gap, so actually do the work for - %% the previous block - Copy(Block), - {Offset, Offset + TotalSize} - end} - end, {InitOffset, {undefined, undefined}}, WorkList) of - {FinalOffset, Block} -> - case WorkList of - [] -> ok; - _ -> Copy(Block), %% do the last remaining block - ok = file_handle_cache:sync(DestinationHdl) - end; - {FinalOffsetZ, _Block} -> - {gc_error, [{expected, FinalOffset}, - {got, FinalOffsetZ}, - {destination, Destination}]} - end. - -force_recovery(BaseDir, Store) -> - Dir = filename:join(BaseDir, atom_to_list(Store)), - case file:delete(filename:join(Dir, ?CLEAN_FILENAME)) of - ok -> ok; - {error, enoent} -> ok - end, - recover_crashed_compactions(BaseDir), - ok. - -foreach_file(D, Fun, Files) -> - [ok = Fun(filename:join(D, File)) || File <- Files]. - -foreach_file(D1, D2, Fun, Files) -> - [ok = Fun(filename:join(D1, File), filename:join(D2, File)) || File <- Files]. - -transform_dir(BaseDir, Store, TransformFun) -> - Dir = filename:join(BaseDir, atom_to_list(Store)), - TmpDir = filename:join(Dir, ?TRANSFORM_TMP), - TransformFile = fun (A, B) -> transform_msg_file(A, B, TransformFun) end, - CopyFile = fun (Src, Dst) -> {ok, _Bytes} = file:copy(Src, Dst), ok end, - case filelib:is_dir(TmpDir) of - true -> throw({error, transform_failed_previously}); - false -> FileList = list_sorted_filenames(Dir, ?FILE_EXTENSION), - foreach_file(Dir, TmpDir, TransformFile, FileList), - foreach_file(Dir, fun file:delete/1, FileList), - foreach_file(TmpDir, Dir, CopyFile, FileList), - foreach_file(TmpDir, fun file:delete/1, FileList), - ok = file:del_dir(TmpDir) - end. - -transform_msg_file(FileOld, FileNew, TransformFun) -> - ok = rabbit_file:ensure_parent_dirs_exist(FileNew), - {ok, RefOld} = file_handle_cache:open(FileOld, [raw, binary, read], []), - {ok, RefNew} = file_handle_cache:open(FileNew, [raw, binary, write], - [{write_buffer, - ?HANDLE_CACHE_BUFFER_SIZE}]), - {ok, _Acc, _IgnoreSize} = - rabbit_msg_file:scan( - RefOld, filelib:file_size(FileOld), - fun({MsgId, _Size, _Offset, BinMsg}, ok) -> - {ok, MsgNew} = case binary_to_term(BinMsg) of - <<>> -> {ok, <<>>}; %% dying client marker - Msg -> TransformFun(Msg) - end, - {ok, _} = rabbit_msg_file:append(RefNew, MsgId, MsgNew), - ok - end, ok), - ok = file_handle_cache:close(RefOld), - ok = file_handle_cache:close(RefNew), - ok. diff --git a/src/rabbit_msg_store_ets_index.erl b/src/rabbit_msg_store_ets_index.erl deleted file mode 100644 index 8af921b1..00000000 --- a/src/rabbit_msg_store_ets_index.erl +++ /dev/null @@ -1,79 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_msg_store_ets_index). - --include("rabbit_msg_store.hrl"). - --behaviour(rabbit_msg_store_index). - --export([new/1, recover/1, - lookup/2, insert/2, update/2, update_fields/3, delete/2, - delete_object/2, delete_by_file/2, terminate/1]). - --define(MSG_LOC_NAME, rabbit_msg_store_ets_index). --define(FILENAME, "msg_store_index.ets"). - --record(state, { table, dir }). - -new(Dir) -> - file:delete(filename:join(Dir, ?FILENAME)), - Tid = ets:new(?MSG_LOC_NAME, [set, public, {keypos, #msg_location.msg_id}]), - #state { table = Tid, dir = Dir }. - -recover(Dir) -> - Path = filename:join(Dir, ?FILENAME), - case ets:file2tab(Path) of - {ok, Tid} -> file:delete(Path), - {ok, #state { table = Tid, dir = Dir }}; - Error -> Error - end. - -lookup(Key, State) -> - case ets:lookup(State #state.table, Key) of - [] -> not_found; - [Entry] -> Entry - end. - -insert(Obj, State) -> - true = ets:insert_new(State #state.table, Obj), - ok. - -update(Obj, State) -> - true = ets:insert(State #state.table, Obj), - ok. - -update_fields(Key, Updates, State) -> - true = ets:update_element(State #state.table, Key, Updates), - ok. - -delete(Key, State) -> - true = ets:delete(State #state.table, Key), - ok. - -delete_object(Obj, State) -> - true = ets:delete_object(State #state.table, Obj), - ok. - -delete_by_file(File, State) -> - MatchHead = #msg_location { file = File, _ = '_' }, - ets:select_delete(State #state.table, [{MatchHead, [], [true]}]), - ok. - -terminate(#state { table = MsgLocations, dir = Dir }) -> - ok = ets:tab2file(MsgLocations, filename:join(Dir, ?FILENAME), - [{extended_info, [object_count]}]), - ets:delete(MsgLocations). diff --git a/src/rabbit_msg_store_gc.erl b/src/rabbit_msg_store_gc.erl deleted file mode 100644 index ebb51cf7..00000000 --- a/src/rabbit_msg_store_gc.erl +++ /dev/null @@ -1,137 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_msg_store_gc). - --behaviour(gen_server2). - --export([start_link/1, combine/3, delete/2, no_readers/2, stop/1]). - --export([set_maximum_since_use/2]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, prioritise_cast/3]). - --record(state, - { pending_no_readers, - on_action, - msg_store_state - }). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/1 :: (rabbit_msg_store:gc_state()) -> - rabbit_types:ok_pid_or_error()). --spec(combine/3 :: (pid(), rabbit_msg_store:file_num(), - rabbit_msg_store:file_num()) -> 'ok'). --spec(delete/2 :: (pid(), rabbit_msg_store:file_num()) -> 'ok'). --spec(no_readers/2 :: (pid(), rabbit_msg_store:file_num()) -> 'ok'). --spec(stop/1 :: (pid()) -> 'ok'). --spec(set_maximum_since_use/2 :: (pid(), non_neg_integer()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(MsgStoreState) -> - gen_server2:start_link(?MODULE, [MsgStoreState], - [{timeout, infinity}]). - -combine(Server, Source, Destination) -> - gen_server2:cast(Server, {combine, Source, Destination}). - -delete(Server, File) -> - gen_server2:cast(Server, {delete, File}). - -no_readers(Server, File) -> - gen_server2:cast(Server, {no_readers, File}). - -stop(Server) -> - gen_server2:call(Server, stop, infinity). - -set_maximum_since_use(Pid, Age) -> - gen_server2:cast(Pid, {set_maximum_since_use, Age}). - -%%---------------------------------------------------------------------------- - -init([MsgStoreState]) -> - ok = file_handle_cache:register_callback(?MODULE, set_maximum_since_use, - [self()]), - {ok, #state { pending_no_readers = dict:new(), - on_action = [], - msg_store_state = MsgStoreState }, hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. - -prioritise_cast({set_maximum_since_use, _Age}, _Len, _State) -> 8; -prioritise_cast(_Msg, _Len, _State) -> 0. - -handle_call(stop, _From, State) -> - {stop, normal, ok, State}. - -handle_cast({combine, Source, Destination}, State) -> - {noreply, attempt_action(combine, [Source, Destination], State), hibernate}; - -handle_cast({delete, File}, State) -> - {noreply, attempt_action(delete, [File], State), hibernate}; - -handle_cast({no_readers, File}, - State = #state { pending_no_readers = Pending }) -> - {noreply, case dict:find(File, Pending) of - error -> - State; - {ok, {Action, Files}} -> - Pending1 = dict:erase(File, Pending), - attempt_action( - Action, Files, - State #state { pending_no_readers = Pending1 }) - end, hibernate}; - -handle_cast({set_maximum_since_use, Age}, State) -> - ok = file_handle_cache:set_maximum_since_use(Age), - {noreply, State, hibernate}. - -handle_info(Info, State) -> - {stop, {unhandled_info, Info}, State}. - -terminate(_Reason, State) -> - State. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -attempt_action(Action, Files, - State = #state { pending_no_readers = Pending, - on_action = Thunks, - msg_store_state = MsgStoreState }) -> - case [File || File <- Files, - rabbit_msg_store:has_readers(File, MsgStoreState)] of - [] -> State #state { - on_action = lists:filter( - fun (Thunk) -> not Thunk() end, - [do_action(Action, Files, MsgStoreState) | - Thunks]) }; - [File | _] -> Pending1 = dict:store(File, {Action, Files}, Pending), - State #state { pending_no_readers = Pending1 } - end. - -do_action(combine, [Source, Destination], MsgStoreState) -> - rabbit_msg_store:combine_files(Source, Destination, MsgStoreState); -do_action(delete, [File], MsgStoreState) -> - rabbit_msg_store:delete_file(File, MsgStoreState). diff --git a/src/rabbit_msg_store_index.erl b/src/rabbit_msg_store_index.erl deleted file mode 100644 index 5d067cc9..00000000 --- a/src/rabbit_msg_store_index.erl +++ /dev/null @@ -1,59 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_msg_store_index). - --include("rabbit_msg_store.hrl"). - --ifdef(use_specs). - --type(dir() :: any()). --type(index_state() :: any()). --type(keyvalue() :: any()). --type(fieldpos() :: non_neg_integer()). --type(fieldvalue() :: any()). - --callback new(dir()) -> index_state(). --callback recover(dir()) -> rabbit_types:ok_or_error2(index_state(), any()). --callback lookup(rabbit_types:msg_id(), index_state()) -> ('not_found' | keyvalue()). --callback insert(keyvalue(), index_state()) -> 'ok'. --callback update(keyvalue(), index_state()) -> 'ok'. --callback update_fields(rabbit_types:msg_id(), ({fieldpos(), fieldvalue()} | - [{fieldpos(), fieldvalue()}]), - index_state()) -> 'ok'. --callback delete(rabbit_types:msg_id(), index_state()) -> 'ok'. --callback delete_object(keyvalue(), index_state()) -> 'ok'. --callback delete_by_file(fieldvalue(), index_state()) -> 'ok'. --callback terminate(index_state()) -> any(). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{new, 1}, - {recover, 1}, - {lookup, 2}, - {insert, 2}, - {update, 2}, - {update_fields, 3}, - {delete, 2}, - {delete_by_file, 2}, - {terminate, 1}]; -behaviour_info(_Other) -> - undefined. - --endif. diff --git a/src/rabbit_net.erl b/src/rabbit_net.erl deleted file mode 100644 index e33c1836..00000000 --- a/src/rabbit_net.erl +++ /dev/null @@ -1,246 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_net). --include("rabbit.hrl"). - --export([is_ssl/1, ssl_info/1, controlling_process/2, getstat/2, - recv/1, sync_recv/2, async_recv/3, port_command/2, getopts/2, - setopts/2, send/2, close/1, fast_close/1, sockname/1, peername/1, - peercert/1, connection_string/2, socket_ends/2, is_loopback/1]). - -%%--------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([socket/0]). - --type(stat_option() :: - 'recv_cnt' | 'recv_max' | 'recv_avg' | 'recv_oct' | 'recv_dvi' | - 'send_cnt' | 'send_max' | 'send_avg' | 'send_oct' | 'send_pend'). --type(ok_val_or_error(A) :: rabbit_types:ok_or_error2(A, any())). --type(ok_or_any_error() :: rabbit_types:ok_or_error(any())). --type(socket() :: port() | #ssl_socket{}). --type(opts() :: [{atom(), any()} | - {raw, non_neg_integer(), non_neg_integer(), binary()}]). --type(host_or_ip() :: binary() | inet:ip_address()). --spec(is_ssl/1 :: (socket()) -> boolean()). --spec(ssl_info/1 :: (socket()) - -> 'nossl' | ok_val_or_error( - {atom(), {atom(), atom(), atom()}})). --spec(controlling_process/2 :: (socket(), pid()) -> ok_or_any_error()). --spec(getstat/2 :: - (socket(), [stat_option()]) - -> ok_val_or_error([{stat_option(), integer()}])). --spec(recv/1 :: (socket()) -> - {'data', [char()] | binary()} | 'closed' | - rabbit_types:error(any()) | {'other', any()}). --spec(sync_recv/2 :: (socket(), integer()) -> rabbit_types:ok(binary()) | - rabbit_types:error(any())). --spec(async_recv/3 :: - (socket(), integer(), timeout()) -> rabbit_types:ok(any())). --spec(port_command/2 :: (socket(), iolist()) -> 'true'). --spec(getopts/2 :: (socket(), [atom() | {raw, - non_neg_integer(), - non_neg_integer(), - non_neg_integer() | binary()}]) - -> ok_val_or_error(opts())). --spec(setopts/2 :: (socket(), opts()) -> ok_or_any_error()). --spec(send/2 :: (socket(), binary() | iolist()) -> ok_or_any_error()). --spec(close/1 :: (socket()) -> ok_or_any_error()). --spec(fast_close/1 :: (socket()) -> ok_or_any_error()). --spec(sockname/1 :: - (socket()) - -> ok_val_or_error({inet:ip_address(), rabbit_networking:ip_port()})). --spec(peername/1 :: - (socket()) - -> ok_val_or_error({inet:ip_address(), rabbit_networking:ip_port()})). --spec(peercert/1 :: - (socket()) - -> 'nossl' | ok_val_or_error(rabbit_ssl:certificate())). --spec(connection_string/2 :: - (socket(), 'inbound' | 'outbound') -> ok_val_or_error(string())). --spec(socket_ends/2 :: - (socket(), 'inbound' | 'outbound') - -> ok_val_or_error({host_or_ip(), rabbit_networking:ip_port(), - host_or_ip(), rabbit_networking:ip_port()})). --spec(is_loopback/1 :: (socket() | inet:ip_address()) -> boolean()). - --endif. - -%%--------------------------------------------------------------------------- - --define(SSL_CLOSE_TIMEOUT, 5000). - --define(IS_SSL(Sock), is_record(Sock, ssl_socket)). - -is_ssl(Sock) -> ?IS_SSL(Sock). - -ssl_info(Sock) when ?IS_SSL(Sock) -> - ssl:connection_info(Sock#ssl_socket.ssl); -ssl_info(_Sock) -> - nossl. - -controlling_process(Sock, Pid) when ?IS_SSL(Sock) -> - ssl:controlling_process(Sock#ssl_socket.ssl, Pid); -controlling_process(Sock, Pid) when is_port(Sock) -> - gen_tcp:controlling_process(Sock, Pid). - -getstat(Sock, Stats) when ?IS_SSL(Sock) -> - inet:getstat(Sock#ssl_socket.tcp, Stats); -getstat(Sock, Stats) when is_port(Sock) -> - inet:getstat(Sock, Stats). - -recv(Sock) when ?IS_SSL(Sock) -> - recv(Sock#ssl_socket.ssl, {ssl, ssl_closed, ssl_error}); -recv(Sock) when is_port(Sock) -> - recv(Sock, {tcp, tcp_closed, tcp_error}). - -recv(S, {DataTag, ClosedTag, ErrorTag}) -> - receive - {DataTag, S, Data} -> {data, Data}; - {ClosedTag, S} -> closed; - {ErrorTag, S, Reason} -> {error, Reason}; - Other -> {other, Other} - end. - -sync_recv(Sock, Length) when ?IS_SSL(Sock) -> - ssl:recv(Sock#ssl_socket.ssl, Length); -sync_recv(Sock, Length) -> - gen_tcp:recv(Sock, Length). - -async_recv(Sock, Length, Timeout) when ?IS_SSL(Sock) -> - Pid = self(), - Ref = make_ref(), - - spawn(fun () -> Pid ! {inet_async, Sock, Ref, - ssl:recv(Sock#ssl_socket.ssl, Length, Timeout)} - end), - - {ok, Ref}; -async_recv(Sock, Length, infinity) when is_port(Sock) -> - prim_inet:async_recv(Sock, Length, -1); -async_recv(Sock, Length, Timeout) when is_port(Sock) -> - prim_inet:async_recv(Sock, Length, Timeout). - -port_command(Sock, Data) when ?IS_SSL(Sock) -> - case ssl:send(Sock#ssl_socket.ssl, Data) of - ok -> self() ! {inet_reply, Sock, ok}, - true; - {error, Reason} -> erlang:error(Reason) - end; -port_command(Sock, Data) when is_port(Sock) -> - erlang:port_command(Sock, Data). - -getopts(Sock, Options) when ?IS_SSL(Sock) -> - ssl:getopts(Sock#ssl_socket.ssl, Options); -getopts(Sock, Options) when is_port(Sock) -> - inet:getopts(Sock, Options). - -setopts(Sock, Options) when ?IS_SSL(Sock) -> - ssl:setopts(Sock#ssl_socket.ssl, Options); -setopts(Sock, Options) when is_port(Sock) -> - inet:setopts(Sock, Options). - -send(Sock, Data) when ?IS_SSL(Sock) -> ssl:send(Sock#ssl_socket.ssl, Data); -send(Sock, Data) when is_port(Sock) -> gen_tcp:send(Sock, Data). - -close(Sock) when ?IS_SSL(Sock) -> ssl:close(Sock#ssl_socket.ssl); -close(Sock) when is_port(Sock) -> gen_tcp:close(Sock). - -fast_close(Sock) when ?IS_SSL(Sock) -> - %% We cannot simply port_close the underlying tcp socket since the - %% TLS protocol is quite insistent that a proper closing handshake - %% should take place (see RFC 5245 s7.2.1). So we call ssl:close - %% instead, but that can block for a very long time, e.g. when - %% there is lots of pending output and there is tcp backpressure, - %% or the ssl_connection process has entered the the - %% workaround_transport_delivery_problems function during - %% termination, which, inexplicably, does a gen_tcp:recv(Socket, - %% 0), which may never return if the client doesn't send a FIN or - %% that gets swallowed by the network. Since there is no timeout - %% variant of ssl:close, we construct our own. - {Pid, MRef} = spawn_monitor(fun () -> ssl:close(Sock#ssl_socket.ssl) end), - erlang:send_after(?SSL_CLOSE_TIMEOUT, self(), {Pid, ssl_close_timeout}), - receive - {Pid, ssl_close_timeout} -> - erlang:demonitor(MRef, [flush]), - exit(Pid, kill); - {'DOWN', MRef, process, Pid, _Reason} -> - ok - end, - catch port_close(Sock#ssl_socket.tcp), - ok; -fast_close(Sock) when is_port(Sock) -> - catch port_close(Sock), ok. - -sockname(Sock) when ?IS_SSL(Sock) -> ssl:sockname(Sock#ssl_socket.ssl); -sockname(Sock) when is_port(Sock) -> inet:sockname(Sock). - -peername(Sock) when ?IS_SSL(Sock) -> ssl:peername(Sock#ssl_socket.ssl); -peername(Sock) when is_port(Sock) -> inet:peername(Sock). - -peercert(Sock) when ?IS_SSL(Sock) -> ssl:peercert(Sock#ssl_socket.ssl); -peercert(Sock) when is_port(Sock) -> nossl. - -connection_string(Sock, Direction) -> - case socket_ends(Sock, Direction) of - {ok, {FromAddress, FromPort, ToAddress, ToPort}} -> - {ok, rabbit_misc:format( - "~s:~p -> ~s:~p", - [maybe_ntoab(FromAddress), FromPort, - maybe_ntoab(ToAddress), ToPort])}; - Error -> - Error - end. - -socket_ends(Sock, Direction) -> - {From, To} = sock_funs(Direction), - case {From(Sock), To(Sock)} of - {{ok, {FromAddress, FromPort}}, {ok, {ToAddress, ToPort}}} -> - {ok, {rdns(FromAddress), FromPort, - rdns(ToAddress), ToPort}}; - {{error, _Reason} = Error, _} -> - Error; - {_, {error, _Reason} = Error} -> - Error - end. - -maybe_ntoab(Addr) when is_tuple(Addr) -> rabbit_misc:ntoab(Addr); -maybe_ntoab(Host) -> Host. - -rdns(Addr) -> - case application:get_env(rabbit, reverse_dns_lookups) of - {ok, true} -> list_to_binary(rabbit_networking:tcp_host(Addr)); - _ -> Addr - end. - -sock_funs(inbound) -> {fun peername/1, fun sockname/1}; -sock_funs(outbound) -> {fun sockname/1, fun peername/1}. - -is_loopback(Sock) when is_port(Sock) ; ?IS_SSL(Sock) -> - case sockname(Sock) of - {ok, {Addr, _Port}} -> is_loopback(Addr); - {error, _} -> false - end; -%% We could parse the results of inet:getifaddrs() instead. But that -%% would be more complex and less maybe Windows-compatible... -is_loopback({127,_,_,_}) -> true; -is_loopback({0,0,0,0,0,0,0,1}) -> true; -is_loopback({0,0,0,0,0,65535,AB,CD}) -> is_loopback(ipv4(AB, CD)); -is_loopback(_) -> false. - -ipv4(AB, CD) -> {AB bsr 8, AB band 255, CD bsr 8, CD band 255}. diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl deleted file mode 100644 index d59b22f6..00000000 --- a/src/rabbit_networking.erl +++ /dev/null @@ -1,539 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_networking). - --export([boot/0, start/0, start_tcp_listener/1, start_ssl_listener/2, - stop_tcp_listener/1, on_node_down/1, active_listeners/0, - node_listeners/1, register_connection/1, unregister_connection/1, - connections/0, connection_info_keys/0, - connection_info/1, connection_info/2, - connection_info_all/0, connection_info_all/1, - close_connection/2, force_connection_event_refresh/1, tcp_host/1]). - -%%used by TCP-based transports, e.g. STOMP adapter --export([tcp_listener_addresses/1, tcp_listener_spec/6, - ensure_ssl/0, fix_ssl_options/1, poodle_check/1, ssl_transform_fun/1]). - --export([tcp_listener_started/3, tcp_listener_stopped/3, - start_client/1, start_ssl_client/2]). - -%% Internal --export([connections_local/0]). - --import(rabbit_misc, [pget/2, pget/3, pset/3]). - --include("rabbit.hrl"). --include_lib("kernel/include/inet.hrl"). - --define(FIRST_TEST_BIND_PORT, 10000). - -%% POODLE --define(BAD_SSL_PROTOCOL_VERSIONS, [sslv3]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([ip_port/0, hostname/0]). - --type(hostname() :: inet:hostname()). --type(ip_port() :: inet:port_number()). - --type(family() :: atom()). --type(listener_config() :: ip_port() | - {hostname(), ip_port()} | - {hostname(), ip_port(), family()}). --type(address() :: {inet:ip_address(), ip_port(), family()}). --type(name_prefix() :: atom()). --type(protocol() :: atom()). --type(label() :: string()). - --spec(start/0 :: () -> 'ok'). --spec(start_tcp_listener/1 :: (listener_config()) -> 'ok'). --spec(start_ssl_listener/2 :: - (listener_config(), rabbit_types:infos()) -> 'ok'). --spec(stop_tcp_listener/1 :: (listener_config()) -> 'ok'). --spec(active_listeners/0 :: () -> [rabbit_types:listener()]). --spec(node_listeners/1 :: (node()) -> [rabbit_types:listener()]). --spec(register_connection/1 :: (pid()) -> ok). --spec(unregister_connection/1 :: (pid()) -> ok). --spec(connections/0 :: () -> [rabbit_types:connection()]). --spec(connections_local/0 :: () -> [rabbit_types:connection()]). --spec(connection_info_keys/0 :: () -> rabbit_types:info_keys()). --spec(connection_info/1 :: - (rabbit_types:connection()) -> rabbit_types:infos()). --spec(connection_info/2 :: - (rabbit_types:connection(), rabbit_types:info_keys()) - -> rabbit_types:infos()). --spec(connection_info_all/0 :: () -> [rabbit_types:infos()]). --spec(connection_info_all/1 :: - (rabbit_types:info_keys()) -> [rabbit_types:infos()]). --spec(close_connection/2 :: (pid(), string()) -> 'ok'). --spec(force_connection_event_refresh/1 :: (reference()) -> 'ok'). - --spec(on_node_down/1 :: (node()) -> 'ok'). --spec(tcp_listener_addresses/1 :: (listener_config()) -> [address()]). --spec(tcp_listener_spec/6 :: - (name_prefix(), address(), [gen_tcp:listen_option()], protocol(), - label(), rabbit_types:mfargs()) -> supervisor:child_spec()). --spec(ensure_ssl/0 :: () -> rabbit_types:infos()). --spec(fix_ssl_options/1 :: (rabbit_types:infos()) -> rabbit_types:infos()). --spec(poodle_check/1 :: (atom()) -> 'ok' | 'danger'). --spec(ssl_transform_fun/1 :: - (rabbit_types:infos()) - -> fun ((rabbit_net:socket()) - -> rabbit_types:ok_or_error(#ssl_socket{}))). - --spec(boot/0 :: () -> 'ok'). --spec(start_client/1 :: - (port() | #ssl_socket{ssl::{'sslsocket',_,_}}) -> - atom() | pid() | port() | {atom(),atom()}). --spec(start_ssl_client/2 :: - (_,port() | #ssl_socket{ssl::{'sslsocket',_,_}}) -> - atom() | pid() | port() | {atom(),atom()}). --spec(tcp_listener_started/3 :: - (_, - string() | - {byte(),byte(),byte(),byte()} | - {char(),char(),char(),char(),char(),char(),char(),char()}, - _) -> - 'ok'). --spec(tcp_listener_stopped/3 :: - (_, - string() | - {byte(),byte(),byte(),byte()} | - {char(),char(),char(),char(),char(),char(),char(),char()}, - _) -> - 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -boot() -> - ok = record_distribution_listener(), - ok = start(), - ok = boot_tcp(), - ok = boot_ssl(). - -boot_tcp() -> - {ok, TcpListeners} = application:get_env(tcp_listeners), - [ok = start_tcp_listener(Listener) || Listener <- TcpListeners], - ok. - -boot_ssl() -> - case application:get_env(ssl_listeners) of - {ok, []} -> - ok; - {ok, SslListeners} -> - SslOpts = ensure_ssl(), - case poodle_check('AMQP') of - ok -> [start_ssl_listener(L, SslOpts) || L <- SslListeners]; - danger -> ok - end, - ok - end. - -start() -> rabbit_sup:start_supervisor_child( - rabbit_tcp_client_sup, rabbit_client_sup, - [{local, rabbit_tcp_client_sup}, - {rabbit_connection_sup,start_link,[]}]). - -ensure_ssl() -> - {ok, SslAppsConfig} = application:get_env(rabbit, ssl_apps), - ok = app_utils:start_applications(SslAppsConfig), - {ok, SslOptsConfig} = application:get_env(rabbit, ssl_options), - fix_ssl_options(SslOptsConfig). - -poodle_check(Context) -> - {ok, Vsn} = application:get_key(ssl, vsn), - case rabbit_misc:version_compare(Vsn, "5.3", gte) of %% R16B01 - true -> ok; - false -> case application:get_env(rabbit, ssl_allow_poodle_attack) of - {ok, true} -> ok; - _ -> log_poodle_fail(Context), - danger - end - end. - -log_poodle_fail(Context) -> - rabbit_log:error( - "The installed version of Erlang (~s) contains the bug OTP-10905,~n" - "which makes it impossible to disable SSLv3. This makes the system~n" - "vulnerable to the POODLE attack. SSL listeners for ~s have therefore~n" - "been disabled.~n~n" - "You are advised to upgrade to a recent Erlang version; R16B01 is the~n" - "first version in which this bug is fixed, but later is usually~n" - "better.~n~n" - "If you cannot upgrade now and want to re-enable SSL listeners, you can~n" - "set the config item 'ssl_allow_poodle_attack' to 'true' in the~n" - "'rabbit' section of your configuration file.~n", - [rabbit_misc:otp_release(), Context]). - -fix_ssl_options(Config) -> - fix_verify_fun(fix_ssl_protocol_versions(Config)). - -fix_verify_fun(SslOptsConfig) -> - case rabbit_misc:pget(verify_fun, SslOptsConfig) of - {Module, Function} -> - rabbit_misc:pset(verify_fun, - fun (ErrorList) -> - Module:Function(ErrorList) - end, SslOptsConfig); - undefined -> - % unknown_ca errors are silently ignored prior to R14B unless we - % supply this verify_fun - remove when at least R14B is required - case proplists:get_value(verify, SslOptsConfig, verify_none) of - verify_none -> SslOptsConfig; - verify_peer -> [{verify_fun, fun([]) -> true; - ([_|_]) -> false - end} - | SslOptsConfig] - end - end. - -fix_ssl_protocol_versions(Config) -> - case application:get_env(rabbit, ssl_allow_poodle_attack) of - {ok, true} -> - Config; - _ -> - Configured = case pget(versions, Config) of - undefined -> pget(available, ssl:versions(), []); - Vs -> Vs - end, - pset(versions, Configured -- ?BAD_SSL_PROTOCOL_VERSIONS, Config) - end. - -ssl_timeout() -> - {ok, Val} = application:get_env(rabbit, ssl_handshake_timeout), - Val. - -ssl_transform_fun(SslOpts) -> - fun (Sock) -> - Timeout = ssl_timeout(), - case catch ssl:ssl_accept(Sock, SslOpts, Timeout) of - {ok, SslSock} -> - {ok, #ssl_socket{tcp = Sock, ssl = SslSock}}; - {error, timeout} -> - {error, {ssl_upgrade_error, timeout}}; - {error, Reason} -> - %% We have no idea what state the ssl_connection - %% process is in - it could still be happily - %% going, it might be stuck, or it could be just - %% about to fail. There is little that our caller - %% can do but close the TCP socket, but this could - %% cause ssl alerts to get dropped (which is bad - %% form, according to the TLS spec). So we give - %% the ssl_connection a little bit of time to send - %% such alerts. - timer:sleep(Timeout), - {error, {ssl_upgrade_error, Reason}}; - {'EXIT', Reason} -> - {error, {ssl_upgrade_failure, Reason}} - end - end. - -tcp_listener_addresses(Port) when is_integer(Port) -> - tcp_listener_addresses_auto(Port); -tcp_listener_addresses({"auto", Port}) -> - %% Variant to prevent lots of hacking around in bash and batch files - tcp_listener_addresses_auto(Port); -tcp_listener_addresses({Host, Port}) -> - %% auto: determine family IPv4 / IPv6 after converting to IP address - tcp_listener_addresses({Host, Port, auto}); -tcp_listener_addresses({Host, Port, Family0}) - when is_integer(Port) andalso (Port >= 0) andalso (Port =< 65535) -> - [{IPAddress, Port, Family} || - {IPAddress, Family} <- getaddr(Host, Family0)]; -tcp_listener_addresses({_Host, Port, _Family0}) -> - rabbit_log:error("invalid port ~p - not 0..65535~n", [Port]), - throw({error, {invalid_port, Port}}). - -tcp_listener_addresses_auto(Port) -> - lists:append([tcp_listener_addresses(Listener) || - Listener <- port_to_listeners(Port)]). - -tcp_listener_spec(NamePrefix, {IPAddress, Port, Family}, SocketOpts, - Protocol, Label, OnConnect) -> - {rabbit_misc:tcp_name(NamePrefix, IPAddress, Port), - {tcp_listener_sup, start_link, - [IPAddress, Port, [Family | SocketOpts], - {?MODULE, tcp_listener_started, [Protocol]}, - {?MODULE, tcp_listener_stopped, [Protocol]}, - OnConnect, Label]}, - transient, infinity, supervisor, [tcp_listener_sup]}. - -start_tcp_listener(Listener) -> - start_listener(Listener, amqp, "TCP Listener", - {?MODULE, start_client, []}). - -start_ssl_listener(Listener, SslOpts) -> - start_listener(Listener, 'amqp/ssl', "SSL Listener", - {?MODULE, start_ssl_client, [SslOpts]}). - -start_listener(Listener, Protocol, Label, OnConnect) -> - [start_listener0(Address, Protocol, Label, OnConnect) || - Address <- tcp_listener_addresses(Listener)], - ok. - -start_listener0(Address, Protocol, Label, OnConnect) -> - Spec = tcp_listener_spec(rabbit_tcp_listener_sup, Address, tcp_opts(), - Protocol, Label, OnConnect), - case supervisor:start_child(rabbit_sup, Spec) of - {ok, _} -> ok; - {error, {shutdown, _}} -> {IPAddress, Port, _Family} = Address, - exit({could_not_start_tcp_listener, - {rabbit_misc:ntoa(IPAddress), Port}}) - end. - -stop_tcp_listener(Listener) -> - [stop_tcp_listener0(Address) || - Address <- tcp_listener_addresses(Listener)], - ok. - -stop_tcp_listener0({IPAddress, Port, _Family}) -> - Name = rabbit_misc:tcp_name(rabbit_tcp_listener_sup, IPAddress, Port), - ok = supervisor:terminate_child(rabbit_sup, Name), - ok = supervisor:delete_child(rabbit_sup, Name). - -tcp_listener_started(Protocol, IPAddress, Port) -> - %% We need the ip to distinguish e.g. 0.0.0.0 and 127.0.0.1 - %% We need the host so we can distinguish multiple instances of the above - %% in a cluster. - ok = mnesia:dirty_write( - rabbit_listener, - #listener{node = node(), - protocol = Protocol, - host = tcp_host(IPAddress), - ip_address = IPAddress, - port = Port}). - -tcp_listener_stopped(Protocol, IPAddress, Port) -> - ok = mnesia:dirty_delete_object( - rabbit_listener, - #listener{node = node(), - protocol = Protocol, - host = tcp_host(IPAddress), - ip_address = IPAddress, - port = Port}). - -record_distribution_listener() -> - {Name, Host} = rabbit_nodes:parts(node()), - {port, Port, _Version} = erl_epmd:port_please(Name, Host), - tcp_listener_started(clustering, {0,0,0,0,0,0,0,0}, Port). - -active_listeners() -> - rabbit_misc:dirty_read_all(rabbit_listener). - -node_listeners(Node) -> - mnesia:dirty_read(rabbit_listener, Node). - -on_node_down(Node) -> - ok = mnesia:dirty_delete(rabbit_listener, Node). - -start_client(Sock, SockTransform) -> - {ok, _Child, Reader} = supervisor:start_child(rabbit_tcp_client_sup, []), - ok = rabbit_net:controlling_process(Sock, Reader), - Reader ! {go, Sock, SockTransform}, - - %% In the event that somebody floods us with connections, the - %% reader processes can spew log events at error_logger faster - %% than it can keep up, causing its mailbox to grow unbounded - %% until we eat all the memory available and crash. So here is a - %% meaningless synchronous call to the underlying gen_event - %% mechanism. When it returns the mailbox is drained, and we - %% return to our caller to accept more connetions. - gen_event:which_handlers(error_logger), - - Reader. - -start_client(Sock) -> - start_client(Sock, fun (S) -> {ok, S} end). - -start_ssl_client(SslOpts, Sock) -> - start_client(Sock, ssl_transform_fun(SslOpts)). - -register_connection(Pid) -> pg_local:join(rabbit_connections, Pid). - -unregister_connection(Pid) -> pg_local:leave(rabbit_connections, Pid). - -connections() -> - rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running), - rabbit_networking, connections_local, []). - -connections_local() -> pg_local:get_members(rabbit_connections). - -connection_info_keys() -> rabbit_reader:info_keys(). - -connection_info(Pid) -> rabbit_reader:info(Pid). -connection_info(Pid, Items) -> rabbit_reader:info(Pid, Items). - -connection_info_all() -> cmap(fun (Q) -> connection_info(Q) end). -connection_info_all(Items) -> cmap(fun (Q) -> connection_info(Q, Items) end). - -close_connection(Pid, Explanation) -> - rabbit_log:info("Closing connection ~p because ~p~n", [Pid, Explanation]), - case lists:member(Pid, connections()) of - true -> rabbit_reader:shutdown(Pid, Explanation); - false -> throw({error, {not_a_connection_pid, Pid}}) - end. - -force_connection_event_refresh(Ref) -> - [rabbit_reader:force_event_refresh(C, Ref) || C <- connections()], - ok. - -%%-------------------------------------------------------------------- - -tcp_host({0,0,0,0}) -> - hostname(); - -tcp_host({0,0,0,0,0,0,0,0}) -> - hostname(); - -tcp_host(IPAddress) -> - case inet:gethostbyaddr(IPAddress) of - {ok, #hostent{h_name = Name}} -> Name; - {error, _Reason} -> rabbit_misc:ntoa(IPAddress) - end. - -hostname() -> - {ok, Hostname} = inet:gethostname(), - case inet:gethostbyname(Hostname) of - {ok, #hostent{h_name = Name}} -> Name; - {error, _Reason} -> Hostname - end. - -cmap(F) -> rabbit_misc:filter_exit_map(F, connections()). - -tcp_opts() -> - {ok, Opts} = application:get_env(rabbit, tcp_listen_options), - Opts. - -%% inet_parse:address takes care of ip string, like "0.0.0.0" -%% inet:getaddr returns immediately for ip tuple {0,0,0,0}, -%% and runs 'inet_gethost' port process for dns lookups. -%% On Windows inet:getaddr runs dns resolver for ip string, which may fail. -getaddr(Host, Family) -> - case inet_parse:address(Host) of - {ok, IPAddress} -> [{IPAddress, resolve_family(IPAddress, Family)}]; - {error, _} -> gethostaddr(Host, Family) - end. - -gethostaddr(Host, auto) -> - Lookups = [{Family, inet:getaddr(Host, Family)} || Family <- [inet, inet6]], - case [{IP, Family} || {Family, {ok, IP}} <- Lookups] of - [] -> host_lookup_error(Host, Lookups); - IPs -> IPs - end; - -gethostaddr(Host, Family) -> - case inet:getaddr(Host, Family) of - {ok, IPAddress} -> [{IPAddress, Family}]; - {error, Reason} -> host_lookup_error(Host, Reason) - end. - -host_lookup_error(Host, Reason) -> - rabbit_log:error("invalid host ~p - ~p~n", [Host, Reason]), - throw({error, {invalid_host, Host, Reason}}). - -resolve_family({_,_,_,_}, auto) -> inet; -resolve_family({_,_,_,_,_,_,_,_}, auto) -> inet6; -resolve_family(IP, auto) -> throw({error, {strange_family, IP}}); -resolve_family(_, F) -> F. - -%%-------------------------------------------------------------------- - -%% There are three kinds of machine (for our purposes). -%% -%% * Those which treat IPv4 addresses as a special kind of IPv6 address -%% ("Single stack") -%% - Linux by default, Windows Vista and later -%% - We also treat any (hypothetical?) IPv6-only machine the same way -%% * Those which consider IPv6 and IPv4 to be completely separate things -%% ("Dual stack") -%% - OpenBSD, Windows XP / 2003, Linux if so configured -%% * Those which do not support IPv6. -%% - Ancient/weird OSes, Linux if so configured -%% -%% How to reconfigure Linux to test this: -%% Single stack (default): -%% echo 0 > /proc/sys/net/ipv6/bindv6only -%% Dual stack: -%% echo 1 > /proc/sys/net/ipv6/bindv6only -%% IPv4 only: -%% add ipv6.disable=1 to GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub then -%% sudo update-grub && sudo reboot -%% -%% This matters in (and only in) the case where the sysadmin (or the -%% app descriptor) has only supplied a port and we wish to bind to -%% "all addresses". This means different things depending on whether -%% we're single or dual stack. On single stack binding to "::" -%% implicitly includes all IPv4 addresses, and subsequently attempting -%% to bind to "0.0.0.0" will fail. On dual stack, binding to "::" will -%% only bind to IPv6 addresses, and we need another listener bound to -%% "0.0.0.0" for IPv4. Finally, on IPv4-only systems we of course only -%% want to bind to "0.0.0.0". -%% -%% Unfortunately it seems there is no way to detect single vs dual stack -%% apart from attempting to bind to the port. -port_to_listeners(Port) -> - IPv4 = {"0.0.0.0", Port, inet}, - IPv6 = {"::", Port, inet6}, - case ipv6_status(?FIRST_TEST_BIND_PORT) of - single_stack -> [IPv6]; - ipv6_only -> [IPv6]; - dual_stack -> [IPv6, IPv4]; - ipv4_only -> [IPv4] - end. - -ipv6_status(TestPort) -> - IPv4 = [inet, {ip, {0,0,0,0}}], - IPv6 = [inet6, {ip, {0,0,0,0,0,0,0,0}}], - case gen_tcp:listen(TestPort, IPv6) of - {ok, LSock6} -> - case gen_tcp:listen(TestPort, IPv4) of - {ok, LSock4} -> - %% Dual stack - gen_tcp:close(LSock6), - gen_tcp:close(LSock4), - dual_stack; - %% Checking the error here would only let us - %% distinguish single stack IPv6 / IPv4 vs IPv6 only, - %% which we figure out below anyway. - {error, _} -> - gen_tcp:close(LSock6), - case gen_tcp:listen(TestPort, IPv4) of - %% Single stack - {ok, LSock4} -> gen_tcp:close(LSock4), - single_stack; - %% IPv6-only machine. Welcome to the future. - {error, eafnosupport} -> ipv6_only; %% Linux - {error, eprotonosupport}-> ipv6_only; %% FreeBSD - %% Dual stack machine with something already - %% on IPv4. - {error, _} -> ipv6_status(TestPort + 1) - end - end; - %% IPv4-only machine. Welcome to the 90s. - {error, eafnosupport} -> %% Linux - ipv4_only; - {error, eprotonosupport} -> %% FreeBSD - ipv4_only; - %% Port in use - {error, _} -> - ipv6_status(TestPort + 1) - end. diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl deleted file mode 100644 index 82a7a89b..00000000 --- a/src/rabbit_node_monitor.erl +++ /dev/null @@ -1,712 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_node_monitor). - --behaviour(gen_server). - --export([start_link/0]). --export([running_nodes_filename/0, - cluster_status_filename/0, prepare_cluster_status_files/0, - write_cluster_status/1, read_cluster_status/0, - update_cluster_status/0, reset_cluster_status/0]). --export([notify_node_up/0, notify_joined_cluster/0, notify_left_cluster/1]). --export([partitions/0, partitions/1, status/1, subscribe/1]). --export([pause_minority_guard/0]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3]). - - %% Utils --export([all_rabbit_nodes_up/0, run_outside_applications/1, ping_all/0, - alive_nodes/1, alive_rabbit_nodes/1]). - --define(SERVER, ?MODULE). --define(RABBIT_UP_RPC_TIMEOUT, 2000). --define(RABBIT_DOWN_PING_INTERVAL, 1000). - --record(state, {monitors, partitions, subscribers, down_ping_timer, - keepalive_timer, autoheal, guid, node_guids}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). - --spec(running_nodes_filename/0 :: () -> string()). --spec(cluster_status_filename/0 :: () -> string()). --spec(prepare_cluster_status_files/0 :: () -> 'ok'). --spec(write_cluster_status/1 :: (rabbit_mnesia:cluster_status()) -> 'ok'). --spec(read_cluster_status/0 :: () -> rabbit_mnesia:cluster_status()). --spec(update_cluster_status/0 :: () -> 'ok'). --spec(reset_cluster_status/0 :: () -> 'ok'). - --spec(notify_node_up/0 :: () -> 'ok'). --spec(notify_joined_cluster/0 :: () -> 'ok'). --spec(notify_left_cluster/1 :: (node()) -> 'ok'). - --spec(partitions/0 :: () -> [node()]). --spec(partitions/1 :: ([node()]) -> [{node(), [node()]}]). --spec(status/1 :: ([node()]) -> {[{node(), [node()]}], [node()]}). --spec(subscribe/1 :: (pid()) -> 'ok'). --spec(pause_minority_guard/0 :: () -> 'ok' | 'pausing'). - --spec(all_rabbit_nodes_up/0 :: () -> boolean()). --spec(run_outside_applications/1 :: (fun (() -> any())) -> pid()). --spec(ping_all/0 :: () -> 'ok'). --spec(alive_nodes/1 :: ([node()]) -> [node()]). --spec(alive_rabbit_nodes/1 :: ([node()]) -> [node()]). - --endif. - -%%---------------------------------------------------------------------------- -%% Start -%%---------------------------------------------------------------------------- - -start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - -%%---------------------------------------------------------------------------- -%% Cluster file operations -%%---------------------------------------------------------------------------- - -%% The cluster file information is kept in two files. The "cluster -%% status file" contains all the clustered nodes and the disc nodes. -%% The "running nodes file" contains the currently running nodes or -%% the running nodes at shutdown when the node is down. -%% -%% We strive to keep the files up to date and we rely on this -%% assumption in various situations. Obviously when mnesia is offline -%% the information we have will be outdated, but it cannot be -%% otherwise. - -running_nodes_filename() -> - filename:join(rabbit_mnesia:dir(), "nodes_running_at_shutdown"). - -cluster_status_filename() -> - rabbit_mnesia:dir() ++ "/cluster_nodes.config". - -prepare_cluster_status_files() -> - rabbit_mnesia:ensure_mnesia_dir(), - Corrupt = fun(F) -> throw({error, corrupt_cluster_status_files, F}) end, - RunningNodes1 = case try_read_file(running_nodes_filename()) of - {ok, [Nodes]} when is_list(Nodes) -> Nodes; - {ok, Other} -> Corrupt(Other); - {error, enoent} -> [] - end, - ThisNode = [node()], - %% The running nodes file might contain a set or a list, in case - %% of the legacy file - RunningNodes2 = lists:usort(ThisNode ++ RunningNodes1), - {AllNodes1, DiscNodes} = - case try_read_file(cluster_status_filename()) of - {ok, [{AllNodes, DiscNodes0}]} -> - {AllNodes, DiscNodes0}; - {ok, [AllNodes0]} when is_list(AllNodes0) -> - {legacy_cluster_nodes(AllNodes0), legacy_disc_nodes(AllNodes0)}; - {ok, Files} -> - Corrupt(Files); - {error, enoent} -> - LegacyNodes = legacy_cluster_nodes([]), - {LegacyNodes, LegacyNodes} - end, - AllNodes2 = lists:usort(AllNodes1 ++ RunningNodes2), - ok = write_cluster_status({AllNodes2, DiscNodes, RunningNodes2}). - -write_cluster_status({All, Disc, Running}) -> - ClusterStatusFN = cluster_status_filename(), - Res = case rabbit_file:write_term_file(ClusterStatusFN, [{All, Disc}]) of - ok -> - RunningNodesFN = running_nodes_filename(), - {RunningNodesFN, - rabbit_file:write_term_file(RunningNodesFN, [Running])}; - E1 = {error, _} -> - {ClusterStatusFN, E1} - end, - case Res of - {_, ok} -> ok; - {FN, {error, E2}} -> throw({error, {could_not_write_file, FN, E2}}) - end. - -read_cluster_status() -> - case {try_read_file(cluster_status_filename()), - try_read_file(running_nodes_filename())} of - {{ok, [{All, Disc}]}, {ok, [Running]}} when is_list(Running) -> - {All, Disc, Running}; - {Stat, Run} -> - throw({error, {corrupt_or_missing_cluster_files, Stat, Run}}) - end. - -update_cluster_status() -> - {ok, Status} = rabbit_mnesia:cluster_status_from_mnesia(), - write_cluster_status(Status). - -reset_cluster_status() -> - write_cluster_status({[node()], [node()], [node()]}). - -%%---------------------------------------------------------------------------- -%% Cluster notifications -%%---------------------------------------------------------------------------- - -notify_node_up() -> - gen_server:cast(?SERVER, notify_node_up). - -notify_joined_cluster() -> - Nodes = rabbit_mnesia:cluster_nodes(running) -- [node()], - gen_server:abcast(Nodes, ?SERVER, - {joined_cluster, node(), rabbit_mnesia:node_type()}), - ok. - -notify_left_cluster(Node) -> - Nodes = rabbit_mnesia:cluster_nodes(running), - gen_server:abcast(Nodes, ?SERVER, {left_cluster, Node}), - ok. - -%%---------------------------------------------------------------------------- -%% Server calls -%%---------------------------------------------------------------------------- - -partitions() -> - gen_server:call(?SERVER, partitions, infinity). - -partitions(Nodes) -> - {Replies, _} = gen_server:multi_call(Nodes, ?SERVER, partitions, infinity), - Replies. - -status(Nodes) -> - gen_server:multi_call(Nodes, ?SERVER, status, infinity). - -subscribe(Pid) -> - gen_server:cast(?SERVER, {subscribe, Pid}). - -%%---------------------------------------------------------------------------- -%% pause_minority safety -%%---------------------------------------------------------------------------- - -%% If we are in a minority and pause_minority mode then a) we are -%% going to shut down imminently and b) we should not confirm anything -%% until then, since anything we confirm is likely to be lost. -%% -%% We could confirm something by having an HA queue see the minority -%% state (and fail over into it) before the node monitor stops us, or -%% by using unmirrored queues and just having them vanish (and -%% confiming messages as thrown away). -%% -%% So we have channels call in here before issuing confirms, to do a -%% lightweight check that we have not entered a minority state. - -pause_minority_guard() -> - case get(pause_minority_guard) of - not_minority_mode -> - ok; - undefined -> - {ok, M} = application:get_env(rabbit, cluster_partition_handling), - case M of - pause_minority -> pause_minority_guard([]); - _ -> put(pause_minority_guard, not_minority_mode), - ok - end; - {minority_mode, Nodes} -> - pause_minority_guard(Nodes) - end. - -pause_minority_guard(LastNodes) -> - case nodes() of - LastNodes -> ok; - _ -> put(pause_minority_guard, {minority_mode, nodes()}), - case majority() of - false -> pausing; - true -> ok - end - end. - -%%---------------------------------------------------------------------------- -%% gen_server callbacks -%%---------------------------------------------------------------------------- - -init([]) -> - %% We trap exits so that the supervisor will not just kill us. We - %% want to be sure that we are not going to be killed while - %% writing out the cluster status files - bad things can then - %% happen. - process_flag(trap_exit, true), - net_kernel:monitor_nodes(true, [nodedown_reason]), - {ok, _} = mnesia:subscribe(system), - {ok, ensure_keepalive_timer(#state{monitors = pmon:new(), - subscribers = pmon:new(), - partitions = [], - guid = rabbit_guid:gen(), - node_guids = orddict:new(), - autoheal = rabbit_autoheal:init()})}. - -handle_call(partitions, _From, State = #state{partitions = Partitions}) -> - {reply, Partitions, State}; - -handle_call(status, _From, State = #state{partitions = Partitions}) -> - {reply, [{partitions, Partitions}, - {nodes, [node() | nodes()]}], State}; - -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_cast(notify_node_up, State = #state{guid = GUID}) -> - Nodes = rabbit_mnesia:cluster_nodes(running) -- [node()], - gen_server:abcast(Nodes, ?SERVER, - {node_up, node(), rabbit_mnesia:node_type(), GUID}), - %% register other active rabbits with this rabbit - DiskNodes = rabbit_mnesia:cluster_nodes(disc), - [gen_server:cast(?SERVER, {node_up, N, case lists:member(N, DiskNodes) of - true -> disc; - false -> ram - end}) || N <- Nodes], - {noreply, State}; - -%%---------------------------------------------------------------------------- -%% Partial partition detection -%% -%% Every node generates a GUID each time it starts, and announces that -%% GUID in 'node_up', with 'announce_guid' sent by return so the new -%% node knows the GUIDs of the others. These GUIDs are sent in all the -%% partial partition related messages to ensure that we ignore partial -%% partition messages from before we restarted (to avoid getting stuck -%% in a loop). -%% -%% When one node gets nodedown from another, it then sends -%% 'check_partial_partition' to all the nodes it still thinks are -%% alive. If any of those (intermediate) nodes still see the "down" -%% node as up, they inform it that this has happened. The original -%% node (in 'ignore' or 'autoheal' mode) will then disconnect from the -%% intermediate node to "upgrade" to a full partition. -%% -%% In pause_minority mode it will instead immediately pause until all -%% nodes come back. This is because the contract for pause_minority is -%% that nodes should never sit in a partitioned state - if it just -%% disconnected, it would become a minority, pause, realise it's not -%% in a minority any more, and come back, still partitioned (albeit no -%% longer partially). -%% ---------------------------------------------------------------------------- - -handle_cast({node_up, Node, NodeType, GUID}, - State = #state{guid = MyGUID, - node_guids = GUIDs}) -> - cast(Node, {announce_guid, node(), MyGUID}), - GUIDs1 = orddict:store(Node, GUID, GUIDs), - handle_cast({node_up, Node, NodeType}, State#state{node_guids = GUIDs1}); - -handle_cast({announce_guid, Node, GUID}, State = #state{node_guids = GUIDs}) -> - {noreply, State#state{node_guids = orddict:store(Node, GUID, GUIDs)}}; - -handle_cast({check_partial_partition, Node, Rep, NodeGUID, MyGUID, RepGUID}, - State = #state{guid = MyGUID, - node_guids = GUIDs}) -> - case lists:member(Node, rabbit_mnesia:cluster_nodes(running)) andalso - orddict:find(Node, GUIDs) =:= {ok, NodeGUID} of - true -> spawn_link( %%[1] - fun () -> - case rpc:call(Node, rabbit, is_running, []) of - {badrpc, _} -> ok; - _ -> cast(Rep, {partial_partition, - Node, node(), RepGUID}) - end - end); - false -> ok - end, - {noreply, State}; -%% [1] We checked that we haven't heard the node go down - but we -%% really should make sure we can actually communicate with -%% it. Otherwise there's a race where we falsely detect a partial -%% partition. -%% -%% Now of course the rpc:call/4 may take a long time to return if -%% connectivity with the node is actually interrupted - but that's OK, -%% we only really want to do something in a timely manner if -%% connectivity is OK. However, of course as always we must not block -%% the node monitor, so we do the check in a separate process. - -handle_cast({check_partial_partition, _Node, _Reporter, - _NodeGUID, _GUID, _ReporterGUID}, State) -> - {noreply, State}; - -handle_cast({partial_partition, NotReallyDown, Proxy, MyGUID}, - State = #state{guid = MyGUID}) -> - FmtBase = "Partial partition detected:~n" - " * We saw DOWN from ~s~n" - " * We can still see ~s which can see ~s~n", - ArgsBase = [NotReallyDown, Proxy, NotReallyDown], - case application:get_env(rabbit, cluster_partition_handling) of - {ok, pause_minority} -> - rabbit_log:error( - FmtBase ++ " * pause_minority mode enabled~n" - "We will therefore pause until the *entire* cluster recovers~n", - ArgsBase), - await_cluster_recovery(fun all_nodes_up/0), - {noreply, State}; - {ok, _} -> - rabbit_log:error( - FmtBase ++ "We will therefore intentionally disconnect from ~s~n", - ArgsBase ++ [Proxy]), - cast(Proxy, {partial_partition_disconnect, node()}), - disconnect(Proxy), - {noreply, State} - end; - -handle_cast({partial_partition, _GUID, _Reporter, _Proxy}, State) -> - {noreply, State}; - -%% Sometimes it appears the Erlang VM does not give us nodedown -%% messages reliably when another node disconnects from us. Therefore -%% we are told just before the disconnection so we can reciprocate. -handle_cast({partial_partition_disconnect, Other}, State) -> - rabbit_log:error("Partial partition disconnect from ~s~n", [Other]), - disconnect(Other), - {noreply, State}; - -%% Note: when updating the status file, we can't simply write the -%% mnesia information since the message can (and will) overtake the -%% mnesia propagation. -handle_cast({node_up, Node, NodeType}, - State = #state{monitors = Monitors}) -> - case pmon:is_monitored({rabbit, Node}, Monitors) of - true -> {noreply, State}; - false -> rabbit_log:info("rabbit on node ~p up~n", [Node]), - {AllNodes, DiscNodes, RunningNodes} = read_cluster_status(), - write_cluster_status({add_node(Node, AllNodes), - case NodeType of - disc -> add_node(Node, DiscNodes); - ram -> DiscNodes - end, - add_node(Node, RunningNodes)}), - ok = handle_live_rabbit(Node), - Monitors1 = pmon:monitor({rabbit, Node}, Monitors), - {noreply, maybe_autoheal(State#state{monitors = Monitors1})} - end; - -handle_cast({joined_cluster, Node, NodeType}, State) -> - {AllNodes, DiscNodes, RunningNodes} = read_cluster_status(), - write_cluster_status({add_node(Node, AllNodes), - case NodeType of - disc -> add_node(Node, DiscNodes); - ram -> DiscNodes - end, - RunningNodes}), - {noreply, State}; - -handle_cast({left_cluster, Node}, State) -> - {AllNodes, DiscNodes, RunningNodes} = read_cluster_status(), - write_cluster_status({del_node(Node, AllNodes), del_node(Node, DiscNodes), - del_node(Node, RunningNodes)}), - {noreply, State}; - -handle_cast({subscribe, Pid}, State = #state{subscribers = Subscribers}) -> - {noreply, State#state{subscribers = pmon:monitor(Pid, Subscribers)}}; - -handle_cast(keepalive, State) -> - {noreply, State}; - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info({'DOWN', _MRef, process, {rabbit, Node}, _Reason}, - State = #state{monitors = Monitors, subscribers = Subscribers}) -> - rabbit_log:info("rabbit on node ~p down~n", [Node]), - {AllNodes, DiscNodes, RunningNodes} = read_cluster_status(), - write_cluster_status({AllNodes, DiscNodes, del_node(Node, RunningNodes)}), - [P ! {node_down, Node} || P <- pmon:monitored(Subscribers)], - {noreply, handle_dead_rabbit( - Node, - State#state{monitors = pmon:erase({rabbit, Node}, Monitors)})}; - -handle_info({'DOWN', _MRef, process, Pid, _Reason}, - State = #state{subscribers = Subscribers}) -> - {noreply, State#state{subscribers = pmon:erase(Pid, Subscribers)}}; - -handle_info({nodedown, Node, Info}, State = #state{guid = MyGUID, - node_guids = GUIDs}) -> - rabbit_log:info("node ~p down: ~p~n", - [Node, proplists:get_value(nodedown_reason, Info)]), - Check = fun (N, CheckGUID, DownGUID) -> - cast(N, {check_partial_partition, - Node, node(), DownGUID, CheckGUID, MyGUID}) - end, - case orddict:find(Node, GUIDs) of - {ok, DownGUID} -> Alive = rabbit_mnesia:cluster_nodes(running) - -- [node(), Node], - [case orddict:find(N, GUIDs) of - {ok, CheckGUID} -> Check(N, CheckGUID, DownGUID); - error -> ok - end || N <- Alive]; - error -> ok - end, - {noreply, handle_dead_node(Node, State)}; - -handle_info({nodeup, Node, _Info}, State) -> - rabbit_log:info("node ~p up~n", [Node]), - {noreply, State}; - -handle_info({mnesia_system_event, - {inconsistent_database, running_partitioned_network, Node}}, - State = #state{partitions = Partitions, - monitors = Monitors}) -> - %% We will not get a node_up from this node - yet we should treat it as - %% up (mostly). - State1 = case pmon:is_monitored({rabbit, Node}, Monitors) of - true -> State; - false -> State#state{ - monitors = pmon:monitor({rabbit, Node}, Monitors)} - end, - ok = handle_live_rabbit(Node), - Partitions1 = lists:usort([Node | Partitions]), - {noreply, maybe_autoheal(State1#state{partitions = Partitions1})}; - -handle_info({autoheal_msg, Msg}, State = #state{autoheal = AState, - partitions = Partitions}) -> - AState1 = rabbit_autoheal:handle_msg(Msg, AState, Partitions), - {noreply, State#state{autoheal = AState1}}; - -handle_info(ping_down_nodes, State) -> - %% We ping nodes when some are down to ensure that we find out - %% about healed partitions quickly. We ping all nodes rather than - %% just the ones we know are down for simplicity; it's not expensive - %% to ping the nodes that are up, after all. - State1 = State#state{down_ping_timer = undefined}, - Self = self(), - %% We ping in a separate process since in a partition it might - %% take some noticeable length of time and we don't want to block - %% the node monitor for that long. - spawn_link(fun () -> - ping_all(), - case all_nodes_up() of - true -> ok; - false -> Self ! ping_down_nodes_again - end - end), - {noreply, State1}; - -handle_info(ping_down_nodes_again, State) -> - {noreply, ensure_ping_timer(State)}; - -handle_info(ping_up_nodes, State) -> - %% In this case we need to ensure that we ping "quickly" - - %% i.e. only nodes that we know to be up. - [cast(N, keepalive) || N <- alive_nodes() -- [node()]], - {noreply, ensure_keepalive_timer(State#state{keepalive_timer = undefined})}; - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, State) -> - rabbit_misc:stop_timer(State, #state.down_ping_timer), - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%---------------------------------------------------------------------------- -%% Functions that call the module specific hooks when nodes go up/down -%%---------------------------------------------------------------------------- - -handle_dead_node(Node, State = #state{autoheal = Autoheal}) -> - %% In general in rabbit_node_monitor we care about whether the - %% rabbit application is up rather than the node; we do this so - %% that we can respond in the same way to "rabbitmqctl stop_app" - %% and "rabbitmqctl stop" as much as possible. - %% - %% However, for pause_minority mode we can't do this, since we - %% depend on looking at whether other nodes are up to decide - %% whether to come back up ourselves - if we decide that based on - %% the rabbit application we would go down and never come back. - case application:get_env(rabbit, cluster_partition_handling) of - {ok, pause_minority} -> - case majority() of - true -> ok; - false -> await_cluster_recovery(fun majority/0) - end, - State; - {ok, ignore} -> - State; - {ok, autoheal} -> - State#state{autoheal = rabbit_autoheal:node_down(Node, Autoheal)}; - {ok, Term} -> - rabbit_log:warning("cluster_partition_handling ~p unrecognised, " - "assuming 'ignore'~n", [Term]), - State - end. - -await_cluster_recovery(Condition) -> - rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", - []), - run_outside_applications(fun () -> - rabbit:stop(), - wait_for_cluster_recovery(Condition) - end), - ok. - -run_outside_applications(Fun) -> - spawn(fun () -> - %% If our group leader is inside an application we are about - %% to stop, application:stop/1 does not return. - group_leader(whereis(init), self()), - %% Ensure only one such process at a time, the - %% exit(badarg) is harmless if one is already running - try register(rabbit_outside_app_process, self()) of - true -> - try - Fun() - catch _:E -> - rabbit_log:error( - "rabbit_outside_app_process:~n~p~n~p~n", - [E, erlang:get_stacktrace()]) - end - catch error:badarg -> - ok - end - end). - -wait_for_cluster_recovery(Condition) -> - ping_all(), - case Condition() of - true -> rabbit:start(); - false -> timer:sleep(?RABBIT_DOWN_PING_INTERVAL), - wait_for_cluster_recovery(Condition) - end. - -handle_dead_rabbit(Node, State = #state{partitions = Partitions, - autoheal = Autoheal}) -> - %% TODO: This may turn out to be a performance hog when there are - %% lots of nodes. We really only need to execute some of these - %% statements on *one* node, rather than all of them. - ok = rabbit_networking:on_node_down(Node), - ok = rabbit_amqqueue:on_node_down(Node), - ok = rabbit_alarm:on_node_down(Node), - ok = rabbit_mnesia:on_node_down(Node), - %% If we have been partitioned, and we are now in the only remaining - %% partition, we no longer care about partitions - forget them. Note - %% that we do not attempt to deal with individual (other) partitions - %% going away. It's only safe to forget anything about partitions when - %% there are no partitions. - Partitions1 = case Partitions -- (Partitions -- alive_rabbit_nodes()) of - [] -> []; - _ -> Partitions - end, - ensure_ping_timer( - State#state{partitions = Partitions1, - autoheal = rabbit_autoheal:rabbit_down(Node, Autoheal)}). - -ensure_ping_timer(State) -> - rabbit_misc:ensure_timer( - State, #state.down_ping_timer, ?RABBIT_DOWN_PING_INTERVAL, - ping_down_nodes). - -ensure_keepalive_timer(State) -> - {ok, Interval} = application:get_env(rabbit, cluster_keepalive_interval), - rabbit_misc:ensure_timer( - State, #state.keepalive_timer, Interval, ping_up_nodes). - -handle_live_rabbit(Node) -> - ok = rabbit_amqqueue:on_node_up(Node), - ok = rabbit_alarm:on_node_up(Node), - ok = rabbit_mnesia:on_node_up(Node). - -maybe_autoheal(State = #state{partitions = []}) -> - State; - -maybe_autoheal(State = #state{autoheal = AState}) -> - case all_nodes_up() of - true -> State#state{autoheal = rabbit_autoheal:maybe_start(AState)}; - false -> State - end. - -%%-------------------------------------------------------------------- -%% Internal utils -%%-------------------------------------------------------------------- - -try_read_file(FileName) -> - case rabbit_file:read_term_file(FileName) of - {ok, Term} -> {ok, Term}; - {error, enoent} -> {error, enoent}; - {error, E} -> throw({error, {cannot_read_file, FileName, E}}) - end. - -legacy_cluster_nodes(Nodes) -> - %% We get all the info that we can, including the nodes from - %% mnesia, which will be there if the node is a disc node (empty - %% list otherwise) - lists:usort(Nodes ++ mnesia:system_info(db_nodes)). - -legacy_disc_nodes(AllNodes) -> - case AllNodes == [] orelse lists:member(node(), AllNodes) of - true -> [node()]; - false -> [] - end. - -add_node(Node, Nodes) -> lists:usort([Node | Nodes]). - -del_node(Node, Nodes) -> Nodes -- [Node]. - -cast(Node, Msg) -> gen_server:cast({?SERVER, Node}, Msg). - -%% When we call this, it's because we want to force Mnesia to detect a -%% partition. But if we just disconnect_node/1 then Mnesia won't -%% detect a very short partition. So we want to force a slightly -%% longer disconnect. Unfortunately we don't have a way to blacklist -%% individual nodes; the best we can do is turn off auto-connect -%% altogether. -disconnect(Node) -> - application:set_env(kernel, dist_auto_connect, never), - erlang:disconnect_node(Node), - timer:sleep(1000), - application:unset_env(kernel, dist_auto_connect), - ok. - -%%-------------------------------------------------------------------- - -%% mnesia:system_info(db_nodes) (and hence -%% rabbit_mnesia:cluster_nodes(running)) does not return all nodes -%% when partitioned, just those that we are sharing Mnesia state -%% with. So we have a small set of replacement functions -%% here. "rabbit" in a function's name implies we test if the rabbit -%% application is up, not just the node. - -%% As we use these functions to decide what to do in pause_minority -%% state, they *must* be fast, even in the case where TCP connections -%% are timing out. So that means we should be careful about whether we -%% connect to nodes which are currently disconnected. - -majority() -> - Nodes = rabbit_mnesia:cluster_nodes(all), - length(alive_nodes(Nodes)) / length(Nodes) > 0.5. - -all_nodes_up() -> - Nodes = rabbit_mnesia:cluster_nodes(all), - length(alive_nodes(Nodes)) =:= length(Nodes). - -all_rabbit_nodes_up() -> - Nodes = rabbit_mnesia:cluster_nodes(all), - length(alive_rabbit_nodes(Nodes)) =:= length(Nodes). - -alive_nodes() -> alive_nodes(rabbit_mnesia:cluster_nodes(all)). -alive_nodes(Nodes) -> [N || N <- Nodes, lists:member(N, [node()|nodes()])]. - -alive_rabbit_nodes() -> alive_rabbit_nodes(rabbit_mnesia:cluster_nodes(all)). - -alive_rabbit_nodes(Nodes) -> - [N || N <- alive_nodes(Nodes), rabbit:is_running(N)]. - -%% This one is allowed to connect! -ping_all() -> - [net_adm:ping(N) || N <- rabbit_mnesia:cluster_nodes(all)], - ok. diff --git a/src/rabbit_nodes.erl b/src/rabbit_nodes.erl deleted file mode 100644 index 7f7fcc31..00000000 --- a/src/rabbit_nodes.erl +++ /dev/null @@ -1,199 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_nodes). - --export([names/1, diagnostics/1, make/1, parts/1, cookie_hash/0, - is_running/2, is_process_running/2, - cluster_name/0, set_cluster_name/1]). - --include_lib("kernel/include/inet.hrl"). - --define(EPMD_TIMEOUT, 30000). --define(TCP_DIAGNOSTIC_TIMEOUT, 5000). - -%%---------------------------------------------------------------------------- -%% Specs -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(names/1 :: (string()) -> rabbit_types:ok_or_error2( - [{string(), integer()}], term())). --spec(diagnostics/1 :: ([node()]) -> string()). --spec(make/1 :: ({string(), string()} | string()) -> node()). --spec(parts/1 :: (node() | string()) -> {string(), string()}). --spec(cookie_hash/0 :: () -> string()). --spec(is_running/2 :: (node(), atom()) -> boolean()). --spec(is_process_running/2 :: (node(), atom()) -> boolean()). --spec(cluster_name/0 :: () -> binary()). --spec(set_cluster_name/1 :: (binary()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -names(Hostname) -> - Self = self(), - Ref = make_ref(), - {Pid, MRef} = spawn_monitor( - fun () -> Self ! {Ref, net_adm:names(Hostname)} end), - timer:exit_after(?EPMD_TIMEOUT, Pid, timeout), - receive - {Ref, Names} -> erlang:demonitor(MRef, [flush]), - Names; - {'DOWN', MRef, process, Pid, Reason} -> {error, Reason} - end. - -diagnostics(Nodes) -> - NodeDiags = [{"~nDIAGNOSTICS~n===========~n~n" - "attempted to contact: ~p~n", [Nodes]}] ++ - [diagnostics_node(Node) || Node <- Nodes] ++ - current_node_details(), - rabbit_misc:format_many(lists:flatten(NodeDiags)). - -current_node_details() -> - [{"~ncurrent node details:~n- node name: ~w", [node()]}, - case init:get_argument(home) of - {ok, [[Home]]} -> {"- home dir: ~s", [Home]}; - Other -> {"- no home dir: ~p", [Other]} - end, - {"- cookie hash: ~s", [cookie_hash()]}]. - -diagnostics_node(Node) -> - {Name, Host} = parts(Node), - [{"~s:", [Node]} | - case names(Host) of - {error, Reason} -> - [{" * unable to connect to epmd (port ~s) on ~s: ~s~n", - [epmd_port(), Host, rabbit_misc:format_inet_error(Reason)]}]; - {ok, NamePorts} -> - [{" * connected to epmd (port ~s) on ~s", - [epmd_port(), Host]}] ++ - case net_adm:ping(Node) of - pong -> dist_working_diagnostics(Node); - pang -> dist_broken_diagnostics(Name, Host, NamePorts) - end - end]. - -epmd_port() -> - case init:get_argument(epmd_port) of - {ok, [[Port | _] | _]} when is_list(Port) -> Port; - error -> "4369" - end. - -dist_working_diagnostics(Node) -> - case rabbit:is_running(Node) of - true -> [{" * node ~s up, 'rabbit' application running", [Node]}]; - false -> [{" * node ~s up, 'rabbit' application not running~n" - " * running applications on ~s: ~p~n" - " * suggestion: start_app on ~s", - [Node, Node, remote_apps(Node), Node]}] - end. - -remote_apps(Node) -> - %% We want a timeout here because really, we don't trust the node, - %% the last thing we want to do is hang. - case rpc:call(Node, application, which_applications, [5000]) of - {badrpc, _} = E -> E; - Apps -> [App || {App, _, _} <- Apps] - end. - -dist_broken_diagnostics(Name, Host, NamePorts) -> - case [{N, P} || {N, P} <- NamePorts, N =:= Name] of - [] -> - {SelfName, SelfHost} = parts(node()), - Others = [list_to_atom(N) || {N, _} <- NamePorts, - N =/= case SelfHost of - Host -> SelfName; - _ -> never_matches - end], - OthersDiag = case Others of - [] -> [{" no other nodes on ~s", - [Host]}]; - _ -> [{" other nodes on ~s: ~p", - [Host, Others]}] - end, - [{" * epmd reports: node '~s' not running at all", [Name]}, - OthersDiag, {" * suggestion: start the node", []}]; - [{Name, Port}] -> - [{" * epmd reports node '~s' running on port ~b", [Name, Port]} | - case diagnose_connect(Host, Port) of - ok -> - [{" * TCP connection succeeded but Erlang distribution " - "failed~n" - " * suggestion: hostname mismatch?~n" - " * suggestion: is the cookie set correctly?", []}]; - {error, Reason} -> - [{" * can't establish TCP connection, reason: ~s~n" - " * suggestion: blocked by firewall?", - [rabbit_misc:format_inet_error(Reason)]}] - end] - end. - -diagnose_connect(Host, Port) -> - case inet:gethostbyname(Host) of - {ok, #hostent{h_addrtype = Family}} -> - case gen_tcp:connect(Host, Port, [Family], - ?TCP_DIAGNOSTIC_TIMEOUT) of - {ok, Socket} -> gen_tcp:close(Socket), - ok; - {error, _} = E -> E - end; - {error, _} = E -> - E - end. - -make({Prefix, Suffix}) -> list_to_atom(lists:append([Prefix, "@", Suffix])); -make(NodeStr) -> make(parts(NodeStr)). - -parts(Node) when is_atom(Node) -> - parts(atom_to_list(Node)); -parts(NodeStr) -> - case lists:splitwith(fun (E) -> E =/= $@ end, NodeStr) of - {Prefix, []} -> {_, Suffix} = parts(node()), - {Prefix, Suffix}; - {Prefix, Suffix} -> {Prefix, tl(Suffix)} - end. - -cookie_hash() -> - base64:encode_to_string(erlang:md5(atom_to_list(erlang:get_cookie()))). - -is_running(Node, Application) -> - case rpc:call(Node, rabbit_misc, which_applications, []) of - {badrpc, _} -> false; - Apps -> proplists:is_defined(Application, Apps) - end. - -is_process_running(Node, Process) -> - case rpc:call(Node, erlang, whereis, [Process]) of - {badrpc, _} -> false; - undefined -> false; - P when is_pid(P) -> true - end. - -cluster_name() -> - rabbit_runtime_parameters:value_global( - cluster_name, cluster_name_default()). - -cluster_name_default() -> - {ID, _} = rabbit_nodes:parts(node()), - {ok, Host} = inet:gethostname(), - {ok, #hostent{h_name = FQDN}} = inet:gethostbyname(Host), - list_to_binary(atom_to_list(rabbit_nodes:make({ID, FQDN}))). - -set_cluster_name(Name) -> - rabbit_runtime_parameters:set_global(cluster_name, Name). diff --git a/src/rabbit_parameter_validation.erl b/src/rabbit_parameter_validation.erl deleted file mode 100644 index c42bcc4a..00000000 --- a/src/rabbit_parameter_validation.erl +++ /dev/null @@ -1,87 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_parameter_validation). - --export([number/2, binary/2, boolean/2, list/2, regex/2, proplist/3, enum/1]). - -number(_Name, Term) when is_number(Term) -> - ok; - -number(Name, Term) -> - {error, "~s should be number, actually was ~p", [Name, Term]}. - -binary(_Name, Term) when is_binary(Term) -> - ok; - -binary(Name, Term) -> - {error, "~s should be binary, actually was ~p", [Name, Term]}. - -boolean(_Name, Term) when is_boolean(Term) -> - ok; -boolean(Name, Term) -> - {error, "~s should be boolean, actually was ~p", [Name, Term]}. - -list(_Name, Term) when is_list(Term) -> - ok; - -list(Name, Term) -> - {error, "~s should be list, actually was ~p", [Name, Term]}. - -regex(Name, Term) when is_binary(Term) -> - case re:compile(Term) of - {ok, _} -> ok; - {error, Reason} -> {error, "~s should be regular expression " - "but is invalid: ~p", [Name, Reason]} - end; -regex(Name, Term) -> - {error, "~s should be a binary but was ~p", [Name, Term]}. - -proplist(Name, Constraints, Term) when is_list(Term) -> - {Results, Remainder} - = lists:foldl( - fun ({Key, Fun, Needed}, {Results0, Term0}) -> - case {lists:keytake(Key, 1, Term0), Needed} of - {{value, {Key, Value}, Term1}, _} -> - {[Fun(Key, Value) | Results0], - Term1}; - {false, mandatory} -> - {[{error, "Key \"~s\" not found in ~s", - [Key, Name]} | Results0], Term0}; - {false, optional} -> - {Results0, Term0} - end - end, {[], Term}, Constraints), - case Remainder of - [] -> Results; - _ -> [{error, "Unrecognised terms ~p in ~s", [Remainder, Name]} - | Results] - end; - -proplist(Name, _Constraints, Term) -> - {error, "~s not a list ~p", [Name, Term]}. - -enum(OptionsA) -> - Options = [list_to_binary(atom_to_list(O)) || O <- OptionsA], - fun (Name, Term) when is_binary(Term) -> - case lists:member(Term, Options) of - true -> ok; - false -> {error, "~s should be one of ~p, actually was ~p", - [Name, Options, Term]} - end; - (Name, Term) -> - {error, "~s should be binary, actually was ~p", [Name, Term]} - end. diff --git a/src/rabbit_plugins.erl b/src/rabbit_plugins.erl deleted file mode 100644 index 55f7359b..00000000 --- a/src/rabbit_plugins.erl +++ /dev/null @@ -1,304 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_plugins). --include("rabbit.hrl"). - --export([setup/0, active/0, read_enabled/1, list/1, dependencies/3]). --export([ensure/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(plugin_name() :: atom()). - --spec(setup/0 :: () -> [plugin_name()]). --spec(active/0 :: () -> [plugin_name()]). --spec(list/1 :: (string()) -> [#plugin{}]). --spec(read_enabled/1 :: (file:filename()) -> [plugin_name()]). --spec(dependencies/3 :: (boolean(), [plugin_name()], [#plugin{}]) -> - [plugin_name()]). --spec(ensure/1 :: (string()) -> {'ok', [atom()], [atom()]} | {error, any()}). --endif. - -%%---------------------------------------------------------------------------- - -ensure(FileJustChanged0) -> - {ok, OurFile0} = application:get_env(rabbit, enabled_plugins_file), - FileJustChanged = filename:nativename(FileJustChanged0), - OurFile = filename:nativename(OurFile0), - case OurFile of - FileJustChanged -> - Enabled = read_enabled(OurFile), - Wanted = prepare_plugins(Enabled), - Current = active(), - Start = Wanted -- Current, - Stop = Current -- Wanted, - rabbit:start_apps(Start), - %% We need sync_notify here since mgmt will attempt to look at all - %% the modules for the disabled plugins - if they are unloaded - %% that won't work. - ok = rabbit_event:sync_notify(plugins_changed, [{enabled, Start}, - {disabled, Stop}]), - rabbit:stop_apps(Stop), - clean_plugins(Stop), - rabbit_log:info("Plugins changed; enabled ~p, disabled ~p~n", - [Start, Stop]), - {ok, Start, Stop}; - _ -> - {error, {enabled_plugins_mismatch, FileJustChanged, OurFile}} - end. - -%% @doc Prepares the file system and installs all enabled plugins. -setup() -> - {ok, ExpandDir} = application:get_env(rabbit, plugins_expand_dir), - - %% Eliminate the contents of the destination directory - case delete_recursively(ExpandDir) of - ok -> ok; - {error, E1} -> throw({error, {cannot_delete_plugins_expand_dir, - [ExpandDir, E1]}}) - end, - - {ok, EnabledFile} = application:get_env(rabbit, enabled_plugins_file), - Enabled = read_enabled(EnabledFile), - prepare_plugins(Enabled). - -%% @doc Lists the plugins which are currently running. -active() -> - {ok, ExpandDir} = application:get_env(rabbit, plugins_expand_dir), - InstalledPlugins = plugin_names(list(ExpandDir)), - [App || {App, _, _} <- rabbit_misc:which_applications(), - lists:member(App, InstalledPlugins)]. - -%% @doc Get the list of plugins which are ready to be enabled. -list(PluginsDir) -> - EZs = [{ez, EZ} || EZ <- filelib:wildcard("*.ez", PluginsDir)], - FreeApps = [{app, App} || - App <- filelib:wildcard("*/ebin/*.app", PluginsDir)], - {AvailablePlugins, Problems} = - lists:foldl(fun ({error, EZ, Reason}, {Plugins1, Problems1}) -> - {Plugins1, [{EZ, Reason} | Problems1]}; - (Plugin = #plugin{}, {Plugins1, Problems1}) -> - {[Plugin|Plugins1], Problems1} - end, {[], []}, - [plugin_info(PluginsDir, Plug) || Plug <- EZs ++ FreeApps]), - case Problems of - [] -> ok; - _ -> rabbit_log:warning( - "Problem reading some plugins: ~p~n", [Problems]) - end, - Plugins = lists:filter(fun(P) -> not plugin_provided_by_otp(P) end, - AvailablePlugins), - ensure_dependencies(Plugins). - -%% @doc Read the list of enabled plugins from the supplied term file. -read_enabled(PluginsFile) -> - case rabbit_file:read_term_file(PluginsFile) of - {ok, [Plugins]} -> Plugins; - {ok, []} -> []; - {ok, [_|_]} -> throw({error, {malformed_enabled_plugins_file, - PluginsFile}}); - {error, enoent} -> []; - {error, Reason} -> throw({error, {cannot_read_enabled_plugins_file, - PluginsFile, Reason}}) - end. - -%% @doc Calculate the dependency graph from <i>Sources</i>. -%% When Reverse =:= true the bottom/leaf level applications are returned in -%% the resulting list, otherwise they're skipped. -dependencies(Reverse, Sources, AllPlugins) -> - {ok, G} = rabbit_misc:build_acyclic_graph( - fun ({App, _Deps}) -> [{App, App}] end, - fun ({App, Deps}) -> [{App, Dep} || Dep <- Deps] end, - [{Name, Deps} || #plugin{name = Name, - dependencies = Deps} <- AllPlugins]), - Dests = case Reverse of - false -> digraph_utils:reachable(Sources, G); - true -> digraph_utils:reaching(Sources, G) - end, - true = digraph:delete(G), - Dests. - -%% For a few known cases, an externally provided plugin can be trusted. -%% In this special case, it overrides the plugin. -plugin_provided_by_otp(#plugin{name = eldap}) -> - %% eldap was added to Erlang/OTP R15B01 (ERTS 5.9.1). In this case, - %% we prefer this version to the plugin. - rabbit_misc:version_compare(erlang:system_info(version), "5.9.1", gte); -plugin_provided_by_otp(_) -> - false. - -%% Make sure we don't list OTP apps in here, and also that we detect -%% missing dependencies. -ensure_dependencies(Plugins) -> - Names = plugin_names(Plugins), - NotThere = [Dep || #plugin{dependencies = Deps} <- Plugins, - Dep <- Deps, - not lists:member(Dep, Names)], - {OTP, Missing} = lists:partition(fun is_loadable/1, lists:usort(NotThere)), - case Missing of - [] -> ok; - _ -> Blame = [Name || #plugin{name = Name, - dependencies = Deps} <- Plugins, - lists:any(fun (Dep) -> - lists:member(Dep, Missing) - end, Deps)], - throw({error, {missing_dependencies, Missing, Blame}}) - end, - [P#plugin{dependencies = Deps -- OTP} - || P = #plugin{dependencies = Deps} <- Plugins]. - -is_loadable(App) -> - case application:load(App) of - {error, {already_loaded, _}} -> true; - ok -> application:unload(App), - true; - _ -> false - end. - -%%---------------------------------------------------------------------------- - -prepare_plugins(Enabled) -> - {ok, PluginsDistDir} = application:get_env(rabbit, plugins_dir), - {ok, ExpandDir} = application:get_env(rabbit, plugins_expand_dir), - - AllPlugins = list(PluginsDistDir), - Wanted = dependencies(false, Enabled, AllPlugins), - WantedPlugins = lookup_plugins(Wanted, AllPlugins), - - case filelib:ensure_dir(ExpandDir ++ "/") of - ok -> ok; - {error, E2} -> throw({error, {cannot_create_plugins_expand_dir, - [ExpandDir, E2]}}) - end, - - [prepare_plugin(Plugin, ExpandDir) || Plugin <- WantedPlugins], - - [prepare_dir_plugin(PluginAppDescPath) || - PluginAppDescPath <- filelib:wildcard(ExpandDir ++ "/*/ebin/*.app")], - Wanted. - -clean_plugins(Plugins) -> - {ok, ExpandDir} = application:get_env(rabbit, plugins_expand_dir), - [clean_plugin(Plugin, ExpandDir) || Plugin <- Plugins]. - -clean_plugin(Plugin, ExpandDir) -> - {ok, Mods} = application:get_key(Plugin, modules), - application:unload(Plugin), - [begin - code:soft_purge(Mod), - code:delete(Mod), - false = code:is_loaded(Mod) - end || Mod <- Mods], - delete_recursively(rabbit_misc:format("~s/~s", [ExpandDir, Plugin])). - -prepare_dir_plugin(PluginAppDescPath) -> - PluginEbinDir = filename:dirname(PluginAppDescPath), - Plugin = filename:basename(PluginAppDescPath, ".app"), - code:add_patha(PluginEbinDir), - case filelib:wildcard(PluginEbinDir++ "/*.beam") of - [] -> - ok; - [BeamPath | _] -> - Module = list_to_atom(filename:basename(BeamPath, ".beam")), - case code:ensure_loaded(Module) of - {module, _} -> - ok; - {error, badfile} -> - rabbit_log:error("Failed to enable plugin \"~s\": " - "it may have been built with an " - "incompatible (more recent?) " - "version of Erlang~n", [Plugin]), - throw({plugin_built_with_incompatible_erlang, Plugin}); - Error -> - throw({plugin_module_unloadable, Plugin, Error}) - end - end. - -%%---------------------------------------------------------------------------- - -delete_recursively(Fn) -> - case rabbit_file:recursive_delete([Fn]) of - ok -> ok; - {error, {Path, E}} -> {error, {cannot_delete, Path, E}} - end. - -prepare_plugin(#plugin{type = ez, location = Location}, ExpandDir) -> - zip:unzip(Location, [{cwd, ExpandDir}]); -prepare_plugin(#plugin{type = dir, name = Name, location = Location}, - ExpandDir) -> - rabbit_file:recursive_copy(Location, filename:join([ExpandDir, Name])). - -plugin_info(Base, {ez, EZ0}) -> - EZ = filename:join([Base, EZ0]), - case read_app_file(EZ) of - {application, Name, Props} -> mkplugin(Name, Props, ez, EZ); - {error, Reason} -> {error, EZ, Reason} - end; -plugin_info(Base, {app, App0}) -> - App = filename:join([Base, App0]), - case rabbit_file:read_term_file(App) of - {ok, [{application, Name, Props}]} -> - mkplugin(Name, Props, dir, - filename:absname( - filename:dirname(filename:dirname(App)))); - {error, Reason} -> - {error, App, {invalid_app, Reason}} - end. - -mkplugin(Name, Props, Type, Location) -> - Version = proplists:get_value(vsn, Props, "0"), - Description = proplists:get_value(description, Props, ""), - Dependencies = proplists:get_value(applications, Props, []), - #plugin{name = Name, version = Version, description = Description, - dependencies = Dependencies, location = Location, type = Type}. - -read_app_file(EZ) -> - case zip:list_dir(EZ) of - {ok, [_|ZippedFiles]} -> - case find_app_files(ZippedFiles) of - [AppPath|_] -> - {ok, [{AppPath, AppFile}]} = - zip:extract(EZ, [{file_list, [AppPath]}, memory]), - parse_binary(AppFile); - [] -> - {error, no_app_file} - end; - {error, Reason} -> - {error, {invalid_ez, Reason}} - end. - -find_app_files(ZippedFiles) -> - {ok, RE} = re:compile("^.*/ebin/.*.app$"), - [Path || {zip_file, Path, _, _, _, _} <- ZippedFiles, - re:run(Path, RE, [{capture, none}]) =:= match]. - -parse_binary(Bin) -> - try - {ok, Ts, _} = erl_scan:string(binary_to_list(Bin)), - {ok, Term} = erl_parse:parse_term(Ts), - Term - catch - Err -> {error, {invalid_app, Err}} - end. - -plugin_names(Plugins) -> - [Name || #plugin{name = Name} <- Plugins]. - -lookup_plugins(Names, AllPlugins) -> - [P || P = #plugin{name = Name} <- AllPlugins, lists:member(Name, Names)]. diff --git a/src/rabbit_plugins_main.erl b/src/rabbit_plugins_main.erl deleted file mode 100644 index 49f699c5..00000000 --- a/src/rabbit_plugins_main.erl +++ /dev/null @@ -1,307 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_plugins_main). --include("rabbit.hrl"). --include("rabbit_cli.hrl"). - --export([start/0, stop/0, action/6]). - --define(GLOBAL_DEFS(Node), [?NODE_DEF(Node)]). - --define(COMMANDS, - [{list, [?VERBOSE_DEF, ?MINIMAL_DEF, ?ENABLED_DEF, ?ENABLED_ALL_DEF]}, - {enable, [?OFFLINE_DEF, ?ONLINE_DEF]}, - {disable, [?OFFLINE_DEF, ?ONLINE_DEF]}, - {set, [?OFFLINE_DEF, ?ONLINE_DEF]}, - {sync, []}]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start/0 :: () -> no_return()). --spec(stop/0 :: () -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - --record(cli, {file, dir, all, enabled, implicit}). - - -start() -> - {ok, [[PluginsFile|_]|_]} = init:get_argument(enabled_plugins_file), - {ok, [[PluginsDir |_]|_]} = init:get_argument(plugins_dist_dir), - rabbit_cli:main( - fun (Args, NodeStr) -> - parse_arguments(Args, NodeStr) - end, - fun (Command, Node, Args, Opts) -> - action(Command, Node, Args, Opts, PluginsFile, PluginsDir) - end, rabbit_plugins_usage). - -stop() -> - ok. - -%%---------------------------------------------------------------------------- - -parse_arguments(CmdLine, NodeStr) -> - rabbit_cli:parse_arguments( - ?COMMANDS, ?GLOBAL_DEFS(NodeStr), ?NODE_OPT, CmdLine). - -action(Command, Node, Args, Opts, PluginsFile, PluginsDir) -> - All = rabbit_plugins:list(PluginsDir), - Enabled = rabbit_plugins:read_enabled(PluginsFile), - case Enabled -- plugin_names(All) of - [] -> ok; - Missing -> io:format("WARNING - plugins currently enabled but " - "missing: ~p~n~n", [Missing]) - end, - Implicit = rabbit_plugins:dependencies(false, Enabled, All), - State = #cli{file = PluginsFile, - dir = PluginsDir, - all = All, - enabled = Enabled, - implicit = Implicit}, - action(Command, Node, Args, Opts, State). - -action(list, Node, [], Opts, State) -> - action(list, Node, [".*"], Opts, State); -action(list, Node, [Pat], Opts, State) -> - format_plugins(Node, Pat, Opts, State); - -action(enable, Node, ToEnable0, Opts, State = #cli{all = All, - implicit = Implicit, - enabled = Enabled}) -> - case ToEnable0 of - [] -> throw({error_string, "Not enough arguments for 'enable'"}); - _ -> ok - end, - ToEnable = [list_to_atom(Name) || Name <- ToEnable0], - Missing = ToEnable -- plugin_names(All), - case Missing of - [] -> ok; - _ -> throw({error_string, fmt_missing(Missing)}) - end, - NewEnabled = lists:usort(Enabled ++ ToEnable), - NewImplicit = write_enabled_plugins(NewEnabled, State), - case NewEnabled -- Implicit of - [] -> io:format("Plugin configuration unchanged.~n"); - _ -> print_list("The following plugins have been enabled:", - NewImplicit -- Implicit) - end, - action_change(Opts, Node, Implicit, NewImplicit, State); - -action(set, Node, NewEnabled0, Opts, State = #cli{all = All, - implicit = Implicit}) -> - NewEnabled = [list_to_atom(Name) || Name <- NewEnabled0], - Missing = NewEnabled -- plugin_names(All), - case Missing of - [] -> ok; - _ -> throw({error_string, fmt_missing(Missing)}) - end, - NewImplicit = write_enabled_plugins(NewEnabled, State), - case NewImplicit of - [] -> io:format("All plugins are now disabled.~n"); - _ -> print_list("The following plugins are now enabled:", - NewImplicit) - end, - action_change(Opts, Node, Implicit, NewImplicit, State); - -action(disable, Node, ToDisable0, Opts, State = #cli{all = All, - implicit = Implicit, - enabled = Enabled}) -> - case ToDisable0 of - [] -> throw({error_string, "Not enough arguments for 'disable'"}); - _ -> ok - end, - ToDisable = [list_to_atom(Name) || Name <- ToDisable0], - Missing = ToDisable -- plugin_names(All), - case Missing of - [] -> ok; - _ -> print_list("Warning: the following plugins could not be found:", - Missing) - end, - ToDisableDeps = rabbit_plugins:dependencies(true, ToDisable, All), - NewEnabled = Enabled -- ToDisableDeps, - NewImplicit = write_enabled_plugins(NewEnabled, State), - case length(Enabled) =:= length(NewEnabled) of - true -> io:format("Plugin configuration unchanged.~n"); - false -> print_list("The following plugins have been disabled:", - Implicit -- NewImplicit) - end, - action_change(Opts, Node, Implicit, NewImplicit, State); - -action(sync, Node, [], _Opts, State) -> - sync(Node, true, State). - -%%---------------------------------------------------------------------------- - -%% Pretty print a list of plugins. -format_plugins(Node, Pattern, Opts, #cli{all = All, - enabled = Enabled, - implicit = Implicit}) -> - Verbose = proplists:get_bool(?VERBOSE_OPT, Opts), - Minimal = proplists:get_bool(?MINIMAL_OPT, Opts), - Format = case {Verbose, Minimal} of - {false, false} -> normal; - {true, false} -> verbose; - {false, true} -> minimal; - {true, true} -> throw({error_string, - "Cannot specify -m and -v together"}) - end, - OnlyEnabled = proplists:get_bool(?ENABLED_OPT, Opts), - OnlyEnabledAll = proplists:get_bool(?ENABLED_ALL_OPT, Opts), - - EnabledImplicitly = Implicit -- Enabled, - {StatusMsg, Running} = - case rabbit_cli:rpc_call(Node, rabbit_plugins, active, []) of - {badrpc, _} -> {"[failed to contact ~s - status not shown]", []}; - Active -> {"* = running on ~s", Active} - end, - {ok, RE} = re:compile(Pattern), - Plugins = [ Plugin || - Plugin = #plugin{name = Name} <- All, - re:run(atom_to_list(Name), RE, [{capture, none}]) =:= match, - if OnlyEnabled -> lists:member(Name, Enabled); - OnlyEnabledAll -> lists:member(Name, Enabled) or - lists:member(Name,EnabledImplicitly); - true -> true - end], - Plugins1 = usort_plugins(Plugins), - MaxWidth = lists:max([length(atom_to_list(Name)) || - #plugin{name = Name} <- Plugins1] ++ [0]), - case Format of - minimal -> ok; - _ -> io:format(" Configured: E = explicitly enabled; " - "e = implicitly enabled~n" - " | Status: ~s~n" - " |/~n", [rabbit_misc:format(StatusMsg, [Node])]) - end, - [format_plugin(P, Enabled, EnabledImplicitly, Running, - Format, MaxWidth) || P <- Plugins1], - ok. - -format_plugin(#plugin{name = Name, version = Version, - description = Description, dependencies = Deps}, - Enabled, EnabledImplicitly, Running, Format, - MaxWidth) -> - EnabledGlyph = case {lists:member(Name, Enabled), - lists:member(Name, EnabledImplicitly)} of - {true, false} -> "E"; - {false, true} -> "e"; - _ -> " " - end, - RunningGlyph = case lists:member(Name, Running) of - true -> "*"; - false -> " " - end, - Glyph = rabbit_misc:format("[~s~s]", [EnabledGlyph, RunningGlyph]), - Opt = fun (_F, A, A) -> ok; - ( F, A, _) -> io:format(F, [A]) - end, - case Format of - minimal -> io:format("~s~n", [Name]); - normal -> io:format("~s ~-" ++ integer_to_list(MaxWidth) ++ "w ", - [Glyph, Name]), - Opt("~s", Version, undefined), - io:format("~n"); - verbose -> io:format("~s ~w~n", [Glyph, Name]), - Opt(" Version: \t~s~n", Version, undefined), - Opt(" Dependencies:\t~p~n", Deps, []), - Opt(" Description: \t~s~n", Description, undefined), - io:format("~n") - end. - -print_list(Header, Plugins) -> - io:format(fmt_list(Header, Plugins)). - -fmt_list(Header, Plugins) -> - lists:flatten( - [Header, $\n, [io_lib:format(" ~s~n", [P]) || P <- Plugins]]). - -fmt_missing(Missing) -> - fmt_list("The following plugins could not be found:", Missing). - -usort_plugins(Plugins) -> - lists:usort(fun plugins_cmp/2, Plugins). - -plugins_cmp(#plugin{name = N1, version = V1}, - #plugin{name = N2, version = V2}) -> - {N1, V1} =< {N2, V2}. - -%% Return the names of the given plugins. -plugin_names(Plugins) -> - [Name || #plugin{name = Name} <- Plugins]. - -%% Write the enabled plugin names on disk. -write_enabled_plugins(Plugins, #cli{file = File, - all = All}) -> - case rabbit_file:write_term_file(File, [Plugins]) of - ok -> rabbit_plugins:dependencies(false, Plugins, All); - {error, Reason} -> throw({error, {cannot_write_enabled_plugins_file, - File, Reason}}) - end. - -action_change(Opts, Node, Old, New, State) -> - action_change0(proplists:get_bool(?OFFLINE_OPT, Opts), - proplists:get_bool(?ONLINE_OPT, Opts), - Node, Old, New, State). - -action_change0(true, _Online, _Node, Same, Same, _State) -> - %% Definitely nothing to do - ok; -action_change0(true, _Online, _Node, _Old, _New, _State) -> - io:format("Offline change; changes will take effect at broker restart.~n"); -action_change0(false, Online, Node, _Old, _New, State) -> - sync(Node, Online, State). - -sync(Node, ForceOnline, #cli{file = File}) -> - rpc_call(Node, ForceOnline, rabbit_plugins, ensure, [File]). - -rpc_call(Node, Online, Mod, Fun, Args) -> - io:format("~nApplying plugin configuration to ~s...", [Node]), - case rabbit_cli:rpc_call(Node, Mod, Fun, Args) of - {ok, [], []} -> - io:format(" nothing to do.~n", []); - {ok, Start, []} -> - io:format(" started ~b plugin~s.~n", [length(Start), plur(Start)]); - {ok, [], Stop} -> - io:format(" stopped ~b plugin~s.~n", [length(Stop), plur(Stop)]); - {ok, Start, Stop} -> - io:format(" stopped ~b plugin~s and started ~b plugin~s.~n", - [length(Stop), plur(Stop), length(Start), plur(Start)]); - {badrpc, nodedown} = Error -> - io:format(" failed.~n", []), - case Online of - true -> Error; - false -> io:format( - " * Could not contact node ~s.~n" - " Changes will take effect at broker restart.~n" - " * Options: --online - fail if broker cannot be " - "contacted.~n" - " --offline - do not try to contact " - "broker.~n", - [Node]) - end; - Error -> - io:format(" failed.~n", []), - Error - end. - -plur([_]) -> ""; -plur(_) -> "s". diff --git a/src/rabbit_policies.erl b/src/rabbit_policies.erl deleted file mode 100644 index cc88765f..00000000 --- a/src/rabbit_policies.erl +++ /dev/null @@ -1,86 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_policies). --behaviour(rabbit_policy_validator). - --include("rabbit.hrl"). - --export([register/0, validate_policy/1]). - --rabbit_boot_step({?MODULE, - [{description, "internal policies"}, - {mfa, {rabbit_policies, register, []}}, - {requires, rabbit_registry}, - {enables, recovery}]}). - -register() -> - [rabbit_registry:register(Class, Name, ?MODULE) || - {Class, Name} <- [{policy_validator, <<"alternate-exchange">>}, - {policy_validator, <<"dead-letter-exchange">>}, - {policy_validator, <<"dead-letter-routing-key">>}, - {policy_validator, <<"message-ttl">>}, - {policy_validator, <<"expires">>}, - {policy_validator, <<"max-length">>}, - {policy_validator, <<"max-length-bytes">>}]], - ok. - -validate_policy(Terms) -> - lists:foldl(fun ({Key, Value}, ok) -> validate_policy0(Key, Value); - (_, Error) -> Error - end, ok, Terms). - -validate_policy0(<<"alternate-exchange">>, Value) - when is_binary(Value) -> - ok; -validate_policy0(<<"alternate-exchange">>, Value) -> - {error, "~p is not a valid alternate exchange name", [Value]}; - -validate_policy0(<<"dead-letter-exchange">>, Value) - when is_binary(Value) -> - ok; -validate_policy0(<<"dead-letter-exchange">>, Value) -> - {error, "~p is not a valid dead letter exchange name", [Value]}; - -validate_policy0(<<"dead-letter-routing-key">>, Value) - when is_binary(Value) -> - ok; -validate_policy0(<<"dead-letter-routing-key">>, Value) -> - {error, "~p is not a valid dead letter routing key", [Value]}; - -validate_policy0(<<"message-ttl">>, Value) - when is_integer(Value), Value >= 0 -> - ok; -validate_policy0(<<"message-ttl">>, Value) -> - {error, "~p is not a valid message TTL", [Value]}; - -validate_policy0(<<"expires">>, Value) - when is_integer(Value), Value >= 1 -> - ok; -validate_policy0(<<"expires">>, Value) -> - {error, "~p is not a valid queue expiry", [Value]}; - -validate_policy0(<<"max-length">>, Value) - when is_integer(Value), Value >= 0 -> - ok; -validate_policy0(<<"max-length">>, Value) -> - {error, "~p is not a valid maximum length", [Value]}; - -validate_policy0(<<"max-length-bytes">>, Value) - when is_integer(Value), Value >= 0 -> - ok; -validate_policy0(<<"max-length-bytes">>, Value) -> - {error, "~p is not a valid maximum length in bytes", [Value]}. diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl deleted file mode 100644 index f5d03360..00000000 --- a/src/rabbit_policy.erl +++ /dev/null @@ -1,347 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_policy). - -%% TODO specs - --behaviour(rabbit_runtime_parameter). - --include("rabbit.hrl"). - --import(rabbit_misc, [pget/2]). - --export([register/0]). --export([invalidate/0, recover/0]). --export([name/1, get/2, get_arg/3, set/1]). --export([validate/5, notify/4, notify_clear/3]). --export([parse_set/6, set/6, delete/2, lookup/2, list/0, list/1, - list_formatted/1, info_keys/0]). - --rabbit_boot_step({?MODULE, - [{description, "policy parameters"}, - {mfa, {rabbit_policy, register, []}}, - {requires, rabbit_registry}, - {enables, recovery}]}). - -register() -> - rabbit_registry:register(runtime_parameter, <<"policy">>, ?MODULE). - -name(#amqqueue{policy = Policy}) -> name0(Policy); -name(#exchange{policy = Policy}) -> name0(Policy). - -name0(undefined) -> none; -name0(Policy) -> pget(name, Policy). - -set(Q = #amqqueue{name = Name}) -> Q#amqqueue{policy = set0(Name)}; -set(X = #exchange{name = Name}) -> X#exchange{policy = set0(Name)}. - -set0(Name = #resource{virtual_host = VHost}) -> match(Name, list(VHost)). - -get(Name, #amqqueue{policy = Policy}) -> get0(Name, Policy); -get(Name, #exchange{policy = Policy}) -> get0(Name, Policy); -%% Caution - SLOW. -get(Name, EntityName = #resource{virtual_host = VHost}) -> - get0(Name, match(EntityName, list(VHost))). - -get0(_Name, undefined) -> undefined; -get0(Name, List) -> case pget(definition, List) of - undefined -> undefined; - Policy -> pget(Name, Policy) - end. - -%% Many heads for optimisation -get_arg(_AName, _PName, #exchange{arguments = [], policy = undefined}) -> - undefined; -get_arg(_AName, PName, X = #exchange{arguments = []}) -> - get(PName, X); -get_arg(AName, PName, X = #exchange{arguments = Args}) -> - case rabbit_misc:table_lookup(Args, AName) of - undefined -> get(PName, X); - {_Type, Arg} -> Arg - end. - -%%---------------------------------------------------------------------------- - -%% Gets called during upgrades - therefore must not assume anything about the -%% state of Mnesia -invalidate() -> - rabbit_file:write_file(invalid_file(), <<"">>). - -recover() -> - case rabbit_file:is_file(invalid_file()) of - true -> recover0(), - rabbit_file:delete(invalid_file()); - false -> ok - end. - -%% To get here we have to have just completed an Mnesia upgrade - i.e. we are -%% the first node starting. So we can rewrite the whole database. Note that -%% recovery has not yet happened; we must work with the rabbit_durable_<thing> -%% variants. -recover0() -> - Xs = mnesia:dirty_match_object(rabbit_durable_exchange, #exchange{_ = '_'}), - Qs = mnesia:dirty_match_object(rabbit_durable_queue, #amqqueue{_ = '_'}), - Policies = list(), - [rabbit_misc:execute_mnesia_transaction( - fun () -> - mnesia:write( - rabbit_durable_exchange, - rabbit_exchange_decorator:set( - X#exchange{policy = match(Name, Policies)}), write) - end) || X = #exchange{name = Name} <- Xs], - [rabbit_misc:execute_mnesia_transaction( - fun () -> - mnesia:write( - rabbit_durable_queue, - rabbit_queue_decorator:set( - Q#amqqueue{policy = match(Name, Policies)}), write) - end) || Q = #amqqueue{name = Name} <- Qs], - ok. - -invalid_file() -> - filename:join(rabbit_mnesia:dir(), "policies_are_invalid"). - -%%---------------------------------------------------------------------------- - -parse_set(VHost, Name, Pattern, Definition, Priority, ApplyTo) -> - try list_to_integer(Priority) of - Num -> parse_set0(VHost, Name, Pattern, Definition, Num, ApplyTo) - catch - error:badarg -> {error, "~p priority must be a number", [Priority]} - end. - -parse_set0(VHost, Name, Pattern, Defn, Priority, ApplyTo) -> - case rabbit_misc:json_decode(Defn) of - {ok, JSON} -> - set0(VHost, Name, - [{<<"pattern">>, list_to_binary(Pattern)}, - {<<"definition">>, rabbit_misc:json_to_term(JSON)}, - {<<"priority">>, Priority}, - {<<"apply-to">>, ApplyTo}]); - error -> - {error_string, "JSON decoding error"} - end. - -set(VHost, Name, Pattern, Definition, Priority, ApplyTo) -> - PolicyProps = [{<<"pattern">>, Pattern}, - {<<"definition">>, Definition}, - {<<"priority">>, case Priority of - undefined -> 0; - _ -> Priority - end}, - {<<"apply-to">>, case ApplyTo of - undefined -> <<"all">>; - _ -> ApplyTo - end}], - set0(VHost, Name, PolicyProps). - -set0(VHost, Name, Term) -> - rabbit_runtime_parameters:set_any(VHost, <<"policy">>, Name, Term, none). - -delete(VHost, Name) -> - rabbit_runtime_parameters:clear_any(VHost, <<"policy">>, Name). - -lookup(VHost, Name) -> - case rabbit_runtime_parameters:lookup(VHost, <<"policy">>, Name) of - not_found -> not_found; - P -> p(P, fun ident/1) - end. - -list() -> - list('_'). - -list(VHost) -> - list0(VHost, fun ident/1). - -list_formatted(VHost) -> - order_policies(list0(VHost, fun format/1)). - -list0(VHost, DefnFun) -> - [p(P, DefnFun) || P <- rabbit_runtime_parameters:list(VHost, <<"policy">>)]. - -order_policies(PropList) -> - lists:sort(fun (A, B) -> pget(priority, A) < pget(priority, B) end, - PropList). - -p(Parameter, DefnFun) -> - Value = pget(value, Parameter), - [{vhost, pget(vhost, Parameter)}, - {name, pget(name, Parameter)}, - {pattern, pget(<<"pattern">>, Value)}, - {'apply-to', pget(<<"apply-to">>, Value)}, - {definition, DefnFun(pget(<<"definition">>, Value))}, - {priority, pget(<<"priority">>, Value)}]. - -format(Term) -> - {ok, JSON} = rabbit_misc:json_encode(rabbit_misc:term_to_json(Term)), - list_to_binary(JSON). - -ident(X) -> X. - -info_keys() -> [vhost, name, 'apply-to', pattern, definition, priority]. - -%%---------------------------------------------------------------------------- - -validate(_VHost, <<"policy">>, Name, Term, _User) -> - rabbit_parameter_validation:proplist( - Name, policy_validation(), Term). - -notify(VHost, <<"policy">>, Name, Term) -> - rabbit_event:notify(policy_set, [{name, Name} | Term]), - update_policies(VHost). - -notify_clear(VHost, <<"policy">>, Name) -> - rabbit_event:notify(policy_cleared, [{name, Name}]), - update_policies(VHost). - -%%---------------------------------------------------------------------------- - -%% [1] We need to prevent this from becoming O(n^2) in a similar -%% manner to rabbit_binding:remove_for_{source,destination}. So see -%% the comment in rabbit_binding:lock_route_tables/0 for more rationale. -%% [2] We could be here in a post-tx fun after the vhost has been -%% deleted; in which case it's fine to do nothing. -update_policies(VHost) -> - Tabs = [rabbit_queue, rabbit_durable_queue, - rabbit_exchange, rabbit_durable_exchange], - {Xs, Qs} = rabbit_misc:execute_mnesia_transaction( - fun() -> - [mnesia:lock({table, T}, write) || T <- Tabs], %% [1] - case catch list(VHost) of - {error, {no_such_vhost, _}} -> - ok; %% [2] - Policies -> - {[update_exchange(X, Policies) || - X <- rabbit_exchange:list(VHost)], - [update_queue(Q, Policies) || - Q <- rabbit_amqqueue:list(VHost)]} - end - end), - [catch notify(X) || X <- Xs], - [catch notify(Q) || Q <- Qs], - ok. - -update_exchange(X = #exchange{name = XName, policy = OldPolicy}, Policies) -> - case match(XName, Policies) of - OldPolicy -> no_change; - NewPolicy -> case rabbit_exchange:update( - XName, fun (X0) -> - rabbit_exchange_decorator:set( - X0 #exchange{policy = NewPolicy}) - end) of - #exchange{} = X1 -> {X, X1}; - not_found -> {X, X } - end - end. - -update_queue(Q = #amqqueue{name = QName, policy = OldPolicy}, Policies) -> - case match(QName, Policies) of - OldPolicy -> no_change; - NewPolicy -> case rabbit_amqqueue:update( - QName, fun(Q1) -> - rabbit_queue_decorator:set( - Q1#amqqueue{policy = NewPolicy}) - end) of - #amqqueue{} = Q1 -> {Q, Q1}; - not_found -> {Q, Q } - end - end. - -notify(no_change)-> - ok; -notify({X1 = #exchange{}, X2 = #exchange{}}) -> - rabbit_exchange:policy_changed(X1, X2); -notify({Q1 = #amqqueue{}, Q2 = #amqqueue{}}) -> - rabbit_amqqueue:policy_changed(Q1, Q2). - -match(Name, Policies) -> - case lists:sort(fun sort_pred/2, [P || P <- Policies, matches(Name, P)]) of - [] -> undefined; - [Policy | _Rest] -> Policy - end. - -matches(#resource{name = Name, kind = Kind, virtual_host = VHost}, Policy) -> - matches_type(Kind, pget('apply-to', Policy)) andalso - match =:= re:run(Name, pget(pattern, Policy), [{capture, none}]) andalso - VHost =:= pget(vhost, Policy). - -matches_type(exchange, <<"exchanges">>) -> true; -matches_type(queue, <<"queues">>) -> true; -matches_type(exchange, <<"all">>) -> true; -matches_type(queue, <<"all">>) -> true; -matches_type(_, _) -> false. - -sort_pred(A, B) -> pget(priority, A) >= pget(priority, B). - -%%---------------------------------------------------------------------------- - -policy_validation() -> - [{<<"priority">>, fun rabbit_parameter_validation:number/2, mandatory}, - {<<"pattern">>, fun rabbit_parameter_validation:regex/2, mandatory}, - {<<"apply-to">>, fun apply_to_validation/2, optional}, - {<<"definition">>, fun validation/2, mandatory}]. - -validation(_Name, []) -> - {error, "no policy provided", []}; -validation(_Name, Terms) when is_list(Terms) -> - {Keys, Modules} = lists:unzip( - rabbit_registry:lookup_all(policy_validator)), - [] = dups(Keys), %% ASSERTION - Validators = lists:zipwith(fun (M, K) -> {M, a2b(K)} end, Modules, Keys), - case is_proplist(Terms) of - true -> {TermKeys, _} = lists:unzip(Terms), - case dups(TermKeys) of - [] -> validation0(Validators, Terms); - Dup -> {error, "~p duplicate keys not allowed", [Dup]} - end; - false -> {error, "definition must be a dictionary: ~p", [Terms]} - end; -validation(_Name, Term) -> - {error, "parse error while reading policy: ~p", [Term]}. - -validation0(Validators, Terms) -> - case lists:foldl( - fun (Mod, {ok, TermsLeft}) -> - ModKeys = proplists:get_all_values(Mod, Validators), - case [T || {Key, _} = T <- TermsLeft, - lists:member(Key, ModKeys)] of - [] -> {ok, TermsLeft}; - Scope -> {Mod:validate_policy(Scope), TermsLeft -- Scope} - end; - (_, Acc) -> - Acc - end, {ok, Terms}, proplists:get_keys(Validators)) of - {ok, []} -> - ok; - {ok, Unvalidated} -> - {error, "~p are not recognised policy settings", [Unvalidated]}; - {Error, _} -> - Error - end. - -a2b(A) -> list_to_binary(atom_to_list(A)). - -dups(L) -> L -- lists:usort(L). - -is_proplist(L) -> length(L) =:= length([I || I = {_, _} <- L]). - -apply_to_validation(_Name, <<"all">>) -> ok; -apply_to_validation(_Name, <<"exchanges">>) -> ok; -apply_to_validation(_Name, <<"queues">>) -> ok; -apply_to_validation(_Name, Term) -> - {error, "apply-to '~s' unrecognised; should be 'queues', 'exchanges' " - "or 'all'", [Term]}. diff --git a/src/rabbit_policy_validator.erl b/src/rabbit_policy_validator.erl deleted file mode 100644 index dd052089..00000000 --- a/src/rabbit_policy_validator.erl +++ /dev/null @@ -1,39 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_policy_validator). - --ifdef(use_specs). - --export_type([validate_results/0]). - --type(validate_results() :: - 'ok' | {error, string(), [term()]} | [validate_results()]). - --callback validate_policy([{binary(), term()}]) -> validate_results(). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [ - {validate_policy, 1} - ]; -behaviour_info(_Other) -> - undefined. - --endif. diff --git a/src/rabbit_prelaunch.erl b/src/rabbit_prelaunch.erl deleted file mode 100644 index 6a6a4ee6..00000000 --- a/src/rabbit_prelaunch.erl +++ /dev/null @@ -1,122 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_prelaunch). - --export([start/0, stop/0]). - --import(rabbit_misc, [pget/2, pget/3]). - --include("rabbit.hrl"). - --define(DIST_PORT_NOT_CONFIGURED, 0). --define(ERROR_CODE, 1). --define(DIST_PORT_CONFIGURED, 2). - -%%---------------------------------------------------------------------------- -%% Specs -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start/0 :: () -> no_return()). --spec(stop/0 :: () -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start() -> - case init:get_plain_arguments() of - [NodeStr] -> - Node = rabbit_nodes:make(NodeStr), - {NodeName, NodeHost} = rabbit_nodes:parts(Node), - ok = duplicate_node_check(NodeName, NodeHost), - ok = dist_port_set_check(), - ok = dist_port_use_check(NodeHost); - [] -> - %% Ignore running node while installing windows service - ok = dist_port_set_check(), - ok - end, - rabbit_misc:quit(?DIST_PORT_NOT_CONFIGURED), - ok. - -stop() -> - ok. - -%%---------------------------------------------------------------------------- - -%% Check whether a node with the same name is already running -duplicate_node_check(NodeName, NodeHost) -> - case rabbit_nodes:names(NodeHost) of - {ok, NamePorts} -> - case proplists:is_defined(NodeName, NamePorts) of - true -> io:format( - "ERROR: node with name ~p already running on ~p~n", - [NodeName, NodeHost]), - rabbit_misc:quit(?ERROR_CODE); - false -> ok - end; - {error, EpmdReason} -> - io:format("ERROR: epmd error for host ~s: ~s~n", - [NodeHost, rabbit_misc:format_inet_error(EpmdReason)]), - rabbit_misc:quit(?ERROR_CODE) - end. - -dist_port_set_check() -> - case os:getenv("RABBITMQ_CONFIG_FILE") of - false -> - ok; - File -> - case file:consult(File ++ ".config") of - {ok, [Config]} -> - Kernel = pget(kernel, Config, []), - case {pget(inet_dist_listen_min, Kernel, none), - pget(inet_dist_listen_max, Kernel, none)} of - {none, none} -> ok; - _ -> rabbit_misc:quit(?DIST_PORT_CONFIGURED) - end; - {ok, _} -> - ok; - {error, _} -> - ok - end - end. - -dist_port_use_check(NodeHost) -> - case os:getenv("RABBITMQ_DIST_PORT") of - false -> ok; - PortStr -> Port = list_to_integer(PortStr), - case gen_tcp:listen(Port, [inet, {reuseaddr, true}]) of - {ok, Sock} -> gen_tcp:close(Sock); - {error, _} -> dist_port_use_check_fail(Port, NodeHost) - end - end. - --ifdef(use_specs). --spec(dist_port_use_check_fail/2 :: (non_neg_integer(), string()) -> - no_return()). --endif. -dist_port_use_check_fail(Port, Host) -> - {ok, Names} = rabbit_nodes:names(Host), - case [N || {N, P} <- Names, P =:= Port] of - [] -> io:format("ERROR: distribution port ~b in use on ~s " - "(by non-Erlang process?)~n", [Port, Host]); - [Name] -> io:format("ERROR: distribution port ~b in use by ~s@~s~n", - [Port, Name, Host]) - end, - rabbit_misc:quit(?ERROR_CODE). diff --git a/src/rabbit_prequeue.erl b/src/rabbit_prequeue.erl deleted file mode 100644 index 16e30cac..00000000 --- a/src/rabbit_prequeue.erl +++ /dev/null @@ -1,104 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License at -%% http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the -%% License for the specific language governing rights and limitations -%% under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_prequeue). - -%% This is the initial gen_server that all queue processes start off -%% as. It handles the decision as to whether we need to start a new -%% slave, a new master/unmirrored, or whether we are restarting (and -%% if so, as what). Thus a crashing queue process can restart from here -%% and always do the right thing. - --export([start_link/3]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3]). - --behaviour(gen_server2). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([start_mode/0]). - --type(start_mode() :: 'declare' | 'recovery' | 'slave'). - --spec(start_link/3 :: (rabbit_types:amqqueue(), start_mode(), pid()) - -> rabbit_types:ok_pid_or_error()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Q, StartMode, Marker) -> - gen_server2:start_link(?MODULE, {Q, StartMode, Marker}, []). - -%%---------------------------------------------------------------------------- - -init({Q, StartMode, Marker}) -> - init(Q, case {is_process_alive(Marker), StartMode} of - {true, slave} -> slave; - {true, _} -> master; - {false, _} -> restart - end). - -init(Q, master) -> rabbit_amqqueue_process:init(Q); -init(Q, slave) -> rabbit_mirror_queue_slave:init(Q); - -init(#amqqueue{name = QueueName}, restart) -> - {ok, Q = #amqqueue{pid = QPid, - slave_pids = SPids}} = rabbit_amqqueue:lookup(QueueName), - LocalOrMasterDown = node(QPid) =:= node() - orelse not rabbit_mnesia:on_running_node(QPid), - Slaves = [SPid || SPid <- SPids, rabbit_mnesia:is_process_alive(SPid)], - case rabbit_mnesia:is_process_alive(QPid) of - true -> false = LocalOrMasterDown, %% assertion - rabbit_mirror_queue_slave:go(self(), async), - rabbit_mirror_queue_slave:init(Q); %% [1] - false -> case LocalOrMasterDown andalso Slaves =:= [] of - true -> crash_restart(Q); %% [2] - false -> timer:sleep(25), - init(Q, restart) %% [3] - end - end. -%% [1] There is a master on another node. Regardless of whether we -%% were originally a master or a slave, we are now a new slave. -%% -%% [2] Nothing is alive. We are the last best hope. Try to restart as a master. -%% -%% [3] The current master is dead but either there are alive slaves to -%% take over or it's all happening on a different node anyway. This is -%% not a stable situation. Sleep and wait for somebody else to make a -%% move. - -crash_restart(Q = #amqqueue{name = QueueName}) -> - rabbit_log:error("Restarting crashed ~s.~n", [rabbit_misc:rs(QueueName)]), - gen_server2:cast(self(), init), - rabbit_amqqueue_process:init(Q#amqqueue{pid = self()}). - -%%---------------------------------------------------------------------------- - -%% This gen_server2 always hands over to some other module at the end -%% of init/1. -handle_call(_Msg, _From, _State) -> exit(unreachable). -handle_cast(_Msg, _State) -> exit(unreachable). -handle_info(_Msg, _State) -> exit(unreachable). -terminate(_Reason, _State) -> exit(unreachable). -code_change(_OldVsn, _State, _Extra) -> exit(unreachable). - diff --git a/src/rabbit_queue_collector.erl b/src/rabbit_queue_collector.erl deleted file mode 100644 index 70a4da1e..00000000 --- a/src/rabbit_queue_collector.erl +++ /dev/null @@ -1,92 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_queue_collector). - --behaviour(gen_server). - --export([start_link/1, register/2, delete_all/1]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --record(state, {monitors, delete_from}). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/1 :: (rabbit_types:proc_name()) -> - rabbit_types:ok_pid_or_error()). --spec(register/2 :: (pid(), pid()) -> 'ok'). --spec(delete_all/1 :: (pid()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(ProcName) -> - gen_server:start_link(?MODULE, [ProcName], []). - -register(CollectorPid, Q) -> - gen_server:call(CollectorPid, {register, Q}, infinity). - -delete_all(CollectorPid) -> - gen_server:call(CollectorPid, delete_all, infinity). - -%%---------------------------------------------------------------------------- - -init([ProcName]) -> - ?store_proc_name(ProcName), - {ok, #state{monitors = pmon:new(), delete_from = undefined}}. - -%%-------------------------------------------------------------------------- - -handle_call({register, QPid}, _From, - State = #state{monitors = QMons, delete_from = Deleting}) -> - case Deleting of - undefined -> ok; - _ -> ok = rabbit_amqqueue:delete_immediately([QPid]) - end, - {reply, ok, State#state{monitors = pmon:monitor(QPid, QMons)}}; - -handle_call(delete_all, From, State = #state{monitors = QMons, - delete_from = undefined}) -> - case pmon:monitored(QMons) of - [] -> {reply, ok, State#state{delete_from = From}}; - QPids -> ok = rabbit_amqqueue:delete_immediately(QPids), - {noreply, State#state{delete_from = From}} - end. - -handle_cast(Msg, State) -> - {stop, {unhandled_cast, Msg}, State}. - -handle_info({'DOWN', _MRef, process, DownPid, _Reason}, - State = #state{monitors = QMons, delete_from = Deleting}) -> - QMons1 = pmon:erase(DownPid, QMons), - case Deleting =/= undefined andalso pmon:is_empty(QMons1) of - true -> gen_server:reply(Deleting, ok); - false -> ok - end, - {noreply, State#state{monitors = QMons1}}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. diff --git a/src/rabbit_queue_consumers.erl b/src/rabbit_queue_consumers.erl deleted file mode 100644 index c60adb5b..00000000 --- a/src/rabbit_queue_consumers.erl +++ /dev/null @@ -1,464 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_queue_consumers). - --export([new/0, max_active_priority/1, inactive/1, all/1, count/0, - unacknowledged_message_count/0, add/9, remove/3, erase_ch/2, - send_drained/0, deliver/3, record_ack/3, subtract_acks/3, - possibly_unblock/3, - resume_fun/0, notify_sent_fun/1, activate_limit_fun/0, - credit/6, utilisation/1]). - -%%---------------------------------------------------------------------------- - --define(UNSENT_MESSAGE_LIMIT, 200). - -%% Utilisation average calculations are all in μs. --define(USE_AVG_HALF_LIFE, 1000000.0). - --record(state, {consumers, use}). - --record(consumer, {tag, ack_required, prefetch, args}). - -%% These are held in our process dictionary --record(cr, {ch_pid, - monitor_ref, - acktags, - consumer_count, - %% Queue of {ChPid, #consumer{}} for consumers which have - %% been blocked for any reason - blocked_consumers, - %% The limiter itself - limiter, - %% Internal flow control for queue -> writer - unsent_message_count}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type time_micros() :: non_neg_integer(). --type ratio() :: float(). --type state() :: #state{consumers ::priority_queue:q(), - use :: {'inactive', - time_micros(), time_micros(), ratio()} | - {'active', time_micros(), ratio()}}. --type ch() :: pid(). --type ack() :: non_neg_integer(). --type cr_fun() :: fun ((#cr{}) -> #cr{}). --type fetch_result() :: {rabbit_types:basic_message(), boolean(), ack()}. - --spec new() -> state(). --spec max_active_priority(state()) -> integer() | 'infinity' | 'empty'. --spec inactive(state()) -> boolean(). --spec all(state()) -> [{ch(), rabbit_types:ctag(), boolean(), - non_neg_integer(), rabbit_framing:amqp_table()}]. --spec count() -> non_neg_integer(). --spec unacknowledged_message_count() -> non_neg_integer(). --spec add(ch(), rabbit_types:ctag(), boolean(), pid(), boolean(), - non_neg_integer(), rabbit_framing:amqp_table(), boolean(), state()) - -> state(). --spec remove(ch(), rabbit_types:ctag(), state()) -> - 'not_found' | state(). --spec erase_ch(ch(), state()) -> - 'not_found' | {[ack()], [rabbit_types:ctag()], - state()}. --spec send_drained() -> 'ok'. --spec deliver(fun ((boolean()) -> {fetch_result(), T}), - rabbit_amqqueue:name(), state()) -> - {'delivered', boolean(), T, state()} | - {'undelivered', boolean(), state()}. --spec record_ack(ch(), pid(), ack()) -> 'ok'. --spec subtract_acks(ch(), [ack()], state()) -> - 'not_found' | 'unchanged' | {'unblocked', state()}. --spec possibly_unblock(cr_fun(), ch(), state()) -> - 'unchanged' | {'unblocked', state()}. --spec resume_fun() -> cr_fun(). --spec notify_sent_fun(non_neg_integer()) -> cr_fun(). --spec activate_limit_fun() -> cr_fun(). --spec credit(boolean(), integer(), boolean(), ch(), rabbit_types:ctag(), - state()) -> 'unchanged' | {'unblocked', state()}. --spec utilisation(state()) -> ratio(). - --endif. - -%%---------------------------------------------------------------------------- - -new() -> #state{consumers = priority_queue:new(), - use = {active, now_micros(), 1.0}}. - -max_active_priority(#state{consumers = Consumers}) -> - priority_queue:highest(Consumers). - -inactive(#state{consumers = Consumers}) -> - priority_queue:is_empty(Consumers). - -all(#state{consumers = Consumers}) -> - lists:foldl(fun (C, Acc) -> consumers(C#cr.blocked_consumers, Acc) end, - consumers(Consumers, []), all_ch_record()). - -consumers(Consumers, Acc) -> - priority_queue:fold( - fun ({ChPid, Consumer}, _P, Acc1) -> - #consumer{tag = CTag, ack_required = Ack, prefetch = Prefetch, - args = Args} = Consumer, - [{ChPid, CTag, Ack, Prefetch, Args} | Acc1] - end, Acc, Consumers). - -count() -> lists:sum([Count || #cr{consumer_count = Count} <- all_ch_record()]). - -unacknowledged_message_count() -> - lists:sum([queue:len(C#cr.acktags) || C <- all_ch_record()]). - -add(ChPid, CTag, NoAck, LimiterPid, LimiterActive, Prefetch, Args, IsEmpty, - State = #state{consumers = Consumers, - use = CUInfo}) -> - C = #cr{consumer_count = Count, - limiter = Limiter} = ch_record(ChPid, LimiterPid), - Limiter1 = case LimiterActive of - true -> rabbit_limiter:activate(Limiter); - false -> Limiter - end, - C1 = C#cr{consumer_count = Count + 1, limiter = Limiter1}, - update_ch_record( - case parse_credit_args(Prefetch, Args) of - {0, auto} -> C1; - {_Credit, auto} when NoAck -> C1; - {Credit, Mode} -> credit_and_drain( - C1, CTag, Credit, Mode, IsEmpty) - end), - Consumer = #consumer{tag = CTag, - ack_required = not NoAck, - prefetch = Prefetch, - args = Args}, - State#state{consumers = add_consumer({ChPid, Consumer}, Consumers), - use = update_use(CUInfo, active)}. - -remove(ChPid, CTag, State = #state{consumers = Consumers}) -> - case lookup_ch(ChPid) of - not_found -> - not_found; - C = #cr{consumer_count = Count, - limiter = Limiter, - blocked_consumers = Blocked} -> - Blocked1 = remove_consumer(ChPid, CTag, Blocked), - Limiter1 = case Count of - 1 -> rabbit_limiter:deactivate(Limiter); - _ -> Limiter - end, - Limiter2 = rabbit_limiter:forget_consumer(Limiter1, CTag), - update_ch_record(C#cr{consumer_count = Count - 1, - limiter = Limiter2, - blocked_consumers = Blocked1}), - State#state{consumers = - remove_consumer(ChPid, CTag, Consumers)} - end. - -erase_ch(ChPid, State = #state{consumers = Consumers}) -> - case lookup_ch(ChPid) of - not_found -> - not_found; - C = #cr{ch_pid = ChPid, - acktags = ChAckTags, - blocked_consumers = BlockedQ} -> - AllConsumers = priority_queue:join(Consumers, BlockedQ), - ok = erase_ch_record(C), - {[AckTag || {AckTag, _CTag} <- queue:to_list(ChAckTags)], - tags(priority_queue:to_list(AllConsumers)), - State#state{consumers = remove_consumers(ChPid, Consumers)}} - end. - -send_drained() -> [update_ch_record(send_drained(C)) || C <- all_ch_record()], - ok. - -deliver(FetchFun, QName, State) -> deliver(FetchFun, QName, false, State). - -deliver(FetchFun, QName, ConsumersChanged, - State = #state{consumers = Consumers}) -> - case priority_queue:out_p(Consumers) of - {empty, _} -> - {undelivered, ConsumersChanged, - State#state{use = update_use(State#state.use, inactive)}}; - {{value, QEntry, Priority}, Tail} -> - case deliver_to_consumer(FetchFun, QEntry, QName) of - {delivered, R} -> - {delivered, ConsumersChanged, R, - State#state{consumers = priority_queue:in(QEntry, Priority, - Tail)}}; - undelivered -> - deliver(FetchFun, QName, true, - State#state{consumers = Tail}) - end - end. - -deliver_to_consumer(FetchFun, E = {ChPid, Consumer}, QName) -> - C = lookup_ch(ChPid), - case is_ch_blocked(C) of - true -> block_consumer(C, E), - undelivered; - false -> case rabbit_limiter:can_send(C#cr.limiter, - Consumer#consumer.ack_required, - Consumer#consumer.tag) of - {suspend, Limiter} -> - block_consumer(C#cr{limiter = Limiter}, E), - undelivered; - {continue, Limiter} -> - {delivered, deliver_to_consumer( - FetchFun, Consumer, - C#cr{limiter = Limiter}, QName)} - end - end. - -deliver_to_consumer(FetchFun, - #consumer{tag = CTag, - ack_required = AckRequired}, - C = #cr{ch_pid = ChPid, - acktags = ChAckTags, - unsent_message_count = Count}, - QName) -> - {{Message, IsDelivered, AckTag}, R} = FetchFun(AckRequired), - rabbit_channel:deliver(ChPid, CTag, AckRequired, - {QName, self(), AckTag, IsDelivered, Message}), - ChAckTags1 = case AckRequired of - true -> queue:in({AckTag, CTag}, ChAckTags); - false -> ChAckTags - end, - update_ch_record(C#cr{acktags = ChAckTags1, - unsent_message_count = Count + 1}), - R. - -record_ack(ChPid, LimiterPid, AckTag) -> - C = #cr{acktags = ChAckTags} = ch_record(ChPid, LimiterPid), - update_ch_record(C#cr{acktags = queue:in({AckTag, none}, ChAckTags)}), - ok. - -subtract_acks(ChPid, AckTags, State) -> - case lookup_ch(ChPid) of - not_found -> - not_found; - C = #cr{acktags = ChAckTags, limiter = Lim} -> - {CTagCounts, AckTags2} = subtract_acks( - AckTags, [], orddict:new(), ChAckTags), - {Unblocked, Lim2} = - orddict:fold( - fun (CTag, Count, {UnblockedN, LimN}) -> - {Unblocked1, LimN1} = - rabbit_limiter:ack_from_queue(LimN, CTag, Count), - {UnblockedN orelse Unblocked1, LimN1} - end, {false, Lim}, CTagCounts), - C2 = C#cr{acktags = AckTags2, limiter = Lim2}, - case Unblocked of - true -> unblock(C2, State); - false -> update_ch_record(C2), - unchanged - end - end. - -subtract_acks([], [], CTagCounts, AckQ) -> - {CTagCounts, AckQ}; -subtract_acks([], Prefix, CTagCounts, AckQ) -> - {CTagCounts, queue:join(queue:from_list(lists:reverse(Prefix)), AckQ)}; -subtract_acks([T | TL] = AckTags, Prefix, CTagCounts, AckQ) -> - case queue:out(AckQ) of - {{value, {T, CTag}}, QTail} -> - subtract_acks(TL, Prefix, - orddict:update_counter(CTag, 1, CTagCounts), QTail); - {{value, V}, QTail} -> - subtract_acks(AckTags, [V | Prefix], CTagCounts, QTail) - end. - -possibly_unblock(Update, ChPid, State) -> - case lookup_ch(ChPid) of - not_found -> unchanged; - C -> C1 = Update(C), - case is_ch_blocked(C) andalso not is_ch_blocked(C1) of - false -> update_ch_record(C1), - unchanged; - true -> unblock(C1, State) - end - end. - -unblock(C = #cr{blocked_consumers = BlockedQ, limiter = Limiter}, - State = #state{consumers = Consumers, use = Use}) -> - case lists:partition( - fun({_P, {_ChPid, #consumer{tag = CTag}}}) -> - rabbit_limiter:is_consumer_blocked(Limiter, CTag) - end, priority_queue:to_list(BlockedQ)) of - {_, []} -> - update_ch_record(C), - unchanged; - {Blocked, Unblocked} -> - BlockedQ1 = priority_queue:from_list(Blocked), - UnblockedQ = priority_queue:from_list(Unblocked), - update_ch_record(C#cr{blocked_consumers = BlockedQ1}), - {unblocked, - State#state{consumers = priority_queue:join(Consumers, UnblockedQ), - use = update_use(Use, active)}} - end. - -resume_fun() -> - fun (C = #cr{limiter = Limiter}) -> - C#cr{limiter = rabbit_limiter:resume(Limiter)} - end. - -notify_sent_fun(Credit) -> - fun (C = #cr{unsent_message_count = Count}) -> - C#cr{unsent_message_count = Count - Credit} - end. - -activate_limit_fun() -> - fun (C = #cr{limiter = Limiter}) -> - C#cr{limiter = rabbit_limiter:activate(Limiter)} - end. - -credit(IsEmpty, Credit, Drain, ChPid, CTag, State) -> - case lookup_ch(ChPid) of - not_found -> - unchanged; - #cr{limiter = Limiter} = C -> - C1 = #cr{limiter = Limiter1} = - credit_and_drain(C, CTag, Credit, drain_mode(Drain), IsEmpty), - case is_ch_blocked(C1) orelse - (not rabbit_limiter:is_consumer_blocked(Limiter, CTag)) orelse - rabbit_limiter:is_consumer_blocked(Limiter1, CTag) of - true -> update_ch_record(C1), - unchanged; - false -> unblock(C1, State) - end - end. - -drain_mode(true) -> drain; -drain_mode(false) -> manual. - -utilisation(#state{use = {active, Since, Avg}}) -> - use_avg(now_micros() - Since, 0, Avg); -utilisation(#state{use = {inactive, Since, Active, Avg}}) -> - use_avg(Active, now_micros() - Since, Avg). - -%%---------------------------------------------------------------------------- - -parse_credit_args(Default, Args) -> - case rabbit_misc:table_lookup(Args, <<"x-credit">>) of - {table, T} -> case {rabbit_misc:table_lookup(T, <<"credit">>), - rabbit_misc:table_lookup(T, <<"drain">>)} of - {{long, C}, {bool, D}} -> {C, drain_mode(D)}; - _ -> {Default, auto} - end; - undefined -> {Default, auto} - end. - -lookup_ch(ChPid) -> - case get({ch, ChPid}) of - undefined -> not_found; - C -> C - end. - -ch_record(ChPid, LimiterPid) -> - Key = {ch, ChPid}, - case get(Key) of - undefined -> MonitorRef = erlang:monitor(process, ChPid), - Limiter = rabbit_limiter:client(LimiterPid), - C = #cr{ch_pid = ChPid, - monitor_ref = MonitorRef, - acktags = queue:new(), - consumer_count = 0, - blocked_consumers = priority_queue:new(), - limiter = Limiter, - unsent_message_count = 0}, - put(Key, C), - C; - C = #cr{} -> C - end. - -update_ch_record(C = #cr{consumer_count = ConsumerCount, - acktags = ChAckTags, - unsent_message_count = UnsentMessageCount}) -> - case {queue:is_empty(ChAckTags), ConsumerCount, UnsentMessageCount} of - {true, 0, 0} -> ok = erase_ch_record(C); - _ -> ok = store_ch_record(C) - end, - C. - -store_ch_record(C = #cr{ch_pid = ChPid}) -> - put({ch, ChPid}, C), - ok. - -erase_ch_record(#cr{ch_pid = ChPid, monitor_ref = MonitorRef}) -> - erlang:demonitor(MonitorRef), - erase({ch, ChPid}), - ok. - -all_ch_record() -> [C || {{ch, _}, C} <- get()]. - -block_consumer(C = #cr{blocked_consumers = Blocked}, QEntry) -> - update_ch_record(C#cr{blocked_consumers = add_consumer(QEntry, Blocked)}). - -is_ch_blocked(#cr{unsent_message_count = Count, limiter = Limiter}) -> - Count >= ?UNSENT_MESSAGE_LIMIT orelse rabbit_limiter:is_suspended(Limiter). - -send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> - case rabbit_limiter:drained(Limiter) of - {[], Limiter} -> C; - {CTagCredit, Limiter2} -> rabbit_channel:send_drained( - ChPid, CTagCredit), - C#cr{limiter = Limiter2} - end. - -credit_and_drain(C = #cr{ch_pid = ChPid, limiter = Limiter}, - CTag, Credit, Mode, IsEmpty) -> - case rabbit_limiter:credit(Limiter, CTag, Credit, Mode, IsEmpty) of - {true, Limiter1} -> rabbit_channel:send_drained(ChPid, - [{CTag, Credit}]), - C#cr{limiter = Limiter1}; - {false, Limiter1} -> C#cr{limiter = Limiter1} - end. - -tags(CList) -> [CTag || {_P, {_ChPid, #consumer{tag = CTag}}} <- CList]. - -add_consumer({ChPid, Consumer = #consumer{args = Args}}, Queue) -> - Priority = case rabbit_misc:table_lookup(Args, <<"x-priority">>) of - {_, P} -> P; - _ -> 0 - end, - priority_queue:in({ChPid, Consumer}, Priority, Queue). - -remove_consumer(ChPid, CTag, Queue) -> - priority_queue:filter(fun ({CP, #consumer{tag = CT}}) -> - (CP /= ChPid) or (CT /= CTag) - end, Queue). - -remove_consumers(ChPid, Queue) -> - priority_queue:filter(fun ({CP, _Consumer}) when CP =:= ChPid -> false; - (_) -> true - end, Queue). - -update_use({inactive, _, _, _} = CUInfo, inactive) -> - CUInfo; -update_use({active, _, _} = CUInfo, active) -> - CUInfo; -update_use({active, Since, Avg}, inactive) -> - Now = now_micros(), - {inactive, Now, Now - Since, Avg}; -update_use({inactive, Since, Active, Avg}, active) -> - Now = now_micros(), - {active, Now, use_avg(Active, Now - Since, Avg)}. - -use_avg(Active, Inactive, Avg) -> - Time = Inactive + Active, - rabbit_misc:moving_average(Time, ?USE_AVG_HALF_LIFE, Active / Time, Avg). - -now_micros() -> timer:now_diff(now(), {0,0,0}). diff --git a/src/rabbit_queue_decorator.erl b/src/rabbit_queue_decorator.erl deleted file mode 100644 index adfe0c7f..00000000 --- a/src/rabbit_queue_decorator.erl +++ /dev/null @@ -1,64 +0,0 @@ --module(rabbit_queue_decorator). - --include("rabbit.hrl"). - --export([select/1, set/1, register/2, unregister/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --callback startup(rabbit_types:amqqueue()) -> 'ok'. - --callback shutdown(rabbit_types:amqqueue()) -> 'ok'. - --callback policy_changed(rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> - 'ok'. - --callback active_for(rabbit_types:amqqueue()) -> boolean(). - -%% called with Queue, MaxActivePriority, IsEmpty --callback consumer_state_changed( - rabbit_types:amqqueue(), integer(), boolean()) -> 'ok'. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{description, 0}, {startup, 1}, {shutdown, 1}, {policy_changed, 2}, - {active_for, 1}, {consumer_state_changed, 3}]; -behaviour_info(_Other) -> - undefined. - --endif. - -%%---------------------------------------------------------------------------- - -select(Modules) -> - [M || M <- Modules, code:which(M) =/= non_existing]. - -set(Q) -> Q#amqqueue{decorators = [D || D <- list(), D:active_for(Q)]}. - -list() -> [M || {_, M} <- rabbit_registry:lookup_all(queue_decorator)]. - -register(TypeName, ModuleName) -> - rabbit_registry:register(queue_decorator, TypeName, ModuleName), - [maybe_recover(Q) || Q <- rabbit_amqqueue:list()], - ok. - -unregister(TypeName) -> - rabbit_registry:unregister(queue_decorator, TypeName), - [maybe_recover(Q) || Q <- rabbit_amqqueue:list()], - ok. - -maybe_recover(Q = #amqqueue{name = Name, - decorators = Decs}) -> - #amqqueue{decorators = Decs1} = set(Q), - Old = lists:sort(select(Decs)), - New = lists:sort(select(Decs1)), - case New of - Old -> ok; - _ -> [M:startup(Q) || M <- New -- Old], - rabbit_amqqueue:update_decorators(Name) - end. diff --git a/src/rabbit_queue_index.erl b/src/rabbit_queue_index.erl deleted file mode 100644 index 0a2c88d4..00000000 --- a/src/rabbit_queue_index.erl +++ /dev/null @@ -1,1175 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_queue_index). - --export([erase/1, init/2, recover/5, - terminate/2, delete_and_terminate/1, - publish/5, deliver/2, ack/2, sync/1, needs_sync/1, flush/1, - read/3, next_segment_boundary/1, bounds/1, start/1, stop/0]). - --export([add_queue_ttl/0, avoid_zeroes/0, store_msg_size/0]). - --define(CLEAN_FILENAME, "clean.dot"). - -%%---------------------------------------------------------------------------- - -%% The queue index is responsible for recording the order of messages -%% within a queue on disk. -%% -%% Because of the fact that the queue can decide at any point to send -%% a queue entry to disk, you can not rely on publishes appearing in -%% order. The only thing you can rely on is a message being published, -%% then delivered, then ack'd. -%% -%% In order to be able to clean up ack'd messages, we write to segment -%% files. These files have a fixed maximum size: ?SEGMENT_ENTRY_COUNT -%% publishes, delivers and acknowledgements. They are numbered, and so -%% it is known that the 0th segment contains messages 0 -> -%% ?SEGMENT_ENTRY_COUNT - 1, the 1st segment contains messages -%% ?SEGMENT_ENTRY_COUNT -> 2*?SEGMENT_ENTRY_COUNT - 1 and so on. As -%% such, in the segment files, we only refer to message sequence ids -%% by the LSBs as SeqId rem ?SEGMENT_ENTRY_COUNT. This gives them a -%% fixed size. -%% -%% However, transient messages which are not sent to disk at any point -%% will cause gaps to appear in segment files. Therefore, we delete a -%% segment file whenever the number of publishes == number of acks -%% (note that although it is not fully enforced, it is assumed that a -%% message will never be ackd before it is delivered, thus this test -%% also implies == number of delivers). In practise, this does not -%% cause disk churn in the pathological case because of the journal -%% and caching (see below). -%% -%% Because of the fact that publishes, delivers and acks can occur all -%% over, we wish to avoid lots of seeking. Therefore we have a fixed -%% sized journal to which all actions are appended. When the number of -%% entries in this journal reaches max_journal_entries, the journal -%% entries are scattered out to their relevant files, and the journal -%% is truncated to zero size. Note that entries in the journal must -%% carry the full sequence id, thus the format of entries in the -%% journal is different to that in the segments. -%% -%% The journal is also kept fully in memory, pre-segmented: the state -%% contains a mapping from segment numbers to state-per-segment (this -%% state is held for all segments which have been "seen": thus a -%% segment which has been read but has no pending entries in the -%% journal is still held in this mapping. Also note that a dict is -%% used for this mapping, not an array because with an array, you will -%% always have entries from 0). Actions are stored directly in this -%% state. Thus at the point of flushing the journal, firstly no -%% reading from disk is necessary, but secondly if the known number of -%% acks and publishes in a segment are equal, given the known state of -%% the segment file combined with the journal, no writing needs to be -%% done to the segment file either (in fact it is deleted if it exists -%% at all). This is safe given that the set of acks is a subset of the -%% set of publishes. When it is necessary to sync messages, it is -%% sufficient to fsync on the journal: when entries are distributed -%% from the journal to segment files, those segments appended to are -%% fsync'd prior to the journal being truncated. -%% -%% This module is also responsible for scanning the queue index files -%% and seeding the message store on start up. -%% -%% Note that in general, the representation of a message's state as -%% the tuple: {('no_pub'|{MsgId, MsgProps, IsPersistent}), -%% ('del'|'no_del'), ('ack'|'no_ack')} is richer than strictly -%% necessary for most operations. However, for startup, and to ensure -%% the safe and correct combination of journal entries with entries -%% read from the segment on disk, this richer representation vastly -%% simplifies and clarifies the code. -%% -%% For notes on Clean Shutdown and startup, see documentation in -%% variable_queue. -%% -%%---------------------------------------------------------------------------- - -%% ---- Journal details ---- - --define(JOURNAL_FILENAME, "journal.jif"). - --define(PUB_PERSIST_JPREFIX, 2#00). --define(PUB_TRANS_JPREFIX, 2#01). --define(DEL_JPREFIX, 2#10). --define(ACK_JPREFIX, 2#11). --define(JPREFIX_BITS, 2). --define(SEQ_BYTES, 8). --define(SEQ_BITS, ((?SEQ_BYTES * 8) - ?JPREFIX_BITS)). - -%% ---- Segment details ---- - --define(SEGMENT_EXTENSION, ".idx"). - -%% TODO: The segment size would be configurable, but deriving all the -%% other values is quite hairy and quite possibly noticably less -%% efficient, depending on how clever the compiler is when it comes to -%% binary generation/matching with constant vs variable lengths. - --define(REL_SEQ_BITS, 14). --define(SEGMENT_ENTRY_COUNT, 16384). %% trunc(math:pow(2,?REL_SEQ_BITS))). - -%% seq only is binary 01 followed by 14 bits of rel seq id -%% (range: 0 - 16383) --define(REL_SEQ_ONLY_PREFIX, 01). --define(REL_SEQ_ONLY_PREFIX_BITS, 2). --define(REL_SEQ_ONLY_RECORD_BYTES, 2). - -%% publish record is binary 1 followed by a bit for is_persistent, -%% then 14 bits of rel seq id, 64 bits for message expiry and 128 bits -%% of md5sum msg id --define(PUB_PREFIX, 1). --define(PUB_PREFIX_BITS, 1). - --define(EXPIRY_BYTES, 8). --define(EXPIRY_BITS, (?EXPIRY_BYTES * 8)). --define(NO_EXPIRY, 0). - --define(MSG_ID_BYTES, 16). %% md5sum is 128 bit or 16 bytes --define(MSG_ID_BITS, (?MSG_ID_BYTES * 8)). - --define(SIZE_BYTES, 4). --define(SIZE_BITS, (?SIZE_BYTES * 8)). - -%% 16 bytes for md5sum + 8 for expiry + 4 for size --define(PUB_RECORD_BODY_BYTES, (?MSG_ID_BYTES + ?EXPIRY_BYTES + ?SIZE_BYTES)). -%% + 2 for seq, bits and prefix --define(PUB_RECORD_BYTES, (?PUB_RECORD_BODY_BYTES + 2)). - -%% 1 publish, 1 deliver, 1 ack per msg --define(SEGMENT_TOTAL_SIZE, ?SEGMENT_ENTRY_COUNT * - (?PUB_RECORD_BYTES + (2 * ?REL_SEQ_ONLY_RECORD_BYTES))). - -%% ---- misc ---- - --define(PUB, {_, _, _}). %% {MsgId, MsgProps, IsPersistent} - --define(READ_MODE, [binary, raw, read]). --define(READ_AHEAD_MODE, [{read_ahead, ?SEGMENT_TOTAL_SIZE} | ?READ_MODE]). --define(WRITE_MODE, [write | ?READ_MODE]). - -%%---------------------------------------------------------------------------- - --record(qistate, { dir, segments, journal_handle, dirty_count, - max_journal_entries, on_sync, unconfirmed }). - --record(segment, { num, path, journal_entries, unacked }). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --rabbit_upgrade({add_queue_ttl, local, []}). --rabbit_upgrade({avoid_zeroes, local, [add_queue_ttl]}). --rabbit_upgrade({store_msg_size, local, [avoid_zeroes]}). - --ifdef(use_specs). - --type(hdl() :: ('undefined' | any())). --type(segment() :: ('undefined' | - #segment { num :: non_neg_integer(), - path :: file:filename(), - journal_entries :: array:array(), - unacked :: non_neg_integer() - })). --type(seq_id() :: integer()). --type(seg_dict() :: {dict:dict(), [segment()]}). --type(on_sync_fun() :: fun ((gb_sets:set()) -> ok)). --type(qistate() :: #qistate { dir :: file:filename(), - segments :: 'undefined' | seg_dict(), - journal_handle :: hdl(), - dirty_count :: integer(), - max_journal_entries :: non_neg_integer(), - on_sync :: on_sync_fun(), - unconfirmed :: gb_sets:set() - }). --type(contains_predicate() :: fun ((rabbit_types:msg_id()) -> boolean())). --type(walker(A) :: fun ((A) -> 'finished' | - {rabbit_types:msg_id(), non_neg_integer(), A})). --type(shutdown_terms() :: [term()] | 'non_clean_shutdown'). - --spec(erase/1 :: (rabbit_amqqueue:name()) -> 'ok'). --spec(init/2 :: (rabbit_amqqueue:name(), on_sync_fun()) -> qistate()). --spec(recover/5 :: (rabbit_amqqueue:name(), shutdown_terms(), boolean(), - contains_predicate(), on_sync_fun()) -> - {'undefined' | non_neg_integer(), - 'undefined' | non_neg_integer(), qistate()}). --spec(terminate/2 :: ([any()], qistate()) -> qistate()). --spec(delete_and_terminate/1 :: (qistate()) -> qistate()). --spec(publish/5 :: (rabbit_types:msg_id(), seq_id(), - rabbit_types:message_properties(), boolean(), qistate()) - -> qistate()). --spec(deliver/2 :: ([seq_id()], qistate()) -> qistate()). --spec(ack/2 :: ([seq_id()], qistate()) -> qistate()). --spec(sync/1 :: (qistate()) -> qistate()). --spec(needs_sync/1 :: (qistate()) -> 'confirms' | 'other' | 'false'). --spec(flush/1 :: (qistate()) -> qistate()). --spec(read/3 :: (seq_id(), seq_id(), qistate()) -> - {[{rabbit_types:msg_id(), seq_id(), - rabbit_types:message_properties(), - boolean(), boolean()}], qistate()}). --spec(next_segment_boundary/1 :: (seq_id()) -> seq_id()). --spec(bounds/1 :: (qistate()) -> - {non_neg_integer(), non_neg_integer(), qistate()}). --spec(start/1 :: ([rabbit_amqqueue:name()]) -> {[[any()]], {walker(A), A}}). - --spec(add_queue_ttl/0 :: () -> 'ok'). - --endif. - - -%%---------------------------------------------------------------------------- -%% public API -%%---------------------------------------------------------------------------- - -erase(Name) -> - #qistate { dir = Dir } = blank_state(Name), - case rabbit_file:is_dir(Dir) of - true -> rabbit_file:recursive_delete([Dir]); - false -> ok - end. - -init(Name, OnSyncFun) -> - State = #qistate { dir = Dir } = blank_state(Name), - false = rabbit_file:is_file(Dir), %% is_file == is file or dir - State #qistate { on_sync = OnSyncFun }. - -recover(Name, Terms, MsgStoreRecovered, ContainsCheckFun, OnSyncFun) -> - State = blank_state(Name), - State1 = State #qistate { on_sync = OnSyncFun }, - CleanShutdown = Terms /= non_clean_shutdown, - case CleanShutdown andalso MsgStoreRecovered of - true -> RecoveredCounts = proplists:get_value(segments, Terms, []), - init_clean(RecoveredCounts, State1); - false -> init_dirty(CleanShutdown, ContainsCheckFun, State1) - end. - -terminate(Terms, State = #qistate { dir = Dir }) -> - {SegmentCounts, State1} = terminate(State), - rabbit_recovery_terms:store(filename:basename(Dir), - [{segments, SegmentCounts} | Terms]), - State1. - -delete_and_terminate(State) -> - {_SegmentCounts, State1 = #qistate { dir = Dir }} = terminate(State), - ok = rabbit_file:recursive_delete([Dir]), - State1. - -publish(MsgId, SeqId, MsgProps, IsPersistent, - State = #qistate { unconfirmed = Unconfirmed }) - when is_binary(MsgId) -> - ?MSG_ID_BYTES = size(MsgId), - {JournalHdl, State1} = - get_journal_handle( - case MsgProps#message_properties.needs_confirming of - true -> Unconfirmed1 = gb_sets:add_element(MsgId, Unconfirmed), - State #qistate { unconfirmed = Unconfirmed1 }; - false -> State - end), - ok = file_handle_cache:append( - JournalHdl, [<<(case IsPersistent of - true -> ?PUB_PERSIST_JPREFIX; - false -> ?PUB_TRANS_JPREFIX - end):?JPREFIX_BITS, - SeqId:?SEQ_BITS>>, - create_pub_record_body(MsgId, MsgProps)]), - maybe_flush_journal( - add_to_journal(SeqId, {MsgId, MsgProps, IsPersistent}, State1)). - -deliver(SeqIds, State) -> - deliver_or_ack(del, SeqIds, State). - -ack(SeqIds, State) -> - deliver_or_ack(ack, SeqIds, State). - -%% This is called when there are outstanding confirms or when the -%% queue is idle and the journal needs syncing (see needs_sync/1). -sync(State = #qistate { journal_handle = undefined }) -> - State; -sync(State = #qistate { journal_handle = JournalHdl }) -> - ok = file_handle_cache:sync(JournalHdl), - notify_sync(State). - -needs_sync(#qistate { journal_handle = undefined }) -> - false; -needs_sync(#qistate { journal_handle = JournalHdl, unconfirmed = UC }) -> - case gb_sets:is_empty(UC) of - true -> case file_handle_cache:needs_sync(JournalHdl) of - true -> other; - false -> false - end; - false -> confirms - end. - -flush(State = #qistate { dirty_count = 0 }) -> State; -flush(State) -> flush_journal(State). - -read(StartEnd, StartEnd, State) -> - {[], State}; -read(Start, End, State = #qistate { segments = Segments, - dir = Dir }) when Start =< End -> - %% Start is inclusive, End is exclusive. - LowerB = {StartSeg, _StartRelSeq} = seq_id_to_seg_and_rel_seq_id(Start), - UpperB = {EndSeg, _EndRelSeq} = seq_id_to_seg_and_rel_seq_id(End - 1), - {Messages, Segments1} = - lists:foldr(fun (Seg, Acc) -> - read_bounded_segment(Seg, LowerB, UpperB, Acc, Dir) - end, {[], Segments}, lists:seq(StartSeg, EndSeg)), - {Messages, State #qistate { segments = Segments1 }}. - -next_segment_boundary(SeqId) -> - {Seg, _RelSeq} = seq_id_to_seg_and_rel_seq_id(SeqId), - reconstruct_seq_id(Seg + 1, 0). - -bounds(State = #qistate { segments = Segments }) -> - %% This is not particularly efficient, but only gets invoked on - %% queue initialisation. - SegNums = lists:sort(segment_nums(Segments)), - %% Don't bother trying to figure out the lowest seq_id, merely the - %% seq_id of the start of the lowest segment. That seq_id may not - %% actually exist, but that's fine. The important thing is that - %% the segment exists and the seq_id reported is on a segment - %% boundary. - %% - %% We also don't really care about the max seq_id. Just start the - %% next segment: it makes life much easier. - %% - %% SegNums is sorted, ascending. - {LowSeqId, NextSeqId} = - case SegNums of - [] -> {0, 0}; - [MinSeg|_] -> {reconstruct_seq_id(MinSeg, 0), - reconstruct_seq_id(1 + lists:last(SegNums), 0)} - end, - {LowSeqId, NextSeqId, State}. - -start(DurableQueueNames) -> - ok = rabbit_recovery_terms:start(), - {DurableTerms, DurableDirectories} = - lists:foldl( - fun(QName, {RecoveryTerms, ValidDirectories}) -> - DirName = queue_name_to_dir_name(QName), - RecoveryInfo = case rabbit_recovery_terms:read(DirName) of - {error, _} -> non_clean_shutdown; - {ok, Terms} -> Terms - end, - {[RecoveryInfo | RecoveryTerms], - sets:add_element(DirName, ValidDirectories)} - end, {[], sets:new()}, DurableQueueNames), - - %% Any queue directory we've not been asked to recover is considered garbage - QueuesDir = queues_dir(), - rabbit_file:recursive_delete( - [filename:join(QueuesDir, DirName) || - DirName <- all_queue_directory_names(QueuesDir), - not sets:is_element(DirName, DurableDirectories)]), - - rabbit_recovery_terms:clear(), - - %% The backing queue interface requires that the queue recovery terms - %% which come back from start/1 are in the same order as DurableQueueNames - OrderedTerms = lists:reverse(DurableTerms), - {OrderedTerms, {fun queue_index_walker/1, {start, DurableQueueNames}}}. - -stop() -> rabbit_recovery_terms:stop(). - -all_queue_directory_names(Dir) -> - case rabbit_file:list_dir(Dir) of - {ok, Entries} -> [E || E <- Entries, - rabbit_file:is_dir(filename:join(Dir, E))]; - {error, enoent} -> [] - end. - -%%---------------------------------------------------------------------------- -%% startup and shutdown -%%---------------------------------------------------------------------------- - -blank_state(QueueName) -> - blank_state_dir( - filename:join(queues_dir(), queue_name_to_dir_name(QueueName))). - -blank_state_dir(Dir) -> - {ok, MaxJournal} = - application:get_env(rabbit, queue_index_max_journal_entries), - #qistate { dir = Dir, - segments = segments_new(), - journal_handle = undefined, - dirty_count = 0, - max_journal_entries = MaxJournal, - on_sync = fun (_) -> ok end, - unconfirmed = gb_sets:new() }. - -init_clean(RecoveredCounts, State) -> - %% Load the journal. Since this is a clean recovery this (almost) - %% gets us back to where we were on shutdown. - State1 = #qistate { dir = Dir, segments = Segments } = load_journal(State), - %% The journal loading only creates records for segments touched - %% by the journal, and the counts are based on the journal entries - %% only. We need *complete* counts for *all* segments. By an - %% amazing coincidence we stored that information on shutdown. - Segments1 = - lists:foldl( - fun ({Seg, UnackedCount}, SegmentsN) -> - Segment = segment_find_or_new(Seg, Dir, SegmentsN), - segment_store(Segment #segment { unacked = UnackedCount }, - SegmentsN) - end, Segments, RecoveredCounts), - %% the counts above include transient messages, which would be the - %% wrong thing to return - {undefined, undefined, State1 # qistate { segments = Segments1 }}. - -init_dirty(CleanShutdown, ContainsCheckFun, State) -> - %% Recover the journal completely. This will also load segments - %% which have entries in the journal and remove duplicates. The - %% counts will correctly reflect the combination of the segment - %% and the journal. - State1 = #qistate { dir = Dir, segments = Segments } = - recover_journal(State), - {Segments1, Count, Bytes, DirtyCount} = - %% Load each segment in turn and filter out messages that are - %% not in the msg_store, by adding acks to the journal. These - %% acks only go to the RAM journal as it doesn't matter if we - %% lose them. Also mark delivered if not clean shutdown. Also - %% find the number of unacked messages. Also accumulate the - %% dirty count here, so we can call maybe_flush_journal below - %% and avoid unnecessary file system operations. - lists:foldl( - fun (Seg, {Segments2, CountAcc, BytesAcc, DirtyCount}) -> - {{Segment = #segment { unacked = UnackedCount }, Dirty}, - UnackedBytes} = - recover_segment(ContainsCheckFun, CleanShutdown, - segment_find_or_new(Seg, Dir, Segments2)), - {segment_store(Segment, Segments2), - CountAcc + UnackedCount, - BytesAcc + UnackedBytes, DirtyCount + Dirty} - end, {Segments, 0, 0, 0}, all_segment_nums(State1)), - State2 = maybe_flush_journal(State1 #qistate { segments = Segments1, - dirty_count = DirtyCount }), - {Count, Bytes, State2}. - -terminate(State = #qistate { journal_handle = JournalHdl, - segments = Segments }) -> - ok = case JournalHdl of - undefined -> ok; - _ -> file_handle_cache:close(JournalHdl) - end, - SegmentCounts = - segment_fold( - fun (#segment { num = Seg, unacked = UnackedCount }, Acc) -> - [{Seg, UnackedCount} | Acc] - end, [], Segments), - {SegmentCounts, State #qistate { journal_handle = undefined, - segments = undefined }}. - -recover_segment(ContainsCheckFun, CleanShutdown, - Segment = #segment { journal_entries = JEntries }) -> - {SegEntries, UnackedCount} = load_segment(false, Segment), - {SegEntries1, UnackedCountDelta} = - segment_plus_journal(SegEntries, JEntries), - array:sparse_foldl( - fun (RelSeq, {{MsgId, MsgProps, IsPersistent}, Del, no_ack}, - {SegmentAndDirtyCount, Bytes}) -> - {recover_message(ContainsCheckFun(MsgId), CleanShutdown, - Del, RelSeq, SegmentAndDirtyCount), - Bytes + case IsPersistent of - true -> MsgProps#message_properties.size; - false -> 0 - end} - end, - {{Segment #segment { unacked = UnackedCount + UnackedCountDelta }, 0}, 0}, - SegEntries1). - -recover_message( true, true, _Del, _RelSeq, SegmentAndDirtyCount) -> - SegmentAndDirtyCount; -recover_message( true, false, del, _RelSeq, SegmentAndDirtyCount) -> - SegmentAndDirtyCount; -recover_message( true, false, no_del, RelSeq, {Segment, DirtyCount}) -> - {add_to_journal(RelSeq, del, Segment), DirtyCount + 1}; -recover_message(false, _, del, RelSeq, {Segment, DirtyCount}) -> - {add_to_journal(RelSeq, ack, Segment), DirtyCount + 1}; -recover_message(false, _, no_del, RelSeq, {Segment, DirtyCount}) -> - {add_to_journal(RelSeq, ack, - add_to_journal(RelSeq, del, Segment)), - DirtyCount + 2}. - -queue_name_to_dir_name(Name = #resource { kind = queue }) -> - <<Num:128>> = erlang:md5(term_to_binary(Name)), - rabbit_misc:format("~.36B", [Num]). - -queues_dir() -> - filename:join(rabbit_mnesia:dir(), "queues"). - -%%---------------------------------------------------------------------------- -%% msg store startup delta function -%%---------------------------------------------------------------------------- - -queue_index_walker({start, DurableQueues}) when is_list(DurableQueues) -> - {ok, Gatherer} = gatherer:start_link(), - [begin - ok = gatherer:fork(Gatherer), - ok = worker_pool:submit_async( - fun () -> link(Gatherer), - ok = queue_index_walker_reader(QueueName, Gatherer), - unlink(Gatherer), - ok - end) - end || QueueName <- DurableQueues], - queue_index_walker({next, Gatherer}); - -queue_index_walker({next, Gatherer}) when is_pid(Gatherer) -> - case gatherer:out(Gatherer) of - empty -> - unlink(Gatherer), - ok = gatherer:stop(Gatherer), - finished; - {value, {MsgId, Count}} -> - {MsgId, Count, {next, Gatherer}} - end. - -queue_index_walker_reader(QueueName, Gatherer) -> - State = blank_state(QueueName), - ok = scan_segments( - fun (_SeqId, MsgId, _MsgProps, true, _IsDelivered, no_ack, ok) -> - gatherer:sync_in(Gatherer, {MsgId, 1}); - (_SeqId, _MsgId, _MsgProps, _IsPersistent, _IsDelivered, - _IsAcked, Acc) -> - Acc - end, ok, State), - ok = gatherer:finish(Gatherer). - -scan_segments(Fun, Acc, State) -> - State1 = #qistate { segments = Segments, dir = Dir } = - recover_journal(State), - Result = lists:foldr( - fun (Seg, AccN) -> - segment_entries_foldr( - fun (RelSeq, {{MsgId, MsgProps, IsPersistent}, - IsDelivered, IsAcked}, AccM) -> - Fun(reconstruct_seq_id(Seg, RelSeq), MsgId, MsgProps, - IsPersistent, IsDelivered, IsAcked, AccM) - end, AccN, segment_find_or_new(Seg, Dir, Segments)) - end, Acc, all_segment_nums(State1)), - {_SegmentCounts, _State} = terminate(State1), - Result. - -%%---------------------------------------------------------------------------- -%% expiry/binary manipulation -%%---------------------------------------------------------------------------- - -create_pub_record_body(MsgId, #message_properties { expiry = Expiry, - size = Size }) -> - [MsgId, expiry_to_binary(Expiry), <<Size:?SIZE_BITS>>]. - -expiry_to_binary(undefined) -> <<?NO_EXPIRY:?EXPIRY_BITS>>; -expiry_to_binary(Expiry) -> <<Expiry:?EXPIRY_BITS>>. - -parse_pub_record_body(<<MsgIdNum:?MSG_ID_BITS, Expiry:?EXPIRY_BITS, - Size:?SIZE_BITS>>) -> - %% work around for binary data fragmentation. See - %% rabbit_msg_file:read_next/2 - <<MsgId:?MSG_ID_BYTES/binary>> = <<MsgIdNum:?MSG_ID_BITS>>, - Exp = case Expiry of - ?NO_EXPIRY -> undefined; - X -> X - end, - {MsgId, #message_properties { expiry = Exp, - size = Size }}. - -%%---------------------------------------------------------------------------- -%% journal manipulation -%%---------------------------------------------------------------------------- - -add_to_journal(SeqId, Action, State = #qistate { dirty_count = DCount, - segments = Segments, - dir = Dir }) -> - {Seg, RelSeq} = seq_id_to_seg_and_rel_seq_id(SeqId), - Segment = segment_find_or_new(Seg, Dir, Segments), - Segment1 = add_to_journal(RelSeq, Action, Segment), - State #qistate { dirty_count = DCount + 1, - segments = segment_store(Segment1, Segments) }; - -add_to_journal(RelSeq, Action, - Segment = #segment { journal_entries = JEntries, - unacked = UnackedCount }) -> - Segment #segment { - journal_entries = add_to_journal(RelSeq, Action, JEntries), - unacked = UnackedCount + case Action of - ?PUB -> +1; - del -> 0; - ack -> -1 - end}; - -add_to_journal(RelSeq, Action, JEntries) -> - case array:get(RelSeq, JEntries) of - undefined -> - array:set(RelSeq, - case Action of - ?PUB -> {Action, no_del, no_ack}; - del -> {no_pub, del, no_ack}; - ack -> {no_pub, no_del, ack} - end, JEntries); - ({Pub, no_del, no_ack}) when Action == del -> - array:set(RelSeq, {Pub, del, no_ack}, JEntries); - ({no_pub, del, no_ack}) when Action == ack -> - array:set(RelSeq, {no_pub, del, ack}, JEntries); - ({?PUB, del, no_ack}) when Action == ack -> - array:reset(RelSeq, JEntries) - end. - -maybe_flush_journal(State = #qistate { dirty_count = DCount, - max_journal_entries = MaxJournal }) - when DCount > MaxJournal -> - flush_journal(State); -maybe_flush_journal(State) -> - State. - -flush_journal(State = #qistate { segments = Segments }) -> - Segments1 = - segment_fold( - fun (#segment { unacked = 0, path = Path }, SegmentsN) -> - case rabbit_file:is_file(Path) of - true -> ok = rabbit_file:delete(Path); - false -> ok - end, - SegmentsN; - (#segment {} = Segment, SegmentsN) -> - segment_store(append_journal_to_segment(Segment), SegmentsN) - end, segments_new(), Segments), - {JournalHdl, State1} = - get_journal_handle(State #qistate { segments = Segments1 }), - ok = file_handle_cache:clear(JournalHdl), - notify_sync(State1 #qistate { dirty_count = 0 }). - -append_journal_to_segment(#segment { journal_entries = JEntries, - path = Path } = Segment) -> - case array:sparse_size(JEntries) of - 0 -> Segment; - _ -> {ok, Hdl} = file_handle_cache:open(Path, ?WRITE_MODE, - [{write_buffer, infinity}]), - array:sparse_foldl(fun write_entry_to_segment/3, Hdl, JEntries), - ok = file_handle_cache:close(Hdl), - Segment #segment { journal_entries = array_new() } - end. - -get_journal_handle(State = #qistate { journal_handle = undefined, - dir = Dir }) -> - Path = filename:join(Dir, ?JOURNAL_FILENAME), - ok = rabbit_file:ensure_dir(Path), - {ok, Hdl} = file_handle_cache:open(Path, ?WRITE_MODE, - [{write_buffer, infinity}]), - {Hdl, State #qistate { journal_handle = Hdl }}; -get_journal_handle(State = #qistate { journal_handle = Hdl }) -> - {Hdl, State}. - -%% Loading Journal. This isn't idempotent and will mess up the counts -%% if you call it more than once on the same state. Assumes the counts -%% are 0 to start with. -load_journal(State = #qistate { dir = Dir }) -> - case rabbit_file:is_file(filename:join(Dir, ?JOURNAL_FILENAME)) of - true -> {JournalHdl, State1} = get_journal_handle(State), - {ok, 0} = file_handle_cache:position(JournalHdl, 0), - load_journal_entries(State1); - false -> State - end. - -%% ditto -recover_journal(State) -> - State1 = #qistate { segments = Segments } = load_journal(State), - Segments1 = - segment_map( - fun (Segment = #segment { journal_entries = JEntries, - unacked = UnackedCountInJournal }) -> - %% We want to keep ack'd entries in so that we can - %% remove them if duplicates are in the journal. The - %% counts here are purely from the segment itself. - {SegEntries, UnackedCountInSeg} = load_segment(true, Segment), - {JEntries1, UnackedCountDuplicates} = - journal_minus_segment(JEntries, SegEntries), - Segment #segment { journal_entries = JEntries1, - unacked = (UnackedCountInJournal + - UnackedCountInSeg - - UnackedCountDuplicates) } - end, Segments), - State1 #qistate { segments = Segments1 }. - -load_journal_entries(State = #qistate { journal_handle = Hdl }) -> - case file_handle_cache:read(Hdl, ?SEQ_BYTES) of - {ok, <<Prefix:?JPREFIX_BITS, SeqId:?SEQ_BITS>>} -> - case Prefix of - ?DEL_JPREFIX -> - load_journal_entries(add_to_journal(SeqId, del, State)); - ?ACK_JPREFIX -> - load_journal_entries(add_to_journal(SeqId, ack, State)); - _ -> - case file_handle_cache:read(Hdl, ?PUB_RECORD_BODY_BYTES) of - %% Journal entry composed only of zeroes was probably - %% produced during a dirty shutdown so stop reading - {ok, <<0:?PUB_RECORD_BODY_BYTES/unit:8>>} -> - State; - {ok, <<Bin:?PUB_RECORD_BODY_BYTES/binary>>} -> - {MsgId, MsgProps} = parse_pub_record_body(Bin), - IsPersistent = case Prefix of - ?PUB_PERSIST_JPREFIX -> true; - ?PUB_TRANS_JPREFIX -> false - end, - load_journal_entries( - add_to_journal( - SeqId, {MsgId, MsgProps, IsPersistent}, State)); - _ErrOrEoF -> %% err, we've lost at least a publish - State - end - end; - _ErrOrEoF -> State - end. - -deliver_or_ack(_Kind, [], State) -> - State; -deliver_or_ack(Kind, SeqIds, State) -> - JPrefix = case Kind of ack -> ?ACK_JPREFIX; del -> ?DEL_JPREFIX end, - {JournalHdl, State1} = get_journal_handle(State), - ok = file_handle_cache:append( - JournalHdl, - [<<JPrefix:?JPREFIX_BITS, SeqId:?SEQ_BITS>> || SeqId <- SeqIds]), - maybe_flush_journal(lists:foldl(fun (SeqId, StateN) -> - add_to_journal(SeqId, Kind, StateN) - end, State1, SeqIds)). - -notify_sync(State = #qistate { unconfirmed = UC, on_sync = OnSyncFun }) -> - case gb_sets:is_empty(UC) of - true -> State; - false -> OnSyncFun(UC), - State #qistate { unconfirmed = gb_sets:new() } - end. - -%%---------------------------------------------------------------------------- -%% segment manipulation -%%---------------------------------------------------------------------------- - -seq_id_to_seg_and_rel_seq_id(SeqId) -> - { SeqId div ?SEGMENT_ENTRY_COUNT, SeqId rem ?SEGMENT_ENTRY_COUNT }. - -reconstruct_seq_id(Seg, RelSeq) -> - (Seg * ?SEGMENT_ENTRY_COUNT) + RelSeq. - -all_segment_nums(#qistate { dir = Dir, segments = Segments }) -> - lists:sort( - sets:to_list( - lists:foldl( - fun (SegName, Set) -> - sets:add_element( - list_to_integer( - lists:takewhile(fun (C) -> $0 =< C andalso C =< $9 end, - SegName)), Set) - end, sets:from_list(segment_nums(Segments)), - rabbit_file:wildcard(".*\\" ++ ?SEGMENT_EXTENSION, Dir)))). - -segment_find_or_new(Seg, Dir, Segments) -> - case segment_find(Seg, Segments) of - {ok, Segment} -> Segment; - error -> SegName = integer_to_list(Seg) ++ ?SEGMENT_EXTENSION, - Path = filename:join(Dir, SegName), - #segment { num = Seg, - path = Path, - journal_entries = array_new(), - unacked = 0 } - end. - -segment_find(Seg, {_Segments, [Segment = #segment { num = Seg } |_]}) -> - {ok, Segment}; %% 1 or (2, matches head) -segment_find(Seg, {_Segments, [_, Segment = #segment { num = Seg }]}) -> - {ok, Segment}; %% 2, matches tail -segment_find(Seg, {Segments, _}) -> %% no match - dict:find(Seg, Segments). - -segment_store(Segment = #segment { num = Seg }, %% 1 or (2, matches head) - {Segments, [#segment { num = Seg } | Tail]}) -> - {Segments, [Segment | Tail]}; -segment_store(Segment = #segment { num = Seg }, %% 2, matches tail - {Segments, [SegmentA, #segment { num = Seg }]}) -> - {Segments, [Segment, SegmentA]}; -segment_store(Segment = #segment { num = Seg }, {Segments, []}) -> - {dict:erase(Seg, Segments), [Segment]}; -segment_store(Segment = #segment { num = Seg }, {Segments, [SegmentA]}) -> - {dict:erase(Seg, Segments), [Segment, SegmentA]}; -segment_store(Segment = #segment { num = Seg }, - {Segments, [SegmentA, SegmentB]}) -> - {dict:store(SegmentB#segment.num, SegmentB, dict:erase(Seg, Segments)), - [Segment, SegmentA]}. - -segment_fold(Fun, Acc, {Segments, CachedSegments}) -> - dict:fold(fun (_Seg, Segment, Acc1) -> Fun(Segment, Acc1) end, - lists:foldl(Fun, Acc, CachedSegments), Segments). - -segment_map(Fun, {Segments, CachedSegments}) -> - {dict:map(fun (_Seg, Segment) -> Fun(Segment) end, Segments), - lists:map(Fun, CachedSegments)}. - -segment_nums({Segments, CachedSegments}) -> - lists:map(fun (#segment { num = Num }) -> Num end, CachedSegments) ++ - dict:fetch_keys(Segments). - -segments_new() -> - {dict:new(), []}. - -write_entry_to_segment(_RelSeq, {?PUB, del, ack}, Hdl) -> - Hdl; -write_entry_to_segment(RelSeq, {Pub, Del, Ack}, Hdl) -> - ok = case Pub of - no_pub -> - ok; - {MsgId, MsgProps, IsPersistent} -> - file_handle_cache:append( - Hdl, [<<?PUB_PREFIX:?PUB_PREFIX_BITS, - (bool_to_int(IsPersistent)):1, - RelSeq:?REL_SEQ_BITS>>, - create_pub_record_body(MsgId, MsgProps)]) - end, - ok = case {Del, Ack} of - {no_del, no_ack} -> - ok; - _ -> - Binary = <<?REL_SEQ_ONLY_PREFIX:?REL_SEQ_ONLY_PREFIX_BITS, - RelSeq:?REL_SEQ_BITS>>, - file_handle_cache:append( - Hdl, case {Del, Ack} of - {del, ack} -> [Binary, Binary]; - _ -> Binary - end) - end, - Hdl. - -read_bounded_segment(Seg, {StartSeg, StartRelSeq}, {EndSeg, EndRelSeq}, - {Messages, Segments}, Dir) -> - Segment = segment_find_or_new(Seg, Dir, Segments), - {segment_entries_foldr( - fun (RelSeq, {{MsgId, MsgProps, IsPersistent}, IsDelivered, no_ack}, Acc) - when (Seg > StartSeg orelse StartRelSeq =< RelSeq) andalso - (Seg < EndSeg orelse EndRelSeq >= RelSeq) -> - [ {MsgId, reconstruct_seq_id(StartSeg, RelSeq), MsgProps, - IsPersistent, IsDelivered == del} | Acc ]; - (_RelSeq, _Value, Acc) -> - Acc - end, Messages, Segment), - segment_store(Segment, Segments)}. - -segment_entries_foldr(Fun, Init, - Segment = #segment { journal_entries = JEntries }) -> - {SegEntries, _UnackedCount} = load_segment(false, Segment), - {SegEntries1, _UnackedCountD} = segment_plus_journal(SegEntries, JEntries), - array:sparse_foldr(Fun, Init, SegEntries1). - -%% Loading segments -%% -%% Does not do any combining with the journal at all. -load_segment(KeepAcked, #segment { path = Path }) -> - Empty = {array_new(), 0}, - case rabbit_file:is_file(Path) of - false -> Empty; - true -> {ok, Hdl} = file_handle_cache:open(Path, ?READ_AHEAD_MODE, []), - {ok, 0} = file_handle_cache:position(Hdl, bof), - Res = case file_handle_cache:read(Hdl, ?SEGMENT_TOTAL_SIZE) of - {ok, SegData} -> load_segment_entries( - KeepAcked, SegData, Empty); - eof -> Empty - end, - ok = file_handle_cache:close(Hdl), - Res - end. - -load_segment_entries(KeepAcked, - <<?PUB_PREFIX:?PUB_PREFIX_BITS, - IsPersistentNum:1, RelSeq:?REL_SEQ_BITS, - PubRecordBody:?PUB_RECORD_BODY_BYTES/binary, - SegData/binary>>, - {SegEntries, UnackedCount}) -> - {MsgId, MsgProps} = parse_pub_record_body(PubRecordBody), - Obj = {{MsgId, MsgProps, 1 == IsPersistentNum}, no_del, no_ack}, - SegEntries1 = array:set(RelSeq, Obj, SegEntries), - load_segment_entries(KeepAcked, SegData, {SegEntries1, UnackedCount + 1}); -load_segment_entries(KeepAcked, - <<?REL_SEQ_ONLY_PREFIX:?REL_SEQ_ONLY_PREFIX_BITS, - RelSeq:?REL_SEQ_BITS, SegData/binary>>, - {SegEntries, UnackedCount}) -> - {UnackedCountDelta, SegEntries1} = - case array:get(RelSeq, SegEntries) of - {Pub, no_del, no_ack} -> - { 0, array:set(RelSeq, {Pub, del, no_ack}, SegEntries)}; - {Pub, del, no_ack} when KeepAcked -> - {-1, array:set(RelSeq, {Pub, del, ack}, SegEntries)}; - {_Pub, del, no_ack} -> - {-1, array:reset(RelSeq, SegEntries)} - end, - load_segment_entries(KeepAcked, SegData, - {SegEntries1, UnackedCount + UnackedCountDelta}); -load_segment_entries(_KeepAcked, _SegData, Res) -> - Res. - -array_new() -> - array:new([{default, undefined}, fixed, {size, ?SEGMENT_ENTRY_COUNT}]). - -bool_to_int(true ) -> 1; -bool_to_int(false) -> 0. - -%%---------------------------------------------------------------------------- -%% journal & segment combination -%%---------------------------------------------------------------------------- - -%% Combine what we have just read from a segment file with what we're -%% holding for that segment in memory. There must be no duplicates. -segment_plus_journal(SegEntries, JEntries) -> - array:sparse_foldl( - fun (RelSeq, JObj, {SegEntriesOut, AdditionalUnacked}) -> - SegEntry = array:get(RelSeq, SegEntriesOut), - {Obj, AdditionalUnackedDelta} = - segment_plus_journal1(SegEntry, JObj), - {case Obj of - undefined -> array:reset(RelSeq, SegEntriesOut); - _ -> array:set(RelSeq, Obj, SegEntriesOut) - end, - AdditionalUnacked + AdditionalUnackedDelta} - end, {SegEntries, 0}, JEntries). - -%% Here, the result is a tuple with the first element containing the -%% item which we may be adding to (for items only in the journal), -%% modifying in (bits in both), or, when returning 'undefined', -%% erasing from (ack in journal, not segment) the segment array. The -%% other element of the tuple is the delta for AdditionalUnacked. -segment_plus_journal1(undefined, {?PUB, no_del, no_ack} = Obj) -> - {Obj, 1}; -segment_plus_journal1(undefined, {?PUB, del, no_ack} = Obj) -> - {Obj, 1}; -segment_plus_journal1(undefined, {?PUB, del, ack}) -> - {undefined, 0}; - -segment_plus_journal1({?PUB = Pub, no_del, no_ack}, {no_pub, del, no_ack}) -> - {{Pub, del, no_ack}, 0}; -segment_plus_journal1({?PUB, no_del, no_ack}, {no_pub, del, ack}) -> - {undefined, -1}; -segment_plus_journal1({?PUB, del, no_ack}, {no_pub, no_del, ack}) -> - {undefined, -1}. - -%% Remove from the journal entries for a segment, items that are -%% duplicates of entries found in the segment itself. Used on start up -%% to clean up the journal. -journal_minus_segment(JEntries, SegEntries) -> - array:sparse_foldl( - fun (RelSeq, JObj, {JEntriesOut, UnackedRemoved}) -> - SegEntry = array:get(RelSeq, SegEntries), - {Obj, UnackedRemovedDelta} = - journal_minus_segment1(JObj, SegEntry), - {case Obj of - keep -> JEntriesOut; - undefined -> array:reset(RelSeq, JEntriesOut); - _ -> array:set(RelSeq, Obj, JEntriesOut) - end, - UnackedRemoved + UnackedRemovedDelta} - end, {JEntries, 0}, JEntries). - -%% Here, the result is a tuple with the first element containing the -%% item we are adding to or modifying in the (initially fresh) journal -%% array. If the item is 'undefined' we leave the journal array -%% alone. The other element of the tuple is the deltas for -%% UnackedRemoved. - -%% Both the same. Must be at least the publish -journal_minus_segment1({?PUB, _Del, no_ack} = Obj, Obj) -> - {undefined, 1}; -journal_minus_segment1({?PUB, _Del, ack} = Obj, Obj) -> - {undefined, 0}; - -%% Just publish in journal -journal_minus_segment1({?PUB, no_del, no_ack}, undefined) -> - {keep, 0}; - -%% Publish and deliver in journal -journal_minus_segment1({?PUB, del, no_ack}, undefined) -> - {keep, 0}; -journal_minus_segment1({?PUB = Pub, del, no_ack}, {Pub, no_del, no_ack}) -> - {{no_pub, del, no_ack}, 1}; - -%% Publish, deliver and ack in journal -journal_minus_segment1({?PUB, del, ack}, undefined) -> - {keep, 0}; -journal_minus_segment1({?PUB = Pub, del, ack}, {Pub, no_del, no_ack}) -> - {{no_pub, del, ack}, 1}; -journal_minus_segment1({?PUB = Pub, del, ack}, {Pub, del, no_ack}) -> - {{no_pub, no_del, ack}, 1}; - -%% Just deliver in journal -journal_minus_segment1({no_pub, del, no_ack}, {?PUB, no_del, no_ack}) -> - {keep, 0}; -journal_minus_segment1({no_pub, del, no_ack}, {?PUB, del, no_ack}) -> - {undefined, 0}; - -%% Just ack in journal -journal_minus_segment1({no_pub, no_del, ack}, {?PUB, del, no_ack}) -> - {keep, 0}; -journal_minus_segment1({no_pub, no_del, ack}, {?PUB, del, ack}) -> - {undefined, -1}; - -%% Deliver and ack in journal -journal_minus_segment1({no_pub, del, ack}, {?PUB, no_del, no_ack}) -> - {keep, 0}; -journal_minus_segment1({no_pub, del, ack}, {?PUB, del, no_ack}) -> - {{no_pub, no_del, ack}, 0}; -journal_minus_segment1({no_pub, del, ack}, {?PUB, del, ack}) -> - {undefined, -1}; - -%% Missing segment. If flush_journal/1 is interrupted after deleting -%% the segment but before truncating the journal we can get these -%% cases: a delivery and an acknowledgement in the journal, or just an -%% acknowledgement in the journal, but with no segment. In both cases -%% we have really forgotten the message; so ignore what's in the -%% journal. -journal_minus_segment1({no_pub, no_del, ack}, undefined) -> - {undefined, 0}; -journal_minus_segment1({no_pub, del, ack}, undefined) -> - {undefined, 0}. - -%%---------------------------------------------------------------------------- -%% upgrade -%%---------------------------------------------------------------------------- - -add_queue_ttl() -> - foreach_queue_index({fun add_queue_ttl_journal/1, - fun add_queue_ttl_segment/1}). - -add_queue_ttl_journal(<<?DEL_JPREFIX:?JPREFIX_BITS, SeqId:?SEQ_BITS, - Rest/binary>>) -> - {<<?DEL_JPREFIX:?JPREFIX_BITS, SeqId:?SEQ_BITS>>, Rest}; -add_queue_ttl_journal(<<?ACK_JPREFIX:?JPREFIX_BITS, SeqId:?SEQ_BITS, - Rest/binary>>) -> - {<<?ACK_JPREFIX:?JPREFIX_BITS, SeqId:?SEQ_BITS>>, Rest}; -add_queue_ttl_journal(<<Prefix:?JPREFIX_BITS, SeqId:?SEQ_BITS, - MsgId:?MSG_ID_BYTES/binary, Rest/binary>>) -> - {[<<Prefix:?JPREFIX_BITS, SeqId:?SEQ_BITS>>, MsgId, - expiry_to_binary(undefined)], Rest}; -add_queue_ttl_journal(_) -> - stop. - -add_queue_ttl_segment(<<?PUB_PREFIX:?PUB_PREFIX_BITS, IsPersistentNum:1, - RelSeq:?REL_SEQ_BITS, MsgId:?MSG_ID_BYTES/binary, - Rest/binary>>) -> - {[<<?PUB_PREFIX:?PUB_PREFIX_BITS, IsPersistentNum:1, RelSeq:?REL_SEQ_BITS>>, - MsgId, expiry_to_binary(undefined)], Rest}; -add_queue_ttl_segment(<<?REL_SEQ_ONLY_PREFIX:?REL_SEQ_ONLY_PREFIX_BITS, - RelSeq:?REL_SEQ_BITS, Rest/binary>>) -> - {<<?REL_SEQ_ONLY_PREFIX:?REL_SEQ_ONLY_PREFIX_BITS, RelSeq:?REL_SEQ_BITS>>, - Rest}; -add_queue_ttl_segment(_) -> - stop. - -avoid_zeroes() -> - foreach_queue_index({none, fun avoid_zeroes_segment/1}). - -avoid_zeroes_segment(<<?PUB_PREFIX:?PUB_PREFIX_BITS, IsPersistentNum:1, - RelSeq:?REL_SEQ_BITS, MsgId:?MSG_ID_BITS, - Expiry:?EXPIRY_BITS, Rest/binary>>) -> - {<<?PUB_PREFIX:?PUB_PREFIX_BITS, IsPersistentNum:1, RelSeq:?REL_SEQ_BITS, - MsgId:?MSG_ID_BITS, Expiry:?EXPIRY_BITS>>, Rest}; -avoid_zeroes_segment(<<0:?REL_SEQ_ONLY_PREFIX_BITS, - RelSeq:?REL_SEQ_BITS, Rest/binary>>) -> - {<<?REL_SEQ_ONLY_PREFIX:?REL_SEQ_ONLY_PREFIX_BITS, RelSeq:?REL_SEQ_BITS>>, - Rest}; -avoid_zeroes_segment(_) -> - stop. - -%% At upgrade time we just define every message's size as 0 - that -%% will save us a load of faff with the message store, and means we -%% can actually use the clean recovery terms in VQ. It does mean we -%% don't count message bodies from before the migration, but we can -%% live with that. -store_msg_size() -> - foreach_queue_index({fun store_msg_size_journal/1, - fun store_msg_size_segment/1}). - -store_msg_size_journal(<<?DEL_JPREFIX:?JPREFIX_BITS, SeqId:?SEQ_BITS, - Rest/binary>>) -> - {<<?DEL_JPREFIX:?JPREFIX_BITS, SeqId:?SEQ_BITS>>, Rest}; -store_msg_size_journal(<<?ACK_JPREFIX:?JPREFIX_BITS, SeqId:?SEQ_BITS, - Rest/binary>>) -> - {<<?ACK_JPREFIX:?JPREFIX_BITS, SeqId:?SEQ_BITS>>, Rest}; -store_msg_size_journal(<<Prefix:?JPREFIX_BITS, SeqId:?SEQ_BITS, - MsgId:?MSG_ID_BITS, Expiry:?EXPIRY_BITS, - Rest/binary>>) -> - {<<Prefix:?JPREFIX_BITS, SeqId:?SEQ_BITS, MsgId:?MSG_ID_BITS, - Expiry:?EXPIRY_BITS, 0:?SIZE_BITS>>, Rest}; -store_msg_size_journal(_) -> - stop. - -store_msg_size_segment(<<?PUB_PREFIX:?PUB_PREFIX_BITS, IsPersistentNum:1, - RelSeq:?REL_SEQ_BITS, MsgId:?MSG_ID_BITS, - Expiry:?EXPIRY_BITS, Rest/binary>>) -> - {<<?PUB_PREFIX:?PUB_PREFIX_BITS, IsPersistentNum:1, RelSeq:?REL_SEQ_BITS, - MsgId:?MSG_ID_BITS, Expiry:?EXPIRY_BITS, 0:?SIZE_BITS>>, Rest}; -store_msg_size_segment(<<?REL_SEQ_ONLY_PREFIX:?REL_SEQ_ONLY_PREFIX_BITS, - RelSeq:?REL_SEQ_BITS, Rest/binary>>) -> - {<<?REL_SEQ_ONLY_PREFIX:?REL_SEQ_ONLY_PREFIX_BITS, RelSeq:?REL_SEQ_BITS>>, - Rest}; -store_msg_size_segment(_) -> - stop. - - -%%---------------------------------------------------------------------------- - -foreach_queue_index(Funs) -> - QueuesDir = queues_dir(), - QueueDirNames = all_queue_directory_names(QueuesDir), - {ok, Gatherer} = gatherer:start_link(), - [begin - ok = gatherer:fork(Gatherer), - ok = worker_pool:submit_async( - fun () -> - transform_queue(filename:join(QueuesDir, QueueDirName), - Gatherer, Funs) - end) - end || QueueDirName <- QueueDirNames], - empty = gatherer:out(Gatherer), - unlink(Gatherer), - ok = gatherer:stop(Gatherer). - -transform_queue(Dir, Gatherer, {JournalFun, SegmentFun}) -> - ok = transform_file(filename:join(Dir, ?JOURNAL_FILENAME), JournalFun), - [ok = transform_file(filename:join(Dir, Seg), SegmentFun) - || Seg <- rabbit_file:wildcard(".*\\" ++ ?SEGMENT_EXTENSION, Dir)], - ok = gatherer:finish(Gatherer). - -transform_file(_Path, none) -> - ok; -transform_file(Path, Fun) when is_function(Fun)-> - PathTmp = Path ++ ".upgrade", - case rabbit_file:file_size(Path) of - 0 -> ok; - Size -> {ok, PathTmpHdl} = - file_handle_cache:open(PathTmp, ?WRITE_MODE, - [{write_buffer, infinity}]), - - {ok, PathHdl} = file_handle_cache:open( - Path, [{read_ahead, Size} | ?READ_MODE], []), - {ok, Content} = file_handle_cache:read(PathHdl, Size), - ok = file_handle_cache:close(PathHdl), - - ok = drive_transform_fun(Fun, PathTmpHdl, Content), - - ok = file_handle_cache:close(PathTmpHdl), - ok = rabbit_file:rename(PathTmp, Path) - end. - -drive_transform_fun(Fun, Hdl, Contents) -> - case Fun(Contents) of - stop -> ok; - {Output, Contents1} -> ok = file_handle_cache:append(Hdl, Output), - drive_transform_fun(Fun, Hdl, Contents1) - end. diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl deleted file mode 100644 index 2033dd14..00000000 --- a/src/rabbit_reader.erl +++ /dev/null @@ -1,1217 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_reader). --include("rabbit_framing.hrl"). --include("rabbit.hrl"). - --export([start_link/1, info_keys/0, info/1, info/2, force_event_refresh/2, - shutdown/2]). - --export([system_continue/3, system_terminate/4, system_code_change/4]). - --export([init/2, mainloop/4, recvloop/4]). - --export([conserve_resources/3, server_properties/1]). - --define(NORMAL_TIMEOUT, 3). --define(CLOSING_TIMEOUT, 30). --define(CHANNEL_TERMINATION_TIMEOUT, 3). --define(SILENT_CLOSE_DELAY, 3). --define(CHANNEL_MIN, 1). - -%%-------------------------------------------------------------------------- - --record(v1, {parent, sock, connection, callback, recv_len, pending_recv, - connection_state, helper_sup, queue_collector, heartbeater, - stats_timer, channel_sup_sup_pid, channel_count, throttle}). - --record(connection, {name, host, peer_host, port, peer_port, - protocol, user, timeout_sec, frame_max, channel_max, vhost, - client_properties, capabilities, - auth_mechanism, auth_state, connected_at}). - --record(throttle, {alarmed_by, last_blocked_by, last_blocked_at}). - --define(STATISTICS_KEYS, [pid, recv_oct, recv_cnt, send_oct, send_cnt, - send_pend, state, channels]). - --define(CREATION_EVENT_KEYS, - [pid, name, port, peer_port, host, - peer_host, ssl, peer_cert_subject, peer_cert_issuer, - peer_cert_validity, auth_mechanism, ssl_protocol, - ssl_key_exchange, ssl_cipher, ssl_hash, protocol, user, vhost, - timeout, frame_max, channel_max, client_properties, connected_at]). - --define(INFO_KEYS, ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [pid]). - --define(IS_RUNNING(State), - (State#v1.connection_state =:= running orelse - State#v1.connection_state =:= blocking orelse - State#v1.connection_state =:= blocked)). - --define(IS_STOPPING(State), - (State#v1.connection_state =:= closing orelse - State#v1.connection_state =:= closed)). - -%%-------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/1 :: (pid()) -> rabbit_types:ok(pid())). --spec(info_keys/0 :: () -> rabbit_types:info_keys()). --spec(info/1 :: (pid()) -> rabbit_types:infos()). --spec(info/2 :: (pid(), rabbit_types:info_keys()) -> rabbit_types:infos()). --spec(force_event_refresh/2 :: (pid(), reference()) -> 'ok'). --spec(shutdown/2 :: (pid(), string()) -> 'ok'). --spec(conserve_resources/3 :: (pid(), atom(), boolean()) -> 'ok'). --spec(server_properties/1 :: (rabbit_types:protocol()) -> - rabbit_framing:amqp_table()). - -%% These specs only exists to add no_return() to keep dialyzer happy --spec(init/2 :: (pid(), pid()) -> no_return()). --spec(start_connection/5 :: - (pid(), pid(), any(), rabbit_net:socket(), - fun ((rabbit_net:socket()) -> - rabbit_types:ok_or_error2( - rabbit_net:socket(), any()))) -> no_return()). - --spec(mainloop/4 :: (_,[binary()], non_neg_integer(), #v1{}) -> any()). --spec(system_code_change/4 :: (_,_,_,_) -> {'ok',_}). --spec(system_continue/3 :: (_,_,{[binary()], non_neg_integer(), #v1{}}) -> - any()). --spec(system_terminate/4 :: (_,_,_,_) -> none()). - --endif. - -%%-------------------------------------------------------------------------- - -start_link(HelperSup) -> - {ok, proc_lib:spawn_link(?MODULE, init, [self(), HelperSup])}. - -shutdown(Pid, Explanation) -> - gen_server:call(Pid, {shutdown, Explanation}, infinity). - -init(Parent, HelperSup) -> - Deb = sys:debug_options([]), - receive - {go, Sock, SockTransform} -> - start_connection(Parent, HelperSup, Deb, Sock, SockTransform) - end. - -system_continue(Parent, Deb, {Buf, BufLen, State}) -> - mainloop(Deb, Buf, BufLen, State#v1{parent = Parent}). - -system_terminate(Reason, _Parent, _Deb, _State) -> - exit(Reason). - -system_code_change(Misc, _Module, _OldVsn, _Extra) -> - {ok, Misc}. - -info_keys() -> ?INFO_KEYS. - -info(Pid) -> - gen_server:call(Pid, info, infinity). - -info(Pid, Items) -> - case gen_server:call(Pid, {info, Items}, infinity) of - {ok, Res} -> Res; - {error, Error} -> throw(Error) - end. - -force_event_refresh(Pid, Ref) -> - gen_server:cast(Pid, {force_event_refresh, Ref}). - -conserve_resources(Pid, Source, Conserve) -> - Pid ! {conserve_resources, Source, Conserve}, - ok. - -server_properties(Protocol) -> - {ok, Product} = application:get_key(rabbit, id), - {ok, Version} = application:get_key(rabbit, vsn), - - %% Get any configuration-specified server properties - {ok, RawConfigServerProps} = application:get_env(rabbit, - server_properties), - - %% Normalize the simplifed (2-tuple) and unsimplified (3-tuple) forms - %% from the config and merge them with the generated built-in properties - NormalizedConfigServerProps = - [{<<"capabilities">>, table, server_capabilities(Protocol)} | - [case X of - {KeyAtom, Value} -> {list_to_binary(atom_to_list(KeyAtom)), - longstr, - maybe_list_to_binary(Value)}; - {BinKey, Type, Value} -> {BinKey, Type, Value} - end || X <- RawConfigServerProps ++ - [{product, Product}, - {version, Version}, - {cluster_name, rabbit_nodes:cluster_name()}, - {platform, "Erlang/OTP"}, - {copyright, ?COPYRIGHT_MESSAGE}, - {information, ?INFORMATION_MESSAGE}]]], - - %% Filter duplicated properties in favour of config file provided values - lists:usort(fun ({K1,_,_}, {K2,_,_}) -> K1 =< K2 end, - NormalizedConfigServerProps). - -maybe_list_to_binary(V) when is_binary(V) -> V; -maybe_list_to_binary(V) when is_list(V) -> list_to_binary(V). - -server_capabilities(rabbit_framing_amqp_0_9_1) -> - [{<<"publisher_confirms">>, bool, true}, - {<<"exchange_exchange_bindings">>, bool, true}, - {<<"basic.nack">>, bool, true}, - {<<"consumer_cancel_notify">>, bool, true}, - {<<"connection.blocked">>, bool, true}, - {<<"consumer_priorities">>, bool, true}, - {<<"authentication_failure_close">>, bool, true}, - {<<"per_consumer_qos">>, bool, true}]; -server_capabilities(_) -> - []. - -%%-------------------------------------------------------------------------- - -log(Level, Fmt, Args) -> rabbit_log:log(connection, Level, Fmt, Args). - -socket_error(Reason) when is_atom(Reason) -> - log(error, "Error on AMQP connection ~p: ~s~n", - [self(), rabbit_misc:format_inet_error(Reason)]); -socket_error(Reason) -> - log(error, "Error on AMQP connection ~p:~n~p~n", [self(), Reason]). - -inet_op(F) -> rabbit_misc:throw_on_error(inet_error, F). - -socket_op(Sock, Fun) -> - case Fun(Sock) of - {ok, Res} -> Res; - {error, Reason} -> socket_error(Reason), - %% NB: this is tcp socket, even in case of ssl - rabbit_net:fast_close(Sock), - exit(normal) - end. - -start_connection(Parent, HelperSup, Deb, Sock, SockTransform) -> - process_flag(trap_exit, true), - Name = case rabbit_net:connection_string(Sock, inbound) of - {ok, Str} -> Str; - {error, enotconn} -> rabbit_net:fast_close(Sock), - exit(normal); - {error, Reason} -> socket_error(Reason), - rabbit_net:fast_close(Sock), - exit(normal) - end, - log(info, "accepting AMQP connection ~p (~s)~n", [self(), Name]), - {ok, HandshakeTimeout} = application:get_env(rabbit, handshake_timeout), - ClientSock = socket_op(Sock, SockTransform), - erlang:send_after(HandshakeTimeout, self(), handshake_timeout), - {PeerHost, PeerPort, Host, Port} = - socket_op(Sock, fun (S) -> rabbit_net:socket_ends(S, inbound) end), - ?store_proc_name(list_to_binary(Name)), - State = #v1{parent = Parent, - sock = ClientSock, - connection = #connection{ - name = list_to_binary(Name), - host = Host, - peer_host = PeerHost, - port = Port, - peer_port = PeerPort, - protocol = none, - user = none, - timeout_sec = (HandshakeTimeout / 1000), - frame_max = ?FRAME_MIN_SIZE, - vhost = none, - client_properties = none, - capabilities = [], - auth_mechanism = none, - auth_state = none, - connected_at = rabbit_misc:now_to_ms(os:timestamp())}, - callback = uninitialized_callback, - recv_len = 0, - pending_recv = false, - connection_state = pre_init, - queue_collector = undefined, %% started on tune-ok - helper_sup = HelperSup, - heartbeater = none, - channel_sup_sup_pid = none, - channel_count = 0, - throttle = #throttle{ - alarmed_by = [], - last_blocked_by = none, - last_blocked_at = never}}, - try - run({?MODULE, recvloop, - [Deb, [], 0, switch_callback(rabbit_event:init_stats_timer( - State, #v1.stats_timer), - handshake, 8)]}), - log(info, "closing AMQP connection ~p (~s)~n", [self(), Name]) - catch - Ex -> log(case Ex of - connection_closed_abruptly -> warning; - _ -> error - end, "closing AMQP connection ~p (~s):~n~p~n", - [self(), Name, Ex]) - after - %% We don't call gen_tcp:close/1 here since it waits for - %% pending output to be sent, which results in unnecessary - %% delays. We could just terminate - the reader is the - %% controlling process and hence its termination will close - %% the socket. However, to keep the file_handle_cache - %% accounting as accurate as possible we ought to close the - %% socket w/o delay before termination. - rabbit_net:fast_close(ClientSock), - rabbit_networking:unregister_connection(self()), - rabbit_event:notify(connection_closed, [{pid, self()}]) - end, - done. - -run({M, F, A}) -> - try apply(M, F, A) - catch {become, MFA} -> run(MFA) - end. - -recvloop(Deb, Buf, BufLen, State = #v1{pending_recv = true}) -> - mainloop(Deb, Buf, BufLen, State); -recvloop(Deb, Buf, BufLen, State = #v1{connection_state = blocked}) -> - mainloop(Deb, Buf, BufLen, State); -recvloop(Deb, Buf, BufLen, State = #v1{connection_state = {become, F}}) -> - throw({become, F(Deb, Buf, BufLen, State)}); -recvloop(Deb, Buf, BufLen, State = #v1{sock = Sock, recv_len = RecvLen}) - when BufLen < RecvLen -> - case rabbit_net:setopts(Sock, [{active, once}]) of - ok -> mainloop(Deb, Buf, BufLen, - State#v1{pending_recv = true}); - {error, Reason} -> stop(Reason, State) - end; -recvloop(Deb, [B], _BufLen, State) -> - {Rest, State1} = handle_input(State#v1.callback, B, State), - recvloop(Deb, [Rest], size(Rest), State1); -recvloop(Deb, Buf, BufLen, State = #v1{recv_len = RecvLen}) -> - {DataLRev, RestLRev} = binlist_split(BufLen - RecvLen, Buf, []), - Data = list_to_binary(lists:reverse(DataLRev)), - {<<>>, State1} = handle_input(State#v1.callback, Data, State), - recvloop(Deb, lists:reverse(RestLRev), BufLen - RecvLen, State1). - -binlist_split(0, L, Acc) -> - {L, Acc}; -binlist_split(Len, L, [Acc0|Acc]) when Len < 0 -> - {H, T} = split_binary(Acc0, -Len), - {[H|L], [T|Acc]}; -binlist_split(Len, [H|T], Acc) -> - binlist_split(Len - size(H), T, [H|Acc]). - -mainloop(Deb, Buf, BufLen, State = #v1{sock = Sock}) -> - case rabbit_net:recv(Sock) of - {data, Data} -> - recvloop(Deb, [Data | Buf], BufLen + size(Data), - State#v1{pending_recv = false}); - closed when State#v1.connection_state =:= closed -> - ok; - closed -> - stop(closed, State); - {error, Reason} -> - stop(Reason, State); - {other, {system, From, Request}} -> - sys:handle_system_msg(Request, From, State#v1.parent, - ?MODULE, Deb, {Buf, BufLen, State}); - {other, Other} -> - case handle_other(Other, State) of - stop -> ok; - NewState -> recvloop(Deb, Buf, BufLen, NewState) - end - end. - -stop(closed, State) -> maybe_emit_stats(State), - throw(connection_closed_abruptly); -stop(Reason, State) -> maybe_emit_stats(State), - throw({inet_error, Reason}). - -handle_other({conserve_resources, Source, Conserve}, - State = #v1{throttle = Throttle = #throttle{alarmed_by = CR}}) -> - CR1 = case Conserve of - true -> lists:usort([Source | CR]); - false -> CR -- [Source] - end, - State1 = control_throttle( - State#v1{throttle = Throttle#throttle{alarmed_by = CR1}}), - case {blocked_by_alarm(State), blocked_by_alarm(State1)} of - {false, true} -> ok = send_blocked(State1); - {true, false} -> ok = send_unblocked(State1); - {_, _} -> ok - end, - State1; -handle_other({channel_closing, ChPid}, State) -> - ok = rabbit_channel:ready_for_close(ChPid), - {_, State1} = channel_cleanup(ChPid, State), - maybe_close(control_throttle(State1)); -handle_other({'EXIT', Parent, Reason}, State = #v1{parent = Parent}) -> - terminate(io_lib:format("broker forced connection closure " - "with reason '~w'", [Reason]), State), - %% this is what we are expected to do according to - %% http://www.erlang.org/doc/man/sys.html - %% - %% If we wanted to be *really* nice we should wait for a while for - %% clients to close the socket at their end, just as we do in the - %% ordinary error case. However, since this termination is - %% initiated by our parent it is probably more important to exit - %% quickly. - maybe_emit_stats(State), - exit(Reason); -handle_other({channel_exit, _Channel, E = {writer, send_failed, _E}}, State) -> - maybe_emit_stats(State), - throw(E); -handle_other({channel_exit, Channel, Reason}, State) -> - handle_exception(State, Channel, Reason); -handle_other({'DOWN', _MRef, process, ChPid, Reason}, State) -> - handle_dependent_exit(ChPid, Reason, State); -handle_other(terminate_connection, State) -> - maybe_emit_stats(State), - stop; -handle_other(handshake_timeout, State) - when ?IS_RUNNING(State) orelse ?IS_STOPPING(State) -> - State; -handle_other(handshake_timeout, State) -> - maybe_emit_stats(State), - throw({handshake_timeout, State#v1.callback}); -handle_other(heartbeat_timeout, State = #v1{connection_state = closed}) -> - State; -handle_other(heartbeat_timeout, State = #v1{connection_state = S}) -> - maybe_emit_stats(State), - throw({heartbeat_timeout, S}); -handle_other({'$gen_call', From, {shutdown, Explanation}}, State) -> - {ForceTermination, NewState} = terminate(Explanation, State), - gen_server:reply(From, ok), - case ForceTermination of - force -> stop; - normal -> NewState - end; -handle_other({'$gen_call', From, info}, State) -> - gen_server:reply(From, infos(?INFO_KEYS, State)), - State; -handle_other({'$gen_call', From, {info, Items}}, State) -> - gen_server:reply(From, try {ok, infos(Items, State)} - catch Error -> {error, Error} - end), - State; -handle_other({'$gen_cast', {force_event_refresh, Ref}}, State) - when ?IS_RUNNING(State) -> - rabbit_event:notify( - connection_created, - [{type, network} | infos(?CREATION_EVENT_KEYS, State)], Ref), - rabbit_event:init_stats_timer(State, #v1.stats_timer); -handle_other({'$gen_cast', {force_event_refresh, _Ref}}, State) -> - %% Ignore, we will emit a created event once we start running. - State; -handle_other(ensure_stats, State) -> - ensure_stats_timer(State); -handle_other(emit_stats, State) -> - emit_stats(State); -handle_other({bump_credit, Msg}, State) -> - credit_flow:handle_bump_msg(Msg), - control_throttle(State); -handle_other(Other, State) -> - %% internal error -> something worth dying for - maybe_emit_stats(State), - exit({unexpected_message, Other}). - -switch_callback(State, Callback, Length) -> - State#v1{callback = Callback, recv_len = Length}. - -terminate(Explanation, State) when ?IS_RUNNING(State) -> - {normal, handle_exception(State, 0, - rabbit_misc:amqp_error( - connection_forced, Explanation, [], none))}; -terminate(_Explanation, State) -> - {force, State}. - -control_throttle(State = #v1{connection_state = CS, throttle = Throttle}) -> - IsThrottled = ((Throttle#throttle.alarmed_by =/= []) orelse - credit_flow:blocked()), - case {CS, IsThrottled} of - {running, true} -> State#v1{connection_state = blocking}; - {blocking, false} -> State#v1{connection_state = running}; - {blocked, false} -> ok = rabbit_heartbeat:resume_monitor( - State#v1.heartbeater), - State#v1{connection_state = running}; - {blocked, true} -> State#v1{throttle = update_last_blocked_by( - Throttle)}; - {_, _} -> State - end. - -maybe_block(State = #v1{connection_state = blocking, - throttle = Throttle}) -> - ok = rabbit_heartbeat:pause_monitor(State#v1.heartbeater), - State1 = State#v1{connection_state = blocked, - throttle = update_last_blocked_by( - Throttle#throttle{ - last_blocked_at = erlang:now()})}, - case {blocked_by_alarm(State), blocked_by_alarm(State1)} of - {false, true} -> ok = send_blocked(State1); - {_, _} -> ok - end, - State1; -maybe_block(State) -> - State. - - -blocked_by_alarm(#v1{connection_state = blocked, - throttle = #throttle{alarmed_by = CR}}) - when CR =/= [] -> - true; -blocked_by_alarm(#v1{}) -> - false. - -send_blocked(#v1{throttle = #throttle{alarmed_by = CR}, - connection = #connection{protocol = Protocol, - capabilities = Capabilities}, - sock = Sock}) -> - case rabbit_misc:table_lookup(Capabilities, <<"connection.blocked">>) of - {bool, true} -> - RStr = string:join([atom_to_list(A) || A <- CR], " & "), - Reason = list_to_binary(rabbit_misc:format("low on ~s", [RStr])), - ok = send_on_channel0(Sock, #'connection.blocked'{reason = Reason}, - Protocol); - _ -> - ok - end. - -send_unblocked(#v1{connection = #connection{protocol = Protocol, - capabilities = Capabilities}, - sock = Sock}) -> - case rabbit_misc:table_lookup(Capabilities, <<"connection.blocked">>) of - {bool, true} -> - ok = send_on_channel0(Sock, #'connection.unblocked'{}, Protocol); - _ -> - ok - end. - -update_last_blocked_by(Throttle = #throttle{alarmed_by = []}) -> - Throttle#throttle{last_blocked_by = flow}; -update_last_blocked_by(Throttle) -> - Throttle#throttle{last_blocked_by = resource}. - -%%-------------------------------------------------------------------------- -%% error handling / termination - -close_connection(State = #v1{queue_collector = Collector, - connection = #connection{ - timeout_sec = TimeoutSec}}) -> - %% The spec says "Exclusive queues may only be accessed by the - %% current connection, and are deleted when that connection - %% closes." This does not strictly imply synchrony, but in - %% practice it seems to be what people assume. - rabbit_queue_collector:delete_all(Collector), - %% We terminate the connection after the specified interval, but - %% no later than ?CLOSING_TIMEOUT seconds. - erlang:send_after((if TimeoutSec > 0 andalso - TimeoutSec < ?CLOSING_TIMEOUT -> TimeoutSec; - true -> ?CLOSING_TIMEOUT - end) * 1000, self(), terminate_connection), - State#v1{connection_state = closed}. - -handle_dependent_exit(ChPid, Reason, State) -> - {Channel, State1} = channel_cleanup(ChPid, State), - case {Channel, termination_kind(Reason)} of - {undefined, controlled} -> State1; - {undefined, uncontrolled} -> exit({abnormal_dependent_exit, - ChPid, Reason}); - {_, controlled} -> maybe_close(control_throttle(State1)); - {_, uncontrolled} -> State2 = handle_exception( - State1, Channel, Reason), - maybe_close(control_throttle(State2)) - end. - -terminate_channels(#v1{channel_count = 0} = State) -> - State; -terminate_channels(#v1{channel_count = ChannelCount} = State) -> - lists:foreach(fun rabbit_channel:shutdown/1, all_channels()), - Timeout = 1000 * ?CHANNEL_TERMINATION_TIMEOUT * ChannelCount, - TimerRef = erlang:send_after(Timeout, self(), cancel_wait), - wait_for_channel_termination(ChannelCount, TimerRef, State). - -wait_for_channel_termination(0, TimerRef, State) -> - case erlang:cancel_timer(TimerRef) of - false -> receive - cancel_wait -> State - end; - _ -> State - end; -wait_for_channel_termination(N, TimerRef, - State = #v1{connection_state = CS, - connection = #connection{ - name = ConnName, - user = User, - vhost = VHost}}) -> - receive - {'DOWN', _MRef, process, ChPid, Reason} -> - {Channel, State1} = channel_cleanup(ChPid, State), - case {Channel, termination_kind(Reason)} of - {undefined, _} -> - exit({abnormal_dependent_exit, ChPid, Reason}); - {_, controlled} -> - wait_for_channel_termination(N-1, TimerRef, State1); - {_, uncontrolled} -> - log(error, "Error on AMQP connection ~p (~s, vhost: '~s'," - " user: '~s', state: ~p), channel ~p:" - "error while terminating:~n~p~n", - [self(), ConnName, VHost, User#user.username, - CS, Channel, Reason]), - wait_for_channel_termination(N-1, TimerRef, State1) - end; - cancel_wait -> - exit(channel_termination_timeout) - end. - -maybe_close(State = #v1{connection_state = closing, - channel_count = 0, - connection = #connection{protocol = Protocol}, - sock = Sock}) -> - NewState = close_connection(State), - ok = send_on_channel0(Sock, #'connection.close_ok'{}, Protocol), - NewState; -maybe_close(State) -> - State. - -termination_kind(normal) -> controlled; -termination_kind(_) -> uncontrolled. - -log_hard_error(#v1{connection_state = CS, - connection = #connection{ - name = ConnName, - user = User, - vhost = VHost}}, Channel, Reason) -> - log(error, - "Error on AMQP connection ~p (~s, vhost: '~s'," - " user: '~s', state: ~p), channel ~p:~n~p~n", - [self(), ConnName, VHost, User#user.username, CS, Channel, Reason]). - -handle_exception(State = #v1{connection_state = closed}, Channel, Reason) -> - log_hard_error(State, Channel, Reason), - State; -handle_exception(State = #v1{connection = #connection{protocol = Protocol}, - connection_state = CS}, - Channel, Reason) - when ?IS_RUNNING(State) orelse CS =:= closing -> - log_hard_error(State, Channel, Reason), - {0, CloseMethod} = - rabbit_binary_generator:map_exception(Channel, Reason, Protocol), - State1 = close_connection(terminate_channels(State)), - ok = send_on_channel0(State1#v1.sock, CloseMethod, Protocol), - State1; -handle_exception(State, Channel, Reason) -> - %% We don't trust the client at this point - force them to wait - %% for a bit so they can't DOS us with repeated failed logins etc. - timer:sleep(?SILENT_CLOSE_DELAY * 1000), - throw({handshake_error, State#v1.connection_state, Channel, Reason}). - -%% we've "lost sync" with the client and hence must not accept any -%% more input -fatal_frame_error(Error, Type, Channel, Payload, State) -> - frame_error(Error, Type, Channel, Payload, State), - %% grace period to allow transmission of error - timer:sleep(?SILENT_CLOSE_DELAY * 1000), - throw(fatal_frame_error). - -frame_error(Error, Type, Channel, Payload, State) -> - {Str, Bin} = payload_snippet(Payload), - handle_exception(State, Channel, - rabbit_misc:amqp_error(frame_error, - "type ~p, ~s octets = ~p: ~p", - [Type, Str, Bin, Error], none)). - -unexpected_frame(Type, Channel, Payload, State) -> - {Str, Bin} = payload_snippet(Payload), - handle_exception(State, Channel, - rabbit_misc:amqp_error(unexpected_frame, - "type ~p, ~s octets = ~p", - [Type, Str, Bin], none)). - -payload_snippet(Payload) when size(Payload) =< 16 -> - {"all", Payload}; -payload_snippet(<<Snippet:16/binary, _/binary>>) -> - {"first 16", Snippet}. - -%%-------------------------------------------------------------------------- - -create_channel(_Channel, - #v1{channel_count = ChannelCount, - connection = #connection{channel_max = ChannelMax}}) - when ChannelMax /= 0 andalso ChannelCount >= ChannelMax -> - {error, rabbit_misc:amqp_error( - not_allowed, "number of channels opened (~w) has reached the " - "negotiated channel_max (~w)", - [ChannelCount, ChannelMax], 'none')}; -create_channel(Channel, - #v1{sock = Sock, - queue_collector = Collector, - channel_sup_sup_pid = ChanSupSup, - channel_count = ChannelCount, - connection = - #connection{name = Name, - protocol = Protocol, - frame_max = FrameMax, - user = User, - vhost = VHost, - capabilities = Capabilities}} = State) -> - {ok, _ChSupPid, {ChPid, AState}} = - rabbit_channel_sup_sup:start_channel( - ChanSupSup, {tcp, Sock, Channel, FrameMax, self(), Name, - Protocol, User, VHost, Capabilities, Collector}), - MRef = erlang:monitor(process, ChPid), - put({ch_pid, ChPid}, {Channel, MRef}), - put({channel, Channel}, {ChPid, AState}), - {ok, {ChPid, AState}, State#v1{channel_count = ChannelCount + 1}}. - -channel_cleanup(ChPid, State = #v1{channel_count = ChannelCount}) -> - case get({ch_pid, ChPid}) of - undefined -> {undefined, State}; - {Channel, MRef} -> credit_flow:peer_down(ChPid), - erase({channel, Channel}), - erase({ch_pid, ChPid}), - erlang:demonitor(MRef, [flush]), - {Channel, State#v1{channel_count = ChannelCount - 1}} - end. - -all_channels() -> [ChPid || {{ch_pid, ChPid}, _ChannelMRef} <- get()]. - -%%-------------------------------------------------------------------------- - -handle_frame(Type, 0, Payload, - State = #v1{connection = #connection{protocol = Protocol}}) - when ?IS_STOPPING(State) -> - case rabbit_command_assembler:analyze_frame(Type, Payload, Protocol) of - {method, MethodName, FieldsBin} -> - handle_method0(MethodName, FieldsBin, State); - _Other -> State - end; -handle_frame(Type, 0, Payload, - State = #v1{connection = #connection{protocol = Protocol}}) -> - case rabbit_command_assembler:analyze_frame(Type, Payload, Protocol) of - error -> frame_error(unknown_frame, Type, 0, Payload, State); - heartbeat -> State; - {method, MethodName, FieldsBin} -> - handle_method0(MethodName, FieldsBin, State); - _Other -> unexpected_frame(Type, 0, Payload, State) - end; -handle_frame(Type, Channel, Payload, - State = #v1{connection = #connection{protocol = Protocol}}) - when ?IS_RUNNING(State) -> - case rabbit_command_assembler:analyze_frame(Type, Payload, Protocol) of - error -> frame_error(unknown_frame, Type, Channel, Payload, State); - heartbeat -> unexpected_frame(Type, Channel, Payload, State); - Frame -> process_frame(Frame, Channel, State) - end; -handle_frame(_Type, _Channel, _Payload, State) when ?IS_STOPPING(State) -> - State; -handle_frame(Type, Channel, Payload, State) -> - unexpected_frame(Type, Channel, Payload, State). - -process_frame(Frame, Channel, State) -> - ChKey = {channel, Channel}, - case (case get(ChKey) of - undefined -> create_channel(Channel, State); - Other -> {ok, Other, State} - end) of - {error, Error} -> - handle_exception(State, Channel, Error); - {ok, {ChPid, AState}, State1} -> - case rabbit_command_assembler:process(Frame, AState) of - {ok, NewAState} -> - put(ChKey, {ChPid, NewAState}), - post_process_frame(Frame, ChPid, State1); - {ok, Method, NewAState} -> - rabbit_channel:do(ChPid, Method), - put(ChKey, {ChPid, NewAState}), - post_process_frame(Frame, ChPid, State1); - {ok, Method, Content, NewAState} -> - rabbit_channel:do_flow(ChPid, Method, Content), - put(ChKey, {ChPid, NewAState}), - post_process_frame(Frame, ChPid, control_throttle(State1)); - {error, Reason} -> - handle_exception(State1, Channel, Reason) - end - end. - -post_process_frame({method, 'channel.close_ok', _}, ChPid, State) -> - {_, State1} = channel_cleanup(ChPid, State), - %% This is not strictly necessary, but more obviously - %% correct. Also note that we do not need to call maybe_close/1 - %% since we cannot possibly be in the 'closing' state. - control_throttle(State1); -post_process_frame({content_header, _, _, _, _}, _ChPid, State) -> - maybe_block(State); -post_process_frame({content_body, _}, _ChPid, State) -> - maybe_block(State); -post_process_frame(_Frame, _ChPid, State) -> - State. - -%%-------------------------------------------------------------------------- - -%% We allow clients to exceed the frame size a little bit since quite -%% a few get it wrong - off-by 1 or 8 (empty frame size) are typical. --define(FRAME_SIZE_FUDGE, ?EMPTY_FRAME_SIZE). - -handle_input(frame_header, <<Type:8,Channel:16,PayloadSize:32, _/binary>>, - State = #v1{connection = #connection{frame_max = FrameMax}}) - when FrameMax /= 0 andalso - PayloadSize > FrameMax - ?EMPTY_FRAME_SIZE + ?FRAME_SIZE_FUDGE -> - fatal_frame_error( - {frame_too_large, PayloadSize, FrameMax - ?EMPTY_FRAME_SIZE}, - Type, Channel, <<>>, State); -handle_input(frame_header, <<Type:8,Channel:16,PayloadSize:32, - Payload:PayloadSize/binary, ?FRAME_END, - Rest/binary>>, - State) -> - {Rest, ensure_stats_timer(handle_frame(Type, Channel, Payload, State))}; -handle_input(frame_header, <<Type:8,Channel:16,PayloadSize:32, Rest/binary>>, - State) -> - {Rest, ensure_stats_timer( - switch_callback(State, - {frame_payload, Type, Channel, PayloadSize}, - PayloadSize + 1))}; -handle_input({frame_payload, Type, Channel, PayloadSize}, Data, State) -> - <<Payload:PayloadSize/binary, EndMarker, Rest/binary>> = Data, - case EndMarker of - ?FRAME_END -> State1 = handle_frame(Type, Channel, Payload, State), - {Rest, switch_callback(State1, frame_header, 7)}; - _ -> fatal_frame_error({invalid_frame_end_marker, EndMarker}, - Type, Channel, Payload, State) - end; -handle_input(handshake, <<"AMQP", A, B, C, D, Rest/binary>>, State) -> - {Rest, handshake({A, B, C, D}, State)}; -handle_input(handshake, <<Other:8/binary, _/binary>>, #v1{sock = Sock}) -> - refuse_connection(Sock, {bad_header, Other}); -handle_input(Callback, Data, _State) -> - throw({bad_input, Callback, Data}). - -%% The two rules pertaining to version negotiation: -%% -%% * If the server cannot support the protocol specified in the -%% protocol header, it MUST respond with a valid protocol header and -%% then close the socket connection. -%% -%% * The server MUST provide a protocol version that is lower than or -%% equal to that requested by the client in the protocol header. -handshake({0, 0, 9, 1}, State) -> - start_connection({0, 9, 1}, rabbit_framing_amqp_0_9_1, State); - -%% This is the protocol header for 0-9, which we can safely treat as -%% though it were 0-9-1. -handshake({1, 1, 0, 9}, State) -> - start_connection({0, 9, 0}, rabbit_framing_amqp_0_9_1, State); - -%% This is what most clients send for 0-8. The 0-8 spec, confusingly, -%% defines the version as 8-0. -handshake({1, 1, 8, 0}, State) -> - start_connection({8, 0, 0}, rabbit_framing_amqp_0_8, State); - -%% The 0-8 spec as on the AMQP web site actually has this as the -%% protocol header; some libraries e.g., py-amqplib, send it when they -%% want 0-8. -handshake({1, 1, 9, 1}, State) -> - start_connection({8, 0, 0}, rabbit_framing_amqp_0_8, State); - -%% ... and finally, the 1.0 spec is crystal clear! -handshake({Id, 1, 0, 0}, State) -> - become_1_0(Id, State); - -handshake(Vsn, #v1{sock = Sock}) -> - refuse_connection(Sock, {bad_version, Vsn}). - -%% Offer a protocol version to the client. Connection.start only -%% includes a major and minor version number, Luckily 0-9 and 0-9-1 -%% are similar enough that clients will be happy with either. -start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, - Protocol, - State = #v1{sock = Sock, connection = Connection}) -> - rabbit_networking:register_connection(self()), - Start = #'connection.start'{ - version_major = ProtocolMajor, - version_minor = ProtocolMinor, - server_properties = server_properties(Protocol), - mechanisms = auth_mechanisms_binary(Sock), - locales = <<"en_US">> }, - ok = send_on_channel0(Sock, Start, Protocol), - switch_callback(State#v1{connection = Connection#connection{ - timeout_sec = ?NORMAL_TIMEOUT, - protocol = Protocol}, - connection_state = starting}, - frame_header, 7). - -refuse_connection(Sock, Exception, {A, B, C, D}) -> - ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",A,B,C,D>>) end), - throw(Exception). - --ifdef(use_specs). --spec(refuse_connection/2 :: (rabbit_net:socket(), any()) -> no_return()). --endif. -refuse_connection(Sock, Exception) -> - refuse_connection(Sock, Exception, {0, 0, 9, 1}). - -ensure_stats_timer(State = #v1{connection_state = running}) -> - rabbit_event:ensure_stats_timer(State, #v1.stats_timer, emit_stats); -ensure_stats_timer(State) -> - State. - -%%-------------------------------------------------------------------------- - -handle_method0(MethodName, FieldsBin, - State = #v1{connection = #connection{protocol = Protocol}}) -> - try - handle_method0(Protocol:decode_method_fields(MethodName, FieldsBin), - State) - catch throw:{inet_error, E} when E =:= closed; E =:= enotconn -> - maybe_emit_stats(State), - throw(connection_closed_abruptly); - exit:#amqp_error{method = none} = Reason -> - handle_exception(State, 0, Reason#amqp_error{method = MethodName}); - Type:Reason -> - Stack = erlang:get_stacktrace(), - handle_exception(State, 0, {Type, Reason, MethodName, Stack}) - end. - -handle_method0(#'connection.start_ok'{mechanism = Mechanism, - response = Response, - client_properties = ClientProperties}, - State0 = #v1{connection_state = starting, - connection = Connection, - sock = Sock}) -> - AuthMechanism = auth_mechanism_to_module(Mechanism, Sock), - Capabilities = - case rabbit_misc:table_lookup(ClientProperties, <<"capabilities">>) of - {table, Capabilities1} -> Capabilities1; - _ -> [] - end, - State = State0#v1{connection_state = securing, - connection = - Connection#connection{ - client_properties = ClientProperties, - capabilities = Capabilities, - auth_mechanism = {Mechanism, AuthMechanism}, - auth_state = AuthMechanism:init(Sock)}}, - auth_phase(Response, State); - -handle_method0(#'connection.secure_ok'{response = Response}, - State = #v1{connection_state = securing}) -> - auth_phase(Response, State); - -handle_method0(#'connection.tune_ok'{frame_max = FrameMax, - channel_max = ChannelMax, - heartbeat = ClientHeartbeat}, - State = #v1{connection_state = tuning, - connection = Connection, - helper_sup = SupPid, - sock = Sock}) -> - ok = validate_negotiated_integer_value( - frame_max, ?FRAME_MIN_SIZE, FrameMax), - ok = validate_negotiated_integer_value( - channel_max, ?CHANNEL_MIN, ChannelMax), - {ok, Collector} = rabbit_connection_helper_sup:start_queue_collector( - SupPid, Connection#connection.name), - Frame = rabbit_binary_generator:build_heartbeat_frame(), - SendFun = fun() -> catch rabbit_net:send(Sock, Frame) end, - Parent = self(), - ReceiveFun = fun() -> Parent ! heartbeat_timeout end, - Heartbeater = rabbit_heartbeat:start( - SupPid, Sock, Connection#connection.name, - ClientHeartbeat, SendFun, ClientHeartbeat, ReceiveFun), - State#v1{connection_state = opening, - connection = Connection#connection{ - frame_max = FrameMax, - channel_max = ChannelMax, - timeout_sec = ClientHeartbeat}, - queue_collector = Collector, - heartbeater = Heartbeater}; - -handle_method0(#'connection.open'{virtual_host = VHostPath}, - State = #v1{connection_state = opening, - connection = Connection = #connection{ - user = User, - protocol = Protocol}, - helper_sup = SupPid, - sock = Sock, - throttle = Throttle}) -> - ok = rabbit_access_control:check_vhost_access(User, VHostPath, Sock), - NewConnection = Connection#connection{vhost = VHostPath}, - ok = send_on_channel0(Sock, #'connection.open_ok'{}, Protocol), - Conserve = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}), - Throttle1 = Throttle#throttle{alarmed_by = Conserve}, - {ok, ChannelSupSupPid} = - rabbit_connection_helper_sup:start_channel_sup_sup(SupPid), - State1 = control_throttle( - State#v1{connection_state = running, - connection = NewConnection, - channel_sup_sup_pid = ChannelSupSupPid, - throttle = Throttle1}), - rabbit_event:notify(connection_created, - [{type, network} | - infos(?CREATION_EVENT_KEYS, State1)]), - maybe_emit_stats(State1), - State1; -handle_method0(#'connection.close'{}, State) when ?IS_RUNNING(State) -> - lists:foreach(fun rabbit_channel:shutdown/1, all_channels()), - maybe_close(State#v1{connection_state = closing}); -handle_method0(#'connection.close'{}, - State = #v1{connection = #connection{protocol = Protocol}, - sock = Sock}) - when ?IS_STOPPING(State) -> - %% We're already closed or closing, so we don't need to cleanup - %% anything. - ok = send_on_channel0(Sock, #'connection.close_ok'{}, Protocol), - State; -handle_method0(#'connection.close_ok'{}, - State = #v1{connection_state = closed}) -> - self() ! terminate_connection, - State; -handle_method0(_Method, State) when ?IS_STOPPING(State) -> - State; -handle_method0(_Method, #v1{connection_state = S}) -> - rabbit_misc:protocol_error( - channel_error, "unexpected method in connection state ~w", [S]). - -validate_negotiated_integer_value(Field, Min, ClientValue) -> - ServerValue = get_env(Field), - if ClientValue /= 0 andalso ClientValue < Min -> - fail_negotiation(Field, min, ServerValue, ClientValue); - ServerValue /= 0 andalso (ClientValue =:= 0 orelse - ClientValue > ServerValue) -> - fail_negotiation(Field, max, ServerValue, ClientValue); - true -> - ok - end. - -%% keep dialyzer happy --spec fail_negotiation(atom(), 'min' | 'max', integer(), integer()) -> - no_return(). -fail_negotiation(Field, MinOrMax, ServerValue, ClientValue) -> - {S1, S2} = case MinOrMax of - min -> {lower, minimum}; - max -> {higher, maximum} - end, - rabbit_misc:protocol_error( - not_allowed, "negotiated ~w = ~w is ~w than the ~w allowed value (~w)", - [Field, ClientValue, S1, S2, ServerValue], 'connection.tune'). - -get_env(Key) -> - {ok, Value} = application:get_env(rabbit, Key), - Value. - -send_on_channel0(Sock, Method, Protocol) -> - ok = rabbit_writer:internal_send_command(Sock, 0, Method, Protocol). - -auth_mechanism_to_module(TypeBin, Sock) -> - case rabbit_registry:binary_to_type(TypeBin) of - {error, not_found} -> - rabbit_misc:protocol_error( - command_invalid, "unknown authentication mechanism '~s'", - [TypeBin]); - T -> - case {lists:member(T, auth_mechanisms(Sock)), - rabbit_registry:lookup_module(auth_mechanism, T)} of - {true, {ok, Module}} -> - Module; - _ -> - rabbit_misc:protocol_error( - command_invalid, - "invalid authentication mechanism '~s'", [T]) - end - end. - -auth_mechanisms(Sock) -> - {ok, Configured} = application:get_env(auth_mechanisms), - [Name || {Name, Module} <- rabbit_registry:lookup_all(auth_mechanism), - Module:should_offer(Sock), lists:member(Name, Configured)]. - -auth_mechanisms_binary(Sock) -> - list_to_binary( - string:join([atom_to_list(A) || A <- auth_mechanisms(Sock)], " ")). - -auth_phase(Response, - State = #v1{connection = Connection = - #connection{protocol = Protocol, - auth_mechanism = {Name, AuthMechanism}, - auth_state = AuthState}, - sock = Sock}) -> - case AuthMechanism:handle_response(Response, AuthState) of - {refused, Msg, Args} -> - auth_fail(Msg, Args, Name, State); - {protocol_error, Msg, Args} -> - rabbit_misc:protocol_error(syntax_error, Msg, Args); - {challenge, Challenge, AuthState1} -> - Secure = #'connection.secure'{challenge = Challenge}, - ok = send_on_channel0(Sock, Secure, Protocol), - State#v1{connection = Connection#connection{ - auth_state = AuthState1}}; - {ok, User = #user{username = Username}} -> - case rabbit_access_control:check_user_loopback(Username, Sock) of - ok -> ok; - not_allowed -> auth_fail("user '~s' can only connect via " - "localhost", [Username], Name, State) - end, - Tune = #'connection.tune'{frame_max = get_env(frame_max), - channel_max = get_env(channel_max), - heartbeat = get_env(heartbeat)}, - ok = send_on_channel0(Sock, Tune, Protocol), - State#v1{connection_state = tuning, - connection = Connection#connection{user = User, - auth_state = none}} - end. - --ifdef(use_specs). --spec(auth_fail/4 :: (string(), [any()], binary(), #v1{}) -> no_return()). --endif. -auth_fail(Msg, Args, AuthName, - State = #v1{connection = #connection{protocol = Protocol, - capabilities = Capabilities}}) -> - AmqpError = rabbit_misc:amqp_error( - access_refused, "~s login refused: ~s", - [AuthName, io_lib:format(Msg, Args)], none), - case rabbit_misc:table_lookup(Capabilities, - <<"authentication_failure_close">>) of - {bool, true} -> - SafeMsg = io_lib:format( - "Login was refused using authentication " - "mechanism ~s. For details see the broker " - "logfile.", [AuthName]), - AmqpError1 = AmqpError#amqp_error{explanation = SafeMsg}, - {0, CloseMethod} = rabbit_binary_generator:map_exception( - 0, AmqpError1, Protocol), - ok = send_on_channel0(State#v1.sock, CloseMethod, Protocol); - _ -> ok - end, - rabbit_misc:protocol_error(AmqpError). - -%%-------------------------------------------------------------------------- - -infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. - -i(pid, #v1{}) -> self(); -i(SockStat, S) when SockStat =:= recv_oct; - SockStat =:= recv_cnt; - SockStat =:= send_oct; - SockStat =:= send_cnt; - SockStat =:= send_pend -> - socket_info(fun (Sock) -> rabbit_net:getstat(Sock, [SockStat]) end, - fun ([{_, I}]) -> I end, S); -i(ssl, #v1{sock = Sock}) -> rabbit_net:is_ssl(Sock); -i(ssl_protocol, S) -> ssl_info(fun ({P, _}) -> P end, S); -i(ssl_key_exchange, S) -> ssl_info(fun ({_, {K, _, _}}) -> K end, S); -i(ssl_cipher, S) -> ssl_info(fun ({_, {_, C, _}}) -> C end, S); -i(ssl_hash, S) -> ssl_info(fun ({_, {_, _, H}}) -> H end, S); -i(peer_cert_issuer, S) -> cert_info(fun rabbit_ssl:peer_cert_issuer/1, S); -i(peer_cert_subject, S) -> cert_info(fun rabbit_ssl:peer_cert_subject/1, S); -i(peer_cert_validity, S) -> cert_info(fun rabbit_ssl:peer_cert_validity/1, S); -i(channels, #v1{channel_count = ChannelCount}) -> ChannelCount; -i(state, #v1{connection_state = ConnectionState, - throttle = #throttle{alarmed_by = Alarms, - last_blocked_by = WasBlockedBy, - last_blocked_at = T}}) -> - case Alarms =:= [] andalso %% not throttled by resource alarms - (credit_flow:blocked() %% throttled by flow now - orelse %% throttled by flow recently - (WasBlockedBy =:= flow andalso T =/= never andalso - timer:now_diff(erlang:now(), T) < 5000000)) of - true -> flow; - false -> ConnectionState - end; -i(Item, #v1{connection = Conn}) -> ic(Item, Conn). - -ic(name, #connection{name = Name}) -> Name; -ic(host, #connection{host = Host}) -> Host; -ic(peer_host, #connection{peer_host = PeerHost}) -> PeerHost; -ic(port, #connection{port = Port}) -> Port; -ic(peer_port, #connection{peer_port = PeerPort}) -> PeerPort; -ic(protocol, #connection{protocol = none}) -> none; -ic(protocol, #connection{protocol = P}) -> P:version(); -ic(user, #connection{user = none}) -> ''; -ic(user, #connection{user = U}) -> U#user.username; -ic(vhost, #connection{vhost = VHost}) -> VHost; -ic(timeout, #connection{timeout_sec = Timeout}) -> Timeout; -ic(frame_max, #connection{frame_max = FrameMax}) -> FrameMax; -ic(channel_max, #connection{channel_max = ChMax}) -> ChMax; -ic(client_properties, #connection{client_properties = CP}) -> CP; -ic(auth_mechanism, #connection{auth_mechanism = none}) -> none; -ic(auth_mechanism, #connection{auth_mechanism = {Name, _Mod}}) -> Name; -ic(connected_at, #connection{connected_at = T}) -> T; -ic(Item, #connection{}) -> throw({bad_argument, Item}). - -socket_info(Get, Select, #v1{sock = Sock}) -> - case Get(Sock) of - {ok, T} -> Select(T); - {error, _} -> '' - end. - -ssl_info(F, #v1{sock = Sock}) -> - %% The first ok form is R14 - %% The second is R13 - the extra term is exportability (by inspection, - %% the docs are wrong) - case rabbit_net:ssl_info(Sock) of - nossl -> ''; - {error, _} -> ''; - {ok, {P, {K, C, H}}} -> F({P, {K, C, H}}); - {ok, {P, {K, C, H, _}}} -> F({P, {K, C, H}}) - end. - -cert_info(F, #v1{sock = Sock}) -> - case rabbit_net:peercert(Sock) of - nossl -> ''; - {error, no_peercert} -> ''; - {ok, Cert} -> list_to_binary(F(Cert)) - end. - -maybe_emit_stats(State) -> - rabbit_event:if_enabled(State, #v1.stats_timer, - fun() -> emit_stats(State) end). - -emit_stats(State) -> - Infos = infos(?STATISTICS_KEYS, State), - rabbit_event:notify(connection_stats, Infos), - State1 = rabbit_event:reset_stats_timer(State, #v1.stats_timer), - %% If we emit an event which looks like we are in flow control, it's not a - %% good idea for it to be our last even if we go idle. Keep emitting - %% events, either we stay busy or we drop out of flow control. - case proplists:get_value(state, Infos) of - flow -> ensure_stats_timer(State1); - _ -> State1 - end. - -%% 1.0 stub --ifdef(use_specs). --spec(become_1_0/2 :: (non_neg_integer(), #v1{}) -> no_return()). --endif. -become_1_0(Id, State = #v1{sock = Sock}) -> - case code:is_loaded(rabbit_amqp1_0_reader) of - false -> refuse_connection(Sock, amqp1_0_plugin_not_enabled); - _ -> Mode = case Id of - 0 -> amqp; - 3 -> sasl; - _ -> refuse_connection( - Sock, {unsupported_amqp1_0_protocol_id, Id}, - {3, 1, 0, 0}) - end, - F = fun (_Deb, Buf, BufLen, S) -> - {rabbit_amqp1_0_reader, init, - [Mode, pack_for_1_0(Buf, BufLen, S)]} - end, - State#v1{connection_state = {become, F}} - end. - -pack_for_1_0(Buf, BufLen, #v1{parent = Parent, - sock = Sock, - recv_len = RecvLen, - pending_recv = PendingRecv, - helper_sup = SupPid}) -> - {Parent, Sock, RecvLen, PendingRecv, SupPid, Buf, BufLen}. diff --git a/src/rabbit_recovery_terms.erl b/src/rabbit_recovery_terms.erl deleted file mode 100644 index 9f837cce..00000000 --- a/src/rabbit_recovery_terms.erl +++ /dev/null @@ -1,138 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - -%% We use a gen_server simply so that during the terminate/2 call -%% (i.e., during shutdown), we can sync/flush the dets table to disk. - --module(rabbit_recovery_terms). - --behaviour(gen_server). - --export([start/0, stop/0, store/2, read/1, clear/0]). - --export([start_link/0]). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --export([upgrade_recovery_terms/0, persistent_bytes/0]). - --rabbit_upgrade({upgrade_recovery_terms, local, []}). --rabbit_upgrade({persistent_bytes, local, [upgrade_recovery_terms]}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start() -> rabbit_types:ok_or_error(term())). --spec(stop() -> rabbit_types:ok_or_error(term())). --spec(store(file:filename(), term()) -> rabbit_types:ok_or_error(term())). --spec(read(file:filename()) -> rabbit_types:ok_or_error2(term(), not_found)). --spec(clear() -> 'ok'). - --endif. % use_specs - -%%---------------------------------------------------------------------------- - --define(SERVER, ?MODULE). - -start() -> rabbit_sup:start_child(?MODULE). - -stop() -> rabbit_sup:stop_child(?MODULE). - -store(DirBaseName, Terms) -> dets:insert(?MODULE, {DirBaseName, Terms}). - -read(DirBaseName) -> - case dets:lookup(?MODULE, DirBaseName) of - [{_, Terms}] -> {ok, Terms}; - _ -> {error, not_found} - end. - -clear() -> - ok = dets:delete_all_objects(?MODULE), - flush(). - -start_link() -> gen_server:start_link(?MODULE, [], []). - -%%---------------------------------------------------------------------------- - -upgrade_recovery_terms() -> - open_table(), - try - QueuesDir = filename:join(rabbit_mnesia:dir(), "queues"), - Dirs = case rabbit_file:list_dir(QueuesDir) of - {ok, Entries} -> Entries; - {error, _} -> [] - end, - [begin - File = filename:join([QueuesDir, Dir, "clean.dot"]), - case rabbit_file:read_term_file(File) of - {ok, Terms} -> ok = store(Dir, Terms); - {error, _} -> ok - end, - file:delete(File) - end || Dir <- Dirs], - ok - after - close_table() - end. - -persistent_bytes() -> dets_upgrade(fun persistent_bytes/1). -persistent_bytes(Props) -> Props ++ [{persistent_bytes, 0}]. - -dets_upgrade(Fun)-> - open_table(), - try - ok = dets:foldl(fun ({DirBaseName, Terms}, Acc) -> - store(DirBaseName, Fun(Terms)), - Acc - end, ok, ?MODULE), - ok - after - close_table() - end. - -%%---------------------------------------------------------------------------- - -init(_) -> - process_flag(trap_exit, true), - open_table(), - {ok, undefined}. - -handle_call(Msg, _, State) -> {stop, {unexpected_call, Msg}, State}. - -handle_cast(Msg, State) -> {stop, {unexpected_cast, Msg}, State}. - -handle_info(_Info, State) -> {noreply, State}. - -terminate(_Reason, _State) -> - close_table(). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%---------------------------------------------------------------------------- - -open_table() -> - File = filename:join(rabbit_mnesia:dir(), "recovery.dets"), - {ok, _} = dets:open_file(?MODULE, [{file, File}, - {ram_file, true}, - {auto_save, infinity}]). - -flush() -> ok = dets:sync(?MODULE). - -close_table() -> - ok = flush(), - ok = dets:close(?MODULE). diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl deleted file mode 100644 index ad8d0d02..00000000 --- a/src/rabbit_registry.erl +++ /dev/null @@ -1,165 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_registry). - --behaviour(gen_server). - --export([start_link/0]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3]). - --export([register/3, unregister/2, - binary_to_type/1, lookup_module/2, lookup_all/1]). - --define(SERVER, ?MODULE). --define(ETS_NAME, ?MODULE). - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(register/3 :: (atom(), binary(), atom()) -> 'ok'). --spec(unregister/2 :: (atom(), binary()) -> 'ok'). --spec(binary_to_type/1 :: - (binary()) -> atom() | rabbit_types:error('not_found')). --spec(lookup_module/2 :: - (atom(), atom()) -> rabbit_types:ok_or_error2(atom(), 'not_found')). --spec(lookup_all/1 :: (atom()) -> [{atom(), atom()}]). - --endif. - -%%--------------------------------------------------------------------------- - -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - -%%--------------------------------------------------------------------------- - -register(Class, TypeName, ModuleName) -> - gen_server:call(?SERVER, {register, Class, TypeName, ModuleName}, infinity). - -unregister(Class, TypeName) -> - gen_server:call(?SERVER, {unregister, Class, TypeName}, infinity). - -%% This is used with user-supplied arguments (e.g., on exchange -%% declare), so we restrict it to existing atoms only. This means it -%% can throw a badarg, indicating that the type cannot have been -%% registered. -binary_to_type(TypeBin) when is_binary(TypeBin) -> - case catch list_to_existing_atom(binary_to_list(TypeBin)) of - {'EXIT', {badarg, _}} -> {error, not_found}; - TypeAtom -> TypeAtom - end. - -lookup_module(Class, T) when is_atom(T) -> - case ets:lookup(?ETS_NAME, {Class, T}) of - [{_, Module}] -> - {ok, Module}; - [] -> - {error, not_found} - end. - -lookup_all(Class) -> - [{K, V} || [K, V] <- ets:match(?ETS_NAME, {{Class, '$1'}, '$2'})]. - -%%--------------------------------------------------------------------------- - -internal_binary_to_type(TypeBin) when is_binary(TypeBin) -> - list_to_atom(binary_to_list(TypeBin)). - -internal_register(Class, TypeName, ModuleName) - when is_atom(Class), is_binary(TypeName), is_atom(ModuleName) -> - ok = sanity_check_module(class_module(Class), ModuleName), - RegArg = {{Class, internal_binary_to_type(TypeName)}, ModuleName}, - true = ets:insert(?ETS_NAME, RegArg), - conditional_register(RegArg), - ok. - -internal_unregister(Class, TypeName) -> - UnregArg = {Class, internal_binary_to_type(TypeName)}, - conditional_unregister(UnregArg), - true = ets:delete(?ETS_NAME, UnregArg), - ok. - -%% register exchange decorator route callback only when implemented, -%% in order to avoid unnecessary decorator calls on the fast -%% publishing path -conditional_register({{exchange_decorator, Type}, ModuleName}) -> - case erlang:function_exported(ModuleName, route, 2) of - true -> true = ets:insert(?ETS_NAME, - {{exchange_decorator_route, Type}, - ModuleName}); - false -> ok - end; -conditional_register(_) -> - ok. - -conditional_unregister({exchange_decorator, Type}) -> - true = ets:delete(?ETS_NAME, {exchange_decorator_route, Type}), - ok; -conditional_unregister(_) -> - ok. - -sanity_check_module(ClassModule, Module) -> - case catch lists:member(ClassModule, - lists:flatten( - [Bs || {Attr, Bs} <- - Module:module_info(attributes), - Attr =:= behavior orelse - Attr =:= behaviour])) of - {'EXIT', {undef, _}} -> {error, not_module}; - false -> {error, {not_type, ClassModule}}; - true -> ok - end. - -class_module(exchange) -> rabbit_exchange_type; -class_module(auth_mechanism) -> rabbit_auth_mechanism; -class_module(runtime_parameter) -> rabbit_runtime_parameter; -class_module(exchange_decorator) -> rabbit_exchange_decorator; -class_module(queue_decorator) -> rabbit_queue_decorator; -class_module(policy_validator) -> rabbit_policy_validator; -class_module(ha_mode) -> rabbit_mirror_queue_mode; -class_module(channel_interceptor) -> rabbit_channel_interceptor. - -%%--------------------------------------------------------------------------- - -init([]) -> - ?ETS_NAME = ets:new(?ETS_NAME, [protected, set, named_table]), - {ok, none}. - -handle_call({register, Class, TypeName, ModuleName}, _From, State) -> - ok = internal_register(Class, TypeName, ModuleName), - {reply, ok, State}; - -handle_call({unregister, Class, TypeName}, _From, State) -> - ok = internal_unregister(Class, TypeName), - {reply, ok, State}; - -handle_call(Request, _From, State) -> - {stop, {unhandled_call, Request}, State}. - -handle_cast(Request, State) -> - {stop, {unhandled_cast, Request}, State}. - -handle_info(Message, State) -> - {stop, {unhandled_info, Message}, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. diff --git a/src/rabbit_restartable_sup.erl b/src/rabbit_restartable_sup.erl deleted file mode 100644 index 3366bad7..00000000 --- a/src/rabbit_restartable_sup.erl +++ /dev/null @@ -1,48 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_restartable_sup). - --behaviour(supervisor2). - --export([start_link/3]). - --export([init/1]). - --include("rabbit.hrl"). - --define(DELAY, 2). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/3 :: (atom(), rabbit_types:mfargs(), boolean()) -> - rabbit_types:ok_pid_or_error()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Name, {_M, _F, _A} = Fun, Delay) -> - supervisor2:start_link({local, Name}, ?MODULE, [Fun, Delay]). - -init([{Mod, _F, _A} = Fun, Delay]) -> - {ok, {{one_for_one, 10, 10}, - [{Mod, Fun, case Delay of - true -> {transient, 1}; - false -> transient - end, ?MAX_WAIT, worker, [Mod]}]}}. diff --git a/src/rabbit_router.erl b/src/rabbit_router.erl deleted file mode 100644 index fca01759..00000000 --- a/src/rabbit_router.erl +++ /dev/null @@ -1,83 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_router). --include_lib("stdlib/include/qlc.hrl"). --include("rabbit.hrl"). - --export([match_bindings/2, match_routing_key/2]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([routing_key/0, match_result/0]). - --type(routing_key() :: binary()). --type(match_result() :: [rabbit_types:binding_destination()]). - --spec(match_bindings/2 :: (rabbit_types:binding_source(), - fun ((rabbit_types:binding()) -> boolean())) -> - match_result()). --spec(match_routing_key/2 :: (rabbit_types:binding_source(), - [routing_key()] | ['_']) -> - match_result()). - --endif. - -%%---------------------------------------------------------------------------- - -%% TODO: Maybe this should be handled by a cursor instead. -%% TODO: This causes a full scan for each entry with the same source -match_bindings(SrcName, Match) -> - Query = qlc:q([DestinationName || - #route{binding = Binding = #binding{ - source = SrcName1, - destination = DestinationName}} <- - mnesia:table(rabbit_route), - SrcName == SrcName1, - Match(Binding)]), - mnesia:async_dirty(fun qlc:e/1, [Query]). - -match_routing_key(SrcName, [RoutingKey]) -> - find_routes(#route{binding = #binding{source = SrcName, - destination = '$1', - key = RoutingKey, - _ = '_'}}, - []); -match_routing_key(SrcName, [_|_] = RoutingKeys) -> - find_routes(#route{binding = #binding{source = SrcName, - destination = '$1', - key = '$2', - _ = '_'}}, - [list_to_tuple(['orelse' | [{'=:=', '$2', RKey} || - RKey <- RoutingKeys]])]). - -%%-------------------------------------------------------------------- - -%% Normally we'd call mnesia:dirty_select/2 here, but that is quite -%% expensive for the same reasons as above, and, additionally, due to -%% mnesia 'fixing' the table with ets:safe_fixtable/2, which is wholly -%% unnecessary. According to the ets docs (and the code in erl_db.c), -%% 'select' is safe anyway ("Functions that internally traverse over a -%% table, like select and match, will give the same guarantee as -%% safe_fixtable.") and, furthermore, even the lower level iterators -%% ('first' and 'next') are safe on ordered_set tables ("Note that for -%% tables of the ordered_set type, safe_fixtable/2 is not necessary as -%% calls to first/1 and next/2 will always succeed."), which -%% rabbit_route is. -find_routes(MatchHead, Conditions) -> - ets:select(rabbit_route, [{MatchHead, Conditions, ['$1']}]). diff --git a/src/rabbit_runtime_parameter.erl b/src/rabbit_runtime_parameter.erl deleted file mode 100644 index 3a5d9606..00000000 --- a/src/rabbit_runtime_parameter.erl +++ /dev/null @@ -1,42 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_runtime_parameter). - --ifdef(use_specs). - --type(validate_results() :: - 'ok' | {error, string(), [term()]} | [validate_results()]). - --callback validate(rabbit_types:vhost(), binary(), binary(), - term(), rabbit_types:user()) -> validate_results(). --callback notify(rabbit_types:vhost(), binary(), binary(), term()) -> 'ok'. --callback notify_clear(rabbit_types:vhost(), binary(), binary()) -> 'ok'. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [ - {validate, 5}, - {notify, 4}, - {notify_clear, 3} - ]; -behaviour_info(_Other) -> - undefined. - --endif. diff --git a/src/rabbit_runtime_parameters.erl b/src/rabbit_runtime_parameters.erl deleted file mode 100644 index f78549ff..00000000 --- a/src/rabbit_runtime_parameters.erl +++ /dev/null @@ -1,270 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_runtime_parameters). - --include("rabbit.hrl"). - --export([parse_set/5, set/5, set_any/5, clear/3, clear_any/3, list/0, list/1, - list_component/1, list/2, list_formatted/1, lookup/3, - value/3, value/4, info_keys/0]). - --export([set_global/2, value_global/1, value_global/2]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(ok_or_error_string() :: 'ok' | {'error_string', string()}). --type(ok_thunk_or_error_string() :: ok_or_error_string() | fun(() -> 'ok')). - --spec(parse_set/5 :: (rabbit_types:vhost(), binary(), binary(), string(), - rabbit_types:user() | 'none') -> ok_or_error_string()). --spec(set/5 :: (rabbit_types:vhost(), binary(), binary(), term(), - rabbit_types:user() | 'none') -> ok_or_error_string()). --spec(set_any/5 :: (rabbit_types:vhost(), binary(), binary(), term(), - rabbit_types:user() | 'none') -> ok_or_error_string()). --spec(set_global/2 :: (atom(), term()) -> 'ok'). --spec(clear/3 :: (rabbit_types:vhost(), binary(), binary()) - -> ok_thunk_or_error_string()). --spec(clear_any/3 :: (rabbit_types:vhost(), binary(), binary()) - -> ok_thunk_or_error_string()). --spec(list/0 :: () -> [rabbit_types:infos()]). --spec(list/1 :: (rabbit_types:vhost() | '_') -> [rabbit_types:infos()]). --spec(list_component/1 :: (binary()) -> [rabbit_types:infos()]). --spec(list/2 :: (rabbit_types:vhost() | '_', binary() | '_') - -> [rabbit_types:infos()]). --spec(list_formatted/1 :: (rabbit_types:vhost()) -> [rabbit_types:infos()]). --spec(lookup/3 :: (rabbit_types:vhost(), binary(), binary()) - -> rabbit_types:infos() | 'not_found'). --spec(value/3 :: (rabbit_types:vhost(), binary(), binary()) -> term()). --spec(value/4 :: (rabbit_types:vhost(), binary(), binary(), term()) -> term()). --spec(value_global/1 :: (atom()) -> term() | 'not_found'). --spec(value_global/2 :: (atom(), term()) -> term()). --spec(info_keys/0 :: () -> rabbit_types:info_keys()). - --endif. - -%%--------------------------------------------------------------------------- - --import(rabbit_misc, [pget/2, pset/3]). - --define(TABLE, rabbit_runtime_parameters). - -%%--------------------------------------------------------------------------- - -parse_set(_, <<"policy">>, _, _, _) -> - {error_string, "policies may not be set using this method"}; -parse_set(VHost, Component, Name, String, User) -> - case rabbit_misc:json_decode(String) of - {ok, JSON} -> set(VHost, Component, Name, - rabbit_misc:json_to_term(JSON), User); - error -> {error_string, "JSON decoding error"} - end. - -set(_, <<"policy">>, _, _, _) -> - {error_string, "policies may not be set using this method"}; -set(VHost, Component, Name, Term, User) -> - set_any(VHost, Component, Name, Term, User). - -set_global(Name, Term) -> - mnesia_update(Name, Term), - event_notify(parameter_set, none, global, [{name, Name}, - {value, Term}]), - ok. - -format_error(L) -> - {error_string, rabbit_misc:format_many([{"Validation failed~n", []} | L])}. - -set_any(VHost, Component, Name, Term, User) -> - case set_any0(VHost, Component, Name, Term, User) of - ok -> ok; - {errors, L} -> format_error(L) - end. - -set_any0(VHost, Component, Name, Term, User) -> - case lookup_component(Component) of - {ok, Mod} -> - case flatten_errors( - Mod:validate(VHost, Component, Name, Term, User)) of - ok -> - case mnesia_update(VHost, Component, Name, Term) of - {old, Term} -> ok; - _ -> event_notify( - parameter_set, VHost, Component, - [{name, Name}, - {value, Term}]), - Mod:notify(VHost, Component, Name, Term) - end, - ok; - E -> - E - end; - E -> - E - end. - -mnesia_update(Key, Term) -> - rabbit_misc:execute_mnesia_transaction(mnesia_update_fun(Key, Term)). - -mnesia_update(VHost, Comp, Name, Term) -> - rabbit_misc:execute_mnesia_transaction( - rabbit_vhost:with(VHost, mnesia_update_fun({VHost, Comp, Name}, Term))). - -mnesia_update_fun(Key, Term) -> - fun () -> - Res = case mnesia:read(?TABLE, Key, read) of - [] -> new; - [Params] -> {old, Params#runtime_parameters.value} - end, - ok = mnesia:write(?TABLE, c(Key, Term), write), - Res - end. - -clear(_, <<"policy">> , _) -> - {error_string, "policies may not be cleared using this method"}; -clear(VHost, Component, Name) -> - clear_any(VHost, Component, Name). - -clear_any(VHost, Component, Name) -> - Notify = fun () -> - case lookup_component(Component) of - {ok, Mod} -> event_notify( - parameter_cleared, VHost, Component, - [{name, Name}]), - Mod:notify_clear(VHost, Component, Name); - _ -> ok - end - end, - case lookup(VHost, Component, Name) of - not_found -> {error_string, "Parameter does not exist"}; - _ -> mnesia_clear(VHost, Component, Name), - case mnesia:is_transaction() of - true -> Notify; - false -> Notify() - end - end. - -mnesia_clear(VHost, Component, Name) -> - F = fun () -> - ok = mnesia:delete(?TABLE, {VHost, Component, Name}, write) - end, - ok = rabbit_misc:execute_mnesia_transaction(rabbit_vhost:with(VHost, F)). - -event_notify(_Event, _VHost, <<"policy">>, _Props) -> - ok; -event_notify(Event, none, Component, Props) -> - rabbit_event:notify(Event, [{component, Component} | Props]); -event_notify(Event, VHost, Component, Props) -> - rabbit_event:notify(Event, [{vhost, VHost}, - {component, Component} | Props]). - -list() -> - [p(P) || #runtime_parameters{ key = {_VHost, Comp, _Name}} = P <- - rabbit_misc:dirty_read_all(?TABLE), Comp /= <<"policy">>]. - -list(VHost) -> list(VHost, '_'). -list_component(Component) -> list('_', Component). - -%% Not dirty_match_object since that would not be transactional when used in a -%% tx context -list(VHost, Component) -> - mnesia:async_dirty( - fun () -> - case VHost of - '_' -> ok; - _ -> rabbit_vhost:assert(VHost) - end, - Match = #runtime_parameters{key = {VHost, Component, '_'}, - _ = '_'}, - [p(P) || #runtime_parameters{key = {_VHost, Comp, _Name}} = P <- - mnesia:match_object(?TABLE, Match, read), - Comp =/= <<"policy">> orelse Component =:= <<"policy">>] - end). - -list_formatted(VHost) -> - [pset(value, format(pget(value, P)), P) || P <- list(VHost)]. - -lookup(VHost, Component, Name) -> - case lookup0({VHost, Component, Name}, rabbit_misc:const(not_found)) of - not_found -> not_found; - Params -> p(Params) - end. - -value(VHost, Comp, Name) -> value0({VHost, Comp, Name}). -value(VHost, Comp, Name, Def) -> value0({VHost, Comp, Name}, Def). - -value_global(Key) -> value0(Key). -value_global(Key, Default) -> value0(Key, Default). - -value0(Key) -> - case lookup0(Key, rabbit_misc:const(not_found)) of - not_found -> not_found; - Params -> Params#runtime_parameters.value - end. - -value0(Key, Default) -> - Params = lookup0(Key, fun () -> lookup_missing(Key, Default) end), - Params#runtime_parameters.value. - -lookup0(Key, DefaultFun) -> - case mnesia:dirty_read(?TABLE, Key) of - [] -> DefaultFun(); - [R] -> R - end. - -lookup_missing(Key, Default) -> - rabbit_misc:execute_mnesia_transaction( - fun () -> - case mnesia:read(?TABLE, Key, read) of - [] -> Record = c(Key, Default), - mnesia:write(?TABLE, Record, write), - Record; - [R] -> R - end - end). - -c(Key, Default) -> - #runtime_parameters{key = Key, - value = Default}. - -p(#runtime_parameters{key = {VHost, Component, Name}, value = Value}) -> - [{vhost, VHost}, - {component, Component}, - {name, Name}, - {value, Value}]. - -info_keys() -> [component, name, value]. - -%%--------------------------------------------------------------------------- - -lookup_component(Component) -> - case rabbit_registry:lookup_module( - runtime_parameter, list_to_atom(binary_to_list(Component))) of - {error, not_found} -> {errors, - [{"component ~s not found", [Component]}]}; - {ok, Module} -> {ok, Module} - end. - -format(Term) -> - {ok, JSON} = rabbit_misc:json_encode(rabbit_misc:term_to_json(Term)), - list_to_binary(JSON). - -flatten_errors(L) -> - case [{F, A} || I <- lists:flatten([L]), {error, F, A} <- [I]] of - [] -> ok; - E -> {errors, E} - end. diff --git a/src/rabbit_sasl_report_file_h.erl b/src/rabbit_sasl_report_file_h.erl deleted file mode 100644 index 2dd16702..00000000 --- a/src/rabbit_sasl_report_file_h.erl +++ /dev/null @@ -1,100 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_sasl_report_file_h). --include("rabbit.hrl"). - --behaviour(gen_event). - --export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, - code_change/3]). - --import(rabbit_error_logger_file_h, [safe_handle_event/3]). - -%% rabbit_sasl_report_file_h is a wrapper around the sasl_report_file_h -%% module because the original's init/1 does not match properly -%% with the result of closing the old handler when swapping handlers. -%% The first init/1 additionally allows for simple log rotation -%% when the suffix is not the empty string. -%% The original init/1 also opened the file in 'write' mode, thus -%% overwriting old logs. To remedy this, init/1 from -%% lib/sasl/src/sasl_report_file_h.erl from R14B3 was copied as -%% init_file/1 and changed so that it opens the file in 'append' mode. - -%% Used only when swapping handlers and performing -%% log rotation -init({{File, Suffix}, []}) -> - case rabbit_file:append_file(File, Suffix) of - ok -> file:delete(File), - ok; - {error, Error} -> - rabbit_log:error("Failed to append contents of " - "sasl log file '~s' to '~s':~n~p~n", - [File, [File, Suffix], Error]) - end, - init(File); -%% Used only when swapping handlers and the original handler -%% failed to terminate or was never installed -init({{File, _}, error}) -> - init(File); -%% Used only when swapping handlers without -%% doing any log rotation -init({File, []}) -> - init(File); -init({File, _Type} = FileInfo) -> - rabbit_file:ensure_parent_dirs_exist(File), - init_file(FileInfo); -init(File) -> - rabbit_file:ensure_parent_dirs_exist(File), - init_file({File, sasl_error_logger_type()}). - -init_file({File, Type}) -> - process_flag(trap_exit, true), - case file:open(File, [append]) of - {ok,Fd} -> {ok, {Fd, File, Type}}; - Error -> Error - end. - -handle_event(Event, State) -> - safe_handle_event(fun handle_event0/2, Event, State). - -handle_event0(Event, State) -> - sasl_report_file_h:handle_event( - truncate:log_event(Event, ?LOG_TRUNC), State). - -handle_info(Info, State) -> - sasl_report_file_h:handle_info(Info, State). - -handle_call(Call, State) -> - sasl_report_file_h:handle_call(Call, State). - -terminate(Reason, State) -> - sasl_report_file_h:terminate(Reason, State). - -code_change(_OldVsn, State, _Extra) -> - %% There is no sasl_report_file_h:code_change/3 - {ok, State}. - -%%---------------------------------------------------------------------- - -sasl_error_logger_type() -> - case application:get_env(sasl, errlog_type) of - {ok, error} -> error; - {ok, progress} -> progress; - {ok, all} -> all; - {ok, Bad} -> throw({error, {wrong_errlog_type, Bad}}); - _ -> all - end. diff --git a/src/rabbit_ssl.erl b/src/rabbit_ssl.erl deleted file mode 100644 index bd5dcf07..00000000 --- a/src/rabbit_ssl.erl +++ /dev/null @@ -1,298 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_ssl). - --include("rabbit.hrl"). - --include_lib("public_key/include/public_key.hrl"). - --export([peer_cert_issuer/1, peer_cert_subject/1, peer_cert_validity/1]). --export([peer_cert_subject_items/2, peer_cert_auth_name/1]). - -%%-------------------------------------------------------------------------- - --ifdef(use_specs). - --export_type([certificate/0]). - --type(certificate() :: binary()). - --spec(peer_cert_issuer/1 :: (certificate()) -> string()). --spec(peer_cert_subject/1 :: (certificate()) -> string()). --spec(peer_cert_validity/1 :: (certificate()) -> string()). --spec(peer_cert_subject_items/2 :: - (certificate(), tuple()) -> [string()] | 'not_found'). --spec(peer_cert_auth_name/1 :: - (certificate()) -> binary() | 'not_found' | 'unsafe'). - --endif. - -%%-------------------------------------------------------------------------- -%% High-level functions used by reader -%%-------------------------------------------------------------------------- - -%% Return a string describing the certificate's issuer. -peer_cert_issuer(Cert) -> - cert_info(fun(#'OTPCertificate' { - tbsCertificate = #'OTPTBSCertificate' { - issuer = Issuer }}) -> - format_rdn_sequence(Issuer) - end, Cert). - -%% Return a string describing the certificate's subject, as per RFC4514. -peer_cert_subject(Cert) -> - cert_info(fun(#'OTPCertificate' { - tbsCertificate = #'OTPTBSCertificate' { - subject = Subject }}) -> - format_rdn_sequence(Subject) - end, Cert). - -%% Return the parts of the certificate's subject. -peer_cert_subject_items(Cert, Type) -> - cert_info(fun(#'OTPCertificate' { - tbsCertificate = #'OTPTBSCertificate' { - subject = Subject }}) -> - find_by_type(Type, Subject) - end, Cert). - -%% Return a string describing the certificate's validity. -peer_cert_validity(Cert) -> - cert_info(fun(#'OTPCertificate' { - tbsCertificate = #'OTPTBSCertificate' { - validity = {'Validity', Start, End} }}) -> - rabbit_misc:format("~s - ~s", [format_asn1_value(Start), - format_asn1_value(End)]) - end, Cert). - -%% Extract a username from the certificate -peer_cert_auth_name(Cert) -> - {ok, Mode} = application:get_env(rabbit, ssl_cert_login_from), - peer_cert_auth_name(Mode, Cert). - -peer_cert_auth_name(distinguished_name, Cert) -> - case auth_config_sane() of - true -> iolist_to_binary(peer_cert_subject(Cert)); - false -> unsafe - end; - -peer_cert_auth_name(common_name, Cert) -> - %% If there is more than one CN then we join them with "," in a - %% vaguely DN-like way. But this is more just so we do something - %% more intelligent than crashing, if you actually want to escape - %% things properly etc, use DN mode. - case auth_config_sane() of - true -> case peer_cert_subject_items(Cert, ?'id-at-commonName') of - not_found -> not_found; - CNs -> list_to_binary(string:join(CNs, ",")) - end; - false -> unsafe - end. - -auth_config_sane() -> - {ok, Opts} = application:get_env(rabbit, ssl_options), - case proplists:get_value(verify, Opts) of - verify_peer -> true; - V -> rabbit_log:warning("SSL certificate authentication " - "disabled, verify=~p~n", [V]), - false - end. - -%%-------------------------------------------------------------------------- - -cert_info(F, Cert) -> - F(case public_key:pkix_decode_cert(Cert, otp) of - {ok, DecCert} -> DecCert; %%pre R14B - DecCert -> DecCert %%R14B onwards - end). - -find_by_type(Type, {rdnSequence, RDNs}) -> - case [V || #'AttributeTypeAndValue'{type = T, value = V} - <- lists:flatten(RDNs), - T == Type] of - [] -> not_found; - L -> [format_asn1_value(V) || V <- L] - end. - -%%-------------------------------------------------------------------------- -%% Formatting functions -%%-------------------------------------------------------------------------- - -%% Format and rdnSequence as a RFC4514 subject string. -format_rdn_sequence({rdnSequence, Seq}) -> - string:join(lists:reverse([format_complex_rdn(RDN) || RDN <- Seq]), ","). - -%% Format an RDN set. -format_complex_rdn(RDNs) -> - string:join([format_rdn(RDN) || RDN <- RDNs], "+"). - -%% Format an RDN. If the type name is unknown, use the dotted decimal -%% representation. See RFC4514, section 2.3. -format_rdn(#'AttributeTypeAndValue'{type = T, value = V}) -> - FV = escape_rdn_value(format_asn1_value(V)), - Fmts = [{?'id-at-surname' , "SN"}, - {?'id-at-givenName' , "GIVENNAME"}, - {?'id-at-initials' , "INITIALS"}, - {?'id-at-generationQualifier' , "GENERATIONQUALIFIER"}, - {?'id-at-commonName' , "CN"}, - {?'id-at-localityName' , "L"}, - {?'id-at-stateOrProvinceName' , "ST"}, - {?'id-at-organizationName' , "O"}, - {?'id-at-organizationalUnitName' , "OU"}, - {?'id-at-title' , "TITLE"}, - {?'id-at-countryName' , "C"}, - {?'id-at-serialNumber' , "SERIALNUMBER"}, - {?'id-at-pseudonym' , "PSEUDONYM"}, - {?'id-domainComponent' , "DC"}, - {?'id-emailAddress' , "EMAILADDRESS"}, - {?'street-address' , "STREET"}, - {{0,9,2342,19200300,100,1,1} , "UID"}], %% Not in public_key.hrl - case proplists:lookup(T, Fmts) of - {_, Fmt} -> - rabbit_misc:format(Fmt ++ "=~s", [FV]); - none when is_tuple(T) -> - TypeL = [rabbit_misc:format("~w", [X]) || X <- tuple_to_list(T)], - rabbit_misc:format("~s=~s", [string:join(TypeL, "."), FV]); - none -> - rabbit_misc:format("~p=~s", [T, FV]) - end. - -%% Escape a string as per RFC4514. -escape_rdn_value(V) -> - escape_rdn_value(V, start). - -escape_rdn_value([], _) -> - []; -escape_rdn_value([C | S], start) when C =:= $ ; C =:= $# -> - [$\\, C | escape_rdn_value(S, middle)]; -escape_rdn_value(S, start) -> - escape_rdn_value(S, middle); -escape_rdn_value([$ ], middle) -> - [$\\, $ ]; -escape_rdn_value([C | S], middle) when C =:= $"; C =:= $+; C =:= $,; C =:= $;; - C =:= $<; C =:= $>; C =:= $\\ -> - [$\\, C | escape_rdn_value(S, middle)]; -escape_rdn_value([C | S], middle) when C < 32 ; C >= 126 -> - %% Of ASCII characters only U+0000 needs escaping, but for display - %% purposes it's handy to escape all non-printable chars. All non-ASCII - %% characters get converted to UTF-8 sequences and then escaped. We've - %% already got a UTF-8 sequence here, so just escape it. - rabbit_misc:format("\\~2.16.0B", [C]) ++ escape_rdn_value(S, middle); -escape_rdn_value([C | S], middle) -> - [C | escape_rdn_value(S, middle)]. - -%% Get the string representation of an OTPCertificate field. -format_asn1_value({ST, S}) when ST =:= teletexString; ST =:= printableString; - ST =:= universalString; ST =:= utf8String; - ST =:= bmpString -> - format_directory_string(ST, S); -format_asn1_value({utcTime, [Y1, Y2, M1, M2, D1, D2, H1, H2, - Min1, Min2, S1, S2, $Z]}) -> - rabbit_misc:format("20~c~c-~c~c-~c~cT~c~c:~c~c:~c~cZ", - [Y1, Y2, M1, M2, D1, D2, H1, H2, Min1, Min2, S1, S2]); -%% We appear to get an untagged value back for an ia5string -%% (e.g. domainComponent). -format_asn1_value(V) when is_list(V) -> - V; -format_asn1_value(V) when is_binary(V) -> - %% OTP does not decode some values when combined with an unknown - %% type. That's probably wrong, so as a last ditch effort let's - %% try manually decoding. 'DirectoryString' is semi-arbitrary - - %% but it is the type which covers the various string types we - %% handle below. - try - {ST, S} = public_key:der_decode('DirectoryString', V), - format_directory_string(ST, S) - catch _:_ -> - rabbit_misc:format("~p", [V]) - end; -format_asn1_value(V) -> - rabbit_misc:format("~p", [V]). - -%% DirectoryString { INTEGER : maxSize } ::= CHOICE { -%% teletexString TeletexString (SIZE (1..maxSize)), -%% printableString PrintableString (SIZE (1..maxSize)), -%% bmpString BMPString (SIZE (1..maxSize)), -%% universalString UniversalString (SIZE (1..maxSize)), -%% uTF8String UTF8String (SIZE (1..maxSize)) } -%% -%% Precise definitions of printable / teletexString are hard to come -%% by. This is what I reconstructed: -%% -%% printableString: -%% "intended to represent the limited character sets available to -%% mainframe input terminals" -%% A-Z a-z 0-9 ' ( ) + , - . / : = ? [space] -%% http://msdn.microsoft.com/en-us/library/bb540814(v=vs.85).aspx -%% -%% teletexString: -%% "a sizable volume of software in the world treats TeletexString -%% (T61String) as a simple 8-bit string with mostly Windows Latin 1 -%% (superset of iso-8859-1) encoding" -%% http://www.mail-archive.com/asn1@asn1.org/msg00460.html -%% -%% (However according to that link X.680 actually defines -%% TeletexString in some much more involved and crazy way. I suggest -%% we treat it as ISO-8859-1 since Erlang does not support Windows -%% Latin 1). -%% -%% bmpString: -%% UCS-2 according to RFC 3641. Hence cannot represent Unicode -%% characters above 65535 (outside the "Basic Multilingual Plane"). -%% -%% universalString: -%% UCS-4 according to RFC 3641. -%% -%% utf8String: -%% UTF-8 according to RFC 3641. -%% -%% Within Rabbit we assume UTF-8 encoding. Since printableString is a -%% subset of ASCII it is also a subset of UTF-8. The others need -%% converting. Fortunately since the Erlang SSL library does the -%% decoding for us (albeit into a weird format, see below), we just -%% need to handle encoding into UTF-8. Note also that utf8Strings come -%% back as binary. -%% -%% Note for testing: the default Ubuntu configuration for openssl will -%% only create printableString or teletexString types no matter what -%% you do. Edit string_mask in the [req] section of -%% /etc/ssl/openssl.cnf to change this (see comments there). You -%% probably also need to set utf8 = yes to get it to accept UTF-8 on -%% the command line. Also note I could not get openssl to generate a -%% universalString. - -format_directory_string(printableString, S) -> S; -format_directory_string(teletexString, S) -> utf8_list_from(S); -format_directory_string(bmpString, S) -> utf8_list_from(S); -format_directory_string(universalString, S) -> utf8_list_from(S); -format_directory_string(utf8String, S) -> binary_to_list(S). - -utf8_list_from(S) -> - binary_to_list( - unicode:characters_to_binary(flatten_ssl_list(S), utf32, utf8)). - -%% The Erlang SSL implementation invents its own representation for -%% non-ascii strings - looking like [97,{0,0,3,187}] (that's LATIN -%% SMALL LETTER A followed by GREEK SMALL LETTER LAMDA). We convert -%% this into a list of unicode characters, which we can tell -%% unicode:characters_to_binary is utf32. - -flatten_ssl_list(L) -> [flatten_ssl_list_item(I) || I <- L]. - -flatten_ssl_list_item({A, B, C, D}) -> - A * (1 bsl 24) + B * (1 bsl 16) + C * (1 bsl 8) + D; -flatten_ssl_list_item(N) when is_number (N) -> - N. diff --git a/src/rabbit_sup.erl b/src/rabbit_sup.erl deleted file mode 100644 index c90bb94c..00000000 --- a/src/rabbit_sup.erl +++ /dev/null @@ -1,102 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_sup). - --behaviour(supervisor). - --export([start_link/0, start_child/1, start_child/2, start_child/3, - start_supervisor_child/1, start_supervisor_child/2, - start_supervisor_child/3, - start_restartable_child/1, start_restartable_child/2, - start_delayed_restartable_child/1, start_delayed_restartable_child/2, - stop_child/1]). - --export([init/1]). - --include("rabbit.hrl"). - --define(SERVER, ?MODULE). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(start_child/1 :: (atom()) -> 'ok'). --spec(start_child/2 :: (atom(), [any()]) -> 'ok'). --spec(start_child/3 :: (atom(), atom(), [any()]) -> 'ok'). --spec(start_supervisor_child/1 :: (atom()) -> 'ok'). --spec(start_supervisor_child/2 :: (atom(), [any()]) -> 'ok'). --spec(start_supervisor_child/3 :: (atom(), atom(), [any()]) -> 'ok'). --spec(start_restartable_child/1 :: (atom()) -> 'ok'). --spec(start_restartable_child/2 :: (atom(), [any()]) -> 'ok'). --spec(start_delayed_restartable_child/1 :: (atom()) -> 'ok'). --spec(start_delayed_restartable_child/2 :: (atom(), [any()]) -> 'ok'). --spec(stop_child/1 :: (atom()) -> rabbit_types:ok_or_error(any())). - --endif. - -%%---------------------------------------------------------------------------- - -start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). - -start_child(Mod) -> start_child(Mod, []). - -start_child(Mod, Args) -> start_child(Mod, Mod, Args). - -start_child(ChildId, Mod, Args) -> - child_reply(supervisor:start_child( - ?SERVER, - {ChildId, {Mod, start_link, Args}, - transient, ?MAX_WAIT, worker, [Mod]})). - -start_supervisor_child(Mod) -> start_supervisor_child(Mod, []). - -start_supervisor_child(Mod, Args) -> start_supervisor_child(Mod, Mod, Args). - -start_supervisor_child(ChildId, Mod, Args) -> - child_reply(supervisor:start_child( - ?SERVER, - {ChildId, {Mod, start_link, Args}, - transient, infinity, supervisor, [Mod]})). - -start_restartable_child(M) -> start_restartable_child(M, [], false). -start_restartable_child(M, A) -> start_restartable_child(M, A, false). -start_delayed_restartable_child(M) -> start_restartable_child(M, [], true). -start_delayed_restartable_child(M, A) -> start_restartable_child(M, A, true). - -start_restartable_child(Mod, Args, Delay) -> - Name = list_to_atom(atom_to_list(Mod) ++ "_sup"), - child_reply(supervisor:start_child( - ?SERVER, - {Name, {rabbit_restartable_sup, start_link, - [Name, {Mod, start_link, Args}, Delay]}, - transient, infinity, supervisor, [rabbit_restartable_sup]})). - -stop_child(ChildId) -> - case supervisor:terminate_child(?SERVER, ChildId) of - ok -> supervisor:delete_child(?SERVER, ChildId); - E -> E - end. - -init([]) -> {ok, {{one_for_all, 0, 1}, []}}. - - -%%---------------------------------------------------------------------------- - -child_reply({ok, _}) -> ok; -child_reply(X) -> X. diff --git a/src/rabbit_table.erl b/src/rabbit_table.erl deleted file mode 100644 index 41bf9585..00000000 --- a/src/rabbit_table.erl +++ /dev/null @@ -1,322 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_table). - --export([create/0, create_local_copy/1, wait_for_replicated/0, wait/1, - force_load/0, is_present/0, is_empty/0, needs_default_data/0, - check_schema_integrity/0, clear_ram_only_tables/0]). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(create/0 :: () -> 'ok'). --spec(create_local_copy/1 :: ('disc' | 'ram') -> 'ok'). --spec(wait_for_replicated/0 :: () -> 'ok'). --spec(wait/1 :: ([atom()]) -> 'ok'). --spec(force_load/0 :: () -> 'ok'). --spec(is_present/0 :: () -> boolean()). --spec(is_empty/0 :: () -> boolean()). --spec(needs_default_data/0 :: () -> boolean()). --spec(check_schema_integrity/0 :: () -> rabbit_types:ok_or_error(any())). --spec(clear_ram_only_tables/0 :: () -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- -%% Main interface -%%---------------------------------------------------------------------------- - -create() -> - lists:foreach(fun ({Tab, TabDef}) -> - TabDef1 = proplists:delete(match, TabDef), - case mnesia:create_table(Tab, TabDef1) of - {atomic, ok} -> ok; - {aborted, Reason} -> - throw({error, {table_creation_failed, - Tab, TabDef1, Reason}}) - end - end, definitions()), - ok. - -%% The sequence in which we delete the schema and then the other -%% tables is important: if we delete the schema first when moving to -%% RAM mnesia will loudly complain since it doesn't make much sense to -%% do that. But when moving to disc, we need to move the schema first. -create_local_copy(disc) -> - create_local_copy(schema, disc_copies), - create_local_copies(disc); -create_local_copy(ram) -> - create_local_copies(ram), - create_local_copy(schema, ram_copies). - -wait_for_replicated() -> - wait([Tab || {Tab, TabDef} <- definitions(), - not lists:member({local_content, true}, TabDef)]). - -wait(TableNames) -> - %% We might be in ctl here for offline ops, in which case we can't - %% get_env() for the rabbit app. - Timeout = case application:get_env(rabbit, mnesia_table_loading_timeout) of - {ok, T} -> T; - undefined -> 30000 - end, - case mnesia:wait_for_tables(TableNames, Timeout) of - ok -> - ok; - {timeout, BadTabs} -> - throw({error, {timeout_waiting_for_tables, BadTabs}}); - {error, Reason} -> - throw({error, {failed_waiting_for_tables, Reason}}) - end. - -force_load() -> [mnesia:force_load_table(T) || T <- names()], ok. - -is_present() -> names() -- mnesia:system_info(tables) =:= []. - -is_empty() -> is_empty(names()). -needs_default_data() -> is_empty([rabbit_user, rabbit_user_permission, - rabbit_vhost]). - -is_empty(Names) -> - lists:all(fun (Tab) -> mnesia:dirty_first(Tab) == '$end_of_table' end, - Names). - -check_schema_integrity() -> - Tables = mnesia:system_info(tables), - case check(fun (Tab, TabDef) -> - case lists:member(Tab, Tables) of - false -> {error, {table_missing, Tab}}; - true -> check_attributes(Tab, TabDef) - end - end) of - ok -> ok = wait(names()), - check(fun check_content/2); - Other -> Other - end. - -clear_ram_only_tables() -> - Node = node(), - lists:foreach( - fun (TabName) -> - case lists:member(Node, mnesia:table_info(TabName, ram_copies)) of - true -> {atomic, ok} = mnesia:clear_table(TabName); - false -> ok - end - end, names()), - ok. - -%%-------------------------------------------------------------------- -%% Internal helpers -%%-------------------------------------------------------------------- - -create_local_copies(Type) -> - lists:foreach( - fun ({Tab, TabDef}) -> - HasDiscCopies = has_copy_type(TabDef, disc_copies), - HasDiscOnlyCopies = has_copy_type(TabDef, disc_only_copies), - LocalTab = proplists:get_bool(local_content, TabDef), - StorageType = - if - Type =:= disc orelse LocalTab -> - if - HasDiscCopies -> disc_copies; - HasDiscOnlyCopies -> disc_only_copies; - true -> ram_copies - end; - Type =:= ram -> - ram_copies - end, - ok = create_local_copy(Tab, StorageType) - end, definitions(Type)), - ok. - -create_local_copy(Tab, Type) -> - StorageType = mnesia:table_info(Tab, storage_type), - {atomic, ok} = - if - StorageType == unknown -> - mnesia:add_table_copy(Tab, node(), Type); - StorageType /= Type -> - mnesia:change_table_copy_type(Tab, node(), Type); - true -> {atomic, ok} - end, - ok. - -has_copy_type(TabDef, DiscType) -> - lists:member(node(), proplists:get_value(DiscType, TabDef, [])). - -check_attributes(Tab, TabDef) -> - {_, ExpAttrs} = proplists:lookup(attributes, TabDef), - case mnesia:table_info(Tab, attributes) of - ExpAttrs -> ok; - Attrs -> {error, {table_attributes_mismatch, Tab, ExpAttrs, Attrs}} - end. - -check_content(Tab, TabDef) -> - {_, Match} = proplists:lookup(match, TabDef), - case mnesia:dirty_first(Tab) of - '$end_of_table' -> - ok; - Key -> - ObjList = mnesia:dirty_read(Tab, Key), - MatchComp = ets:match_spec_compile([{Match, [], ['$_']}]), - case ets:match_spec_run(ObjList, MatchComp) of - ObjList -> ok; - _ -> {error, {table_content_invalid, Tab, Match, ObjList}} - end - end. - -check(Fun) -> - case [Error || {Tab, TabDef} <- definitions(), - case Fun(Tab, TabDef) of - ok -> Error = none, false; - {error, Error} -> true - end] of - [] -> ok; - Errors -> {error, Errors} - end. - -%%-------------------------------------------------------------------- -%% Table definitions -%%-------------------------------------------------------------------- - -names() -> [Tab || {Tab, _} <- definitions()]. - -%% The tables aren't supposed to be on disk on a ram node -definitions(disc) -> - definitions(); -definitions(ram) -> - [{Tab, [{disc_copies, []}, {ram_copies, [node()]} | - proplists:delete( - ram_copies, proplists:delete(disc_copies, TabDef))]} || - {Tab, TabDef} <- definitions()]. - -definitions() -> - [{rabbit_user, - [{record_name, internal_user}, - {attributes, record_info(fields, internal_user)}, - {disc_copies, [node()]}, - {match, #internal_user{_='_'}}]}, - {rabbit_user_permission, - [{record_name, user_permission}, - {attributes, record_info(fields, user_permission)}, - {disc_copies, [node()]}, - {match, #user_permission{user_vhost = #user_vhost{_='_'}, - permission = #permission{_='_'}, - _='_'}}]}, - {rabbit_vhost, - [{record_name, vhost}, - {attributes, record_info(fields, vhost)}, - {disc_copies, [node()]}, - {match, #vhost{_='_'}}]}, - {rabbit_listener, - [{record_name, listener}, - {attributes, record_info(fields, listener)}, - {type, bag}, - {match, #listener{_='_'}}]}, - {rabbit_durable_route, - [{record_name, route}, - {attributes, record_info(fields, route)}, - {disc_copies, [node()]}, - {match, #route{binding = binding_match(), _='_'}}]}, - {rabbit_semi_durable_route, - [{record_name, route}, - {attributes, record_info(fields, route)}, - {type, ordered_set}, - {match, #route{binding = binding_match(), _='_'}}]}, - {rabbit_route, - [{record_name, route}, - {attributes, record_info(fields, route)}, - {type, ordered_set}, - {match, #route{binding = binding_match(), _='_'}}]}, - {rabbit_reverse_route, - [{record_name, reverse_route}, - {attributes, record_info(fields, reverse_route)}, - {type, ordered_set}, - {match, #reverse_route{reverse_binding = reverse_binding_match(), - _='_'}}]}, - {rabbit_topic_trie_node, - [{record_name, topic_trie_node}, - {attributes, record_info(fields, topic_trie_node)}, - {type, ordered_set}, - {match, #topic_trie_node{trie_node = trie_node_match(), _='_'}}]}, - {rabbit_topic_trie_edge, - [{record_name, topic_trie_edge}, - {attributes, record_info(fields, topic_trie_edge)}, - {type, ordered_set}, - {match, #topic_trie_edge{trie_edge = trie_edge_match(), _='_'}}]}, - {rabbit_topic_trie_binding, - [{record_name, topic_trie_binding}, - {attributes, record_info(fields, topic_trie_binding)}, - {type, ordered_set}, - {match, #topic_trie_binding{trie_binding = trie_binding_match(), - _='_'}}]}, - {rabbit_durable_exchange, - [{record_name, exchange}, - {attributes, record_info(fields, exchange)}, - {disc_copies, [node()]}, - {match, #exchange{name = exchange_name_match(), _='_'}}]}, - {rabbit_exchange, - [{record_name, exchange}, - {attributes, record_info(fields, exchange)}, - {match, #exchange{name = exchange_name_match(), _='_'}}]}, - {rabbit_exchange_serial, - [{record_name, exchange_serial}, - {attributes, record_info(fields, exchange_serial)}, - {match, #exchange_serial{name = exchange_name_match(), _='_'}}]}, - {rabbit_runtime_parameters, - [{record_name, runtime_parameters}, - {attributes, record_info(fields, runtime_parameters)}, - {disc_copies, [node()]}, - {match, #runtime_parameters{_='_'}}]}, - {rabbit_durable_queue, - [{record_name, amqqueue}, - {attributes, record_info(fields, amqqueue)}, - {disc_copies, [node()]}, - {match, #amqqueue{name = queue_name_match(), _='_'}}]}, - {rabbit_queue, - [{record_name, amqqueue}, - {attributes, record_info(fields, amqqueue)}, - {match, #amqqueue{name = queue_name_match(), _='_'}}]}] - ++ gm:table_definitions() - ++ mirrored_supervisor:table_definitions(). - -binding_match() -> - #binding{source = exchange_name_match(), - destination = binding_destination_match(), - _='_'}. -reverse_binding_match() -> - #reverse_binding{destination = binding_destination_match(), - source = exchange_name_match(), - _='_'}. -binding_destination_match() -> - resource_match('_'). -trie_node_match() -> - #trie_node{ exchange_name = exchange_name_match(), _='_'}. -trie_edge_match() -> - #trie_edge{ exchange_name = exchange_name_match(), _='_'}. -trie_binding_match() -> - #trie_binding{exchange_name = exchange_name_match(), _='_'}. -exchange_name_match() -> - resource_match(exchange). -queue_name_match() -> - resource_match(queue). -resource_match(Kind) -> - #resource{kind = Kind, _='_'}. diff --git a/src/rabbit_trace.erl b/src/rabbit_trace.erl deleted file mode 100644 index dbc2856d..00000000 --- a/src/rabbit_trace.erl +++ /dev/null @@ -1,135 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_trace). - --export([init/1, enabled/1, tap_in/5, tap_out/5, start/1, stop/1]). - --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - --define(TRACE_VHOSTS, trace_vhosts). --define(XNAME, <<"amq.rabbitmq.trace">>). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(state() :: rabbit_types:exchange() | 'none'). - --spec(init/1 :: (rabbit_types:vhost()) -> state()). --spec(enabled/1 :: (rabbit_types:vhost()) -> boolean()). --spec(tap_in/5 :: (rabbit_types:basic_message(), binary(), - rabbit_channel:channel_number(), - rabbit_types:username(), state()) -> 'ok'). --spec(tap_out/5 :: (rabbit_amqqueue:qmsg(), binary(), - rabbit_channel:channel_number(), - rabbit_types:username(), state()) -> 'ok'). - --spec(start/1 :: (rabbit_types:vhost()) -> 'ok'). --spec(stop/1 :: (rabbit_types:vhost()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - -init(VHost) -> - case enabled(VHost) of - false -> none; - true -> {ok, X} = rabbit_exchange:lookup( - rabbit_misc:r(VHost, exchange, ?XNAME)), - X - end. - -enabled(VHost) -> - {ok, VHosts} = application:get_env(rabbit, ?TRACE_VHOSTS), - lists:member(VHost, VHosts). - -tap_in(_Msg, _ConnName, _ChannelNum, _Username, none) -> ok; -tap_in(Msg = #basic_message{exchange_name = #resource{name = XName, - virtual_host = VHost}}, - ConnName, ChannelNum, Username, TraceX) -> - trace(TraceX, Msg, <<"publish">>, XName, - [{<<"vhost">>, longstr, VHost}, - {<<"connection">>, longstr, ConnName}, - {<<"channel">>, signedint, ChannelNum}, - {<<"user">>, longstr, Username}]). - -tap_out(_Msg, _ConnName, _ChannelNum, _Username, none) -> ok; -tap_out({#resource{name = QName, virtual_host = VHost}, - _QPid, _QMsgId, Redelivered, Msg}, - ConnName, ChannelNum, Username, TraceX) -> - RedeliveredNum = case Redelivered of true -> 1; false -> 0 end, - trace(TraceX, Msg, <<"deliver">>, QName, - [{<<"redelivered">>, signedint, RedeliveredNum}, - {<<"vhost">>, longstr, VHost}, - {<<"connection">>, longstr, ConnName}, - {<<"channel">>, signedint, ChannelNum}, - {<<"user">>, longstr, Username}]). - -%%---------------------------------------------------------------------------- - -start(VHost) -> - rabbit_log:info("Enabling tracing for vhost '~s'~n", [VHost]), - update_config(fun (VHosts) -> [VHost | VHosts -- [VHost]] end). - -stop(VHost) -> - rabbit_log:info("Disabling tracing for vhost '~s'~n", [VHost]), - update_config(fun (VHosts) -> VHosts -- [VHost] end). - -update_config(Fun) -> - {ok, VHosts0} = application:get_env(rabbit, ?TRACE_VHOSTS), - VHosts = Fun(VHosts0), - application:set_env(rabbit, ?TRACE_VHOSTS, VHosts), - rabbit_channel:refresh_config_local(), - ok. - -%%---------------------------------------------------------------------------- - -trace(#exchange{name = Name}, #basic_message{exchange_name = Name}, - _RKPrefix, _RKSuffix, _Extra) -> - ok; -trace(X, Msg = #basic_message{content = #content{payload_fragments_rev = PFR}}, - RKPrefix, RKSuffix, Extra) -> - {ok, _} = rabbit_basic:publish( - X, <<RKPrefix/binary, ".", RKSuffix/binary>>, - #'P_basic'{headers = msg_to_table(Msg) ++ Extra}, PFR), - ok. - -msg_to_table(#basic_message{exchange_name = #resource{name = XName}, - routing_keys = RoutingKeys, - content = Content}) -> - #content{properties = Props} = - rabbit_binary_parser:ensure_content_decoded(Content), - {PropsTable, _Ix} = - lists:foldl(fun (K, {L, Ix}) -> - V = element(Ix, Props), - NewL = case V of - undefined -> L; - _ -> [{a2b(K), type(V), V} | L] - end, - {NewL, Ix + 1} - end, {[], 2}, record_info(fields, 'P_basic')), - [{<<"exchange_name">>, longstr, XName}, - {<<"routing_keys">>, array, [{longstr, K} || K <- RoutingKeys]}, - {<<"properties">>, table, PropsTable}, - {<<"node">>, longstr, a2b(node())}]. - -a2b(A) -> list_to_binary(atom_to_list(A)). - -type(V) when is_list(V) -> table; -type(V) when is_integer(V) -> signedint; -type(_V) -> longstr. diff --git a/src/rabbit_types.erl b/src/rabbit_types.erl deleted file mode 100644 index 039568df..00000000 --- a/src/rabbit_types.erl +++ /dev/null @@ -1,167 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_types). - --include("rabbit.hrl"). - --ifdef(use_specs). - --export_type([maybe/1, info/0, infos/0, info_key/0, info_keys/0, - message/0, msg_id/0, basic_message/0, - delivery/0, content/0, decoded_content/0, undecoded_content/0, - unencoded_content/0, encoded_content/0, message_properties/0, - vhost/0, ctag/0, amqp_error/0, r/1, r2/2, r3/3, listener/0, - binding/0, binding_source/0, binding_destination/0, - amqqueue/0, exchange/0, - connection/0, protocol/0, auth_user/0, user/0, internal_user/0, - username/0, password/0, password_hash/0, - ok/1, error/1, ok_or_error/1, ok_or_error2/2, ok_pid_or_error/0, - channel_exit/0, connection_exit/0, mfargs/0, proc_name/0, - proc_type_and_name/0]). - --type(maybe(T) :: T | 'none'). --type(vhost() :: binary()). --type(ctag() :: binary()). - -%% TODO: make this more precise by tying specific class_ids to -%% specific properties --type(undecoded_content() :: - #content{class_id :: rabbit_framing:amqp_class_id(), - properties :: 'none', - properties_bin :: binary(), - payload_fragments_rev :: [binary()]} | - #content{class_id :: rabbit_framing:amqp_class_id(), - properties :: rabbit_framing:amqp_property_record(), - properties_bin :: 'none', - payload_fragments_rev :: [binary()]}). --type(unencoded_content() :: undecoded_content()). --type(decoded_content() :: - #content{class_id :: rabbit_framing:amqp_class_id(), - properties :: rabbit_framing:amqp_property_record(), - properties_bin :: maybe(binary()), - payload_fragments_rev :: [binary()]}). --type(encoded_content() :: - #content{class_id :: rabbit_framing:amqp_class_id(), - properties :: maybe(rabbit_framing:amqp_property_record()), - properties_bin :: binary(), - payload_fragments_rev :: [binary()]}). --type(content() :: undecoded_content() | decoded_content()). --type(msg_id() :: rabbit_guid:guid()). --type(basic_message() :: - #basic_message{exchange_name :: rabbit_exchange:name(), - routing_keys :: [rabbit_router:routing_key()], - content :: content(), - id :: msg_id(), - is_persistent :: boolean()}). --type(message() :: basic_message()). --type(delivery() :: - #delivery{mandatory :: boolean(), - sender :: pid(), - message :: message()}). --type(message_properties() :: - #message_properties{expiry :: pos_integer() | 'undefined', - needs_confirming :: boolean()}). - --type(info_key() :: atom()). --type(info_keys() :: [info_key()]). - --type(info() :: {info_key(), any()}). --type(infos() :: [info()]). - --type(amqp_error() :: - #amqp_error{name :: rabbit_framing:amqp_exception(), - explanation :: string(), - method :: rabbit_framing:amqp_method_name()}). - --type(r(Kind) :: - r2(vhost(), Kind)). --type(r2(VirtualHost, Kind) :: - r3(VirtualHost, Kind, rabbit_misc:resource_name())). --type(r3(VirtualHost, Kind, Name) :: - #resource{virtual_host :: VirtualHost, - kind :: Kind, - name :: Name}). - --type(listener() :: - #listener{node :: node(), - protocol :: atom(), - host :: rabbit_networking:hostname(), - port :: rabbit_networking:ip_port()}). - --type(binding_source() :: rabbit_exchange:name()). --type(binding_destination() :: rabbit_amqqueue:name() | rabbit_exchange:name()). - --type(binding() :: - #binding{source :: rabbit_exchange:name(), - destination :: binding_destination(), - key :: rabbit_binding:key(), - args :: rabbit_framing:amqp_table()}). - --type(amqqueue() :: - #amqqueue{name :: rabbit_amqqueue:name(), - durable :: boolean(), - auto_delete :: boolean(), - exclusive_owner :: rabbit_types:maybe(pid()), - arguments :: rabbit_framing:amqp_table(), - pid :: rabbit_types:maybe(pid()), - slave_pids :: [pid()]}). - --type(exchange() :: - #exchange{name :: rabbit_exchange:name(), - type :: rabbit_exchange:type(), - durable :: boolean(), - auto_delete :: boolean(), - arguments :: rabbit_framing:amqp_table()}). - --type(connection() :: pid()). - --type(protocol() :: rabbit_framing:protocol()). - --type(auth_user() :: - #auth_user{username :: username(), - tags :: [atom()], - impl :: any()}). - --type(user() :: - #user{username :: username(), - tags :: [atom()], - authz_backends :: [{atom(), any()}]}). - --type(internal_user() :: - #internal_user{username :: username(), - password_hash :: password_hash(), - tags :: [atom()]}). - --type(username() :: binary()). --type(password() :: binary()). --type(password_hash() :: binary()). - --type(ok(A) :: {'ok', A}). --type(error(A) :: {'error', A}). --type(ok_or_error(A) :: 'ok' | error(A)). --type(ok_or_error2(A, B) :: ok(A) | error(B)). --type(ok_pid_or_error() :: ok_or_error2(pid(), any())). - --type(channel_exit() :: no_return()). --type(connection_exit() :: no_return()). - --type(mfargs() :: {atom(), atom(), [any()]}). - --type(proc_name() :: term()). --type(proc_type_and_name() :: {atom(), proc_name()}). - --endif. % use_specs diff --git a/src/rabbit_upgrade.erl b/src/rabbit_upgrade.erl deleted file mode 100644 index 72bf7855..00000000 --- a/src/rabbit_upgrade.erl +++ /dev/null @@ -1,284 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_upgrade). - --export([maybe_upgrade_mnesia/0, maybe_upgrade_local/0]). - --include("rabbit.hrl"). - --define(VERSION_FILENAME, "schema_version"). --define(LOCK_FILENAME, "schema_upgrade_lock"). - -%% ------------------------------------------------------------------- - --ifdef(use_specs). - --spec(maybe_upgrade_mnesia/0 :: () -> 'ok'). --spec(maybe_upgrade_local/0 :: () -> 'ok' | - 'version_not_available' | - 'starting_from_scratch'). - --endif. - -%% ------------------------------------------------------------------- - -%% The upgrade logic is quite involved, due to the existence of -%% clusters. -%% -%% Firstly, we have two different types of upgrades to do: Mnesia and -%% everythinq else. Mnesia upgrades must only be done by one node in -%% the cluster (we treat a non-clustered node as a single-node -%% cluster). This is the primary upgrader. The other upgrades need to -%% be done by all nodes. -%% -%% The primary upgrader has to start first (and do its Mnesia -%% upgrades). Secondary upgraders need to reset their Mnesia database -%% and then rejoin the cluster. They can't do the Mnesia upgrades as -%% well and then merge databases since the cookie for each table will -%% end up different and the merge will fail. -%% -%% This in turn means that we need to determine whether we are the -%% primary or secondary upgrader *before* Mnesia comes up. If we -%% didn't then the secondary upgrader would try to start Mnesia, and -%% either hang waiting for a node which is not yet up, or fail since -%% its schema differs from the other nodes in the cluster. -%% -%% Also, the primary upgrader needs to start Mnesia to do its -%% upgrades, but needs to forcibly load tables rather than wait for -%% them (in case it was not the last node to shut down, in which case -%% it would wait forever). -%% -%% This in turn means that maybe_upgrade_mnesia/0 has to be patched -%% into the boot process by prelaunch before the mnesia application is -%% started. By the time Mnesia is started the upgrades have happened -%% (on the primary), or Mnesia has been reset (on the secondary) and -%% rabbit_mnesia:init_db_unchecked/2 can then make the node rejoin the cluster -%% in the normal way. -%% -%% The non-mnesia upgrades are then triggered by -%% rabbit_mnesia:init_db_unchecked/2. Of course, it's possible for a given -%% upgrade process to only require Mnesia upgrades, or only require -%% non-Mnesia upgrades. In the latter case no Mnesia resets and -%% reclusterings occur. -%% -%% The primary upgrader needs to be a disc node. Ideally we would like -%% it to be the last disc node to shut down (since otherwise there's a -%% risk of data loss). On each node we therefore record the disc nodes -%% that were still running when we shut down. A disc node that knows -%% other nodes were up when it shut down, or a ram node, will refuse -%% to be the primary upgrader, and will thus not start when upgrades -%% are needed. -%% -%% However, this is racy if several nodes are shut down at once. Since -%% rabbit records the running nodes, and shuts down before mnesia, the -%% race manifests as all disc nodes thinking they are not the primary -%% upgrader. Therefore the user can remove the record of the last disc -%% node to shut down to get things going again. This may lose any -%% mnesia changes that happened after the node chosen as the primary -%% upgrader was shut down. - -%% ------------------------------------------------------------------- - -ensure_backup_taken() -> - case filelib:is_file(lock_filename()) of - false -> case filelib:is_dir(backup_dir()) of - false -> ok = take_backup(); - _ -> ok - end; - true -> throw({error, previous_upgrade_failed}) - end. - -take_backup() -> - BackupDir = backup_dir(), - case rabbit_mnesia:copy_db(BackupDir) of - ok -> info("upgrades: Mnesia dir backed up to ~p~n", - [BackupDir]); - {error, E} -> throw({could_not_back_up_mnesia_dir, E}) - end. - -ensure_backup_removed() -> - case filelib:is_dir(backup_dir()) of - true -> ok = remove_backup(); - _ -> ok - end. - -remove_backup() -> - ok = rabbit_file:recursive_delete([backup_dir()]), - info("upgrades: Mnesia backup removed~n", []). - -maybe_upgrade_mnesia() -> - AllNodes = rabbit_mnesia:cluster_nodes(all), - case rabbit_version:upgrades_required(mnesia) of - {error, starting_from_scratch} -> - ok; - {error, version_not_available} -> - case AllNodes of - [] -> die("Cluster upgrade needed but upgrading from " - "< 2.1.1.~nUnfortunately you will need to " - "rebuild the cluster.", []); - _ -> ok - end; - {error, _} = Err -> - throw(Err); - {ok, []} -> - ok; - {ok, Upgrades} -> - ensure_backup_taken(), - ok = case upgrade_mode(AllNodes) of - primary -> primary_upgrade(Upgrades, AllNodes); - secondary -> secondary_upgrade(AllNodes) - end - end. - -upgrade_mode(AllNodes) -> - case nodes_running(AllNodes) of - [] -> - AfterUs = rabbit_mnesia:cluster_nodes(running) -- [node()], - case {node_type_legacy(), AfterUs} of - {disc, []} -> - primary; - {disc, _} -> - Filename = rabbit_node_monitor:running_nodes_filename(), - die("Cluster upgrade needed but other disc nodes shut " - "down after this one.~nPlease first start the last " - "disc node to shut down.~n~nNote: if several disc " - "nodes were shut down simultaneously they may " - "all~nshow this message. In which case, remove " - "the lock file on one of them and~nstart that node. " - "The lock file on this node is:~n~n ~s ", [Filename]); - {ram, _} -> - die("Cluster upgrade needed but this is a ram node.~n" - "Please first start the last disc node to shut down.", - []) - end; - [Another|_] -> - MyVersion = rabbit_version:desired_for_scope(mnesia), - ErrFun = fun (ClusterVersion) -> - %% The other node(s) are running an - %% unexpected version. - die("Cluster upgrade needed but other nodes are " - "running ~p~nand I want ~p", - [ClusterVersion, MyVersion]) - end, - case rpc:call(Another, rabbit_version, desired_for_scope, - [mnesia]) of - {badrpc, {'EXIT', {undef, _}}} -> ErrFun(unknown_old_version); - {badrpc, Reason} -> ErrFun({unknown, Reason}); - CV -> case rabbit_version:matches( - MyVersion, CV) of - true -> secondary; - false -> ErrFun(CV) - end - end - end. - -die(Msg, Args) -> - %% We don't throw or exit here since that gets thrown - %% straight out into do_boot, generating an erl_crash.dump - %% and displaying any error message in a confusing way. - rabbit_log:error(Msg, Args), - Str = rabbit_misc:format( - "~n~n****~n~n" ++ Msg ++ "~n~n****~n~n~n", Args), - io:format(Str), - error_logger:logfile(close), - case application:get_env(rabbit, halt_on_upgrade_failure) of - {ok, false} -> throw({upgrade_error, Str}); - _ -> halt(1) %% i.e. true or undefined - end. - -primary_upgrade(Upgrades, Nodes) -> - Others = Nodes -- [node()], - ok = apply_upgrades( - mnesia, - Upgrades, - fun () -> - rabbit_table:force_load(), - case Others of - [] -> ok; - _ -> info("mnesia upgrades: Breaking cluster~n", []), - [{atomic, ok} = mnesia:del_table_copy(schema, Node) - || Node <- Others] - end - end), - ok. - -secondary_upgrade(AllNodes) -> - %% must do this before we wipe out schema - NodeType = node_type_legacy(), - rabbit_misc:ensure_ok(mnesia:delete_schema([node()]), - cannot_delete_schema), - rabbit_misc:ensure_ok(mnesia:start(), cannot_start_mnesia), - ok = rabbit_mnesia:init_db_unchecked(AllNodes, NodeType), - ok = rabbit_version:record_desired_for_scope(mnesia), - ok. - -nodes_running(Nodes) -> - [N || N <- Nodes, rabbit:is_running(N)]. - -%% ------------------------------------------------------------------- - -maybe_upgrade_local() -> - case rabbit_version:upgrades_required(local) of - {error, version_not_available} -> version_not_available; - {error, starting_from_scratch} -> starting_from_scratch; - {error, _} = Err -> throw(Err); - {ok, []} -> ensure_backup_removed(), - ok; - {ok, Upgrades} -> mnesia:stop(), - ensure_backup_taken(), - ok = apply_upgrades(local, Upgrades, - fun () -> ok end), - ensure_backup_removed(), - ok - end. - -%% ------------------------------------------------------------------- - -apply_upgrades(Scope, Upgrades, Fun) -> - ok = rabbit_file:lock_file(lock_filename()), - info("~s upgrades: ~w to apply~n", [Scope, length(Upgrades)]), - rabbit_misc:ensure_ok(mnesia:start(), cannot_start_mnesia), - Fun(), - [apply_upgrade(Scope, Upgrade) || Upgrade <- Upgrades], - info("~s upgrades: All upgrades applied successfully~n", [Scope]), - ok = rabbit_version:record_desired_for_scope(Scope), - ok = file:delete(lock_filename()). - -apply_upgrade(Scope, {M, F}) -> - info("~s upgrades: Applying ~w:~w~n", [Scope, M, F]), - ok = apply(M, F, []). - -%% ------------------------------------------------------------------- - -dir() -> rabbit_mnesia:dir(). - -lock_filename() -> lock_filename(dir()). -lock_filename(Dir) -> filename:join(Dir, ?LOCK_FILENAME). -backup_dir() -> dir() ++ "-upgrade-backup". - -node_type_legacy() -> - %% This is pretty ugly but we can't start Mnesia and ask it (will - %% hang), we can't look at the config file (may not include us - %% even if we're a disc node). We also can't use - %% rabbit_mnesia:node_type/0 because that will give false - %% postivies on Rabbit up to 2.5.1. - case filelib:is_regular(filename:join(dir(), "rabbit_durable_exchange.DCD")) of - true -> disc; - false -> ram - end. - -info(Msg, Args) -> rabbit_log:info(Msg, Args). diff --git a/src/rabbit_upgrade_functions.erl b/src/rabbit_upgrade_functions.erl deleted file mode 100644 index 9f6dc21a..00000000 --- a/src/rabbit_upgrade_functions.erl +++ /dev/null @@ -1,445 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_upgrade_functions). - -%% If you are tempted to add include("rabbit.hrl"). here, don't. Using record -%% defs here leads to pain later. - --compile([export_all]). - --rabbit_upgrade({remove_user_scope, mnesia, []}). --rabbit_upgrade({hash_passwords, mnesia, []}). --rabbit_upgrade({add_ip_to_listener, mnesia, []}). --rabbit_upgrade({internal_exchanges, mnesia, []}). --rabbit_upgrade({user_to_internal_user, mnesia, [hash_passwords]}). --rabbit_upgrade({topic_trie, mnesia, []}). --rabbit_upgrade({semi_durable_route, mnesia, []}). --rabbit_upgrade({exchange_event_serial, mnesia, []}). --rabbit_upgrade({trace_exchanges, mnesia, [internal_exchanges]}). --rabbit_upgrade({user_admin_to_tags, mnesia, [user_to_internal_user]}). --rabbit_upgrade({ha_mirrors, mnesia, []}). --rabbit_upgrade({gm, mnesia, []}). --rabbit_upgrade({exchange_scratch, mnesia, [trace_exchanges]}). --rabbit_upgrade({mirrored_supervisor, mnesia, []}). --rabbit_upgrade({topic_trie_node, mnesia, []}). --rabbit_upgrade({runtime_parameters, mnesia, []}). --rabbit_upgrade({exchange_scratches, mnesia, [exchange_scratch]}). --rabbit_upgrade({policy, mnesia, - [exchange_scratches, ha_mirrors]}). --rabbit_upgrade({sync_slave_pids, mnesia, [policy]}). --rabbit_upgrade({no_mirror_nodes, mnesia, [sync_slave_pids]}). --rabbit_upgrade({gm_pids, mnesia, [no_mirror_nodes]}). --rabbit_upgrade({exchange_decorators, mnesia, [policy]}). --rabbit_upgrade({policy_apply_to, mnesia, [runtime_parameters]}). --rabbit_upgrade({queue_decorators, mnesia, [gm_pids]}). --rabbit_upgrade({internal_system_x, mnesia, [exchange_decorators]}). --rabbit_upgrade({cluster_name, mnesia, [runtime_parameters]}). --rabbit_upgrade({down_slave_nodes, mnesia, [queue_decorators]}). --rabbit_upgrade({queue_state, mnesia, [down_slave_nodes]}). - -%% ------------------------------------------------------------------- - --ifdef(use_specs). - --spec(remove_user_scope/0 :: () -> 'ok'). --spec(hash_passwords/0 :: () -> 'ok'). --spec(add_ip_to_listener/0 :: () -> 'ok'). --spec(internal_exchanges/0 :: () -> 'ok'). --spec(user_to_internal_user/0 :: () -> 'ok'). --spec(topic_trie/0 :: () -> 'ok'). --spec(semi_durable_route/0 :: () -> 'ok'). --spec(exchange_event_serial/0 :: () -> 'ok'). --spec(trace_exchanges/0 :: () -> 'ok'). --spec(user_admin_to_tags/0 :: () -> 'ok'). --spec(ha_mirrors/0 :: () -> 'ok'). --spec(gm/0 :: () -> 'ok'). --spec(exchange_scratch/0 :: () -> 'ok'). --spec(mirrored_supervisor/0 :: () -> 'ok'). --spec(topic_trie_node/0 :: () -> 'ok'). --spec(runtime_parameters/0 :: () -> 'ok'). --spec(policy/0 :: () -> 'ok'). --spec(sync_slave_pids/0 :: () -> 'ok'). --spec(no_mirror_nodes/0 :: () -> 'ok'). --spec(gm_pids/0 :: () -> 'ok'). --spec(exchange_decorators/0 :: () -> 'ok'). --spec(policy_apply_to/0 :: () -> 'ok'). --spec(queue_decorators/0 :: () -> 'ok'). --spec(internal_system_x/0 :: () -> 'ok'). --spec(cluster_name/0 :: () -> 'ok'). --spec(down_slave_nodes/0 :: () -> 'ok'). --spec(queue_state/0 :: () -> 'ok'). - --endif. - -%%-------------------------------------------------------------------- - -%% It's a bad idea to use records or record_info here, even for the -%% destination form. Because in the future, the destination form of -%% your current transform may not match the record any more, and it -%% would be messy to have to go back and fix old transforms at that -%% point. - -remove_user_scope() -> - transform( - rabbit_user_permission, - fun ({user_permission, UV, {permission, _Scope, Conf, Write, Read}}) -> - {user_permission, UV, {permission, Conf, Write, Read}} - end, - [user_vhost, permission]). - -hash_passwords() -> - transform( - rabbit_user, - fun ({user, Username, Password, IsAdmin}) -> - Hash = rabbit_auth_backend_internal:hash_password(Password), - {user, Username, Hash, IsAdmin} - end, - [username, password_hash, is_admin]). - -add_ip_to_listener() -> - transform( - rabbit_listener, - fun ({listener, Node, Protocol, Host, Port}) -> - {listener, Node, Protocol, Host, {0,0,0,0}, Port} - end, - [node, protocol, host, ip_address, port]). - -internal_exchanges() -> - Tables = [rabbit_exchange, rabbit_durable_exchange], - AddInternalFun = - fun ({exchange, Name, Type, Durable, AutoDelete, Args}) -> - {exchange, Name, Type, Durable, AutoDelete, false, Args} - end, - [ ok = transform(T, - AddInternalFun, - [name, type, durable, auto_delete, internal, arguments]) - || T <- Tables ], - ok. - -user_to_internal_user() -> - transform( - rabbit_user, - fun({user, Username, PasswordHash, IsAdmin}) -> - {internal_user, Username, PasswordHash, IsAdmin} - end, - [username, password_hash, is_admin], internal_user). - -topic_trie() -> - create(rabbit_topic_trie_edge, [{record_name, topic_trie_edge}, - {attributes, [trie_edge, node_id]}, - {type, ordered_set}]), - create(rabbit_topic_trie_binding, [{record_name, topic_trie_binding}, - {attributes, [trie_binding, value]}, - {type, ordered_set}]). - -semi_durable_route() -> - create(rabbit_semi_durable_route, [{record_name, route}, - {attributes, [binding, value]}]). - -exchange_event_serial() -> - create(rabbit_exchange_serial, [{record_name, exchange_serial}, - {attributes, [name, next]}]). - -trace_exchanges() -> - [declare_exchange( - rabbit_misc:r(VHost, exchange, <<"amq.rabbitmq.trace">>), topic) || - VHost <- rabbit_vhost:list()], - ok. - -user_admin_to_tags() -> - transform( - rabbit_user, - fun({internal_user, Username, PasswordHash, true}) -> - {internal_user, Username, PasswordHash, [administrator]}; - ({internal_user, Username, PasswordHash, false}) -> - {internal_user, Username, PasswordHash, [management]} - end, - [username, password_hash, tags], internal_user). - -ha_mirrors() -> - Tables = [rabbit_queue, rabbit_durable_queue], - AddMirrorPidsFun = - fun ({amqqueue, Name, Durable, AutoDelete, Owner, Arguments, Pid}) -> - {amqqueue, Name, Durable, AutoDelete, Owner, Arguments, Pid, - [], undefined} - end, - [ ok = transform(T, - AddMirrorPidsFun, - [name, durable, auto_delete, exclusive_owner, arguments, - pid, slave_pids, mirror_nodes]) - || T <- Tables ], - ok. - -gm() -> - create(gm_group, [{record_name, gm_group}, - {attributes, [name, version, members]}]). - -exchange_scratch() -> - ok = exchange_scratch(rabbit_exchange), - ok = exchange_scratch(rabbit_durable_exchange). - -exchange_scratch(Table) -> - transform( - Table, - fun ({exchange, Name, Type, Dur, AutoDel, Int, Args}) -> - {exchange, Name, Type, Dur, AutoDel, Int, Args, undefined} - end, - [name, type, durable, auto_delete, internal, arguments, scratch]). - -mirrored_supervisor() -> - create(mirrored_sup_childspec, - [{record_name, mirrored_sup_childspec}, - {attributes, [key, mirroring_pid, childspec]}]). - -topic_trie_node() -> - create(rabbit_topic_trie_node, - [{record_name, topic_trie_node}, - {attributes, [trie_node, edge_count, binding_count]}, - {type, ordered_set}]). - -runtime_parameters() -> - create(rabbit_runtime_parameters, - [{record_name, runtime_parameters}, - {attributes, [key, value]}, - {disc_copies, [node()]}]). - -exchange_scratches() -> - ok = exchange_scratches(rabbit_exchange), - ok = exchange_scratches(rabbit_durable_exchange). - -exchange_scratches(Table) -> - transform( - Table, - fun ({exchange, Name, Type = <<"x-federation">>, Dur, AutoDel, Int, Args, - Scratch}) -> - Scratches = orddict:store(federation, Scratch, orddict:new()), - {exchange, Name, Type, Dur, AutoDel, Int, Args, Scratches}; - %% We assert here that nothing else uses the scratch mechanism ATM - ({exchange, Name, Type, Dur, AutoDel, Int, Args, undefined}) -> - {exchange, Name, Type, Dur, AutoDel, Int, Args, undefined} - end, - [name, type, durable, auto_delete, internal, arguments, scratches]). - -policy() -> - ok = exchange_policy(rabbit_exchange), - ok = exchange_policy(rabbit_durable_exchange), - ok = queue_policy(rabbit_queue), - ok = queue_policy(rabbit_durable_queue). - -exchange_policy(Table) -> - transform( - Table, - fun ({exchange, Name, Type, Dur, AutoDel, Int, Args, Scratches}) -> - {exchange, Name, Type, Dur, AutoDel, Int, Args, Scratches, - undefined} - end, - [name, type, durable, auto_delete, internal, arguments, scratches, - policy]). - -queue_policy(Table) -> - transform( - Table, - fun ({amqqueue, Name, Dur, AutoDel, Excl, Args, Pid, SPids, MNodes}) -> - {amqqueue, Name, Dur, AutoDel, Excl, Args, Pid, SPids, MNodes, - undefined} - end, - [name, durable, auto_delete, exclusive_owner, arguments, pid, - slave_pids, mirror_nodes, policy]). - -sync_slave_pids() -> - Tables = [rabbit_queue, rabbit_durable_queue], - AddSyncSlavesFun = - fun ({amqqueue, N, D, AD, Excl, Args, Pid, SPids, MNodes, Pol}) -> - {amqqueue, N, D, AD, Excl, Args, Pid, SPids, [], MNodes, Pol} - end, - [ok = transform(T, AddSyncSlavesFun, - [name, durable, auto_delete, exclusive_owner, arguments, - pid, slave_pids, sync_slave_pids, mirror_nodes, policy]) - || T <- Tables], - ok. - -no_mirror_nodes() -> - Tables = [rabbit_queue, rabbit_durable_queue], - RemoveMirrorNodesFun = - fun ({amqqueue, N, D, AD, O, A, Pid, SPids, SSPids, _MNodes, Pol}) -> - {amqqueue, N, D, AD, O, A, Pid, SPids, SSPids, Pol} - end, - [ok = transform(T, RemoveMirrorNodesFun, - [name, durable, auto_delete, exclusive_owner, arguments, - pid, slave_pids, sync_slave_pids, policy]) - || T <- Tables], - ok. - -gm_pids() -> - Tables = [rabbit_queue, rabbit_durable_queue], - AddGMPidsFun = - fun ({amqqueue, N, D, AD, O, A, Pid, SPids, SSPids, Pol}) -> - {amqqueue, N, D, AD, O, A, Pid, SPids, SSPids, Pol, []} - end, - [ok = transform(T, AddGMPidsFun, - [name, durable, auto_delete, exclusive_owner, arguments, - pid, slave_pids, sync_slave_pids, policy, gm_pids]) - || T <- Tables], - ok. - -exchange_decorators() -> - ok = exchange_decorators(rabbit_exchange), - ok = exchange_decorators(rabbit_durable_exchange). - -exchange_decorators(Table) -> - transform( - Table, - fun ({exchange, Name, Type, Dur, AutoDel, Int, Args, Scratches, - Policy}) -> - {exchange, Name, Type, Dur, AutoDel, Int, Args, Scratches, Policy, - {[], []}} - end, - [name, type, durable, auto_delete, internal, arguments, scratches, policy, - decorators]). - -policy_apply_to() -> - transform( - rabbit_runtime_parameters, - fun ({runtime_parameters, Key = {_VHost, <<"policy">>, _Name}, Value}) -> - ApplyTo = apply_to(proplists:get_value(<<"definition">>, Value)), - {runtime_parameters, Key, [{<<"apply-to">>, ApplyTo} | Value]}; - ({runtime_parameters, Key, Value}) -> - {runtime_parameters, Key, Value} - end, - [key, value]), - rabbit_policy:invalidate(), - ok. - -apply_to(Def) -> - case [proplists:get_value(K, Def) || - K <- [<<"federation-upstream-set">>, <<"ha-mode">>]] of - [undefined, undefined] -> <<"all">>; - [_, undefined] -> <<"exchanges">>; - [undefined, _] -> <<"queues">>; - [_, _] -> <<"all">> - end. - -queue_decorators() -> - ok = queue_decorators(rabbit_queue), - ok = queue_decorators(rabbit_durable_queue). - -queue_decorators(Table) -> - transform( - Table, - fun ({amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments, - Pid, SlavePids, SyncSlavePids, Policy, GmPids}) -> - {amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments, - Pid, SlavePids, SyncSlavePids, Policy, GmPids, []} - end, - [name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids, - sync_slave_pids, policy, gm_pids, decorators]). - -internal_system_x() -> - transform( - rabbit_durable_exchange, - fun ({exchange, Name = {resource, _, _, <<"amq.rabbitmq.", _/binary>>}, - Type, Dur, AutoDel, _Int, Args, Scratches, Policy, Decorators}) -> - {exchange, Name, Type, Dur, AutoDel, true, Args, Scratches, - Policy, Decorators}; - (X) -> - X - end, - [name, type, durable, auto_delete, internal, arguments, scratches, policy, - decorators]). - -cluster_name() -> - {atomic, ok} = mnesia:transaction(fun cluster_name_tx/0), - ok. - -cluster_name_tx() -> - %% mnesia:transform_table/4 does not let us delete records - T = rabbit_runtime_parameters, - mnesia:write_lock_table(T), - Ks = [K || {_VHost, <<"federation">>, <<"local-nodename">>} = K - <- mnesia:all_keys(T)], - case Ks of - [] -> ok; - [K|Tl] -> [{runtime_parameters, _K, Name}] = mnesia:read(T, K, write), - R = {runtime_parameters, cluster_name, Name}, - mnesia:write(T, R, write), - case Tl of - [] -> ok; - _ -> {VHost, _, _} = K, - error_logger:warning_msg( - "Multiple local-nodenames found, picking '~s' " - "from '~s' for cluster name~n", [Name, VHost]) - end - end, - [mnesia:delete(T, K, write) || K <- Ks], - ok. - -down_slave_nodes() -> - ok = down_slave_nodes(rabbit_queue), - ok = down_slave_nodes(rabbit_durable_queue). - -down_slave_nodes(Table) -> - transform( - Table, - fun ({amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments, - Pid, SlavePids, SyncSlavePids, Policy, GmPids, Decorators}) -> - {amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments, - Pid, SlavePids, SyncSlavePids, [], Policy, GmPids, Decorators} - end, - [name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids, - sync_slave_pids, down_slave_nodes, policy, gm_pids, decorators]). - -queue_state() -> - ok = queue_state(rabbit_queue), - ok = queue_state(rabbit_durable_queue). - -queue_state(Table) -> - transform( - Table, - fun ({amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments, - Pid, SlavePids, SyncSlavePids, DSN, Policy, GmPids, Decorators}) -> - {amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments, - Pid, SlavePids, SyncSlavePids, DSN, Policy, GmPids, Decorators, - live} - end, - [name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids, - sync_slave_pids, down_slave_nodes, policy, gm_pids, decorators, state]). - -%%-------------------------------------------------------------------- - -transform(TableName, Fun, FieldList) -> - rabbit_table:wait([TableName]), - {atomic, ok} = mnesia:transform_table(TableName, Fun, FieldList), - ok. - -transform(TableName, Fun, FieldList, NewRecordName) -> - rabbit_table:wait([TableName]), - {atomic, ok} = mnesia:transform_table(TableName, Fun, FieldList, - NewRecordName), - ok. - -create(Tab, TabDef) -> - {atomic, ok} = mnesia:create_table(Tab, TabDef), - ok. - -%% Dumb replacement for rabbit_exchange:declare that does not require -%% the exchange type registry or worker pool to be running by dint of -%% not validating anything and assuming the exchange type does not -%% require serialisation. -%% NB: this assumes the pre-exchange-scratch-space format -declare_exchange(XName, Type) -> - X = {exchange, XName, Type, true, false, false, []}, - ok = mnesia:dirty_write(rabbit_durable_exchange, X). diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl deleted file mode 100644 index d076b534..00000000 --- a/src/rabbit_variable_queue.erl +++ /dev/null @@ -1,1907 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_variable_queue). - --export([init/3, terminate/2, delete_and_terminate/2, delete_crashed/1, - purge/1, purge_acks/1, - publish/5, publish_delivered/4, discard/3, drain_confirmed/1, - dropwhile/2, fetchwhile/4, fetch/2, drop/2, ack/2, requeue/2, - ackfold/4, fold/3, len/1, is_empty/1, depth/1, - set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, - handle_pre_hibernate/1, resume/1, msg_rates/1, - info/2, invoke/3, is_duplicate/2, multiple_routing_keys/0]). - --export([start/1, stop/0]). - -%% exported for testing only --export([start_msg_store/2, stop_msg_store/0, init/5]). - -%%---------------------------------------------------------------------------- -%% Definitions: - -%% alpha: this is a message where both the message itself, and its -%% position within the queue are held in RAM -%% -%% beta: this is a message where the message itself is only held on -%% disk, but its position within the queue is held in RAM. -%% -%% gamma: this is a message where the message itself is only held on -%% disk, but its position is both in RAM and on disk. -%% -%% delta: this is a collection of messages, represented by a single -%% term, where the messages and their position are only held on -%% disk. -%% -%% Note that for persistent messages, the message and its position -%% within the queue are always held on disk, *in addition* to being in -%% one of the above classifications. -%% -%% Also note that within this code, the term gamma seldom -%% appears. It's frequently the case that gammas are defined by betas -%% who have had their queue position recorded on disk. -%% -%% In general, messages move q1 -> q2 -> delta -> q3 -> q4, though -%% many of these steps are frequently skipped. q1 and q4 only hold -%% alphas, q2 and q3 hold both betas and gammas. When a message -%% arrives, its classification is determined. It is then added to the -%% rightmost appropriate queue. -%% -%% If a new message is determined to be a beta or gamma, q1 is -%% empty. If a new message is determined to be a delta, q1 and q2 are -%% empty (and actually q4 too). -%% -%% When removing messages from a queue, if q4 is empty then q3 is read -%% directly. If q3 becomes empty then the next segment's worth of -%% messages from delta are read into q3, reducing the size of -%% delta. If the queue is non empty, either q4 or q3 contain -%% entries. It is never permitted for delta to hold all the messages -%% in the queue. -%% -%% The duration indicated to us by the memory_monitor is used to -%% calculate, given our current ingress and egress rates, how many -%% messages we should hold in RAM (i.e. as alphas). We track the -%% ingress and egress rates for both messages and pending acks and -%% rates for both are considered when calculating the number of -%% messages to hold in RAM. When we need to push alphas to betas or -%% betas to gammas, we favour writing out messages that are further -%% from the head of the queue. This minimises writes to disk, as the -%% messages closer to the tail of the queue stay in the queue for -%% longer, thus do not need to be replaced as quickly by sending other -%% messages to disk. -%% -%% Whilst messages are pushed to disk and forgotten from RAM as soon -%% as requested by a new setting of the queue RAM duration, the -%% inverse is not true: we only load messages back into RAM as -%% demanded as the queue is read from. Thus only publishes to the -%% queue will take up available spare capacity. -%% -%% When we report our duration to the memory monitor, we calculate -%% average ingress and egress rates over the last two samples, and -%% then calculate our duration based on the sum of the ingress and -%% egress rates. More than two samples could be used, but it's a -%% balance between responding quickly enough to changes in -%% producers/consumers versus ignoring temporary blips. The problem -%% with temporary blips is that with just a few queues, they can have -%% substantial impact on the calculation of the average duration and -%% hence cause unnecessary I/O. Another alternative is to increase the -%% amqqueue_process:RAM_DURATION_UPDATE_PERIOD to beyond 5 -%% seconds. However, that then runs the risk of being too slow to -%% inform the memory monitor of changes. Thus a 5 second interval, -%% plus a rolling average over the last two samples seems to work -%% well in practice. -%% -%% The sum of the ingress and egress rates is used because the egress -%% rate alone is not sufficient. Adding in the ingress rate means that -%% queues which are being flooded by messages are given more memory, -%% resulting in them being able to process the messages faster (by -%% doing less I/O, or at least deferring it) and thus helping keep -%% their mailboxes empty and thus the queue as a whole is more -%% responsive. If such a queue also has fast but previously idle -%% consumers, the consumer can then start to be driven as fast as it -%% can go, whereas if only egress rate was being used, the incoming -%% messages may have to be written to disk and then read back in, -%% resulting in the hard disk being a bottleneck in driving the -%% consumers. Generally, we want to give Rabbit every chance of -%% getting rid of messages as fast as possible and remaining -%% responsive, and using only the egress rate impacts that goal. -%% -%% Once the queue has more alphas than the target_ram_count, the -%% surplus must be converted to betas, if not gammas, if not rolled -%% into delta. The conditions under which these transitions occur -%% reflect the conflicting goals of minimising RAM cost per msg, and -%% minimising CPU cost per msg. Once the msg has become a beta, its -%% payload is no longer in RAM, thus a read from the msg_store must -%% occur before the msg can be delivered, but the RAM cost of a beta -%% is the same as a gamma, so converting a beta to gamma will not free -%% up any further RAM. To reduce the RAM cost further, the gamma must -%% be rolled into delta. Whilst recovering a beta or a gamma to an -%% alpha requires only one disk read (from the msg_store), recovering -%% a msg from within delta will require two reads (queue_index and -%% then msg_store). But delta has a near-0 per-msg RAM cost. So the -%% conflict is between using delta more, which will free up more -%% memory, but require additional CPU and disk ops, versus using delta -%% less and gammas and betas more, which will cost more memory, but -%% require fewer disk ops and less CPU overhead. -%% -%% In the case of a persistent msg published to a durable queue, the -%% msg is immediately written to the msg_store and queue_index. If -%% then additionally converted from an alpha, it'll immediately go to -%% a gamma (as it's already in queue_index), and cannot exist as a -%% beta. Thus a durable queue with a mixture of persistent and -%% transient msgs in it which has more messages than permitted by the -%% target_ram_count may contain an interspersed mixture of betas and -%% gammas in q2 and q3. -%% -%% There is then a ratio that controls how many betas and gammas there -%% can be. This is based on the target_ram_count and thus expresses -%% the fact that as the number of permitted alphas in the queue falls, -%% so should the number of betas and gammas fall (i.e. delta -%% grows). If q2 and q3 contain more than the permitted number of -%% betas and gammas, then the surplus are forcibly converted to gammas -%% (as necessary) and then rolled into delta. The ratio is that -%% delta/(betas+gammas+delta) equals -%% (betas+gammas+delta)/(target_ram_count+betas+gammas+delta). I.e. as -%% the target_ram_count shrinks to 0, so must betas and gammas. -%% -%% The conversion of betas to gammas is done in batches of at least -%% ?IO_BATCH_SIZE. This value should not be too small, otherwise the -%% frequent operations on the queues of q2 and q3 will not be -%% effectively amortised (switching the direction of queue access -%% defeats amortisation). Note that there is a natural upper bound due -%% to credit_flow limits on the alpha to beta conversion. -%% -%% The conversion from alphas to betas is chunked due to the -%% credit_flow limits of the msg_store. This further smooths the -%% effects of changes to the target_ram_count and ensures the queue -%% remains responsive even when there is a large amount of IO work to -%% do. The 'resume' callback is utilised to ensure that conversions -%% are done as promptly as possible whilst ensuring the queue remains -%% responsive. -%% -%% In the queue we keep track of both messages that are pending -%% delivery and messages that are pending acks. In the event of a -%% queue purge, we only need to load qi segments if the queue has -%% elements in deltas (i.e. it came under significant memory -%% pressure). In the event of a queue deletion, in addition to the -%% preceding, by keeping track of pending acks in RAM, we do not need -%% to search through qi segments looking for messages that are yet to -%% be acknowledged. -%% -%% Pending acks are recorded in memory by storing the message itself. -%% If the message has been sent to disk, we do not store the message -%% content. During memory reduction, pending acks containing message -%% content have that content removed and the corresponding messages -%% are pushed out to disk. -%% -%% Messages from pending acks are returned to q4, q3 and delta during -%% requeue, based on the limits of seq_id contained in each. Requeued -%% messages retain their original seq_id, maintaining order -%% when requeued. -%% -%% The order in which alphas are pushed to betas and pending acks -%% are pushed to disk is determined dynamically. We always prefer to -%% push messages for the source (alphas or acks) that is growing the -%% fastest (with growth measured as avg. ingress - avg. egress). -%% -%% Notes on Clean Shutdown -%% (This documents behaviour in variable_queue, queue_index and -%% msg_store.) -%% -%% In order to try to achieve as fast a start-up as possible, if a -%% clean shutdown occurs, we try to save out state to disk to reduce -%% work on startup. In the msg_store this takes the form of the -%% index_module's state, plus the file_summary ets table, and client -%% refs. In the VQ, this takes the form of the count of persistent -%% messages in the queue and references into the msg_stores. The -%% queue_index adds to these terms the details of its segments and -%% stores the terms in the queue directory. -%% -%% Two message stores are used. One is created for persistent messages -%% to durable queues that must survive restarts, and the other is used -%% for all other messages that just happen to need to be written to -%% disk. On start up we can therefore nuke the transient message -%% store, and be sure that the messages in the persistent store are -%% all that we need. -%% -%% The references to the msg_stores are there so that the msg_store -%% knows to only trust its saved state if all of the queues it was -%% previously talking to come up cleanly. Likewise, the queues -%% themselves (esp queue_index) skips work in init if all the queues -%% and msg_store were shutdown cleanly. This gives both good speed -%% improvements and also robustness so that if anything possibly went -%% wrong in shutdown (or there was subsequent manual tampering), all -%% messages and queues that can be recovered are recovered, safely. -%% -%% To delete transient messages lazily, the variable_queue, on -%% startup, stores the next_seq_id reported by the queue_index as the -%% transient_threshold. From that point on, whenever it's reading a -%% message off disk via the queue_index, if the seq_id is below this -%% threshold and the message is transient then it drops the message -%% (the message itself won't exist on disk because it would have been -%% stored in the transient msg_store which would have had its saved -%% state nuked on startup). This avoids the expensive operation of -%% scanning the entire queue on startup in order to delete transient -%% messages that were only pushed to disk to save memory. -%% -%%---------------------------------------------------------------------------- - --behaviour(rabbit_backing_queue). - --record(vqstate, - { q1, - q2, - delta, - q3, - q4, - next_seq_id, - ram_pending_ack, - disk_pending_ack, - index_state, - msg_store_clients, - durable, - transient_threshold, - - len, %% w/o unacked - bytes, %% w/o unacked - unacked_bytes, - persistent_count, %% w unacked - persistent_bytes, %% w unacked - - target_ram_count, - ram_msg_count, %% w/o unacked - ram_msg_count_prev, - ram_ack_count_prev, - ram_bytes, %% w unacked - out_counter, - in_counter, - rates, - msgs_on_disk, - msg_indices_on_disk, - unconfirmed, - confirmed, - ack_out_counter, - ack_in_counter - }). - --record(rates, { in, out, ack_in, ack_out, timestamp }). - --record(msg_status, - { seq_id, - msg_id, - msg, - is_persistent, - is_delivered, - msg_on_disk, - index_on_disk, - msg_props - }). - --record(delta, - { start_seq_id, %% start_seq_id is inclusive - count, - end_seq_id %% end_seq_id is exclusive - }). - -%% When we discover that we should write some indices to disk for some -%% betas, the IO_BATCH_SIZE sets the number of betas that we must be -%% due to write indices for before we do any work at all. --define(IO_BATCH_SIZE, 2048). %% next power-of-2 after ?CREDIT_DISC_BOUND --define(PERSISTENT_MSG_STORE, msg_store_persistent). --define(TRANSIENT_MSG_STORE, msg_store_transient). --define(QUEUE, lqueue). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --rabbit_upgrade({multiple_routing_keys, local, []}). - --ifdef(use_specs). - --type(timestamp() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}). --type(seq_id() :: non_neg_integer()). - --type(rates() :: #rates { in :: float(), - out :: float(), - ack_in :: float(), - ack_out :: float(), - timestamp :: timestamp()}). - --type(delta() :: #delta { start_seq_id :: non_neg_integer(), - count :: non_neg_integer(), - end_seq_id :: non_neg_integer() }). - -%% The compiler (rightfully) complains that ack() and state() are -%% unused. For this reason we duplicate a -spec from -%% rabbit_backing_queue with the only intent being to remove -%% warnings. The problem here is that we can't parameterise the BQ -%% behaviour by these two types as we would like to. We still leave -%% these here for documentation purposes. --type(ack() :: seq_id()). --type(state() :: #vqstate { - q1 :: ?QUEUE:?QUEUE(), - q2 :: ?QUEUE:?QUEUE(), - delta :: delta(), - q3 :: ?QUEUE:?QUEUE(), - q4 :: ?QUEUE:?QUEUE(), - next_seq_id :: seq_id(), - ram_pending_ack :: gb_trees:tree(), - disk_pending_ack :: gb_trees:tree(), - index_state :: any(), - msg_store_clients :: 'undefined' | {{any(), binary()}, - {any(), binary()}}, - durable :: boolean(), - transient_threshold :: non_neg_integer(), - - len :: non_neg_integer(), - bytes :: non_neg_integer(), - unacked_bytes :: non_neg_integer(), - - persistent_count :: non_neg_integer(), - persistent_bytes :: non_neg_integer(), - - target_ram_count :: non_neg_integer() | 'infinity', - ram_msg_count :: non_neg_integer(), - ram_msg_count_prev :: non_neg_integer(), - ram_ack_count_prev :: non_neg_integer(), - ram_bytes :: non_neg_integer(), - out_counter :: non_neg_integer(), - in_counter :: non_neg_integer(), - rates :: rates(), - msgs_on_disk :: gb_sets:set(), - msg_indices_on_disk :: gb_sets:set(), - unconfirmed :: gb_sets:set(), - confirmed :: gb_sets:set(), - ack_out_counter :: non_neg_integer(), - ack_in_counter :: non_neg_integer() }). -%% Duplicated from rabbit_backing_queue --spec(ack/2 :: ([ack()], state()) -> {[rabbit_guid:guid()], state()}). - --spec(multiple_routing_keys/0 :: () -> 'ok'). - --endif. - --define(BLANK_DELTA, #delta { start_seq_id = undefined, - count = 0, - end_seq_id = undefined }). --define(BLANK_DELTA_PATTERN(Z), #delta { start_seq_id = Z, - count = 0, - end_seq_id = Z }). - --define(MICROS_PER_SECOND, 1000000.0). - -%% We're sampling every 5s for RAM duration; a half life that is of -%% the same order of magnitude is probably about right. --define(RATE_AVG_HALF_LIFE, 5.0). - -%% We will recalculate the #rates{} every time we get asked for our -%% RAM duration, or every N messages published, whichever is -%% sooner. We do this since the priority calculations in -%% rabbit_amqqueue_process need fairly fresh rates. --define(MSGS_PER_RATE_CALC, 100). - -%%---------------------------------------------------------------------------- -%% Public API -%%---------------------------------------------------------------------------- - -start(DurableQueues) -> - {AllTerms, StartFunState} = rabbit_queue_index:start(DurableQueues), - start_msg_store( - [Ref || Terms <- AllTerms, - Terms /= non_clean_shutdown, - begin - Ref = proplists:get_value(persistent_ref, Terms), - Ref =/= undefined - end], - StartFunState), - {ok, AllTerms}. - -stop() -> - ok = stop_msg_store(), - ok = rabbit_queue_index:stop(). - -start_msg_store(Refs, StartFunState) -> - ok = rabbit_sup:start_child(?TRANSIENT_MSG_STORE, rabbit_msg_store, - [?TRANSIENT_MSG_STORE, rabbit_mnesia:dir(), - undefined, {fun (ok) -> finished end, ok}]), - ok = rabbit_sup:start_child(?PERSISTENT_MSG_STORE, rabbit_msg_store, - [?PERSISTENT_MSG_STORE, rabbit_mnesia:dir(), - Refs, StartFunState]). - -stop_msg_store() -> - ok = rabbit_sup:stop_child(?PERSISTENT_MSG_STORE), - ok = rabbit_sup:stop_child(?TRANSIENT_MSG_STORE). - -init(Queue, Recover, AsyncCallback) -> - init(Queue, Recover, AsyncCallback, - fun (MsgIds, ActionTaken) -> - msgs_written_to_disk(AsyncCallback, MsgIds, ActionTaken) - end, - fun (MsgIds) -> msg_indices_written_to_disk(AsyncCallback, MsgIds) end). - -init(#amqqueue { name = QueueName, durable = IsDurable }, new, - AsyncCallback, MsgOnDiskFun, MsgIdxOnDiskFun) -> - IndexState = rabbit_queue_index:init(QueueName, MsgIdxOnDiskFun), - init(IsDurable, IndexState, 0, 0, [], - case IsDurable of - true -> msg_store_client_init(?PERSISTENT_MSG_STORE, - MsgOnDiskFun, AsyncCallback); - false -> undefined - end, - msg_store_client_init(?TRANSIENT_MSG_STORE, undefined, AsyncCallback)); - -%% We can be recovering a transient queue if it crashed -init(#amqqueue { name = QueueName, durable = IsDurable }, Terms, - AsyncCallback, MsgOnDiskFun, MsgIdxOnDiskFun) -> - {PRef, RecoveryTerms} = process_recovery_terms(Terms), - {PersistentClient, ContainsCheckFun} = - case IsDurable of - true -> C = msg_store_client_init(?PERSISTENT_MSG_STORE, PRef, - MsgOnDiskFun, AsyncCallback), - {C, fun (MId) -> rabbit_msg_store:contains(MId, C) end}; - false -> {undefined, fun(_MsgId) -> false end} - end, - TransientClient = msg_store_client_init(?TRANSIENT_MSG_STORE, - undefined, AsyncCallback), - {DeltaCount, DeltaBytes, IndexState} = - rabbit_queue_index:recover( - QueueName, RecoveryTerms, - rabbit_msg_store:successfully_recovered_state(?PERSISTENT_MSG_STORE), - ContainsCheckFun, MsgIdxOnDiskFun), - init(IsDurable, IndexState, DeltaCount, DeltaBytes, RecoveryTerms, - PersistentClient, TransientClient). - -process_recovery_terms(Terms=non_clean_shutdown) -> - {rabbit_guid:gen(), Terms}; -process_recovery_terms(Terms) -> - case proplists:get_value(persistent_ref, Terms) of - undefined -> {rabbit_guid:gen(), []}; - PRef -> {PRef, Terms} - end. - -terminate(_Reason, State) -> - State1 = #vqstate { persistent_count = PCount, - persistent_bytes = PBytes, - index_state = IndexState, - msg_store_clients = {MSCStateP, MSCStateT} } = - purge_pending_ack(true, State), - PRef = case MSCStateP of - undefined -> undefined; - _ -> ok = rabbit_msg_store:client_terminate(MSCStateP), - rabbit_msg_store:client_ref(MSCStateP) - end, - ok = rabbit_msg_store:client_delete_and_terminate(MSCStateT), - Terms = [{persistent_ref, PRef}, - {persistent_count, PCount}, - {persistent_bytes, PBytes}], - a(State1 #vqstate { index_state = rabbit_queue_index:terminate( - Terms, IndexState), - msg_store_clients = undefined }). - -%% the only difference between purge and delete is that delete also -%% needs to delete everything that's been delivered and not ack'd. -delete_and_terminate(_Reason, State) -> - %% TODO: there is no need to interact with qi at all - which we do - %% as part of 'purge' and 'purge_pending_ack', other than - %% deleting it. - {_PurgeCount, State1} = purge(State), - State2 = #vqstate { index_state = IndexState, - msg_store_clients = {MSCStateP, MSCStateT} } = - purge_pending_ack(false, State1), - IndexState1 = rabbit_queue_index:delete_and_terminate(IndexState), - case MSCStateP of - undefined -> ok; - _ -> rabbit_msg_store:client_delete_and_terminate(MSCStateP) - end, - rabbit_msg_store:client_delete_and_terminate(MSCStateT), - a(State2 #vqstate { index_state = IndexState1, - msg_store_clients = undefined }). - -delete_crashed(#amqqueue{name = QName}) -> - ok = rabbit_queue_index:erase(QName). - -purge(State = #vqstate { q4 = Q4, - index_state = IndexState, - msg_store_clients = MSCState, - len = Len, - ram_bytes = RamBytes, - persistent_count = PCount, - persistent_bytes = PBytes }) -> - %% TODO: when there are no pending acks, which is a common case, - %% we could simply wipe the qi instead of issuing delivers and - %% acks for all the messages. - Stats = {RamBytes, PCount, PBytes}, - {Stats1, IndexState1} = - remove_queue_entries(Q4, Stats, IndexState, MSCState), - - {Stats2, State1 = #vqstate { q1 = Q1, - index_state = IndexState2, - msg_store_clients = MSCState1 }} = - - purge_betas_and_deltas( - Stats1, State #vqstate { q4 = ?QUEUE:new(), - index_state = IndexState1 }), - - {{RamBytes3, PCount3, PBytes3}, IndexState3} = - remove_queue_entries(Q1, Stats2, IndexState2, MSCState1), - - {Len, a(State1 #vqstate { q1 = ?QUEUE:new(), - index_state = IndexState3, - len = 0, - bytes = 0, - ram_msg_count = 0, - ram_bytes = RamBytes3, - persistent_count = PCount3, - persistent_bytes = PBytes3 })}. - -purge_acks(State) -> a(purge_pending_ack(false, State)). - -publish(Msg = #basic_message { is_persistent = IsPersistent, id = MsgId }, - MsgProps = #message_properties { needs_confirming = NeedsConfirming }, - IsDelivered, _ChPid, State = #vqstate { q1 = Q1, q3 = Q3, q4 = Q4, - next_seq_id = SeqId, - len = Len, - in_counter = InCount, - persistent_count = PCount, - durable = IsDurable, - unconfirmed = UC }) -> - IsPersistent1 = IsDurable andalso IsPersistent, - MsgStatus = msg_status(IsPersistent1, IsDelivered, SeqId, Msg, MsgProps), - {MsgStatus1, State1} = maybe_write_to_disk(false, false, MsgStatus, State), - State2 = case ?QUEUE:is_empty(Q3) of - false -> State1 #vqstate { q1 = ?QUEUE:in(m(MsgStatus1), Q1) }; - true -> State1 #vqstate { q4 = ?QUEUE:in(m(MsgStatus1), Q4) } - end, - InCount1 = InCount + 1, - PCount1 = PCount + one_if(IsPersistent1), - UC1 = gb_sets_maybe_insert(NeedsConfirming, MsgId, UC), - State3 = upd_bytes( - 1, 0, MsgStatus1, - inc_ram_msg_count(State2 #vqstate { next_seq_id = SeqId + 1, - len = Len + 1, - in_counter = InCount1, - persistent_count = PCount1, - unconfirmed = UC1 })), - a(reduce_memory_use(maybe_update_rates(State3))). - -publish_delivered(Msg = #basic_message { is_persistent = IsPersistent, - id = MsgId }, - MsgProps = #message_properties { - needs_confirming = NeedsConfirming }, - _ChPid, State = #vqstate { next_seq_id = SeqId, - out_counter = OutCount, - in_counter = InCount, - persistent_count = PCount, - durable = IsDurable, - unconfirmed = UC }) -> - IsPersistent1 = IsDurable andalso IsPersistent, - MsgStatus = msg_status(IsPersistent1, true, SeqId, Msg, MsgProps), - {MsgStatus1, State1} = maybe_write_to_disk(false, false, MsgStatus, State), - State2 = record_pending_ack(m(MsgStatus1), State1), - PCount1 = PCount + one_if(IsPersistent1), - UC1 = gb_sets_maybe_insert(NeedsConfirming, MsgId, UC), - State3 = upd_bytes(0, 1, MsgStatus, - State2 #vqstate { next_seq_id = SeqId + 1, - out_counter = OutCount + 1, - in_counter = InCount + 1, - persistent_count = PCount1, - unconfirmed = UC1 }), - {SeqId, a(reduce_memory_use(maybe_update_rates(State3)))}. - -discard(_MsgId, _ChPid, State) -> State. - -drain_confirmed(State = #vqstate { confirmed = C }) -> - case gb_sets:is_empty(C) of - true -> {[], State}; %% common case - false -> {gb_sets:to_list(C), State #vqstate { - confirmed = gb_sets:new() }} - end. - -dropwhile(Pred, State) -> - case queue_out(State) of - {empty, State1} -> - {undefined, a(State1)}; - {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> - case Pred(MsgProps) of - true -> {_, State2} = remove(false, MsgStatus, State1), - dropwhile(Pred, State2); - false -> {MsgProps, a(in_r(MsgStatus, State1))} - end - end. - -fetchwhile(Pred, Fun, Acc, State) -> - case queue_out(State) of - {empty, State1} -> - {undefined, Acc, a(State1)}; - {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> - case Pred(MsgProps) of - true -> {Msg, State2} = read_msg(MsgStatus, State1), - {AckTag, State3} = remove(true, MsgStatus, State2), - fetchwhile(Pred, Fun, Fun(Msg, AckTag, Acc), State3); - false -> {MsgProps, Acc, a(in_r(MsgStatus, State1))} - end - end. - -fetch(AckRequired, State) -> - case queue_out(State) of - {empty, State1} -> - {empty, a(State1)}; - {{value, MsgStatus}, State1} -> - %% it is possible that the message wasn't read from disk - %% at this point, so read it in. - {Msg, State2} = read_msg(MsgStatus, State1), - {AckTag, State3} = remove(AckRequired, MsgStatus, State2), - {{Msg, MsgStatus#msg_status.is_delivered, AckTag}, a(State3)} - end. - -drop(AckRequired, State) -> - case queue_out(State) of - {empty, State1} -> - {empty, a(State1)}; - {{value, MsgStatus}, State1} -> - {AckTag, State2} = remove(AckRequired, MsgStatus, State1), - {{MsgStatus#msg_status.msg_id, AckTag}, a(State2)} - end. - -ack([], State) -> - {[], State}; -%% optimisation: this head is essentially a partial evaluation of the -%% general case below, for the single-ack case. -ack([SeqId], State) -> - {#msg_status { msg_id = MsgId, - is_persistent = IsPersistent, - msg_on_disk = MsgOnDisk, - index_on_disk = IndexOnDisk }, - State1 = #vqstate { index_state = IndexState, - msg_store_clients = MSCState, - ack_out_counter = AckOutCount }} = - remove_pending_ack(true, SeqId, State), - IndexState1 = case IndexOnDisk of - true -> rabbit_queue_index:ack([SeqId], IndexState); - false -> IndexState - end, - case MsgOnDisk of - true -> ok = msg_store_remove(MSCState, IsPersistent, [MsgId]); - false -> ok - end, - {[MsgId], - a(State1 #vqstate { index_state = IndexState1, - ack_out_counter = AckOutCount + 1 })}; -ack(AckTags, State) -> - {{IndexOnDiskSeqIds, MsgIdsByStore, AllMsgIds}, - State1 = #vqstate { index_state = IndexState, - msg_store_clients = MSCState, - ack_out_counter = AckOutCount }} = - lists:foldl( - fun (SeqId, {Acc, State2}) -> - {MsgStatus, State3} = remove_pending_ack(true, SeqId, State2), - {accumulate_ack(MsgStatus, Acc), State3} - end, {accumulate_ack_init(), State}, AckTags), - IndexState1 = rabbit_queue_index:ack(IndexOnDiskSeqIds, IndexState), - [ok = msg_store_remove(MSCState, IsPersistent, MsgIds) - || {IsPersistent, MsgIds} <- orddict:to_list(MsgIdsByStore)], - {lists:reverse(AllMsgIds), - a(State1 #vqstate { index_state = IndexState1, - ack_out_counter = AckOutCount + length(AckTags) })}. - -requeue(AckTags, #vqstate { delta = Delta, - q3 = Q3, - q4 = Q4, - in_counter = InCounter, - len = Len } = State) -> - {SeqIds, Q4a, MsgIds, State1} = queue_merge(lists:sort(AckTags), Q4, [], - beta_limit(Q3), - fun publish_alpha/2, State), - {SeqIds1, Q3a, MsgIds1, State2} = queue_merge(SeqIds, Q3, MsgIds, - delta_limit(Delta), - fun publish_beta/2, State1), - {Delta1, MsgIds2, State3} = delta_merge(SeqIds1, Delta, MsgIds1, - State2), - MsgCount = length(MsgIds2), - {MsgIds2, a(reduce_memory_use( - maybe_update_rates( - State3 #vqstate { delta = Delta1, - q3 = Q3a, - q4 = Q4a, - in_counter = InCounter + MsgCount, - len = Len + MsgCount })))}. - -ackfold(MsgFun, Acc, State, AckTags) -> - {AccN, StateN} = - lists:foldl(fun(SeqId, {Acc0, State0}) -> - MsgStatus = lookup_pending_ack(SeqId, State0), - {Msg, State1} = read_msg(MsgStatus, State0), - {MsgFun(Msg, SeqId, Acc0), State1} - end, {Acc, State}, AckTags), - {AccN, a(StateN)}. - -fold(Fun, Acc, State = #vqstate{index_state = IndexState}) -> - {Its, IndexState1} = lists:foldl(fun inext/2, {[], IndexState}, - [msg_iterator(State), - disk_ack_iterator(State), - ram_ack_iterator(State)]), - ifold(Fun, Acc, Its, State#vqstate{index_state = IndexState1}). - -len(#vqstate { len = Len }) -> Len. - -is_empty(State) -> 0 == len(State). - -depth(State = #vqstate { ram_pending_ack = RPA, disk_pending_ack = DPA }) -> - len(State) + gb_trees:size(RPA) + gb_trees:size(DPA). - -set_ram_duration_target( - DurationTarget, State = #vqstate { - rates = #rates { in = AvgIngressRate, - out = AvgEgressRate, - ack_in = AvgAckIngressRate, - ack_out = AvgAckEgressRate }, - target_ram_count = TargetRamCount }) -> - Rate = - AvgEgressRate + AvgIngressRate + AvgAckEgressRate + AvgAckIngressRate, - TargetRamCount1 = - case DurationTarget of - infinity -> infinity; - _ -> trunc(DurationTarget * Rate) %% msgs = sec * msgs/sec - end, - State1 = State #vqstate { target_ram_count = TargetRamCount1 }, - a(case TargetRamCount1 == infinity orelse - (TargetRamCount =/= infinity andalso - TargetRamCount1 >= TargetRamCount) of - true -> State1; - false -> reduce_memory_use(State1) - end). - -maybe_update_rates(State = #vqstate{ in_counter = InCount, - out_counter = OutCount }) - when InCount + OutCount > ?MSGS_PER_RATE_CALC -> - update_rates(State); -maybe_update_rates(State) -> - State. - -update_rates(State = #vqstate{ in_counter = InCount, - out_counter = OutCount, - ack_in_counter = AckInCount, - ack_out_counter = AckOutCount, - rates = #rates{ in = InRate, - out = OutRate, - ack_in = AckInRate, - ack_out = AckOutRate, - timestamp = TS }}) -> - Now = erlang:now(), - - Rates = #rates { in = update_rate(Now, TS, InCount, InRate), - out = update_rate(Now, TS, OutCount, OutRate), - ack_in = update_rate(Now, TS, AckInCount, AckInRate), - ack_out = update_rate(Now, TS, AckOutCount, AckOutRate), - timestamp = Now }, - - State#vqstate{ in_counter = 0, - out_counter = 0, - ack_in_counter = 0, - ack_out_counter = 0, - rates = Rates }. - -update_rate(Now, TS, Count, Rate) -> - Time = timer:now_diff(Now, TS) / ?MICROS_PER_SECOND, - rabbit_misc:moving_average(Time, ?RATE_AVG_HALF_LIFE, Count / Time, Rate). - -ram_duration(State) -> - State1 = #vqstate { rates = #rates { in = AvgIngressRate, - out = AvgEgressRate, - ack_in = AvgAckIngressRate, - ack_out = AvgAckEgressRate }, - ram_msg_count = RamMsgCount, - ram_msg_count_prev = RamMsgCountPrev, - ram_pending_ack = RPA, - ram_ack_count_prev = RamAckCountPrev } = - update_rates(State), - - RamAckCount = gb_trees:size(RPA), - - Duration = %% msgs+acks / (msgs+acks/sec) == sec - case lists:all(fun (X) -> X < 0.01 end, - [AvgEgressRate, AvgIngressRate, - AvgAckEgressRate, AvgAckIngressRate]) of - true -> infinity; - false -> (RamMsgCountPrev + RamMsgCount + - RamAckCount + RamAckCountPrev) / - (4 * (AvgEgressRate + AvgIngressRate + - AvgAckEgressRate + AvgAckIngressRate)) - end, - - {Duration, State1}. - -needs_timeout(#vqstate { index_state = IndexState }) -> - case rabbit_queue_index:needs_sync(IndexState) of - confirms -> timed; - other -> idle; - false -> false - end. - -timeout(State = #vqstate { index_state = IndexState }) -> - State #vqstate { index_state = rabbit_queue_index:sync(IndexState) }. - -handle_pre_hibernate(State = #vqstate { index_state = IndexState }) -> - State #vqstate { index_state = rabbit_queue_index:flush(IndexState) }. - -resume(State) -> a(reduce_memory_use(State)). - -msg_rates(#vqstate { rates = #rates { in = AvgIngressRate, - out = AvgEgressRate } }) -> - {AvgIngressRate, AvgEgressRate}. - -info(messages_ready_ram, #vqstate{ram_msg_count = RamMsgCount}) -> - RamMsgCount; -info(messages_unacknowledged_ram, #vqstate{ram_pending_ack = RPA}) -> - gb_trees:size(RPA); -info(messages_ram, State) -> - info(messages_ready_ram, State) + info(messages_unacknowledged_ram, State); -info(messages_persistent, #vqstate{persistent_count = PersistentCount}) -> - PersistentCount; -info(message_bytes, #vqstate{bytes = Bytes, - unacked_bytes = UBytes}) -> - Bytes + UBytes; -info(message_bytes_ready, #vqstate{bytes = Bytes}) -> - Bytes; -info(message_bytes_unacknowledged, #vqstate{unacked_bytes = UBytes}) -> - UBytes; -info(message_bytes_ram, #vqstate{ram_bytes = RamBytes}) -> - RamBytes; -info(message_bytes_persistent, #vqstate{persistent_bytes = PersistentBytes}) -> - PersistentBytes; -info(backing_queue_status, #vqstate { - q1 = Q1, q2 = Q2, delta = Delta, q3 = Q3, q4 = Q4, - len = Len, - target_ram_count = TargetRamCount, - next_seq_id = NextSeqId, - rates = #rates { in = AvgIngressRate, - out = AvgEgressRate, - ack_in = AvgAckIngressRate, - ack_out = AvgAckEgressRate }}) -> - - [ {q1 , ?QUEUE:len(Q1)}, - {q2 , ?QUEUE:len(Q2)}, - {delta , Delta}, - {q3 , ?QUEUE:len(Q3)}, - {q4 , ?QUEUE:len(Q4)}, - {len , Len}, - {target_ram_count , TargetRamCount}, - {next_seq_id , NextSeqId}, - {avg_ingress_rate , AvgIngressRate}, - {avg_egress_rate , AvgEgressRate}, - {avg_ack_ingress_rate, AvgAckIngressRate}, - {avg_ack_egress_rate , AvgAckEgressRate} ]; -info(Item, _) -> - throw({bad_argument, Item}). - -invoke(?MODULE, Fun, State) -> Fun(?MODULE, State); -invoke( _, _, State) -> State. - -is_duplicate(_Msg, State) -> {false, State}. - -%%---------------------------------------------------------------------------- -%% Minor helpers -%%---------------------------------------------------------------------------- - -a(State = #vqstate { q1 = Q1, q2 = Q2, delta = Delta, q3 = Q3, q4 = Q4, - len = Len, - bytes = Bytes, - unacked_bytes = UnackedBytes, - persistent_count = PersistentCount, - persistent_bytes = PersistentBytes, - ram_msg_count = RamMsgCount, - ram_bytes = RamBytes}) -> - E1 = ?QUEUE:is_empty(Q1), - E2 = ?QUEUE:is_empty(Q2), - ED = Delta#delta.count == 0, - E3 = ?QUEUE:is_empty(Q3), - E4 = ?QUEUE:is_empty(Q4), - LZ = Len == 0, - - true = E1 or not E3, - true = E2 or not ED, - true = ED or not E3, - true = LZ == (E3 and E4), - - true = Len >= 0, - true = Bytes >= 0, - true = UnackedBytes >= 0, - true = PersistentCount >= 0, - true = PersistentBytes >= 0, - true = RamMsgCount >= 0, - true = RamMsgCount =< Len, - true = RamBytes >= 0, - true = RamBytes =< Bytes + UnackedBytes, - - State. - -d(Delta = #delta { start_seq_id = Start, count = Count, end_seq_id = End }) - when Start + Count =< End -> - Delta. - -m(MsgStatus = #msg_status { msg = Msg, - is_persistent = IsPersistent, - msg_on_disk = MsgOnDisk, - index_on_disk = IndexOnDisk }) -> - true = (not IsPersistent) or IndexOnDisk, - true = (not IndexOnDisk) or MsgOnDisk, - true = (Msg =/= undefined) or MsgOnDisk, - - MsgStatus. - -one_if(true ) -> 1; -one_if(false) -> 0. - -cons_if(true, E, L) -> [E | L]; -cons_if(false, _E, L) -> L. - -gb_sets_maybe_insert(false, _Val, Set) -> Set; -gb_sets_maybe_insert(true, Val, Set) -> gb_sets:add(Val, Set). - -msg_status(IsPersistent, IsDelivered, SeqId, - Msg = #basic_message {id = MsgId}, MsgProps) -> - #msg_status{seq_id = SeqId, - msg_id = MsgId, - msg = Msg, - is_persistent = IsPersistent, - is_delivered = IsDelivered, - msg_on_disk = false, - index_on_disk = false, - msg_props = MsgProps}. - -beta_msg_status({MsgId, SeqId, MsgProps, IsPersistent, IsDelivered}) -> - #msg_status{seq_id = SeqId, - msg_id = MsgId, - msg = undefined, - is_persistent = IsPersistent, - is_delivered = IsDelivered, - msg_on_disk = true, - index_on_disk = true, - msg_props = MsgProps}. - -trim_msg_status(MsgStatus) -> MsgStatus #msg_status { msg = undefined }. - -with_msg_store_state({MSCStateP, MSCStateT}, true, Fun) -> - {Result, MSCStateP1} = Fun(MSCStateP), - {Result, {MSCStateP1, MSCStateT}}; -with_msg_store_state({MSCStateP, MSCStateT}, false, Fun) -> - {Result, MSCStateT1} = Fun(MSCStateT), - {Result, {MSCStateP, MSCStateT1}}. - -with_immutable_msg_store_state(MSCState, IsPersistent, Fun) -> - {Res, MSCState} = with_msg_store_state(MSCState, IsPersistent, - fun (MSCState1) -> - {Fun(MSCState1), MSCState1} - end), - Res. - -msg_store_client_init(MsgStore, MsgOnDiskFun, Callback) -> - msg_store_client_init(MsgStore, rabbit_guid:gen(), MsgOnDiskFun, - Callback). - -msg_store_client_init(MsgStore, Ref, MsgOnDiskFun, Callback) -> - CloseFDsFun = msg_store_close_fds_fun(MsgStore =:= ?PERSISTENT_MSG_STORE), - rabbit_msg_store:client_init(MsgStore, Ref, MsgOnDiskFun, - fun () -> Callback(?MODULE, CloseFDsFun) end). - -msg_store_write(MSCState, IsPersistent, MsgId, Msg) -> - with_immutable_msg_store_state( - MSCState, IsPersistent, - fun (MSCState1) -> - rabbit_msg_store:write_flow(MsgId, Msg, MSCState1) - end). - -msg_store_read(MSCState, IsPersistent, MsgId) -> - with_msg_store_state( - MSCState, IsPersistent, - fun (MSCState1) -> - rabbit_msg_store:read(MsgId, MSCState1) - end). - -msg_store_remove(MSCState, IsPersistent, MsgIds) -> - with_immutable_msg_store_state( - MSCState, IsPersistent, - fun (MCSState1) -> - rabbit_msg_store:remove(MsgIds, MCSState1) - end). - -msg_store_close_fds(MSCState, IsPersistent) -> - with_msg_store_state( - MSCState, IsPersistent, - fun (MSCState1) -> rabbit_msg_store:close_all_indicated(MSCState1) end). - -msg_store_close_fds_fun(IsPersistent) -> - fun (?MODULE, State = #vqstate { msg_store_clients = MSCState }) -> - {ok, MSCState1} = msg_store_close_fds(MSCState, IsPersistent), - State #vqstate { msg_store_clients = MSCState1 } - end. - -maybe_write_delivered(false, _SeqId, IndexState) -> - IndexState; -maybe_write_delivered(true, SeqId, IndexState) -> - rabbit_queue_index:deliver([SeqId], IndexState). - -betas_from_index_entries(List, TransientThreshold, RPA, DPA, IndexState) -> - {Filtered, Delivers, Acks} = - lists:foldr( - fun ({_MsgId, SeqId, _MsgProps, IsPersistent, IsDelivered} = M, - {Filtered1, Delivers1, Acks1} = Acc) -> - case SeqId < TransientThreshold andalso not IsPersistent of - true -> {Filtered1, - cons_if(not IsDelivered, SeqId, Delivers1), - [SeqId | Acks1]}; - false -> case (gb_trees:is_defined(SeqId, RPA) orelse - gb_trees:is_defined(SeqId, DPA)) of - false -> {?QUEUE:in_r(m(beta_msg_status(M)), - Filtered1), - Delivers1, Acks1}; - true -> Acc - end - end - end, {?QUEUE:new(), [], []}, List), - {Filtered, rabbit_queue_index:ack( - Acks, rabbit_queue_index:deliver(Delivers, IndexState))}. - -expand_delta(SeqId, ?BLANK_DELTA_PATTERN(X)) -> - d(#delta { start_seq_id = SeqId, count = 1, end_seq_id = SeqId + 1 }); -expand_delta(SeqId, #delta { start_seq_id = StartSeqId, - count = Count } = Delta) - when SeqId < StartSeqId -> - d(Delta #delta { start_seq_id = SeqId, count = Count + 1 }); -expand_delta(SeqId, #delta { count = Count, - end_seq_id = EndSeqId } = Delta) - when SeqId >= EndSeqId -> - d(Delta #delta { count = Count + 1, end_seq_id = SeqId + 1 }); -expand_delta(_SeqId, #delta { count = Count } = Delta) -> - d(Delta #delta { count = Count + 1 }). - -%%---------------------------------------------------------------------------- -%% Internal major helpers for Public API -%%---------------------------------------------------------------------------- - -init(IsDurable, IndexState, DeltaCount, DeltaBytes, Terms, - PersistentClient, TransientClient) -> - {LowSeqId, NextSeqId, IndexState1} = rabbit_queue_index:bounds(IndexState), - - {DeltaCount1, DeltaBytes1} = - case Terms of - non_clean_shutdown -> {DeltaCount, DeltaBytes}; - _ -> {proplists:get_value(persistent_count, - Terms, DeltaCount), - proplists:get_value(persistent_bytes, - Terms, DeltaBytes)} - end, - Delta = case DeltaCount1 == 0 andalso DeltaCount /= undefined of - true -> ?BLANK_DELTA; - false -> d(#delta { start_seq_id = LowSeqId, - count = DeltaCount1, - end_seq_id = NextSeqId }) - end, - Now = now(), - State = #vqstate { - q1 = ?QUEUE:new(), - q2 = ?QUEUE:new(), - delta = Delta, - q3 = ?QUEUE:new(), - q4 = ?QUEUE:new(), - next_seq_id = NextSeqId, - ram_pending_ack = gb_trees:empty(), - disk_pending_ack = gb_trees:empty(), - index_state = IndexState1, - msg_store_clients = {PersistentClient, TransientClient}, - durable = IsDurable, - transient_threshold = NextSeqId, - - len = DeltaCount1, - persistent_count = DeltaCount1, - bytes = DeltaBytes1, - persistent_bytes = DeltaBytes1, - - target_ram_count = infinity, - ram_msg_count = 0, - ram_msg_count_prev = 0, - ram_ack_count_prev = 0, - ram_bytes = 0, - unacked_bytes = 0, - out_counter = 0, - in_counter = 0, - rates = blank_rates(Now), - msgs_on_disk = gb_sets:new(), - msg_indices_on_disk = gb_sets:new(), - unconfirmed = gb_sets:new(), - confirmed = gb_sets:new(), - ack_out_counter = 0, - ack_in_counter = 0 }, - a(maybe_deltas_to_betas(State)). - -blank_rates(Now) -> - #rates { in = 0.0, - out = 0.0, - ack_in = 0.0, - ack_out = 0.0, - timestamp = Now}. - -in_r(MsgStatus = #msg_status { msg = undefined }, - State = #vqstate { q3 = Q3, q4 = Q4 }) -> - case ?QUEUE:is_empty(Q4) of - true -> State #vqstate { q3 = ?QUEUE:in_r(MsgStatus, Q3) }; - false -> {Msg, State1 = #vqstate { q4 = Q4a }} = - read_msg(MsgStatus, State), - upd_ram_bytes( - 1, MsgStatus, - inc_ram_msg_count( - State1 #vqstate { q4 = ?QUEUE:in_r(MsgStatus#msg_status { - msg = Msg }, Q4a) })) - end; -in_r(MsgStatus, State = #vqstate { q4 = Q4 }) -> - State #vqstate { q4 = ?QUEUE:in_r(MsgStatus, Q4) }. - -queue_out(State = #vqstate { q4 = Q4 }) -> - case ?QUEUE:out(Q4) of - {empty, _Q4} -> - case fetch_from_q3(State) of - {empty, _State1} = Result -> Result; - {loaded, {MsgStatus, State1}} -> {{value, MsgStatus}, State1} - end; - {{value, MsgStatus}, Q4a} -> - {{value, MsgStatus}, State #vqstate { q4 = Q4a }} - end. - -read_msg(#msg_status{msg = undefined, - msg_id = MsgId, - is_persistent = IsPersistent}, State) -> - read_msg(MsgId, IsPersistent, State); -read_msg(#msg_status{msg = Msg}, State) -> - {Msg, State}. - -read_msg(MsgId, IsPersistent, State = #vqstate{msg_store_clients = MSCState}) -> - {{ok, Msg = #basic_message {}}, MSCState1} = - msg_store_read(MSCState, IsPersistent, MsgId), - {Msg, State #vqstate {msg_store_clients = MSCState1}}. - -inc_ram_msg_count(State = #vqstate{ram_msg_count = RamMsgCount}) -> - State#vqstate{ram_msg_count = RamMsgCount + 1}. - -upd_bytes(SignReady, SignUnacked, - MsgStatus = #msg_status{msg = undefined}, State) -> - upd_bytes0(SignReady, SignUnacked, MsgStatus, State); -upd_bytes(SignReady, SignUnacked, MsgStatus = #msg_status{msg = _}, State) -> - upd_ram_bytes(SignReady + SignUnacked, MsgStatus, - upd_bytes0(SignReady, SignUnacked, MsgStatus, State)). - -upd_bytes0(SignReady, SignUnacked, MsgStatus = #msg_status{is_persistent = IsP}, - State = #vqstate{bytes = Bytes, - unacked_bytes = UBytes, - persistent_bytes = PBytes}) -> - S = msg_size(MsgStatus), - SignTotal = SignReady + SignUnacked, - State#vqstate{bytes = Bytes + SignReady * S, - unacked_bytes = UBytes + SignUnacked * S, - persistent_bytes = PBytes + one_if(IsP) * S * SignTotal}. - -upd_ram_bytes(Sign, MsgStatus, State = #vqstate{ram_bytes = RamBytes}) -> - State#vqstate{ram_bytes = RamBytes + Sign * msg_size(MsgStatus)}. - -msg_size(#msg_status{msg_props = #message_properties{size = Size}}) -> Size. - -remove(AckRequired, MsgStatus = #msg_status { - seq_id = SeqId, - msg_id = MsgId, - msg = Msg, - is_persistent = IsPersistent, - is_delivered = IsDelivered, - msg_on_disk = MsgOnDisk, - index_on_disk = IndexOnDisk }, - State = #vqstate {ram_msg_count = RamMsgCount, - out_counter = OutCount, - index_state = IndexState, - msg_store_clients = MSCState, - len = Len, - persistent_count = PCount}) -> - %% 1. Mark it delivered if necessary - IndexState1 = maybe_write_delivered( - IndexOnDisk andalso not IsDelivered, - SeqId, IndexState), - - %% 2. Remove from msg_store and queue index, if necessary - Rem = fun () -> - ok = msg_store_remove(MSCState, IsPersistent, [MsgId]) - end, - Ack = fun () -> rabbit_queue_index:ack([SeqId], IndexState1) end, - IndexState2 = case {AckRequired, MsgOnDisk, IndexOnDisk} of - {false, true, false} -> Rem(), IndexState1; - {false, true, true} -> Rem(), Ack(); - _ -> IndexState1 - end, - - %% 3. If an ack is required, add something sensible to PA - {AckTag, State1} = case AckRequired of - true -> StateN = record_pending_ack( - MsgStatus #msg_status { - is_delivered = true }, State), - {SeqId, StateN}; - false -> {undefined, State} - end, - - PCount1 = PCount - one_if(IsPersistent andalso not AckRequired), - RamMsgCount1 = RamMsgCount - one_if(Msg =/= undefined), - State2 = case AckRequired of - false -> upd_bytes(-1, 0, MsgStatus, State1); - true -> upd_bytes(-1, 1, MsgStatus, State1) - end, - {AckTag, maybe_update_rates( - State2 #vqstate {ram_msg_count = RamMsgCount1, - out_counter = OutCount + 1, - index_state = IndexState2, - len = Len - 1, - persistent_count = PCount1})}. - -purge_betas_and_deltas(Stats, - State = #vqstate { q3 = Q3, - index_state = IndexState, - msg_store_clients = MSCState }) -> - case ?QUEUE:is_empty(Q3) of - true -> {Stats, State}; - false -> {Stats1, IndexState1} = remove_queue_entries( - Q3, Stats, IndexState, MSCState), - purge_betas_and_deltas(Stats1, - maybe_deltas_to_betas( - State #vqstate { - q3 = ?QUEUE:new(), - index_state = IndexState1 })) - end. - -remove_queue_entries(Q, {RamBytes, PCount, PBytes}, - IndexState, MSCState) -> - {MsgIdsByStore, RamBytes1, PBytes1, Delivers, Acks} = - ?QUEUE:foldl(fun remove_queue_entries1/2, - {orddict:new(), RamBytes, PBytes, [], []}, Q), - ok = orddict:fold(fun (IsPersistent, MsgIds, ok) -> - msg_store_remove(MSCState, IsPersistent, MsgIds) - end, ok, MsgIdsByStore), - {{RamBytes1, - PCount - case orddict:find(true, MsgIdsByStore) of - error -> 0; - {ok, Ids} -> length(Ids) - end, - PBytes1}, - rabbit_queue_index:ack(Acks, - rabbit_queue_index:deliver(Delivers, IndexState))}. - -remove_queue_entries1( - #msg_status { msg_id = MsgId, seq_id = SeqId, msg = Msg, - is_delivered = IsDelivered, msg_on_disk = MsgOnDisk, - index_on_disk = IndexOnDisk, is_persistent = IsPersistent, - msg_props = #message_properties { size = Size } }, - {MsgIdsByStore, RamBytes, PBytes, Delivers, Acks}) -> - {case MsgOnDisk of - true -> rabbit_misc:orddict_cons(IsPersistent, MsgId, MsgIdsByStore); - false -> MsgIdsByStore - end, - RamBytes - Size * one_if(Msg =/= undefined), - PBytes - Size * one_if(IsPersistent), - cons_if(IndexOnDisk andalso not IsDelivered, SeqId, Delivers), - cons_if(IndexOnDisk, SeqId, Acks)}. - -%%---------------------------------------------------------------------------- -%% Internal gubbins for publishing -%%---------------------------------------------------------------------------- - -maybe_write_msg_to_disk(_Force, MsgStatus = #msg_status { - msg_on_disk = true }, _MSCState) -> - MsgStatus; -maybe_write_msg_to_disk(Force, MsgStatus = #msg_status { - msg = Msg, msg_id = MsgId, - is_persistent = IsPersistent }, MSCState) - when Force orelse IsPersistent -> - Msg1 = Msg #basic_message { - %% don't persist any recoverable decoded properties - content = rabbit_binary_parser:clear_decoded_content( - Msg #basic_message.content)}, - ok = msg_store_write(MSCState, IsPersistent, MsgId, Msg1), - MsgStatus #msg_status { msg_on_disk = true }; -maybe_write_msg_to_disk(_Force, MsgStatus, _MSCState) -> - MsgStatus. - -maybe_write_index_to_disk(_Force, MsgStatus = #msg_status { - index_on_disk = true }, IndexState) -> - true = MsgStatus #msg_status.msg_on_disk, %% ASSERTION - {MsgStatus, IndexState}; -maybe_write_index_to_disk(Force, MsgStatus = #msg_status { - msg_id = MsgId, - seq_id = SeqId, - is_persistent = IsPersistent, - is_delivered = IsDelivered, - msg_props = MsgProps}, IndexState) - when Force orelse IsPersistent -> - true = MsgStatus #msg_status.msg_on_disk, %% ASSERTION - IndexState1 = rabbit_queue_index:publish( - MsgId, SeqId, MsgProps, IsPersistent, IndexState), - {MsgStatus #msg_status { index_on_disk = true }, - maybe_write_delivered(IsDelivered, SeqId, IndexState1)}; -maybe_write_index_to_disk(_Force, MsgStatus, IndexState) -> - {MsgStatus, IndexState}. - -maybe_write_to_disk(ForceMsg, ForceIndex, MsgStatus, - State = #vqstate { index_state = IndexState, - msg_store_clients = MSCState }) -> - MsgStatus1 = maybe_write_msg_to_disk(ForceMsg, MsgStatus, MSCState), - {MsgStatus2, IndexState1} = - maybe_write_index_to_disk(ForceIndex, MsgStatus1, IndexState), - {MsgStatus2, State #vqstate { index_state = IndexState1 }}. - -%%---------------------------------------------------------------------------- -%% Internal gubbins for acks -%%---------------------------------------------------------------------------- - -record_pending_ack(#msg_status { seq_id = SeqId, msg = Msg } = MsgStatus, - State = #vqstate { ram_pending_ack = RPA, - disk_pending_ack = DPA, - ack_in_counter = AckInCount}) -> - {RPA1, DPA1} = - case Msg of - undefined -> {RPA, gb_trees:insert(SeqId, MsgStatus, DPA)}; - _ -> {gb_trees:insert(SeqId, MsgStatus, RPA), DPA} - end, - State #vqstate { ram_pending_ack = RPA1, - disk_pending_ack = DPA1, - ack_in_counter = AckInCount + 1}. - -lookup_pending_ack(SeqId, #vqstate { ram_pending_ack = RPA, - disk_pending_ack = DPA }) -> - case gb_trees:lookup(SeqId, RPA) of - {value, V} -> V; - none -> gb_trees:get(SeqId, DPA) - end. - -%% First parameter = UpdatePersistentCount -remove_pending_ack(true, SeqId, State) -> - {MsgStatus, State1 = #vqstate { persistent_count = PCount }} = - remove_pending_ack(false, SeqId, State), - PCount1 = PCount - one_if(MsgStatus#msg_status.is_persistent), - {MsgStatus, upd_bytes(0, -1, MsgStatus, - State1 # vqstate{ persistent_count = PCount1 })}; -remove_pending_ack(false, SeqId, State = #vqstate { ram_pending_ack = RPA, - disk_pending_ack = DPA }) -> - case gb_trees:lookup(SeqId, RPA) of - {value, V} -> RPA1 = gb_trees:delete(SeqId, RPA), - {V, State #vqstate { ram_pending_ack = RPA1 }}; - none -> DPA1 = gb_trees:delete(SeqId, DPA), - {gb_trees:get(SeqId, DPA), - State #vqstate { disk_pending_ack = DPA1 }} - end. - -purge_pending_ack(KeepPersistent, - State = #vqstate { ram_pending_ack = RPA, - disk_pending_ack = DPA, - index_state = IndexState, - msg_store_clients = MSCState }) -> - F = fun (_SeqId, MsgStatus, Acc) -> accumulate_ack(MsgStatus, Acc) end, - {IndexOnDiskSeqIds, MsgIdsByStore, _AllMsgIds} = - rabbit_misc:gb_trees_fold( - F, rabbit_misc:gb_trees_fold(F, accumulate_ack_init(), RPA), DPA), - State1 = State #vqstate { ram_pending_ack = gb_trees:empty(), - disk_pending_ack = gb_trees:empty() }, - - case KeepPersistent of - true -> case orddict:find(false, MsgIdsByStore) of - error -> State1; - {ok, MsgIds} -> ok = msg_store_remove(MSCState, false, - MsgIds), - State1 - end; - false -> IndexState1 = - rabbit_queue_index:ack(IndexOnDiskSeqIds, IndexState), - [ok = msg_store_remove(MSCState, IsPersistent, MsgIds) - || {IsPersistent, MsgIds} <- orddict:to_list(MsgIdsByStore)], - State1 #vqstate { index_state = IndexState1 } - end. - -accumulate_ack_init() -> {[], orddict:new(), []}. - -accumulate_ack(#msg_status { seq_id = SeqId, - msg_id = MsgId, - is_persistent = IsPersistent, - msg_on_disk = MsgOnDisk, - index_on_disk = IndexOnDisk }, - {IndexOnDiskSeqIdsAcc, MsgIdsByStore, AllMsgIds}) -> - {cons_if(IndexOnDisk, SeqId, IndexOnDiskSeqIdsAcc), - case MsgOnDisk of - true -> rabbit_misc:orddict_cons(IsPersistent, MsgId, MsgIdsByStore); - false -> MsgIdsByStore - end, - [MsgId | AllMsgIds]}. - -%%---------------------------------------------------------------------------- -%% Internal plumbing for confirms (aka publisher acks) -%%---------------------------------------------------------------------------- - -record_confirms(MsgIdSet, State = #vqstate { msgs_on_disk = MOD, - msg_indices_on_disk = MIOD, - unconfirmed = UC, - confirmed = C }) -> - State #vqstate { - msgs_on_disk = rabbit_misc:gb_sets_difference(MOD, MsgIdSet), - msg_indices_on_disk = rabbit_misc:gb_sets_difference(MIOD, MsgIdSet), - unconfirmed = rabbit_misc:gb_sets_difference(UC, MsgIdSet), - confirmed = gb_sets:union(C, MsgIdSet) }. - -msgs_written_to_disk(Callback, MsgIdSet, ignored) -> - Callback(?MODULE, - fun (?MODULE, State) -> record_confirms(MsgIdSet, State) end); -msgs_written_to_disk(Callback, MsgIdSet, written) -> - Callback(?MODULE, - fun (?MODULE, State = #vqstate { msgs_on_disk = MOD, - msg_indices_on_disk = MIOD, - unconfirmed = UC }) -> - Confirmed = gb_sets:intersection(UC, MsgIdSet), - record_confirms(gb_sets:intersection(MsgIdSet, MIOD), - State #vqstate { - msgs_on_disk = - gb_sets:union(MOD, Confirmed) }) - end). - -msg_indices_written_to_disk(Callback, MsgIdSet) -> - Callback(?MODULE, - fun (?MODULE, State = #vqstate { msgs_on_disk = MOD, - msg_indices_on_disk = MIOD, - unconfirmed = UC }) -> - Confirmed = gb_sets:intersection(UC, MsgIdSet), - record_confirms(gb_sets:intersection(MsgIdSet, MOD), - State #vqstate { - msg_indices_on_disk = - gb_sets:union(MIOD, Confirmed) }) - end). - -%%---------------------------------------------------------------------------- -%% Internal plumbing for requeue -%%---------------------------------------------------------------------------- - -publish_alpha(#msg_status { msg = undefined } = MsgStatus, State) -> - {Msg, State1} = read_msg(MsgStatus, State), - {MsgStatus#msg_status { msg = Msg }, - upd_ram_bytes(1, MsgStatus, inc_ram_msg_count(State1))}; %% [1] -publish_alpha(MsgStatus, State) -> - {MsgStatus, inc_ram_msg_count(State)}. -%% [1] We increase the ram_bytes here because we paged the message in -%% to requeue it, not purely because we requeued it. Hence in the -%% second head it's already accounted for as already in memory. OTOH -%% ram_msg_count does not include unacked messages, so it needs -%% incrementing in both heads. - -publish_beta(MsgStatus, State) -> - {MsgStatus1, State1} = maybe_write_to_disk(true, false, MsgStatus, State), - {m(trim_msg_status(MsgStatus1)), State1}. - -%% Rebuild queue, inserting sequence ids to maintain ordering -queue_merge(SeqIds, Q, MsgIds, Limit, PubFun, State) -> - queue_merge(SeqIds, Q, ?QUEUE:new(), MsgIds, - Limit, PubFun, State). - -queue_merge([SeqId | Rest] = SeqIds, Q, Front, MsgIds, - Limit, PubFun, State) - when Limit == undefined orelse SeqId < Limit -> - case ?QUEUE:out(Q) of - {{value, #msg_status { seq_id = SeqIdQ } = MsgStatus}, Q1} - when SeqIdQ < SeqId -> - %% enqueue from the remaining queue - queue_merge(SeqIds, Q1, ?QUEUE:in(MsgStatus, Front), MsgIds, - Limit, PubFun, State); - {_, _Q1} -> - %% enqueue from the remaining list of sequence ids - {MsgStatus, State1} = msg_from_pending_ack(SeqId, State), - {#msg_status { msg_id = MsgId } = MsgStatus1, State2} = - PubFun(MsgStatus, State1), - queue_merge(Rest, Q, ?QUEUE:in(MsgStatus1, Front), [MsgId | MsgIds], - Limit, PubFun, upd_bytes(1, -1, MsgStatus, State2)) - end; -queue_merge(SeqIds, Q, Front, MsgIds, - _Limit, _PubFun, State) -> - {SeqIds, ?QUEUE:join(Front, Q), MsgIds, State}. - -delta_merge([], Delta, MsgIds, State) -> - {Delta, MsgIds, State}; -delta_merge(SeqIds, Delta, MsgIds, State) -> - lists:foldl(fun (SeqId, {Delta0, MsgIds0, State0}) -> - {#msg_status { msg_id = MsgId } = MsgStatus, State1} = - msg_from_pending_ack(SeqId, State0), - {_MsgStatus, State2} = - maybe_write_to_disk(true, true, MsgStatus, State1), - {expand_delta(SeqId, Delta0), [MsgId | MsgIds0], - upd_bytes(1, -1, MsgStatus, State2)} - end, {Delta, MsgIds, State}, SeqIds). - -%% Mostly opposite of record_pending_ack/2 -msg_from_pending_ack(SeqId, State) -> - {#msg_status { msg_props = MsgProps } = MsgStatus, State1} = - remove_pending_ack(false, SeqId, State), - {MsgStatus #msg_status { - msg_props = MsgProps #message_properties { needs_confirming = false } }, - State1}. - -beta_limit(Q) -> - case ?QUEUE:peek(Q) of - {value, #msg_status { seq_id = SeqId }} -> SeqId; - empty -> undefined - end. - -delta_limit(?BLANK_DELTA_PATTERN(_X)) -> undefined; -delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. - -%%---------------------------------------------------------------------------- -%% Iterator -%%---------------------------------------------------------------------------- - -ram_ack_iterator(State) -> - {ack, gb_trees:iterator(State#vqstate.ram_pending_ack)}. - -disk_ack_iterator(State) -> - {ack, gb_trees:iterator(State#vqstate.disk_pending_ack)}. - -msg_iterator(State) -> istate(start, State). - -istate(start, State) -> {q4, State#vqstate.q4, State}; -istate(q4, State) -> {q3, State#vqstate.q3, State}; -istate(q3, State) -> {delta, State#vqstate.delta, State}; -istate(delta, State) -> {q2, State#vqstate.q2, State}; -istate(q2, State) -> {q1, State#vqstate.q1, State}; -istate(q1, _State) -> done. - -next({ack, It}, IndexState) -> - case gb_trees:next(It) of - none -> {empty, IndexState}; - {_SeqId, MsgStatus, It1} -> Next = {ack, It1}, - {value, MsgStatus, true, Next, IndexState} - end; -next(done, IndexState) -> {empty, IndexState}; -next({delta, #delta{start_seq_id = SeqId, - end_seq_id = SeqId}, State}, IndexState) -> - next(istate(delta, State), IndexState); -next({delta, #delta{start_seq_id = SeqId, - end_seq_id = SeqIdEnd} = Delta, State}, IndexState) -> - SeqIdB = rabbit_queue_index:next_segment_boundary(SeqId), - SeqId1 = lists:min([SeqIdB, SeqIdEnd]), - {List, IndexState1} = rabbit_queue_index:read(SeqId, SeqId1, IndexState), - next({delta, Delta#delta{start_seq_id = SeqId1}, List, State}, IndexState1); -next({delta, Delta, [], State}, IndexState) -> - next({delta, Delta, State}, IndexState); -next({delta, Delta, [{_, SeqId, _, _, _} = M | Rest], State}, IndexState) -> - case (gb_trees:is_defined(SeqId, State#vqstate.ram_pending_ack) orelse - gb_trees:is_defined(SeqId, State#vqstate.disk_pending_ack)) of - false -> Next = {delta, Delta, Rest, State}, - {value, beta_msg_status(M), false, Next, IndexState}; - true -> next({delta, Delta, Rest, State}, IndexState) - end; -next({Key, Q, State}, IndexState) -> - case ?QUEUE:out(Q) of - {empty, _Q} -> next(istate(Key, State), IndexState); - {{value, MsgStatus}, QN} -> Next = {Key, QN, State}, - {value, MsgStatus, false, Next, IndexState} - end. - -inext(It, {Its, IndexState}) -> - case next(It, IndexState) of - {empty, IndexState1} -> - {Its, IndexState1}; - {value, MsgStatus1, Unacked, It1, IndexState1} -> - {[{MsgStatus1, Unacked, It1} | Its], IndexState1} - end. - -ifold(_Fun, Acc, [], State) -> - {Acc, State}; -ifold(Fun, Acc, Its, State) -> - [{MsgStatus, Unacked, It} | Rest] = - lists:sort(fun ({#msg_status{seq_id = SeqId1}, _, _}, - {#msg_status{seq_id = SeqId2}, _, _}) -> - SeqId1 =< SeqId2 - end, Its), - {Msg, State1} = read_msg(MsgStatus, State), - case Fun(Msg, MsgStatus#msg_status.msg_props, Unacked, Acc) of - {stop, Acc1} -> - {Acc1, State}; - {cont, Acc1} -> - {Its1, IndexState1} = inext(It, {Rest, State1#vqstate.index_state}), - ifold(Fun, Acc1, Its1, State1#vqstate{index_state = IndexState1}) - end. - -%%---------------------------------------------------------------------------- -%% Phase changes -%%---------------------------------------------------------------------------- - -reduce_memory_use(State = #vqstate { target_ram_count = infinity }) -> - State; -reduce_memory_use(State = #vqstate { - ram_pending_ack = RPA, - ram_msg_count = RamMsgCount, - target_ram_count = TargetRamCount, - rates = #rates { in = AvgIngress, - out = AvgEgress, - ack_in = AvgAckIngress, - ack_out = AvgAckEgress } }) -> - - State1 = #vqstate { q2 = Q2, q3 = Q3 } = - case chunk_size(RamMsgCount + gb_trees:size(RPA), TargetRamCount) of - 0 -> State; - %% Reduce memory of pending acks and alphas. The order is - %% determined based on which is growing faster. Whichever - %% comes second may very well get a quota of 0 if the - %% first manages to push out the max number of messages. - S1 -> Funs = case ((AvgAckIngress - AvgAckEgress) > - (AvgIngress - AvgEgress)) of - true -> [fun limit_ram_acks/2, - fun push_alphas_to_betas/2]; - false -> [fun push_alphas_to_betas/2, - fun limit_ram_acks/2] - end, - {_, State2} = lists:foldl(fun (ReduceFun, {QuotaN, StateN}) -> - ReduceFun(QuotaN, StateN) - end, {S1, State}, Funs), - State2 - end, - - case chunk_size(?QUEUE:len(Q2) + ?QUEUE:len(Q3), - permitted_beta_count(State1)) of - S2 when S2 >= ?IO_BATCH_SIZE -> - %% There is an implicit, but subtle, upper bound here. We - %% may shuffle a lot of messages from Q2/3 into delta, but - %% the number of these that require any disk operation, - %% namely index writing, i.e. messages that are genuine - %% betas and not gammas, is bounded by the credit_flow - %% limiting of the alpha->beta conversion above. - push_betas_to_deltas(S2, State1); - _ -> - State1 - end. - -limit_ram_acks(0, State) -> - {0, State}; -limit_ram_acks(Quota, State = #vqstate { ram_pending_ack = RPA, - disk_pending_ack = DPA }) -> - case gb_trees:is_empty(RPA) of - true -> - {Quota, State}; - false -> - {SeqId, MsgStatus, RPA1} = gb_trees:take_largest(RPA), - {MsgStatus1, State1} = - maybe_write_to_disk(true, false, MsgStatus, State), - DPA1 = gb_trees:insert(SeqId, m(trim_msg_status(MsgStatus1)), DPA), - limit_ram_acks(Quota - 1, - upd_ram_bytes( - -1, MsgStatus1, - State1 #vqstate { ram_pending_ack = RPA1, - disk_pending_ack = DPA1 })) - end. - -permitted_beta_count(#vqstate { len = 0 }) -> - infinity; -permitted_beta_count(#vqstate { target_ram_count = 0, q3 = Q3 }) -> - lists:min([?QUEUE:len(Q3), rabbit_queue_index:next_segment_boundary(0)]); -permitted_beta_count(#vqstate { q1 = Q1, - q4 = Q4, - target_ram_count = TargetRamCount, - len = Len }) -> - BetaDelta = Len - ?QUEUE:len(Q1) - ?QUEUE:len(Q4), - lists:max([rabbit_queue_index:next_segment_boundary(0), - BetaDelta - ((BetaDelta * BetaDelta) div - (BetaDelta + TargetRamCount))]). - -chunk_size(Current, Permitted) - when Permitted =:= infinity orelse Permitted >= Current -> - 0; -chunk_size(Current, Permitted) -> - Current - Permitted. - -fetch_from_q3(State = #vqstate { q1 = Q1, - q2 = Q2, - delta = #delta { count = DeltaCount }, - q3 = Q3, - q4 = Q4 }) -> - case ?QUEUE:out(Q3) of - {empty, _Q3} -> - {empty, State}; - {{value, MsgStatus}, Q3a} -> - State1 = State #vqstate { q3 = Q3a }, - State2 = case {?QUEUE:is_empty(Q3a), 0 == DeltaCount} of - {true, true} -> - %% q3 is now empty, it wasn't before; - %% delta is still empty. So q2 must be - %% empty, and we know q4 is empty - %% otherwise we wouldn't be loading from - %% q3. As such, we can just set q4 to Q1. - true = ?QUEUE:is_empty(Q2), %% ASSERTION - true = ?QUEUE:is_empty(Q4), %% ASSERTION - State1 #vqstate { q1 = ?QUEUE:new(), q4 = Q1 }; - {true, false} -> - maybe_deltas_to_betas(State1); - {false, _} -> - %% q3 still isn't empty, we've not - %% touched delta, so the invariants - %% between q1, q2, delta and q3 are - %% maintained - State1 - end, - {loaded, {MsgStatus, State2}} - end. - -maybe_deltas_to_betas(State = #vqstate { delta = ?BLANK_DELTA_PATTERN(X) }) -> - State; -maybe_deltas_to_betas(State = #vqstate { - q2 = Q2, - delta = Delta, - q3 = Q3, - index_state = IndexState, - ram_pending_ack = RPA, - disk_pending_ack = DPA, - transient_threshold = TransientThreshold }) -> - #delta { start_seq_id = DeltaSeqId, - count = DeltaCount, - end_seq_id = DeltaSeqIdEnd } = Delta, - DeltaSeqId1 = - lists:min([rabbit_queue_index:next_segment_boundary(DeltaSeqId), - DeltaSeqIdEnd]), - {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, - IndexState), - {Q3a, IndexState2} = betas_from_index_entries(List, TransientThreshold, - RPA, DPA, IndexState1), - State1 = State #vqstate { index_state = IndexState2 }, - case ?QUEUE:len(Q3a) of - 0 -> - %% we ignored every message in the segment due to it being - %% transient and below the threshold - maybe_deltas_to_betas( - State1 #vqstate { - delta = d(Delta #delta { start_seq_id = DeltaSeqId1 })}); - Q3aLen -> - Q3b = ?QUEUE:join(Q3, Q3a), - case DeltaCount - Q3aLen of - 0 -> - %% delta is now empty, but it wasn't before, so - %% can now join q2 onto q3 - State1 #vqstate { q2 = ?QUEUE:new(), - delta = ?BLANK_DELTA, - q3 = ?QUEUE:join(Q3b, Q2) }; - N when N > 0 -> - Delta1 = d(#delta { start_seq_id = DeltaSeqId1, - count = N, - end_seq_id = DeltaSeqIdEnd }), - State1 #vqstate { delta = Delta1, - q3 = Q3b } - end - end. - -push_alphas_to_betas(Quota, State) -> - {Quota1, State1} = - push_alphas_to_betas( - fun ?QUEUE:out/1, - fun (MsgStatus, Q1a, - State0 = #vqstate { q3 = Q3, delta = #delta { count = 0 } }) -> - State0 #vqstate { q1 = Q1a, q3 = ?QUEUE:in(MsgStatus, Q3) }; - (MsgStatus, Q1a, State0 = #vqstate { q2 = Q2 }) -> - State0 #vqstate { q1 = Q1a, q2 = ?QUEUE:in(MsgStatus, Q2) } - end, Quota, State #vqstate.q1, State), - {Quota2, State2} = - push_alphas_to_betas( - fun ?QUEUE:out_r/1, - fun (MsgStatus, Q4a, State0 = #vqstate { q3 = Q3 }) -> - State0 #vqstate { q3 = ?QUEUE:in_r(MsgStatus, Q3), q4 = Q4a } - end, Quota1, State1 #vqstate.q4, State1), - {Quota2, State2}. - -push_alphas_to_betas(_Generator, _Consumer, Quota, _Q, - State = #vqstate { ram_msg_count = RamMsgCount, - target_ram_count = TargetRamCount }) - when Quota =:= 0 orelse - TargetRamCount =:= infinity orelse - TargetRamCount >= RamMsgCount -> - {Quota, State}; -push_alphas_to_betas(Generator, Consumer, Quota, Q, State) -> - case credit_flow:blocked() of - true -> {Quota, State}; - false -> case Generator(Q) of - {empty, _Q} -> - {Quota, State}; - {{value, MsgStatus}, Qa} -> - {MsgStatus1 = #msg_status { msg_on_disk = true }, - State1 = #vqstate { ram_msg_count = RamMsgCount }} = - maybe_write_to_disk(true, false, MsgStatus, State), - MsgStatus2 = m(trim_msg_status(MsgStatus1)), - State2 = Consumer( - MsgStatus2, Qa, - upd_ram_bytes( - -1, MsgStatus2, - State1 #vqstate { - ram_msg_count = RamMsgCount - 1})), - push_alphas_to_betas(Generator, Consumer, Quota - 1, - Qa, State2) - end - end. - -push_betas_to_deltas(Quota, State = #vqstate { q2 = Q2, - delta = Delta, - q3 = Q3, - index_state = IndexState }) -> - PushState = {Quota, Delta, IndexState}, - {Q3a, PushState1} = push_betas_to_deltas( - fun ?QUEUE:out_r/1, - fun rabbit_queue_index:next_segment_boundary/1, - Q3, PushState), - {Q2a, PushState2} = push_betas_to_deltas( - fun ?QUEUE:out/1, - fun (Q2MinSeqId) -> Q2MinSeqId end, - Q2, PushState1), - {_, Delta1, IndexState1} = PushState2, - State #vqstate { q2 = Q2a, - delta = Delta1, - q3 = Q3a, - index_state = IndexState1 }. - -push_betas_to_deltas(Generator, LimitFun, Q, PushState) -> - case ?QUEUE:is_empty(Q) of - true -> - {Q, PushState}; - false -> - {value, #msg_status { seq_id = MinSeqId }} = ?QUEUE:peek(Q), - {value, #msg_status { seq_id = MaxSeqId }} = ?QUEUE:peek_r(Q), - Limit = LimitFun(MinSeqId), - case MaxSeqId < Limit of - true -> {Q, PushState}; - false -> push_betas_to_deltas1(Generator, Limit, Q, PushState) - end - end. - -push_betas_to_deltas1(_Generator, _Limit, Q, - {0, _Delta, _IndexState} = PushState) -> - {Q, PushState}; -push_betas_to_deltas1(Generator, Limit, Q, - {Quota, Delta, IndexState} = PushState) -> - case Generator(Q) of - {empty, _Q} -> - {Q, PushState}; - {{value, #msg_status { seq_id = SeqId }}, _Qa} - when SeqId < Limit -> - {Q, PushState}; - {{value, MsgStatus = #msg_status { seq_id = SeqId }}, Qa} -> - {#msg_status { index_on_disk = true }, IndexState1} = - maybe_write_index_to_disk(true, MsgStatus, IndexState), - Delta1 = expand_delta(SeqId, Delta), - push_betas_to_deltas1(Generator, Limit, Qa, - {Quota - 1, Delta1, IndexState1}) - end. - -%%---------------------------------------------------------------------------- -%% Upgrading -%%---------------------------------------------------------------------------- - -multiple_routing_keys() -> - transform_storage( - fun ({basic_message, ExchangeName, Routing_Key, Content, - MsgId, Persistent}) -> - {ok, {basic_message, ExchangeName, [Routing_Key], Content, - MsgId, Persistent}}; - (_) -> {error, corrupt_message} - end), - ok. - - -%% Assumes message store is not running -transform_storage(TransformFun) -> - transform_store(?PERSISTENT_MSG_STORE, TransformFun), - transform_store(?TRANSIENT_MSG_STORE, TransformFun). - -transform_store(Store, TransformFun) -> - rabbit_msg_store:force_recovery(rabbit_mnesia:dir(), Store), - rabbit_msg_store:transform_dir(rabbit_mnesia:dir(), Store, TransformFun). diff --git a/src/rabbit_version.erl b/src/rabbit_version.erl deleted file mode 100644 index 3a041508..00000000 --- a/src/rabbit_version.erl +++ /dev/null @@ -1,175 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_version). - --export([recorded/0, matches/2, desired/0, desired_for_scope/1, - record_desired/0, record_desired_for_scope/1, - upgrades_required/1]). - -%% ------------------------------------------------------------------- --ifdef(use_specs). - --export_type([scope/0, step/0]). - --type(scope() :: atom()). --type(scope_version() :: [atom()]). --type(step() :: {atom(), atom()}). - --type(version() :: [atom()]). - --spec(recorded/0 :: () -> rabbit_types:ok_or_error2(version(), any())). --spec(matches/2 :: ([A], [A]) -> boolean()). --spec(desired/0 :: () -> version()). --spec(desired_for_scope/1 :: (scope()) -> scope_version()). --spec(record_desired/0 :: () -> 'ok'). --spec(record_desired_for_scope/1 :: - (scope()) -> rabbit_types:ok_or_error(any())). --spec(upgrades_required/1 :: - (scope()) -> rabbit_types:ok_or_error2([step()], any())). - --endif. -%% ------------------------------------------------------------------- - --define(VERSION_FILENAME, "schema_version"). --define(SCOPES, [mnesia, local]). - -%% ------------------------------------------------------------------- - -recorded() -> case rabbit_file:read_term_file(schema_filename()) of - {ok, [V]} -> {ok, V}; - {error, _} = Err -> Err - end. - -record(V) -> ok = rabbit_file:write_term_file(schema_filename(), [V]). - -recorded_for_scope(Scope) -> - case recorded() of - {error, _} = Err -> - Err; - {ok, Version} -> - {ok, case lists:keysearch(Scope, 1, categorise_by_scope(Version)) of - false -> []; - {value, {Scope, SV1}} -> SV1 - end} - end. - -record_for_scope(Scope, ScopeVersion) -> - case recorded() of - {error, _} = Err -> - Err; - {ok, Version} -> - Version1 = lists:keystore(Scope, 1, categorise_by_scope(Version), - {Scope, ScopeVersion}), - ok = record([Name || {_Scope, Names} <- Version1, Name <- Names]) - end. - -%% ------------------------------------------------------------------- - -matches(VerA, VerB) -> - lists:usort(VerA) =:= lists:usort(VerB). - -%% ------------------------------------------------------------------- - -desired() -> [Name || Scope <- ?SCOPES, Name <- desired_for_scope(Scope)]. - -desired_for_scope(Scope) -> with_upgrade_graph(fun heads/1, Scope). - -record_desired() -> record(desired()). - -record_desired_for_scope(Scope) -> - record_for_scope(Scope, desired_for_scope(Scope)). - -upgrades_required(Scope) -> - case recorded_for_scope(Scope) of - {error, enoent} -> - case filelib:is_file(rabbit_guid:filename()) of - false -> {error, starting_from_scratch}; - true -> {error, version_not_available} - end; - {ok, CurrentHeads} -> - with_upgrade_graph( - fun (G) -> - case unknown_heads(CurrentHeads, G) of - [] -> {ok, upgrades_to_apply(CurrentHeads, G)}; - Unknown -> {error, {future_upgrades_found, Unknown}} - end - end, Scope) - end. - -%% ------------------------------------------------------------------- - -with_upgrade_graph(Fun, Scope) -> - case rabbit_misc:build_acyclic_graph( - fun ({_App, Module, Steps}) -> vertices(Module, Steps, Scope) end, - fun ({_App, Module, Steps}) -> edges(Module, Steps, Scope) end, - rabbit_misc:all_module_attributes(rabbit_upgrade)) of - {ok, G} -> try - Fun(G) - after - true = digraph:delete(G) - end; - {error, {vertex, duplicate, StepName}} -> - throw({error, {duplicate_upgrade_step, StepName}}); - {error, {edge, {bad_vertex, StepName}, _From, _To}} -> - throw({error, {dependency_on_unknown_upgrade_step, StepName}}); - {error, {edge, {bad_edge, StepNames}, _From, _To}} -> - throw({error, {cycle_in_upgrade_steps, StepNames}}) - end. - -vertices(Module, Steps, Scope0) -> - [{StepName, {Module, StepName}} || {StepName, Scope1, _Reqs} <- Steps, - Scope0 == Scope1]. - -edges(_Module, Steps, Scope0) -> - [{Require, StepName} || {StepName, Scope1, Requires} <- Steps, - Require <- Requires, - Scope0 == Scope1]. -unknown_heads(Heads, G) -> - [H || H <- Heads, digraph:vertex(G, H) =:= false]. - -upgrades_to_apply(Heads, G) -> - %% Take all the vertices which can reach the known heads. That's - %% everything we've already applied. Subtract that from all - %% vertices: that's what we have to apply. - Unsorted = sets:to_list( - sets:subtract( - sets:from_list(digraph:vertices(G)), - sets:from_list(digraph_utils:reaching(Heads, G)))), - %% Form a subgraph from that list and find a topological ordering - %% so we can invoke them in order. - [element(2, digraph:vertex(G, StepName)) || - StepName <- digraph_utils:topsort(digraph_utils:subgraph(G, Unsorted))]. - -heads(G) -> - lists:sort([V || V <- digraph:vertices(G), digraph:out_degree(G, V) =:= 0]). - -%% ------------------------------------------------------------------- - -categorise_by_scope(Version) when is_list(Version) -> - Categorised = - [{Scope, Name} || {_App, _Module, Attributes} <- - rabbit_misc:all_module_attributes(rabbit_upgrade), - {Name, Scope, _Requires} <- Attributes, - lists:member(Name, Version)], - orddict:to_list( - lists:foldl(fun ({Scope, Name}, CatVersion) -> - rabbit_misc:orddict_cons(Scope, Name, CatVersion) - end, orddict:new(), Categorised)). - -dir() -> rabbit_mnesia:dir(). - -schema_filename() -> filename:join(dir(), ?VERSION_FILENAME). diff --git a/src/rabbit_vhost.erl b/src/rabbit_vhost.erl deleted file mode 100644 index 18d44225..00000000 --- a/src/rabbit_vhost.erl +++ /dev/null @@ -1,155 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_vhost). - --include("rabbit.hrl"). - -%%---------------------------------------------------------------------------- - --export([add/1, delete/1, exists/1, list/0, with/2, assert/1]). --export([info/1, info/2, info_all/0, info_all/1]). - --ifdef(use_specs). - --spec(add/1 :: (rabbit_types:vhost()) -> 'ok'). --spec(delete/1 :: (rabbit_types:vhost()) -> 'ok'). --spec(exists/1 :: (rabbit_types:vhost()) -> boolean()). --spec(list/0 :: () -> [rabbit_types:vhost()]). --spec(with/2 :: (rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A). --spec(assert/1 :: (rabbit_types:vhost()) -> 'ok'). - --spec(info/1 :: (rabbit_types:vhost()) -> rabbit_types:infos()). --spec(info/2 :: (rabbit_types:vhost(), rabbit_types:info_keys()) - -> rabbit_types:infos()). --spec(info_all/0 :: () -> [rabbit_types:infos()]). --spec(info_all/1 :: (rabbit_types:info_keys()) -> [rabbit_types:infos()]). - --endif. - -%%---------------------------------------------------------------------------- - --define(INFO_KEYS, [name, tracing]). - -add(VHostPath) -> - rabbit_log:info("Adding vhost '~s'~n", [VHostPath]), - R = rabbit_misc:execute_mnesia_transaction( - fun () -> - case mnesia:wread({rabbit_vhost, VHostPath}) of - [] -> ok = mnesia:write(rabbit_vhost, - #vhost{virtual_host = VHostPath}, - write); - [_] -> mnesia:abort({vhost_already_exists, VHostPath}) - end - end, - fun (ok, true) -> - ok; - (ok, false) -> - [rabbit_exchange:declare( - rabbit_misc:r(VHostPath, exchange, Name), - Type, true, false, Internal, []) || - {Name, Type, Internal} <- - [{<<"">>, direct, false}, - {<<"amq.direct">>, direct, false}, - {<<"amq.topic">>, topic, false}, - %% per 0-9-1 pdf - {<<"amq.match">>, headers, false}, - %% per 0-9-1 xml - {<<"amq.headers">>, headers, false}, - {<<"amq.fanout">>, fanout, false}, - {<<"amq.rabbitmq.trace">>, topic, true}]], - ok - end), - rabbit_event:notify(vhost_created, info(VHostPath)), - R. - -delete(VHostPath) -> - %% FIXME: We are forced to delete the queues and exchanges outside - %% the TX below. Queue deletion involves sending messages to the queue - %% process, which in turn results in further mnesia actions and - %% eventually the termination of that process. Exchange deletion causes - %% notifications which must be sent outside the TX - rabbit_log:info("Deleting vhost '~s'~n", [VHostPath]), - QDelFun = fun (Q) -> rabbit_amqqueue:delete(Q, false, false) end, - [assert_benign(rabbit_amqqueue:with(Name, QDelFun)) || - #amqqueue{name = Name} <- rabbit_amqqueue:list(VHostPath)], - [assert_benign(rabbit_exchange:delete(Name, false)) || - #exchange{name = Name} <- rabbit_exchange:list(VHostPath)], - Funs = rabbit_misc:execute_mnesia_transaction( - with(VHostPath, fun () -> internal_delete(VHostPath) end)), - ok = rabbit_event:notify(vhost_deleted, [{name, VHostPath}]), - [ok = Fun() || Fun <- Funs], - ok. - -assert_benign(ok) -> ok; -assert_benign({ok, _}) -> ok; -assert_benign({error, not_found}) -> ok; -assert_benign({error, {absent, Q, _}}) -> - %% Removing the mnesia entries here is safe. If/when the down node - %% restarts, it will clear out the on-disk storage of the queue. - case rabbit_amqqueue:internal_delete(Q#amqqueue.name) of - ok -> ok; - {error, not_found} -> ok - end. - -internal_delete(VHostPath) -> - [ok = rabbit_auth_backend_internal:clear_permissions( - proplists:get_value(user, Info), VHostPath) - || Info <- rabbit_auth_backend_internal:list_vhost_permissions(VHostPath)], - Fs1 = [rabbit_runtime_parameters:clear(VHostPath, - proplists:get_value(component, Info), - proplists:get_value(name, Info)) - || Info <- rabbit_runtime_parameters:list(VHostPath)], - Fs2 = [rabbit_policy:delete(VHostPath, proplists:get_value(name, Info)) - || Info <- rabbit_policy:list(VHostPath)], - ok = mnesia:delete({rabbit_vhost, VHostPath}), - Fs1 ++ Fs2. - -exists(VHostPath) -> - mnesia:dirty_read({rabbit_vhost, VHostPath}) /= []. - -list() -> - mnesia:dirty_all_keys(rabbit_vhost). - -with(VHostPath, Thunk) -> - fun () -> - case mnesia:read({rabbit_vhost, VHostPath}) of - [] -> - mnesia:abort({no_such_vhost, VHostPath}); - [_V] -> - Thunk() - end - end. - -%% Like with/2 but outside an Mnesia tx -assert(VHostPath) -> case exists(VHostPath) of - true -> ok; - false -> throw({error, {no_such_vhost, VHostPath}}) - end. - -%%---------------------------------------------------------------------------- - -infos(Items, X) -> [{Item, i(Item, X)} || Item <- Items]. - -i(name, VHost) -> VHost; -i(tracing, VHost) -> rabbit_trace:enabled(VHost); -i(Item, _) -> throw({bad_argument, Item}). - -info(VHost) -> infos(?INFO_KEYS, VHost). -info(VHost, Items) -> infos(Items, VHost). - -info_all() -> info_all(?INFO_KEYS). -info_all(Items) -> [info(VHost, Items) || VHost <- list()]. diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl deleted file mode 100644 index 4d613cce..00000000 --- a/src/rabbit_vm.erl +++ /dev/null @@ -1,315 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_vm). - --export([memory/0, binary/0]). - --define(MAGIC_PLUGINS, ["mochiweb", "webmachine", "cowboy", "sockjs", - "rfc4627_jsonrpc"]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(memory/0 :: () -> rabbit_types:infos()). --spec(binary/0 :: () -> rabbit_types:infos()). - --endif. - -%%---------------------------------------------------------------------------- - -%% Like erlang:memory(), but with awareness of rabbit-y things -memory() -> - All = interesting_sups(), - {Sums, _Other} = sum_processes( - lists:append(All), distinguishers(), [memory]), - - [Qs, QsSlave, ConnsReader, ConnsWriter, ConnsChannel, ConnsOther, - MsgIndexProc, MgmtDbProc, Plugins] = - [aggregate(Names, Sums, memory, fun (X) -> X end) - || Names <- distinguished_interesting_sups()], - - Mnesia = mnesia_memory(), - MsgIndexETS = ets_memory([msg_store_persistent, msg_store_transient]), - MgmtDbETS = ets_memory([rabbit_mgmt_db]), - - [{total, Total}, - {processes, Processes}, - {ets, ETS}, - {atom, Atom}, - {binary, Bin}, - {code, Code}, - {system, System}] = - erlang:memory([total, processes, ets, atom, binary, code, system]), - - OtherProc = Processes - - ConnsReader - ConnsWriter - ConnsChannel - ConnsOther - - Qs - QsSlave - MsgIndexProc - Plugins - MgmtDbProc, - - [{total, Total}, - {connection_readers, ConnsReader}, - {connection_writers, ConnsWriter}, - {connection_channels, ConnsChannel}, - {connection_other, ConnsOther}, - {queue_procs, Qs}, - {queue_slave_procs, QsSlave}, - {plugins, Plugins}, - {other_proc, lists:max([0, OtherProc])}, %% [1] - {mnesia, Mnesia}, - {mgmt_db, MgmtDbETS + MgmtDbProc}, - {msg_index, MsgIndexETS + MsgIndexProc}, - {other_ets, ETS - Mnesia - MsgIndexETS - MgmtDbETS}, - {binary, Bin}, - {code, Code}, - {atom, Atom}, - {other_system, System - ETS - Atom - Bin - Code}]. - -%% [1] - erlang:memory(processes) can be less than the sum of its -%% parts. Rather than display something nonsensical, just silence any -%% claims about negative memory. See -%% http://erlang.org/pipermail/erlang-questions/2012-September/069320.html - -binary() -> - All = interesting_sups(), - {Sums, Rest} = - sum_processes( - lists:append(All), - fun (binary, Info, Acc) -> - lists:foldl(fun ({Ptr, Sz, _RefCnt}, Acc0) -> - sets:add_element({Ptr, Sz}, Acc0) - end, Acc, Info) - end, distinguishers(), [{binary, sets:new()}]), - [Other, Qs, QsSlave, ConnsReader, ConnsWriter, ConnsChannel, ConnsOther, - MsgIndexProc, MgmtDbProc, Plugins] = - [aggregate(Names, [{other, Rest} | Sums], binary, fun sum_binary/1) - || Names <- [[other] | distinguished_interesting_sups()]], - [{connection_readers, ConnsReader}, - {connection_writers, ConnsWriter}, - {connection_channels, ConnsChannel}, - {connection_other, ConnsOther}, - {queue_procs, Qs}, - {queue_slave_procs, QsSlave}, - {plugins, Plugins}, - {mgmt_db, MgmtDbProc}, - {msg_index, MsgIndexProc}, - {other, Other}]. - -%%---------------------------------------------------------------------------- - -mnesia_memory() -> - case mnesia:system_info(is_running) of - yes -> lists:sum([bytes(mnesia:table_info(Tab, memory)) || - Tab <- mnesia:system_info(tables)]); - _ -> 0 - end. - -ets_memory(OwnerNames) -> - Owners = [whereis(N) || N <- OwnerNames], - lists:sum([bytes(ets:info(T, memory)) || T <- ets:all(), - O <- [ets:info(T, owner)], - lists:member(O, Owners)]). - -bytes(Words) -> Words * erlang:system_info(wordsize). - -interesting_sups() -> - [[rabbit_amqqueue_sup_sup], conn_sups() | interesting_sups0()]. - -interesting_sups0() -> - MsgIndexProcs = [msg_store_transient, msg_store_persistent], - MgmtDbProcs = [rabbit_mgmt_sup_sup], - PluginProcs = plugin_sups(), - [MsgIndexProcs, MgmtDbProcs, PluginProcs]. - -conn_sups() -> [rabbit_tcp_client_sup, ssl_connection_sup, amqp_sup]. -conn_sups(With) -> [{Sup, With} || Sup <- conn_sups()]. - -distinguishers() -> [{rabbit_amqqueue_sup_sup, fun queue_type/1} | - conn_sups(fun conn_type/1)]. - -distinguished_interesting_sups() -> - [[{rabbit_amqqueue_sup_sup, master}], - [{rabbit_amqqueue_sup_sup, slave}], - conn_sups(reader), - conn_sups(writer), - conn_sups(channel), - conn_sups(other)] - ++ interesting_sups0(). - -plugin_sups() -> - lists:append([plugin_sup(App) || - {App, _, _} <- rabbit_misc:which_applications(), - is_plugin(atom_to_list(App))]). - -plugin_sup(App) -> - case application_controller:get_master(App) of - undefined -> []; - Master -> case application_master:get_child(Master) of - {Pid, _} when is_pid(Pid) -> [process_name(Pid)]; - Pid when is_pid(Pid) -> [process_name(Pid)]; - _ -> [] - end - end. - -process_name(Pid) -> - case process_info(Pid, registered_name) of - {registered_name, Name} -> Name; - _ -> Pid - end. - -is_plugin("rabbitmq_" ++ _) -> true; -is_plugin(App) -> lists:member(App, ?MAGIC_PLUGINS). - -aggregate(Names, Sums, Key, Fun) -> - lists:sum([extract(Name, Sums, Key, Fun) || Name <- Names]). - -extract(Name, Sums, Key, Fun) -> - case keyfind(Name, Sums) of - {value, Accs} -> Fun(keyfetch(Key, Accs)); - false -> 0 - end. - -sum_binary(Set) -> - sets:fold(fun({_Pt, Sz}, Acc) -> Acc + Sz end, 0, Set). - -queue_type(PDict) -> - case keyfind(process_name, PDict) of - {value, {rabbit_mirror_queue_slave, _}} -> slave; - _ -> master - end. - -conn_type(PDict) -> - case keyfind(process_name, PDict) of - {value, {rabbit_reader, _}} -> reader; - {value, {rabbit_writer, _}} -> writer; - {value, {rabbit_channel, _}} -> channel; - _ -> other - end. - -%%---------------------------------------------------------------------------- - -%% NB: this code is non-rabbit specific. - --ifdef(use_specs). --type(process() :: pid() | atom()). --type(info_key() :: atom()). --type(info_value() :: any()). --type(info_item() :: {info_key(), info_value()}). --type(accumulate() :: fun ((info_key(), info_value(), info_value()) -> - info_value())). --type(distinguisher() :: fun (([{term(), term()}]) -> atom())). --type(distinguishers() :: [{info_key(), distinguisher()}]). --spec(sum_processes/3 :: ([process()], distinguishers(), [info_key()]) -> - {[{process(), [info_item()]}], [info_item()]}). --spec(sum_processes/4 :: ([process()], accumulate(), distinguishers(), - [info_item()]) -> - {[{process(), [info_item()]}], [info_item()]}). --endif. - -sum_processes(Names, Distinguishers, Items) -> - sum_processes(Names, fun (_, X, Y) -> X + Y end, Distinguishers, - [{Item, 0} || Item <- Items]). - -%% summarize the process_info of all processes based on their -%% '$ancestor' hierarchy, recorded in their process dictionary. -%% -%% The function takes -%% -%% 1) a list of names/pids of processes that are accumulation points -%% in the hierarchy. -%% -%% 2) a function that aggregates individual info items -taking the -%% info item key, value and accumulated value as the input and -%% producing a new accumulated value. -%% -%% 3) a list of info item key / initial accumulator value pairs. -%% -%% The process_info of a process is accumulated at the nearest of its -%% ancestors that is mentioned in the first argument, or, if no such -%% ancestor exists or the ancestor information is absent, in a special -%% 'other' bucket. -%% -%% The result is a pair consisting of -%% -%% 1) a k/v list, containing for each of the accumulation names/pids a -%% list of info items, containing the accumulated data, and -%% -%% 2) the 'other' bucket - a list of info items containing the -%% accumulated data of all processes with no matching ancestors -%% -%% Note that this function operates on names as well as pids, but -%% these must match whatever is contained in the '$ancestor' process -%% dictionary entry. Generally that means for all registered processes -%% the name should be used. -sum_processes(Names, Fun, Distinguishers, Acc0) -> - Items = [Item || {Item, _Blank0} <- Acc0], - {NameAccs, OtherAcc} = - lists:foldl( - fun (Pid, Acc) -> - InfoItems = [registered_name, dictionary | Items], - case process_info(Pid, InfoItems) of - undefined -> - Acc; - [{registered_name, RegName}, {dictionary, D} | Vals] -> - %% see docs for process_info/2 for the - %% special handling of 'registered_name' - %% info items - Extra = case RegName of - [] -> []; - N -> [N] - end, - Name0 = find_ancestor(Extra, D, Names), - Name = case keyfind(Name0, Distinguishers) of - {value, DistFun} -> {Name0, DistFun(D)}; - false -> Name0 - end, - accumulate( - Name, Fun, orddict:from_list(Vals), Acc, Acc0) - end - end, {orddict:new(), Acc0}, processes()), - %% these conversions aren't strictly necessary; we do them simply - %% for the sake of encapsulating the representation. - {[{Name, orddict:to_list(Accs)} || - {Name, Accs} <- orddict:to_list(NameAccs)], - orddict:to_list(OtherAcc)}. - -find_ancestor(Extra, D, Names) -> - Ancestors = case keyfind('$ancestors', D) of - {value, Ancs} -> Ancs; - false -> [] - end, - case lists:splitwith(fun (A) -> not lists:member(A, Names) end, - Extra ++ Ancestors) of - {_, []} -> undefined; - {_, [Name | _]} -> Name - end. - -accumulate(undefined, Fun, ValsDict, {NameAccs, OtherAcc}, _Acc0) -> - {NameAccs, orddict:merge(Fun, ValsDict, OtherAcc)}; -accumulate(Name, Fun, ValsDict, {NameAccs, OtherAcc}, Acc0) -> - F = fun (NameAcc) -> orddict:merge(Fun, ValsDict, NameAcc) end, - {case orddict:is_key(Name, NameAccs) of - true -> orddict:update(Name, F, NameAccs); - false -> orddict:store( Name, F(Acc0), NameAccs) - end, OtherAcc}. - -keyfetch(K, L) -> {value, {_, V}} = lists:keysearch(K, 1, L), - V. - -keyfind(K, L) -> case lists:keysearch(K, 1, L) of - {value, {_, V}} -> {value, V}; - false -> false - end. diff --git a/src/rabbit_writer.erl b/src/rabbit_writer.erl deleted file mode 100644 index d42d7050..00000000 --- a/src/rabbit_writer.erl +++ /dev/null @@ -1,354 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(rabbit_writer). --include("rabbit.hrl"). --include("rabbit_framing.hrl"). - --export([start/6, start_link/6, start/7, start_link/7]). - --export([system_continue/3, system_terminate/4, system_code_change/4]). - --export([send_command/2, send_command/3, - send_command_sync/2, send_command_sync/3, - send_command_and_notify/4, send_command_and_notify/5, - send_command_flow/2, send_command_flow/3, - flush/1]). --export([internal_send_command/4, internal_send_command/6]). - -%% internal --export([enter_mainloop/2, mainloop/2, mainloop1/2]). - --record(wstate, {sock, channel, frame_max, protocol, reader, - stats_timer, pending}). - --define(HIBERNATE_AFTER, 5000). - -%%--------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start/6 :: - (rabbit_net:socket(), rabbit_channel:channel_number(), - non_neg_integer(), rabbit_types:protocol(), pid(), - rabbit_types:proc_name()) - -> rabbit_types:ok(pid())). --spec(start_link/6 :: - (rabbit_net:socket(), rabbit_channel:channel_number(), - non_neg_integer(), rabbit_types:protocol(), pid(), - rabbit_types:proc_name()) - -> rabbit_types:ok(pid())). --spec(start/7 :: - (rabbit_net:socket(), rabbit_channel:channel_number(), - non_neg_integer(), rabbit_types:protocol(), pid(), - rabbit_types:proc_name(), boolean()) - -> rabbit_types:ok(pid())). --spec(start_link/7 :: - (rabbit_net:socket(), rabbit_channel:channel_number(), - non_neg_integer(), rabbit_types:protocol(), pid(), - rabbit_types:proc_name(), boolean()) - -> rabbit_types:ok(pid())). - --spec(system_code_change/4 :: (_,_,_,_) -> {'ok',_}). --spec(system_continue/3 :: (_,_,#wstate{}) -> any()). --spec(system_terminate/4 :: (_,_,_,_) -> none()). - --spec(send_command/2 :: - (pid(), rabbit_framing:amqp_method_record()) -> 'ok'). --spec(send_command/3 :: - (pid(), rabbit_framing:amqp_method_record(), rabbit_types:content()) - -> 'ok'). --spec(send_command_sync/2 :: - (pid(), rabbit_framing:amqp_method_record()) -> 'ok'). --spec(send_command_sync/3 :: - (pid(), rabbit_framing:amqp_method_record(), rabbit_types:content()) - -> 'ok'). --spec(send_command_and_notify/4 :: - (pid(), pid(), pid(), rabbit_framing:amqp_method_record()) - -> 'ok'). --spec(send_command_and_notify/5 :: - (pid(), pid(), pid(), rabbit_framing:amqp_method_record(), - rabbit_types:content()) - -> 'ok'). --spec(send_command_flow/2 :: - (pid(), rabbit_framing:amqp_method_record()) -> 'ok'). --spec(send_command_flow/3 :: - (pid(), rabbit_framing:amqp_method_record(), rabbit_types:content()) - -> 'ok'). --spec(flush/1 :: (pid()) -> 'ok'). --spec(internal_send_command/4 :: - (rabbit_net:socket(), rabbit_channel:channel_number(), - rabbit_framing:amqp_method_record(), rabbit_types:protocol()) - -> 'ok'). --spec(internal_send_command/6 :: - (rabbit_net:socket(), rabbit_channel:channel_number(), - rabbit_framing:amqp_method_record(), rabbit_types:content(), - non_neg_integer(), rabbit_types:protocol()) - -> 'ok'). - --endif. - -%%--------------------------------------------------------------------------- - -start(Sock, Channel, FrameMax, Protocol, ReaderPid, Identity) -> - start(Sock, Channel, FrameMax, Protocol, ReaderPid, Identity, false). - -start_link(Sock, Channel, FrameMax, Protocol, ReaderPid, Identity) -> - start_link(Sock, Channel, FrameMax, Protocol, ReaderPid, Identity, false). - -start(Sock, Channel, FrameMax, Protocol, ReaderPid, Identity, - ReaderWantsStats) -> - State = initial_state(Sock, Channel, FrameMax, Protocol, ReaderPid, - ReaderWantsStats), - {ok, proc_lib:spawn(?MODULE, enter_mainloop, [Identity, State])}. - -start_link(Sock, Channel, FrameMax, Protocol, ReaderPid, Identity, - ReaderWantsStats) -> - State = initial_state(Sock, Channel, FrameMax, Protocol, ReaderPid, - ReaderWantsStats), - {ok, proc_lib:spawn_link(?MODULE, enter_mainloop, [Identity, State])}. - -initial_state(Sock, Channel, FrameMax, Protocol, ReaderPid, ReaderWantsStats) -> - (case ReaderWantsStats of - true -> fun rabbit_event:init_stats_timer/2; - false -> fun rabbit_event:init_disabled_stats_timer/2 - end)(#wstate{sock = Sock, - channel = Channel, - frame_max = FrameMax, - protocol = Protocol, - reader = ReaderPid, - pending = []}, - #wstate.stats_timer). - -system_continue(Parent, Deb, State) -> - mainloop(Deb, State#wstate{reader = Parent}). - -system_terminate(Reason, _Parent, _Deb, _State) -> - exit(Reason). - -system_code_change(Misc, _Module, _OldVsn, _Extra) -> - {ok, Misc}. - -enter_mainloop(Identity, State) -> - Deb = sys:debug_options([]), - ?store_proc_name(Identity), - mainloop(Deb, State). - -mainloop(Deb, State) -> - try - mainloop1(Deb, State) - catch - exit:Error -> #wstate{reader = ReaderPid, channel = Channel} = State, - ReaderPid ! {channel_exit, Channel, Error} - end, - done. - -mainloop1(Deb, State = #wstate{pending = []}) -> - receive - Message -> {Deb1, State1} = handle_message(Deb, Message, State), - ?MODULE:mainloop1(Deb1, State1) - after ?HIBERNATE_AFTER -> - erlang:hibernate(?MODULE, mainloop, [Deb, State]) - end; -mainloop1(Deb, State) -> - receive - Message -> {Deb1, State1} = handle_message(Deb, Message, State), - ?MODULE:mainloop1(Deb1, State1) - after 0 -> - ?MODULE:mainloop1(Deb, internal_flush(State)) - end. - -handle_message(Deb, {system, From, Req}, State = #wstate{reader = Parent}) -> - sys:handle_system_msg(Req, From, Parent, ?MODULE, Deb, State); -handle_message(Deb, Message, State) -> - {Deb, handle_message(Message, State)}. - -handle_message({send_command, MethodRecord}, State) -> - internal_send_command_async(MethodRecord, State); -handle_message({send_command, MethodRecord, Content}, State) -> - internal_send_command_async(MethodRecord, Content, State); -handle_message({send_command_flow, MethodRecord, Sender}, State) -> - credit_flow:ack(Sender), - internal_send_command_async(MethodRecord, State); -handle_message({send_command_flow, MethodRecord, Content, Sender}, State) -> - credit_flow:ack(Sender), - internal_send_command_async(MethodRecord, Content, State); -handle_message({'$gen_call', From, {send_command_sync, MethodRecord}}, State) -> - State1 = internal_flush( - internal_send_command_async(MethodRecord, State)), - gen_server:reply(From, ok), - State1; -handle_message({'$gen_call', From, {send_command_sync, MethodRecord, Content}}, - State) -> - State1 = internal_flush( - internal_send_command_async(MethodRecord, Content, State)), - gen_server:reply(From, ok), - State1; -handle_message({'$gen_call', From, flush}, State) -> - State1 = internal_flush(State), - gen_server:reply(From, ok), - State1; -handle_message({send_command_and_notify, QPid, ChPid, MethodRecord}, State) -> - State1 = internal_send_command_async(MethodRecord, State), - rabbit_amqqueue:notify_sent(QPid, ChPid), - State1; -handle_message({send_command_and_notify, QPid, ChPid, MethodRecord, Content}, - State) -> - State1 = internal_send_command_async(MethodRecord, Content, State), - rabbit_amqqueue:notify_sent(QPid, ChPid), - State1; -handle_message({'DOWN', _MRef, process, QPid, _Reason}, State) -> - rabbit_amqqueue:notify_sent_queue_down(QPid), - State; -handle_message({inet_reply, _, ok}, State) -> - rabbit_event:ensure_stats_timer(State, #wstate.stats_timer, emit_stats); -handle_message({inet_reply, _, Status}, _State) -> - exit({writer, send_failed, Status}); -handle_message(emit_stats, State = #wstate{reader = ReaderPid}) -> - ReaderPid ! ensure_stats, - rabbit_event:reset_stats_timer(State, #wstate.stats_timer); -handle_message(Message, _State) -> - exit({writer, message_not_understood, Message}). - -%%--------------------------------------------------------------------------- - -send_command(W, MethodRecord) -> - W ! {send_command, MethodRecord}, - ok. - -send_command(W, MethodRecord, Content) -> - W ! {send_command, MethodRecord, Content}, - ok. - -send_command_flow(W, MethodRecord) -> - credit_flow:send(W), - W ! {send_command_flow, MethodRecord, self()}, - ok. - -send_command_flow(W, MethodRecord, Content) -> - credit_flow:send(W), - W ! {send_command_flow, MethodRecord, Content, self()}, - ok. - -send_command_sync(W, MethodRecord) -> - call(W, {send_command_sync, MethodRecord}). - -send_command_sync(W, MethodRecord, Content) -> - call(W, {send_command_sync, MethodRecord, Content}). - -send_command_and_notify(W, Q, ChPid, MethodRecord) -> - W ! {send_command_and_notify, Q, ChPid, MethodRecord}, - ok. - -send_command_and_notify(W, Q, ChPid, MethodRecord, Content) -> - W ! {send_command_and_notify, Q, ChPid, MethodRecord, Content}, - ok. - -flush(W) -> call(W, flush). - -%%--------------------------------------------------------------------------- - -call(Pid, Msg) -> - {ok, Res} = gen:call(Pid, '$gen_call', Msg, infinity), - Res. - -%%--------------------------------------------------------------------------- - -assemble_frame(Channel, MethodRecord, Protocol) -> - rabbit_binary_generator:build_simple_method_frame( - Channel, MethodRecord, Protocol). - -assemble_frames(Channel, MethodRecord, Content, FrameMax, Protocol) -> - MethodName = rabbit_misc:method_record_type(MethodRecord), - true = Protocol:method_has_content(MethodName), % assertion - MethodFrame = rabbit_binary_generator:build_simple_method_frame( - Channel, MethodRecord, Protocol), - ContentFrames = rabbit_binary_generator:build_simple_content_frames( - Channel, Content, FrameMax, Protocol), - [MethodFrame | ContentFrames]. - -tcp_send(Sock, Data) -> - rabbit_misc:throw_on_error(inet_error, - fun () -> rabbit_net:send(Sock, Data) end). - -internal_send_command(Sock, Channel, MethodRecord, Protocol) -> - ok = tcp_send(Sock, assemble_frame(Channel, MethodRecord, Protocol)). - -internal_send_command(Sock, Channel, MethodRecord, Content, FrameMax, - Protocol) -> - ok = lists:foldl(fun (Frame, ok) -> tcp_send(Sock, Frame); - (_Frame, Other) -> Other - end, ok, assemble_frames(Channel, MethodRecord, - Content, FrameMax, Protocol)). - -internal_send_command_async(MethodRecord, - State = #wstate{channel = Channel, - protocol = Protocol, - pending = Pending}) -> - Frame = assemble_frame(Channel, MethodRecord, Protocol), - maybe_flush(State#wstate{pending = [Frame | Pending]}). - -internal_send_command_async(MethodRecord, Content, - State = #wstate{channel = Channel, - frame_max = FrameMax, - protocol = Protocol, - pending = Pending}) -> - Frames = assemble_frames(Channel, MethodRecord, Content, FrameMax, - Protocol), - rabbit_basic:maybe_gc_large_msg(Content), - maybe_flush(State#wstate{pending = [Frames | Pending]}). - -%% This magic number is the tcp-over-ethernet MSS (1460) minus the -%% minimum size of a AMQP basic.deliver method frame (24) plus basic -%% content header (22). The idea is that we want to flush just before -%% exceeding the MSS. --define(FLUSH_THRESHOLD, 1414). - -maybe_flush(State = #wstate{pending = Pending}) -> - case iolist_size(Pending) >= ?FLUSH_THRESHOLD of - true -> internal_flush(State); - false -> State - end. - -internal_flush(State = #wstate{pending = []}) -> - State; -internal_flush(State = #wstate{sock = Sock, pending = Pending}) -> - ok = port_cmd(Sock, lists:reverse(Pending)), - State#wstate{pending = []}. - -%% gen_tcp:send/2 does a selective receive of {inet_reply, Sock, -%% Status} to obtain the result. That is bad when it is called from -%% the writer since it requires scanning of the writers possibly quite -%% large message queue. -%% -%% So instead we lift the code from prim_inet:send/2, which is what -%% gen_tcp:send/2 calls, do the first half here and then just process -%% the result code in handle_message/2 as and when it arrives. -%% -%% This means we may end up happily sending data down a closed/broken -%% socket, but that's ok since a) data in the buffers will be lost in -%% any case (so qualitatively we are no worse off than if we used -%% gen_tcp:send/2), and b) we do detect the changed socket status -%% eventually, i.e. when we get round to handling the result code. -%% -%% Also note that the port has bounded buffers and port_command blocks -%% when these are full. So the fact that we process the result -%% asynchronously does not impact flow control. -port_cmd(Sock, Data) -> - true = try rabbit_net:port_command(Sock, Data) - catch error:Error -> exit({writer, send_failed, Error}) - end, - ok. diff --git a/src/supervised_lifecycle.erl b/src/supervised_lifecycle.erl deleted file mode 100644 index fcfa90b6..00000000 --- a/src/supervised_lifecycle.erl +++ /dev/null @@ -1,68 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - -%% Invoke callbacks on startup and termination. -%% -%% Simply hook this process into a supervision hierarchy, to have the -%% callbacks invoked at a precise point during the establishment and -%% teardown of that hierarchy, respectively. -%% -%% Or launch the process independently, and link to it, to have the -%% callbacks invoked on startup and when the linked process -%% terminates, respectively. - --module(supervised_lifecycle). - --behavior(gen_server). - --export([start_link/3]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/3 :: (atom(), rabbit_types:mfargs(), rabbit_types:mfargs()) -> - rabbit_types:ok_pid_or_error()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Name, StartMFA, StopMFA) -> - gen_server:start_link({local, Name}, ?MODULE, [StartMFA, StopMFA], []). - -%%---------------------------------------------------------------------------- - -init([{M, F, A}, StopMFA]) -> - process_flag(trap_exit, true), - apply(M, F, A), - {ok, StopMFA}. - -handle_call(_Request, _From, State) -> {noreply, State}. - -handle_cast(_Msg, State) -> {noreply, State}. - -handle_info(_Info, State) -> {noreply, State}. - -terminate(_Reason, {M, F, A}) -> - apply(M, F, A), - ok. - -code_change(_OldVsn, State, _Extra) -> {ok, State}. diff --git a/src/supervisor2.erl b/src/supervisor2.erl deleted file mode 100644 index 57c3bfc1..00000000 --- a/src/supervisor2.erl +++ /dev/null @@ -1,1566 +0,0 @@ -%% This file is a copy of supervisor.erl from the R16B Erlang/OTP -%% distribution, with the following modifications: -%% -%% 1) the module name is supervisor2 -%% -%% 2) a find_child/2 utility function has been added -%% -%% 3) Added an 'intrinsic' restart type. Like the transient type, this -%% type means the child should only be restarted if the child exits -%% abnormally. Unlike the transient type, if the child exits -%% normally, the supervisor itself also exits normally. If the -%% child is a supervisor and it exits normally (i.e. with reason of -%% 'shutdown') then the child's parent also exits normally. -%% -%% 4) child specifications can contain, as the restart type, a tuple -%% {permanent, Delay} | {transient, Delay} | {intrinsic, Delay} -%% where Delay >= 0 (see point (4) below for intrinsic). The delay, -%% in seconds, indicates what should happen if a child, upon being -%% restarted, exceeds the MaxT and MaxR parameters. Thus, if a -%% child exits, it is restarted as normal. If it exits sufficiently -%% quickly and often to exceed the boundaries set by the MaxT and -%% MaxR parameters, and a Delay is specified, then rather than -%% stopping the supervisor, the supervisor instead continues and -%% tries to start up the child again, Delay seconds later. -%% -%% Note that if a child is delay-restarted this will reset the -%% count of restarts towrds MaxR and MaxT. This matters if MaxT > -%% Delay, since otherwise we would fail to restart after the delay. -%% -%% Sometimes, you may wish for a transient or intrinsic child to -%% exit abnormally so that it gets restarted, but still log -%% nothing. gen_server will log any exit reason other than -%% 'normal', 'shutdown' or {'shutdown', _}. Thus the exit reason of -%% {'shutdown', 'restart'} is interpreted to mean you wish the -%% child to be restarted according to the delay parameters, but -%% gen_server will not log the error. Thus from gen_server's -%% perspective it's a normal exit, whilst from supervisor's -%% perspective, it's an abnormal exit. -%% -%% 5) normal, and {shutdown, _} exit reasons are all treated the same -%% (i.e. are regarded as normal exits) -%% -%% All modifications are (C) 2010-2013 GoPivotal, Inc. -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2012. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% --module(supervisor2). - --behaviour(gen_server). - -%% External exports --export([start_link/2, start_link/3, - start_child/2, restart_child/2, - delete_child/2, terminate_child/2, - which_children/1, count_children/1, - find_child/2, check_childspecs/1]). - -%% Internal exports --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). --export([try_again_restart/3]). - -%%-------------------------------------------------------------------------- --ifdef(use_specs). --export_type([child_spec/0, startchild_ret/0, strategy/0, sup_name/0]). --endif. -%%-------------------------------------------------------------------------- - --ifdef(use_specs). --type child() :: 'undefined' | pid(). --type child_id() :: term(). --type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. --type modules() :: [module()] | 'dynamic'. --type delay() :: non_neg_integer(). --type restart() :: 'permanent' | 'transient' | 'temporary' | 'intrinsic' | {'permanent', delay()} | {'transient', delay()} | {'intrinsic', delay()}. --type shutdown() :: 'brutal_kill' | timeout(). --type worker() :: 'worker' | 'supervisor'. --type sup_name() :: {'local', Name :: atom()} | {'global', Name :: atom()}. --type sup_ref() :: (Name :: atom()) - | {Name :: atom(), Node :: node()} - | {'global', Name :: atom()} - | pid(). --type child_spec() :: {Id :: child_id(), - StartFunc :: mfargs(), - Restart :: restart(), - Shutdown :: shutdown(), - Type :: worker(), - Modules :: modules()}. - --type strategy() :: 'one_for_all' | 'one_for_one' - | 'rest_for_one' | 'simple_one_for_one'. --endif. - -%%-------------------------------------------------------------------------- - --ifdef(use_specs). --record(child, {% pid is undefined when child is not running - pid = undefined :: child() | {restarting,pid()} | [pid()], - name :: child_id(), - mfargs :: mfargs(), - restart_type :: restart(), - shutdown :: shutdown(), - child_type :: worker(), - modules = [] :: modules()}). --type child_rec() :: #child{}. --else. --record(child, { - pid = undefined, - name, - mfargs, - restart_type, - shutdown, - child_type, - modules = []}). --endif. - --define(DICT, dict). --define(SETS, sets). --define(SET, set). - --ifdef(use_specs). --record(state, {name, - strategy :: strategy(), - children = [] :: [child_rec()], - dynamics :: ?DICT:?DICT() | ?SET:?SET(), - intensity :: non_neg_integer(), - period :: pos_integer(), - restarts = [], - module, - args}). --type state() :: #state{}. --else. --record(state, {name, - strategy, - children = [], - dynamics, - intensity, - period, - restarts = [], - module, - args}). --endif. - --define(is_simple(State), State#state.strategy =:= simple_one_for_one). --define(is_permanent(R), ((R =:= permanent) orelse - (is_tuple(R) andalso - tuple_size(R) == 2 andalso - element(1, R) =:= permanent))). --define(is_explicit_restart(R), - R == {shutdown, restart}). - --ifdef(use_specs). --callback init(Args :: term()) -> - {ok, {{RestartStrategy :: strategy(), - MaxR :: non_neg_integer(), - MaxT :: non_neg_integer()}, - [ChildSpec :: child_spec()]}} - | ignore. --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{init,1}]; -behaviour_info(_Other) -> - undefined. - --endif. --define(restarting(_Pid_), {restarting,_Pid_}). - -%%% --------------------------------------------------- -%%% This is a general process supervisor built upon gen_server.erl. -%%% Servers/processes should/could also be built using gen_server.erl. -%%% SupName = {local, atom()} | {global, atom()}. -%%% --------------------------------------------------- --ifdef(use_specs). --type startlink_err() :: {'already_started', pid()} - | {'shutdown', term()} - | term(). --type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}. - --spec start_link(Module, Args) -> startlink_ret() when - Module :: module(), - Args :: term(). - --endif. -start_link(Mod, Args) -> - gen_server:start_link(?MODULE, {self, Mod, Args}, []). - --ifdef(use_specs). --spec start_link(SupName, Module, Args) -> startlink_ret() when - SupName :: sup_name(), - Module :: module(), - Args :: term(). --endif. -start_link(SupName, Mod, Args) -> - gen_server:start_link(SupName, ?MODULE, {SupName, Mod, Args}, []). - -%%% --------------------------------------------------- -%%% Interface functions. -%%% --------------------------------------------------- --ifdef(use_specs). --type startchild_err() :: 'already_present' - | {'already_started', Child :: child()} | term(). --type startchild_ret() :: {'ok', Child :: child()} - | {'ok', Child :: child(), Info :: term()} - | {'error', startchild_err()}. - --spec start_child(SupRef, ChildSpec) -> startchild_ret() when - SupRef :: sup_ref(), - ChildSpec :: child_spec() | (List :: [term()]). --endif. -start_child(Supervisor, ChildSpec) -> - call(Supervisor, {start_child, ChildSpec}). - --ifdef(use_specs). --spec restart_child(SupRef, Id) -> Result when - SupRef :: sup_ref(), - Id :: child_id(), - Result :: {'ok', Child :: child()} - | {'ok', Child :: child(), Info :: term()} - | {'error', Error}, - Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one' | - term(). --endif. -restart_child(Supervisor, Name) -> - call(Supervisor, {restart_child, Name}). - --ifdef(use_specs). --spec delete_child(SupRef, Id) -> Result when - SupRef :: sup_ref(), - Id :: child_id(), - Result :: 'ok' | {'error', Error}, - Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one'. --endif. -delete_child(Supervisor, Name) -> - call(Supervisor, {delete_child, Name}). - -%%----------------------------------------------------------------- -%% Func: terminate_child/2 -%% Returns: ok | {error, Reason} -%% Note that the child is *always* terminated in some -%% way (maybe killed). -%%----------------------------------------------------------------- --ifdef(use_specs). --spec terminate_child(SupRef, Id) -> Result when - SupRef :: sup_ref(), - Id :: pid() | child_id(), - Result :: 'ok' | {'error', Error}, - Error :: 'not_found' | 'simple_one_for_one'. --endif. -terminate_child(Supervisor, Name) -> - call(Supervisor, {terminate_child, Name}). - --ifdef(use_specs). --spec which_children(SupRef) -> [{Id,Child,Type,Modules}] when - SupRef :: sup_ref(), - Id :: child_id() | undefined, - Child :: child() | 'restarting', - Type :: worker(), - Modules :: modules(). --endif. -which_children(Supervisor) -> - call(Supervisor, which_children). - --ifdef(use_specs). --spec count_children(SupRef) -> PropListOfCounts when - SupRef :: sup_ref(), - PropListOfCounts :: [Count], - Count :: {specs, ChildSpecCount :: non_neg_integer()} - | {active, ActiveProcessCount :: non_neg_integer()} - | {supervisors, ChildSupervisorCount :: non_neg_integer()} - |{workers, ChildWorkerCount :: non_neg_integer()}. --endif. -count_children(Supervisor) -> - call(Supervisor, count_children). - --ifdef(use_specs). --spec find_child(Supervisor, Name) -> [pid()] when - Supervisor :: sup_ref(), - Name :: child_id(). --endif. -find_child(Supervisor, Name) -> - [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), - Name1 =:= Name]. - -call(Supervisor, Req) -> - gen_server:call(Supervisor, Req, infinity). - --ifdef(use_specs). --spec check_childspecs(ChildSpecs) -> Result when - ChildSpecs :: [child_spec()], - Result :: 'ok' | {'error', Error :: term()}. --endif. -check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> - case check_startspec(ChildSpecs) of - {ok, _} -> ok; - Error -> {error, Error} - end; -check_childspecs(X) -> {error, {badarg, X}}. - -%%%----------------------------------------------------------------- -%%% Called by timer:apply_after from restart/2 --ifdef(use_specs). --spec try_again_restart(SupRef, Child, Reason) -> ok when - SupRef :: sup_ref(), - Child :: child_id() | pid(), - Reason :: term(). --endif. -try_again_restart(Supervisor, Child, Reason) -> - cast(Supervisor, {try_again_restart, Child, Reason}). - -cast(Supervisor, Req) -> - gen_server:cast(Supervisor, Req). - -%%% --------------------------------------------------- -%%% -%%% Initialize the supervisor. -%%% -%%% --------------------------------------------------- --ifdef(use_specs). --type init_sup_name() :: sup_name() | 'self'. - --type stop_rsn() :: {'shutdown', term()} - | {'bad_return', {module(),'init', term()}} - | {'bad_start_spec', term()} - | {'start_spec', term()} - | {'supervisor_data', term()}. - --spec init({init_sup_name(), module(), [term()]}) -> - {'ok', state()} | 'ignore' | {'stop', stop_rsn()}. --endif. -init({SupName, Mod, Args}) -> - process_flag(trap_exit, true), - case Mod:init(Args) of - {ok, {SupFlags, StartSpec}} -> - case init_state(SupName, SupFlags, Mod, Args) of - {ok, State} when ?is_simple(State) -> - init_dynamic(State, StartSpec); - {ok, State} -> - init_children(State, StartSpec); - Error -> - {stop, {supervisor_data, Error}} - end; - ignore -> - ignore; - Error -> - {stop, {bad_return, {Mod, init, Error}}} - end. - -init_children(State, StartSpec) -> - SupName = State#state.name, - case check_startspec(StartSpec) of - {ok, Children} -> - case start_children(Children, SupName) of - {ok, NChildren} -> - {ok, State#state{children = NChildren}}; - {error, NChildren, Reason} -> - terminate_children(NChildren, SupName), - {stop, {shutdown, Reason}} - end; - Error -> - {stop, {start_spec, Error}} - end. - -init_dynamic(State, [StartSpec]) -> - case check_startspec([StartSpec]) of - {ok, Children} -> - {ok, State#state{children = Children}}; - Error -> - {stop, {start_spec, Error}} - end; -init_dynamic(_State, StartSpec) -> - {stop, {bad_start_spec, StartSpec}}. - -%%----------------------------------------------------------------- -%% Func: start_children/2 -%% Args: Children = [child_rec()] in start order -%% SupName = {local, atom()} | {global, atom()} | {pid(), Mod} -%% Purpose: Start all children. The new list contains #child's -%% with pids. -%% Returns: {ok, NChildren} | {error, NChildren, Reason} -%% NChildren = [child_rec()] in termination order (reversed -%% start order) -%%----------------------------------------------------------------- -start_children(Children, SupName) -> start_children(Children, [], SupName). - -start_children([Child|Chs], NChildren, SupName) -> - case do_start_child(SupName, Child) of - {ok, undefined} when Child#child.restart_type =:= temporary -> - start_children(Chs, NChildren, SupName); - {ok, Pid} -> - start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); - {ok, Pid, _Extra} -> - start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); - {error, Reason} -> - report_error(start_error, Reason, Child, SupName), - {error, lists:reverse(Chs) ++ [Child | NChildren], - {failed_to_start_child,Child#child.name,Reason}} - end; -start_children([], NChildren, _SupName) -> - {ok, NChildren}. - -do_start_child(SupName, Child) -> - #child{mfargs = {M, F, Args}} = Child, - case catch apply(M, F, Args) of - {ok, Pid} when is_pid(Pid) -> - NChild = Child#child{pid = Pid}, - report_progress(NChild, SupName), - {ok, Pid}; - {ok, Pid, Extra} when is_pid(Pid) -> - NChild = Child#child{pid = Pid}, - report_progress(NChild, SupName), - {ok, Pid, Extra}; - ignore -> - {ok, undefined}; - {error, What} -> {error, What}; - What -> {error, What} - end. - -do_start_child_i(M, F, A) -> - case catch apply(M, F, A) of - {ok, Pid} when is_pid(Pid) -> - {ok, Pid}; - {ok, Pid, Extra} when is_pid(Pid) -> - {ok, Pid, Extra}; - ignore -> - {ok, undefined}; - {error, Error} -> - {error, Error}; - What -> - {error, What} - end. - -%%% --------------------------------------------------- -%%% -%%% Callback functions. -%%% -%%% --------------------------------------------------- --ifdef(use_specs). --type call() :: 'which_children' | 'count_children' | {_, _}. % XXX: refine --spec handle_call(call(), term(), state()) -> {'reply', term(), state()}. --endif. -handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> - Child = hd(State#state.children), - #child{mfargs = {M, F, A}} = Child, - Args = A ++ EArgs, - case do_start_child_i(M, F, Args) of - {ok, undefined} when Child#child.restart_type =:= temporary -> - {reply, {ok, undefined}, State}; - {ok, Pid} -> - NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State), - {reply, {ok, Pid}, NState}; - {ok, Pid, Extra} -> - NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State), - {reply, {ok, Pid, Extra}, NState}; - What -> - {reply, What, State} - end; - -%% terminate_child for simple_one_for_one can only be done with pid -handle_call({terminate_child, Name}, _From, State) when not is_pid(Name), - ?is_simple(State) -> - {reply, {error, simple_one_for_one}, State}; - -handle_call({terminate_child, Name}, _From, State) -> - case get_child(Name, State, ?is_simple(State)) of - {value, Child} -> - case do_terminate(Child, State#state.name) of - #child{restart_type=RT} when RT=:=temporary; ?is_simple(State) -> - {reply, ok, state_del_child(Child, State)}; - NChild -> - {reply, ok, replace_child(NChild, State)} - end; - false -> - {reply, {error, not_found}, State} - end; - -%%% The requests delete_child and restart_child are invalid for -%%% simple_one_for_one supervisors. -handle_call({_Req, _Data}, _From, State) when ?is_simple(State) -> - {reply, {error, simple_one_for_one}, State}; - -handle_call({start_child, ChildSpec}, _From, State) -> - case check_childspec(ChildSpec) of - {ok, Child} -> - {Resp, NState} = handle_start_child(Child, State), - {reply, Resp, NState}; - What -> - {reply, {error, What}, State} - end; - -handle_call({restart_child, Name}, _From, State) -> - case get_child(Name, State) of - {value, Child} when Child#child.pid =:= undefined -> - case do_start_child(State#state.name, Child) of - {ok, Pid} -> - NState = replace_child(Child#child{pid = Pid}, State), - {reply, {ok, Pid}, NState}; - {ok, Pid, Extra} -> - NState = replace_child(Child#child{pid = Pid}, State), - {reply, {ok, Pid, Extra}, NState}; - Error -> - {reply, Error, State} - end; - {value, #child{pid=?restarting(_)}} -> - {reply, {error, restarting}, State}; - {value, _} -> - {reply, {error, running}, State}; - _ -> - {reply, {error, not_found}, State} - end; - -handle_call({delete_child, Name}, _From, State) -> - case get_child(Name, State) of - {value, Child} when Child#child.pid =:= undefined -> - NState = remove_child(Child, State), - {reply, ok, NState}; - {value, #child{pid=?restarting(_)}} -> - {reply, {error, restarting}, State}; - {value, _} -> - {reply, {error, running}, State}; - _ -> - {reply, {error, not_found}, State} - end; - -handle_call(which_children, _From, #state{children = [#child{restart_type = temporary, - child_type = CT, - modules = Mods}]} = - State) when ?is_simple(State) -> - Reply = lists:map(fun(Pid) -> {undefined, Pid, CT, Mods} end, - ?SETS:to_list(dynamics_db(temporary, State#state.dynamics))), - {reply, Reply, State}; - -handle_call(which_children, _From, #state{children = [#child{restart_type = RType, - child_type = CT, - modules = Mods}]} = - State) when ?is_simple(State) -> - Reply = lists:map(fun({?restarting(_),_}) -> {undefined,restarting,CT,Mods}; - ({Pid, _}) -> {undefined, Pid, CT, Mods} end, - ?DICT:to_list(dynamics_db(RType, State#state.dynamics))), - {reply, Reply, State}; - -handle_call(which_children, _From, State) -> - Resp = - lists:map(fun(#child{pid = ?restarting(_), name = Name, - child_type = ChildType, modules = Mods}) -> - {Name, restarting, ChildType, Mods}; - (#child{pid = Pid, name = Name, - child_type = ChildType, modules = Mods}) -> - {Name, Pid, ChildType, Mods} - end, - State#state.children), - {reply, Resp, State}; - - -handle_call(count_children, _From, #state{children = [#child{restart_type = temporary, - child_type = CT}]} = State) - when ?is_simple(State) -> - {Active, Count} = - ?SETS:fold(fun(Pid, {Alive, Tot}) -> - case is_pid(Pid) andalso is_process_alive(Pid) of - true ->{Alive+1, Tot +1}; - false -> - {Alive, Tot + 1} - end - end, {0, 0}, dynamics_db(temporary, State#state.dynamics)), - Reply = case CT of - supervisor -> [{specs, 1}, {active, Active}, - {supervisors, Count}, {workers, 0}]; - worker -> [{specs, 1}, {active, Active}, - {supervisors, 0}, {workers, Count}] - end, - {reply, Reply, State}; - -handle_call(count_children, _From, #state{children = [#child{restart_type = RType, - child_type = CT}]} = State) - when ?is_simple(State) -> - {Active, Count} = - ?DICT:fold(fun(Pid, _Val, {Alive, Tot}) -> - case is_pid(Pid) andalso is_process_alive(Pid) of - true -> - {Alive+1, Tot +1}; - false -> - {Alive, Tot + 1} - end - end, {0, 0}, dynamics_db(RType, State#state.dynamics)), - Reply = case CT of - supervisor -> [{specs, 1}, {active, Active}, - {supervisors, Count}, {workers, 0}]; - worker -> [{specs, 1}, {active, Active}, - {supervisors, 0}, {workers, Count}] - end, - {reply, Reply, State}; - -handle_call(count_children, _From, State) -> - %% Specs and children are together on the children list... - {Specs, Active, Supers, Workers} = - lists:foldl(fun(Child, Counts) -> - count_child(Child, Counts) - end, {0,0,0,0}, State#state.children), - - %% Reformat counts to a property list. - Reply = [{specs, Specs}, {active, Active}, - {supervisors, Supers}, {workers, Workers}], - {reply, Reply, State}. - - -count_child(#child{pid = Pid, child_type = worker}, - {Specs, Active, Supers, Workers}) -> - case is_pid(Pid) andalso is_process_alive(Pid) of - true -> {Specs+1, Active+1, Supers, Workers+1}; - false -> {Specs+1, Active, Supers, Workers+1} - end; -count_child(#child{pid = Pid, child_type = supervisor}, - {Specs, Active, Supers, Workers}) -> - case is_pid(Pid) andalso is_process_alive(Pid) of - true -> {Specs+1, Active+1, Supers+1, Workers}; - false -> {Specs+1, Active, Supers+1, Workers} - end. - - -%%% If a restart attempt failed, this message is sent via -%%% timer:apply_after(0,...) in order to give gen_server the chance to -%%% check it's inbox before trying again. --ifdef(use_specs). --spec handle_cast({try_again_restart, child_id() | pid(), term()}, state()) -> - {'noreply', state()} | {stop, shutdown, state()}. --endif. -handle_cast({try_again_restart,Pid,Reason}, #state{children=[Child]}=State) - when ?is_simple(State) -> - RT = Child#child.restart_type, - RPid = restarting(Pid), - case dynamic_child_args(RPid, dynamics_db(RT, State#state.dynamics)) of - {ok, Args} -> - {M, F, _} = Child#child.mfargs, - NChild = Child#child{pid = RPid, mfargs = {M, F, Args}}, - try_restart(Child#child.restart_type, Reason, NChild, State); - error -> - {noreply, State} - end; - -handle_cast({try_again_restart,Name,Reason}, State) -> - %% we still support >= R12-B3 in which lists:keyfind/3 doesn't exist - case lists:keysearch(Name,#child.name,State#state.children) of - {value, Child = #child{pid=?restarting(_), restart_type=RestartType}} -> - try_restart(RestartType, Reason, Child, State); - _ -> - {noreply,State} - end. - -%% -%% Take care of terminated children. -%% --ifdef(use_specs). --spec handle_info(term(), state()) -> - {'noreply', state()} | {'stop', 'shutdown', state()}. --endif. -handle_info({'EXIT', Pid, Reason}, State) -> - case restart_child(Pid, Reason, State) of - {ok, State1} -> - {noreply, State1}; - {shutdown, State1} -> - {stop, shutdown, State1} - end; - -handle_info({delayed_restart, {RestartType, Reason, Child}}, State) - when ?is_simple(State) -> - try_restart(RestartType, Reason, Child, State#state{restarts = []}); %% [1] -handle_info({delayed_restart, {RestartType, Reason, Child}}, State) -> - case get_child(Child#child.name, State) of - {value, Child1} -> - try_restart(RestartType, Reason, Child1, - State#state{restarts = []}); %% [1] - _What -> - {noreply, State} - end; -%% [1] When we receive a delayed_restart message we want to reset the -%% restarts field since otherwise the MaxT might not have elapsed and -%% we would just delay again and again. Since a common use of the -%% delayed restart feature is for MaxR = 1, MaxT = some huge number -%% (so that we don't end up bouncing around in non-delayed restarts) -%% this is important. - -handle_info(Msg, State) -> - error_logger:error_msg("Supervisor received unexpected message: ~p~n", - [Msg]), - {noreply, State}. - -%% -%% Terminate this server. -%% --ifdef(use_specs). --spec terminate(term(), state()) -> 'ok'. --endif. -terminate(_Reason, #state{children=[Child]} = State) when ?is_simple(State) -> - terminate_dynamic_children(Child, dynamics_db(Child#child.restart_type, - State#state.dynamics), - State#state.name); -terminate(_Reason, State) -> - terminate_children(State#state.children, State#state.name). - -%% -%% Change code for the supervisor. -%% Call the new call-back module and fetch the new start specification. -%% Combine the new spec. with the old. If the new start spec. is -%% not valid the code change will not succeed. -%% Use the old Args as argument to Module:init/1. -%% NOTE: This requires that the init function of the call-back module -%% does not have any side effects. -%% --ifdef(use_specs). --spec code_change(term(), state(), term()) -> - {'ok', state()} | {'error', term()}. --endif. -code_change(_, State, _) -> - case (State#state.module):init(State#state.args) of - {ok, {SupFlags, StartSpec}} -> - case catch check_flags(SupFlags) of - ok -> - {Strategy, MaxIntensity, Period} = SupFlags, - update_childspec(State#state{strategy = Strategy, - intensity = MaxIntensity, - period = Period}, - StartSpec); - Error -> - {error, Error} - end; - ignore -> - {ok, State}; - Error -> - Error - end. - -check_flags({Strategy, MaxIntensity, Period}) -> - validStrategy(Strategy), - validIntensity(MaxIntensity), - validPeriod(Period), - ok; -check_flags(What) -> - {bad_flags, What}. - -update_childspec(State, StartSpec) when ?is_simple(State) -> - case check_startspec(StartSpec) of - {ok, [Child]} -> - {ok, State#state{children = [Child]}}; - Error -> - {error, Error} - end; -update_childspec(State, StartSpec) -> - case check_startspec(StartSpec) of - {ok, Children} -> - OldC = State#state.children, % In reverse start order ! - NewC = update_childspec1(OldC, Children, []), - {ok, State#state{children = NewC}}; - Error -> - {error, Error} - end. - -update_childspec1([Child|OldC], Children, KeepOld) -> - case update_chsp(Child, Children) of - {ok,NewChildren} -> - update_childspec1(OldC, NewChildren, KeepOld); - false -> - update_childspec1(OldC, Children, [Child|KeepOld]) - end; -update_childspec1([], Children, KeepOld) -> - %% Return them in (kept) reverse start order. - lists:reverse(Children ++ KeepOld). - -update_chsp(OldCh, Children) -> - case lists:map(fun(Ch) when OldCh#child.name =:= Ch#child.name -> - Ch#child{pid = OldCh#child.pid}; - (Ch) -> - Ch - end, - Children) of - Children -> - false; % OldCh not found in new spec. - NewC -> - {ok, NewC} - end. - -%%% --------------------------------------------------- -%%% Start a new child. -%%% --------------------------------------------------- - -handle_start_child(Child, State) -> - case get_child(Child#child.name, State) of - false -> - case do_start_child(State#state.name, Child) of - {ok, undefined} when Child#child.restart_type =:= temporary -> - {{ok, undefined}, State}; - {ok, Pid} -> - {{ok, Pid}, save_child(Child#child{pid = Pid}, State)}; - {ok, Pid, Extra} -> - {{ok, Pid, Extra}, save_child(Child#child{pid = Pid}, State)}; - {error, What} -> - {{error, {What, Child}}, State} - end; - {value, OldChild} when is_pid(OldChild#child.pid) -> - {{error, {already_started, OldChild#child.pid}}, State}; - {value, _OldChild} -> - {{error, already_present}, State} - end. - -%%% --------------------------------------------------- -%%% Restart. A process has terminated. -%%% Returns: {ok, state()} | {shutdown, state()} -%%% --------------------------------------------------- - -restart_child(Pid, Reason, #state{children = [Child]} = State) when ?is_simple(State) -> - RestartType = Child#child.restart_type, - case dynamic_child_args(Pid, dynamics_db(RestartType, State#state.dynamics)) of - {ok, Args} -> - {M, F, _} = Child#child.mfargs, - NChild = Child#child{pid = Pid, mfargs = {M, F, Args}}, - do_restart(RestartType, Reason, NChild, State); - error -> - {ok, State} - end; - -restart_child(Pid, Reason, State) -> - Children = State#state.children, - %% we still support >= R12-B3 in which lists:keyfind/3 doesn't exist - case lists:keysearch(Pid, #child.pid, Children) of - {value, #child{restart_type = RestartType} = Child} -> - do_restart(RestartType, Reason, Child, State); - false -> - {ok, State} - end. - -try_restart(RestartType, Reason, Child, State) -> - case handle_restart(RestartType, Reason, Child, State) of - {ok, NState} -> {noreply, NState}; - {shutdown, State2} -> {stop, shutdown, State2} - end. - -do_restart(RestartType, Reason, Child, State) -> - maybe_report_error(RestartType, Reason, Child, State), - handle_restart(RestartType, Reason, Child, State). - -maybe_report_error(permanent, Reason, Child, State) -> - report_child_termination(Reason, Child, State); -maybe_report_error({permanent, _}, Reason, Child, State) -> - report_child_termination(Reason, Child, State); -maybe_report_error(_Type, Reason, Child, State) -> - case is_abnormal_termination(Reason) of - true -> report_child_termination(Reason, Child, State); - false -> ok - end. - -report_child_termination(Reason, Child, State) -> - report_error(child_terminated, Reason, Child, State#state.name). - -handle_restart(permanent, _Reason, Child, State) -> - restart(Child, State); -handle_restart(transient, Reason, Child, State) -> - restart_if_explicit_or_abnormal(fun restart/2, - fun delete_child_and_continue/2, - Reason, Child, State); -handle_restart(intrinsic, Reason, Child, State) -> - restart_if_explicit_or_abnormal(fun restart/2, - fun delete_child_and_stop/2, - Reason, Child, State); -handle_restart(temporary, _Reason, Child, State) -> - delete_child_and_continue(Child, State); -handle_restart({permanent, _Delay}=Restart, Reason, Child, State) -> - do_restart_delay(Restart, Reason, Child, State); -handle_restart({transient, _Delay}=Restart, Reason, Child, State) -> - restart_if_explicit_or_abnormal(defer_to_restart_delay(Restart, Reason), - fun delete_child_and_continue/2, - Reason, Child, State); -handle_restart({intrinsic, _Delay}=Restart, Reason, Child, State) -> - restart_if_explicit_or_abnormal(defer_to_restart_delay(Restart, Reason), - fun delete_child_and_stop/2, - Reason, Child, State). - -restart_if_explicit_or_abnormal(RestartHow, Otherwise, Reason, Child, State) -> - case ?is_explicit_restart(Reason) orelse is_abnormal_termination(Reason) of - true -> RestartHow(Child, State); - false -> Otherwise(Child, State) - end. - -defer_to_restart_delay(Restart, Reason) -> - fun(Child, State) -> do_restart_delay(Restart, Reason, Child, State) end. - -delete_child_and_continue(Child, State) -> - {ok, state_del_child(Child, State)}. - -delete_child_and_stop(Child, State) -> - {shutdown, state_del_child(Child, State)}. - -is_abnormal_termination(normal) -> false; -is_abnormal_termination(shutdown) -> false; -is_abnormal_termination({shutdown, _}) -> false; -is_abnormal_termination(_Other) -> true. - -do_restart_delay({RestartType, Delay}, Reason, Child, State) -> - case add_restart(State) of - {ok, NState} -> - maybe_restart(NState#state.strategy, Child, NState); - {terminate, _NState} -> - %% we've reached the max restart intensity, but the - %% add_restart will have added to the restarts - %% field. Given we don't want to die here, we need to go - %% back to the old restarts field otherwise we'll never - %% attempt to restart later, which is why we ignore - %% NState for this clause. - _TRef = erlang:send_after(trunc(Delay*1000), self(), - {delayed_restart, - {{RestartType, Delay}, Reason, Child}}), - {ok, state_del_child(Child, State)} - end. - -restart(Child, State) -> - case add_restart(State) of - {ok, NState} -> - maybe_restart(NState#state.strategy, Child, NState); - {terminate, NState} -> - report_error(shutdown, reached_max_restart_intensity, - Child, State#state.name), - {shutdown, remove_child(Child, NState)} - end. - -maybe_restart(Strategy, Child, State) -> - case restart(Strategy, Child, State) of - {try_again, Reason, NState2} -> - %% Leaving control back to gen_server before - %% trying again. This way other incoming requsts - %% for the supervisor can be handled - e.g. a - %% shutdown request for the supervisor or the - %% child. - Id = if ?is_simple(State) -> Child#child.pid; - true -> Child#child.name - end, - timer:apply_after(0,?MODULE,try_again_restart,[self(),Id,Reason]), - {ok,NState2}; - Other -> - Other - end. - -restart(simple_one_for_one, Child, State) -> - #child{pid = OldPid, mfargs = {M, F, A}} = Child, - Dynamics = ?DICT:erase(OldPid, dynamics_db(Child#child.restart_type, - State#state.dynamics)), - case do_start_child_i(M, F, A) of - {ok, Pid} -> - NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)}, - {ok, NState}; - {ok, Pid, _Extra} -> - NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)}, - {ok, NState}; - {error, Error} -> - NState = State#state{dynamics = ?DICT:store(restarting(OldPid), A, - Dynamics)}, - report_error(start_error, Error, Child, State#state.name), - {try_again, Error, NState} - end; -restart(one_for_one, Child, State) -> - OldPid = Child#child.pid, - case do_start_child(State#state.name, Child) of - {ok, Pid} -> - NState = replace_child(Child#child{pid = Pid}, State), - {ok, NState}; - {ok, Pid, _Extra} -> - NState = replace_child(Child#child{pid = Pid}, State), - {ok, NState}; - {error, Reason} -> - NState = replace_child(Child#child{pid = restarting(OldPid)}, State), - report_error(start_error, Reason, Child, State#state.name), - {try_again, Reason, NState} - end; -restart(rest_for_one, Child, State) -> - {ChAfter, ChBefore} = split_child(Child#child.pid, State#state.children), - ChAfter2 = terminate_children(ChAfter, State#state.name), - case start_children(ChAfter2, State#state.name) of - {ok, ChAfter3} -> - {ok, State#state{children = ChAfter3 ++ ChBefore}}; - {error, ChAfter3, Reason} -> - NChild = Child#child{pid=restarting(Child#child.pid)}, - NState = State#state{children = ChAfter3 ++ ChBefore}, - {try_again, Reason, replace_child(NChild,NState)} - end; -restart(one_for_all, Child, State) -> - Children1 = del_child(Child#child.pid, State#state.children), - Children2 = terminate_children(Children1, State#state.name), - case start_children(Children2, State#state.name) of - {ok, NChs} -> - {ok, State#state{children = NChs}}; - {error, NChs, Reason} -> - NChild = Child#child{pid=restarting(Child#child.pid)}, - NState = State#state{children = NChs}, - {try_again, Reason, replace_child(NChild,NState)} - end. - -restarting(Pid) when is_pid(Pid) -> ?restarting(Pid); -restarting(RPid) -> RPid. - -%%----------------------------------------------------------------- -%% Func: terminate_children/2 -%% Args: Children = [child_rec()] in termination order -%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} -%% Returns: NChildren = [child_rec()] in -%% startup order (reversed termination order) -%%----------------------------------------------------------------- -terminate_children(Children, SupName) -> - terminate_children(Children, SupName, []). - -%% Temporary children should not be restarted and thus should -%% be skipped when building the list of terminated children, although -%% we do want them to be shut down as many functions from this module -%% use this function to just clear everything. -terminate_children([Child = #child{restart_type=temporary} | Children], SupName, Res) -> - do_terminate(Child, SupName), - terminate_children(Children, SupName, Res); -terminate_children([Child | Children], SupName, Res) -> - NChild = do_terminate(Child, SupName), - terminate_children(Children, SupName, [NChild | Res]); -terminate_children([], _SupName, Res) -> - Res. - -do_terminate(Child, SupName) when is_pid(Child#child.pid) -> - case shutdown(Child#child.pid, Child#child.shutdown) of - ok -> - ok; - {error, normal} when not ?is_permanent(Child#child.restart_type) -> - ok; - {error, OtherReason} -> - report_error(shutdown_error, OtherReason, Child, SupName) - end, - Child#child{pid = undefined}; -do_terminate(Child, _SupName) -> - Child#child{pid = undefined}. - -%%----------------------------------------------------------------- -%% Shutdowns a child. We must check the EXIT value -%% of the child, because it might have died with another reason than -%% the wanted. In that case we want to report the error. We put a -%% monitor on the child an check for the 'DOWN' message instead of -%% checking for the 'EXIT' message, because if we check the 'EXIT' -%% message a "naughty" child, who does unlink(Sup), could hang the -%% supervisor. -%% Returns: ok | {error, OtherReason} (this should be reported) -%%----------------------------------------------------------------- -shutdown(Pid, brutal_kill) -> - case monitor_child(Pid) of - ok -> - exit(Pid, kill), - receive - {'DOWN', _MRef, process, Pid, killed} -> - ok; - {'DOWN', _MRef, process, Pid, OtherReason} -> - {error, OtherReason} - end; - {error, Reason} -> - {error, Reason} - end; -shutdown(Pid, Time) -> - case monitor_child(Pid) of - ok -> - exit(Pid, shutdown), %% Try to shutdown gracefully - receive - {'DOWN', _MRef, process, Pid, shutdown} -> - ok; - {'DOWN', _MRef, process, Pid, OtherReason} -> - {error, OtherReason} - after Time -> - exit(Pid, kill), %% Force termination. - receive - {'DOWN', _MRef, process, Pid, OtherReason} -> - {error, OtherReason} - end - end; - {error, Reason} -> - {error, Reason} - end. - -%% Help function to shutdown/2 switches from link to monitor approach -monitor_child(Pid) -> - - %% Do the monitor operation first so that if the child dies - %% before the monitoring is done causing a 'DOWN'-message with - %% reason noproc, we will get the real reason in the 'EXIT'-message - %% unless a naughty child has already done unlink... - erlang:monitor(process, Pid), - unlink(Pid), - - receive - %% If the child dies before the unlik we must empty - %% the mail-box of the 'EXIT'-message and the 'DOWN'-message. - {'EXIT', Pid, Reason} -> - receive - {'DOWN', _, process, Pid, _} -> - {error, Reason} - end - after 0 -> - %% If a naughty child did unlink and the child dies before - %% monitor the result will be that shutdown/2 receives a - %% 'DOWN'-message with reason noproc. - %% If the child should die after the unlink there - %% will be a 'DOWN'-message with a correct reason - %% that will be handled in shutdown/2. - ok - end. - - -%%----------------------------------------------------------------- -%% Func: terminate_dynamic_children/3 -%% Args: Child = child_rec() -%% Dynamics = ?DICT() | ?SET() -%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} -%% Returns: ok -%% -%% -%% Shutdown all dynamic children. This happens when the supervisor is -%% stopped. Because the supervisor can have millions of dynamic children, we -%% can have an significative overhead here. -%%----------------------------------------------------------------- -terminate_dynamic_children(Child, Dynamics, SupName) -> - {Pids, EStack0} = monitor_dynamic_children(Child, Dynamics), - Sz = ?SETS:size(Pids), - EStack = case Child#child.shutdown of - brutal_kill -> - ?SETS:fold(fun(P, _) -> exit(P, kill) end, ok, Pids), - wait_dynamic_children(Child, Pids, Sz, undefined, EStack0); - infinity -> - ?SETS:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids), - wait_dynamic_children(Child, Pids, Sz, undefined, EStack0); - Time -> - ?SETS:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids), - TRef = erlang:start_timer(Time, self(), kill), - wait_dynamic_children(Child, Pids, Sz, TRef, EStack0) - end, - %% Unroll stacked errors and report them - ?DICT:fold(fun(Reason, Ls, _) -> - report_error(shutdown_error, Reason, - Child#child{pid=Ls}, SupName) - end, ok, EStack). - - -monitor_dynamic_children(#child{restart_type=temporary}, Dynamics) -> - ?SETS:fold(fun(P, {Pids, EStack}) -> - case monitor_child(P) of - ok -> - {?SETS:add_element(P, Pids), EStack}; - {error, normal} -> - {Pids, EStack}; - {error, Reason} -> - {Pids, ?DICT:append(Reason, P, EStack)} - end - end, {?SETS:new(), ?DICT:new()}, Dynamics); -monitor_dynamic_children(#child{restart_type=RType}, Dynamics) -> - ?DICT:fold(fun(P, _, {Pids, EStack}) when is_pid(P) -> - case monitor_child(P) of - ok -> - {?SETS:add_element(P, Pids), EStack}; - {error, normal} when not ?is_permanent(RType) -> - {Pids, EStack}; - {error, Reason} -> - {Pids, ?DICT:append(Reason, P, EStack)} - end; - (?restarting(_), _, {Pids, EStack}) -> - {Pids, EStack} - end, {?SETS:new(), ?DICT:new()}, Dynamics). - -wait_dynamic_children(_Child, _Pids, 0, undefined, EStack) -> - EStack; -wait_dynamic_children(_Child, _Pids, 0, TRef, EStack) -> - %% If the timer has expired before its cancellation, we must empty the - %% mail-box of the 'timeout'-message. - erlang:cancel_timer(TRef), - receive - {timeout, TRef, kill} -> - EStack - after 0 -> - EStack - end; -wait_dynamic_children(#child{shutdown=brutal_kill} = Child, Pids, Sz, - TRef, EStack) -> - receive - {'DOWN', _MRef, process, Pid, killed} -> - wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, - TRef, EStack); - - {'DOWN', _MRef, process, Pid, Reason} -> - wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, - TRef, ?DICT:append(Reason, Pid, EStack)) - end; -wait_dynamic_children(#child{restart_type=RType} = Child, Pids, Sz, - TRef, EStack) -> - receive - {'DOWN', _MRef, process, Pid, shutdown} -> - wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, - TRef, EStack); - - {'DOWN', _MRef, process, Pid, normal} when not ?is_permanent(RType) -> - wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, - TRef, EStack); - - {'DOWN', _MRef, process, Pid, Reason} -> - wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, - TRef, ?DICT:append(Reason, Pid, EStack)); - - {timeout, TRef, kill} -> - ?SETS:fold(fun(P, _) -> exit(P, kill) end, ok, Pids), - wait_dynamic_children(Child, Pids, Sz-1, undefined, EStack) - end. - -%%----------------------------------------------------------------- -%% Child/State manipulating functions. -%%----------------------------------------------------------------- - -%% Note we do not want to save the parameter list for temporary processes as -%% they will not be restarted, and hence we do not need this information. -%% Especially for dynamic children to simple_one_for_one supervisors -%% it could become very costly as it is not uncommon to spawn -%% very many such processes. -save_child(#child{restart_type = temporary, - mfargs = {M, F, _}} = Child, #state{children = Children} = State) -> - State#state{children = [Child#child{mfargs = {M, F, undefined}} |Children]}; -save_child(Child, #state{children = Children} = State) -> - State#state{children = [Child |Children]}. - -save_dynamic_child(temporary, Pid, _, #state{dynamics = Dynamics} = State) -> - State#state{dynamics = ?SETS:add_element(Pid, dynamics_db(temporary, Dynamics))}; -save_dynamic_child(RestartType, Pid, Args, #state{dynamics = Dynamics} = State) -> - State#state{dynamics = ?DICT:store(Pid, Args, dynamics_db(RestartType, Dynamics))}. - -dynamics_db(temporary, undefined) -> - ?SETS:new(); -dynamics_db(_, undefined) -> - ?DICT:new(); -dynamics_db(_,Dynamics) -> - Dynamics. - -dynamic_child_args(Pid, Dynamics) -> - case ?SETS:is_set(Dynamics) of - true -> - {ok, undefined}; - false -> - ?DICT:find(Pid, Dynamics) - end. - -state_del_child(#child{pid = Pid, restart_type = temporary}, State) when ?is_simple(State) -> - NDynamics = ?SETS:del_element(Pid, dynamics_db(temporary, State#state.dynamics)), - State#state{dynamics = NDynamics}; -state_del_child(#child{pid = Pid, restart_type = RType}, State) when ?is_simple(State) -> - NDynamics = ?DICT:erase(Pid, dynamics_db(RType, State#state.dynamics)), - State#state{dynamics = NDynamics}; -state_del_child(Child, State) -> - NChildren = del_child(Child#child.name, State#state.children), - State#state{children = NChildren}. - -del_child(Name, [Ch=#child{pid = ?restarting(_)}|_]=Chs) when Ch#child.name =:= Name -> - Chs; -del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name, Ch#child.restart_type =:= temporary -> - Chs; -del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name -> - [Ch#child{pid = undefined} | Chs]; -del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid, Ch#child.restart_type =:= temporary -> - Chs; -del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid -> - [Ch#child{pid = undefined} | Chs]; -del_child(Name, [Ch|Chs]) -> - [Ch|del_child(Name, Chs)]; -del_child(_, []) -> - []. - -%% Chs = [S4, S3, Ch, S1, S0] -%% Ret: {[S4, S3, Ch], [S1, S0]} -split_child(Name, Chs) -> - split_child(Name, Chs, []). - -split_child(Name, [Ch|Chs], After) when Ch#child.name =:= Name -> - {lists:reverse([Ch#child{pid = undefined} | After]), Chs}; -split_child(Pid, [Ch|Chs], After) when Ch#child.pid =:= Pid -> - {lists:reverse([Ch#child{pid = undefined} | After]), Chs}; -split_child(Name, [Ch|Chs], After) -> - split_child(Name, Chs, [Ch | After]); -split_child(_, [], After) -> - {lists:reverse(After), []}. - -get_child(Name, State) -> - get_child(Name, State, false). -get_child(Pid, State, AllowPid) when AllowPid, is_pid(Pid) -> - get_dynamic_child(Pid, State); -get_child(Name, State, _) -> - lists:keysearch(Name, #child.name, State#state.children). - -get_dynamic_child(Pid, #state{children=[Child], dynamics=Dynamics}) -> - DynamicsDb = dynamics_db(Child#child.restart_type, Dynamics), - case is_dynamic_pid(Pid, DynamicsDb) of - true -> - {value, Child#child{pid=Pid}}; - false -> - RPid = restarting(Pid), - case is_dynamic_pid(RPid, DynamicsDb) of - true -> - {value, Child#child{pid=RPid}}; - false -> - case erlang:is_process_alive(Pid) of - true -> false; - false -> {value, Child} - end - end - end. - -is_dynamic_pid(Pid, Dynamics) -> - case ?SETS:is_set(Dynamics) of - true -> - ?SETS:is_element(Pid, Dynamics); - false -> - ?DICT:is_key(Pid, Dynamics) - end. - -replace_child(Child, State) -> - Chs = do_replace_child(Child, State#state.children), - State#state{children = Chs}. - -do_replace_child(Child, [Ch|Chs]) when Ch#child.name =:= Child#child.name -> - [Child | Chs]; -do_replace_child(Child, [Ch|Chs]) -> - [Ch|do_replace_child(Child, Chs)]. - -remove_child(Child, State) -> - Chs = lists:keydelete(Child#child.name, #child.name, State#state.children), - State#state{children = Chs}. - -%%----------------------------------------------------------------- -%% Func: init_state/4 -%% Args: SupName = {local, atom()} | {global, atom()} | self -%% Type = {Strategy, MaxIntensity, Period} -%% Strategy = one_for_one | one_for_all | simple_one_for_one | -%% rest_for_one -%% MaxIntensity = integer() >= 0 -%% Period = integer() > 0 -%% Mod :== atom() -%% Args :== term() -%% Purpose: Check that Type is of correct type (!) -%% Returns: {ok, state()} | Error -%%----------------------------------------------------------------- -init_state(SupName, Type, Mod, Args) -> - case catch init_state1(SupName, Type, Mod, Args) of - {ok, State} -> - {ok, State}; - Error -> - Error - end. - -init_state1(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) -> - validStrategy(Strategy), - validIntensity(MaxIntensity), - validPeriod(Period), - {ok, #state{name = supname(SupName,Mod), - strategy = Strategy, - intensity = MaxIntensity, - period = Period, - module = Mod, - args = Args}}; -init_state1(_SupName, Type, _, _) -> - {invalid_type, Type}. - -validStrategy(simple_one_for_one) -> true; -validStrategy(one_for_one) -> true; -validStrategy(one_for_all) -> true; -validStrategy(rest_for_one) -> true; -validStrategy(What) -> throw({invalid_strategy, What}). - -validIntensity(Max) when is_integer(Max), - Max >= 0 -> true; -validIntensity(What) -> throw({invalid_intensity, What}). - -validPeriod(Period) when is_integer(Period), - Period > 0 -> true; -validPeriod(What) -> throw({invalid_period, What}). - -supname(self, Mod) -> {self(), Mod}; -supname(N, _) -> N. - -%%% ------------------------------------------------------ -%%% Check that the children start specification is valid. -%%% Shall be a six (6) tuple -%%% {Name, Func, RestartType, Shutdown, ChildType, Modules} -%%% where Name is an atom -%%% Func is {Mod, Fun, Args} == {atom(), atom(), list()} -%%% RestartType is permanent | temporary | transient | -%%% intrinsic | {permanent, Delay} | -%%% {transient, Delay} | {intrinsic, Delay} -%% where Delay >= 0 -%%% Shutdown = integer() > 0 | infinity | brutal_kill -%%% ChildType = supervisor | worker -%%% Modules = [atom()] | dynamic -%%% Returns: {ok, [child_rec()]} | Error -%%% ------------------------------------------------------ - -check_startspec(Children) -> check_startspec(Children, []). - -check_startspec([ChildSpec|T], Res) -> - case check_childspec(ChildSpec) of - {ok, Child} -> - case lists:keymember(Child#child.name, #child.name, Res) of - true -> {duplicate_child_name, Child#child.name}; - false -> check_startspec(T, [Child | Res]) - end; - Error -> Error - end; -check_startspec([], Res) -> - {ok, lists:reverse(Res)}. - -check_childspec({Name, Func, RestartType, Shutdown, ChildType, Mods}) -> - catch check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods); -check_childspec(X) -> {invalid_child_spec, X}. - -check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods) -> - validName(Name), - validFunc(Func), - validRestartType(RestartType), - validChildType(ChildType), - validShutdown(Shutdown, ChildType), - validMods(Mods), - {ok, #child{name = Name, mfargs = Func, restart_type = RestartType, - shutdown = Shutdown, child_type = ChildType, modules = Mods}}. - -validChildType(supervisor) -> true; -validChildType(worker) -> true; -validChildType(What) -> throw({invalid_child_type, What}). - -validName(_Name) -> true. - -validFunc({M, F, A}) when is_atom(M), - is_atom(F), - is_list(A) -> true; -validFunc(Func) -> throw({invalid_mfa, Func}). - -validRestartType(permanent) -> true; -validRestartType(temporary) -> true; -validRestartType(transient) -> true; -validRestartType(intrinsic) -> true; -validRestartType({permanent, Delay}) -> validDelay(Delay); -validRestartType({intrinsic, Delay}) -> validDelay(Delay); -validRestartType({transient, Delay}) -> validDelay(Delay); -validRestartType(RestartType) -> throw({invalid_restart_type, - RestartType}). - -validDelay(Delay) when is_number(Delay), - Delay >= 0 -> true; -validDelay(What) -> throw({invalid_delay, What}). - -validShutdown(Shutdown, _) - when is_integer(Shutdown), Shutdown > 0 -> true; -validShutdown(infinity, _) -> true; -validShutdown(brutal_kill, _) -> true; -validShutdown(Shutdown, _) -> throw({invalid_shutdown, Shutdown}). - -validMods(dynamic) -> true; -validMods(Mods) when is_list(Mods) -> - lists:foreach(fun(Mod) -> - if - is_atom(Mod) -> ok; - true -> throw({invalid_module, Mod}) - end - end, - Mods); -validMods(Mods) -> throw({invalid_modules, Mods}). - -%%% ------------------------------------------------------ -%%% Add a new restart and calculate if the max restart -%%% intensity has been reached (in that case the supervisor -%%% shall terminate). -%%% All restarts accured inside the period amount of seconds -%%% are kept in the #state.restarts list. -%%% Returns: {ok, State'} | {terminate, State'} -%%% ------------------------------------------------------ - -add_restart(State) -> - I = State#state.intensity, - P = State#state.period, - R = State#state.restarts, - Now = erlang:now(), - R1 = add_restart([Now|R], Now, P), - State1 = State#state{restarts = R1}, - case length(R1) of - CurI when CurI =< I -> - {ok, State1}; - _ -> - {terminate, State1} - end. - -add_restart([R|Restarts], Now, Period) -> - case inPeriod(R, Now, Period) of - true -> - [R|add_restart(Restarts, Now, Period)]; - _ -> - [] - end; -add_restart([], _, _) -> - []. - -inPeriod(Time, Now, Period) -> - case difference(Time, Now) of - T when T > Period -> - false; - _ -> - true - end. - -%% -%% Time = {MegaSecs, Secs, MicroSecs} (NOTE: MicroSecs is ignored) -%% Calculate the time elapsed in seconds between two timestamps. -%% If MegaSecs is equal just subtract Secs. -%% Else calculate the Mega difference and add the Secs difference, -%% note that Secs difference can be negative, e.g. -%% {827, 999999, 676} diff {828, 1, 653753} == > 2 secs. -%% -difference({TimeM, TimeS, _}, {CurM, CurS, _}) when CurM > TimeM -> - ((CurM - TimeM) * 1000000) + (CurS - TimeS); -difference({_, TimeS, _}, {_, CurS, _}) -> - CurS - TimeS. - -%%% ------------------------------------------------------ -%%% Error and progress reporting. -%%% ------------------------------------------------------ - -report_error(Error, Reason, Child, SupName) -> - ErrorMsg = [{supervisor, SupName}, - {errorContext, Error}, - {reason, Reason}, - {offender, extract_child(Child)}], - error_logger:error_report(supervisor_report, ErrorMsg). - - -extract_child(Child) when is_list(Child#child.pid) -> - [{nb_children, length(Child#child.pid)}, - {name, Child#child.name}, - {mfargs, Child#child.mfargs}, - {restart_type, Child#child.restart_type}, - {shutdown, Child#child.shutdown}, - {child_type, Child#child.child_type}]; -extract_child(Child) -> - [{pid, Child#child.pid}, - {name, Child#child.name}, - {mfargs, Child#child.mfargs}, - {restart_type, Child#child.restart_type}, - {shutdown, Child#child.shutdown}, - {child_type, Child#child.child_type}]. - -report_progress(Child, SupName) -> - Progress = [{supervisor, SupName}, - {started, extract_child(Child)}], - error_logger:info_report(progress, Progress). diff --git a/src/tcp_acceptor.erl b/src/tcp_acceptor.erl deleted file mode 100644 index 047b85c5..00000000 --- a/src/tcp_acceptor.erl +++ /dev/null @@ -1,105 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(tcp_acceptor). - --behaviour(gen_server). - --export([start_link/2]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --record(state, {callback, sock, ref}). - -%%-------------------------------------------------------------------- - -start_link(Callback, LSock) -> - gen_server:start_link(?MODULE, {Callback, LSock}, []). - -%%-------------------------------------------------------------------- - -init({Callback, LSock}) -> - gen_server:cast(self(), accept), - {ok, #state{callback=Callback, sock=LSock}}. - -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_cast(accept, State) -> - ok = file_handle_cache:obtain(), - accept(State); - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info({inet_async, LSock, Ref, {ok, Sock}}, - State = #state{callback={M,F,A}, sock=LSock, ref=Ref}) -> - - %% patch up the socket so it looks like one we got from - %% gen_tcp:accept/1 - {ok, Mod} = inet_db:lookup_socket(LSock), - inet_db:register_socket(Sock, Mod), - - %% handle - case tune_buffer_size(Sock) of - ok -> file_handle_cache:transfer( - apply(M, F, A ++ [Sock])), - ok = file_handle_cache:obtain(); - {error, enotconn} -> catch port_close(Sock); - {error, Err} -> {ok, {IPAddress, Port}} = inet:sockname(LSock), - error_logger:error_msg( - "failed to tune buffer size of " - "connection accepted on ~s:~p - ~s~n", - [rabbit_misc:ntoab(IPAddress), Port, - rabbit_misc:format_inet_error(Err)]), - catch port_close(Sock) - end, - - %% accept more - accept(State); - -handle_info({inet_async, LSock, Ref, {error, Reason}}, - State=#state{sock=LSock, ref=Ref}) -> - case Reason of - closed -> {stop, normal, State}; %% listening socket closed - econnaborted -> accept(State); %% client sent RST before we accepted - _ -> {stop, {accept_failed, Reason}, State} - end; - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%-------------------------------------------------------------------- - -accept(State = #state{sock=LSock}) -> - case prim_inet:async_accept(LSock, -1) of - {ok, Ref} -> {noreply, State#state{ref=Ref}}; - Error -> {stop, {cannot_accept, Error}, State} - end. - -tune_buffer_size(Sock) -> - case inet:getopts(Sock, [sndbuf, recbuf, buffer]) of - {ok, BufSizes} -> BufSz = lists:max([Sz || {_Opt, Sz} <- BufSizes]), - inet:setopts(Sock, [{buffer, BufSz}]); - Error -> Error - end. diff --git a/src/tcp_acceptor_sup.erl b/src/tcp_acceptor_sup.erl deleted file mode 100644 index 10b10e4a..00000000 --- a/src/tcp_acceptor_sup.erl +++ /dev/null @@ -1,43 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(tcp_acceptor_sup). - --behaviour(supervisor). - --export([start_link/2]). - --export([init/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(mfargs() :: {atom(), atom(), [any()]}). - --spec(start_link/2 :: (atom(), mfargs()) -> rabbit_types:ok_pid_or_error()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(Name, Callback) -> - supervisor:start_link({local,Name}, ?MODULE, Callback). - -init(Callback) -> - {ok, {{simple_one_for_one, 10, 10}, - [{tcp_acceptor, {tcp_acceptor, start_link, [Callback]}, - transient, brutal_kill, worker, [tcp_acceptor]}]}}. diff --git a/src/tcp_listener.erl b/src/tcp_listener.erl deleted file mode 100644 index 7c464c6a..00000000 --- a/src/tcp_listener.erl +++ /dev/null @@ -1,98 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(tcp_listener). - --behaviour(gen_server). - --export([start_link/8]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --record(state, {sock, on_startup, on_shutdown, label}). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(mfargs() :: {atom(), atom(), [any()]}). - --spec(start_link/8 :: - (inet:ip_address(), inet:port_number(), [gen_tcp:listen_option()], - integer(), atom(), mfargs(), mfargs(), string()) -> - rabbit_types:ok_pid_or_error()). - --endif. - -%%-------------------------------------------------------------------- - -start_link(IPAddress, Port, SocketOpts, - ConcurrentAcceptorCount, AcceptorSup, - OnStartup, OnShutdown, Label) -> - gen_server:start_link( - ?MODULE, {IPAddress, Port, SocketOpts, - ConcurrentAcceptorCount, AcceptorSup, - OnStartup, OnShutdown, Label}, []). - -%%-------------------------------------------------------------------- - -init({IPAddress, Port, SocketOpts, - ConcurrentAcceptorCount, AcceptorSup, - {M,F,A} = OnStartup, OnShutdown, Label}) -> - process_flag(trap_exit, true), - case gen_tcp:listen(Port, SocketOpts ++ [{ip, IPAddress}, - {active, false}]) of - {ok, LSock} -> - lists:foreach(fun (_) -> - {ok, _APid} = supervisor:start_child( - AcceptorSup, [LSock]) - end, - lists:duplicate(ConcurrentAcceptorCount, dummy)), - {ok, {LIPAddress, LPort}} = inet:sockname(LSock), - error_logger:info_msg( - "started ~s on ~s:~p~n", - [Label, rabbit_misc:ntoab(LIPAddress), LPort]), - apply(M, F, A ++ [IPAddress, Port]), - {ok, #state{sock = LSock, - on_startup = OnStartup, on_shutdown = OnShutdown, - label = Label}}; - {error, Reason} -> - error_logger:error_msg( - "failed to start ~s on ~s:~p - ~p (~s)~n", - [Label, rabbit_misc:ntoab(IPAddress), Port, - Reason, inet:format_error(Reason)]), - {stop, {cannot_listen, IPAddress, Port, Reason}} - end. - -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, #state{sock=LSock, on_shutdown = {M,F,A}, label=Label}) -> - {ok, {IPAddress, Port}} = inet:sockname(LSock), - gen_tcp:close(LSock), - error_logger:info_msg("stopped ~s on ~s:~p~n", - [Label, rabbit_misc:ntoab(IPAddress), Port]), - apply(M, F, A ++ [IPAddress, Port]). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. diff --git a/src/tcp_listener_sup.erl b/src/tcp_listener_sup.erl deleted file mode 100644 index b3e1c69b..00000000 --- a/src/tcp_listener_sup.erl +++ /dev/null @@ -1,70 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(tcp_listener_sup). - --behaviour(supervisor). - --export([start_link/7, start_link/8]). - --export([init/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(mfargs() :: {atom(), atom(), [any()]}). - --spec(start_link/7 :: - (inet:ip_address(), inet:port_number(), [gen_tcp:listen_option()], - mfargs(), mfargs(), mfargs(), string()) -> - rabbit_types:ok_pid_or_error()). --spec(start_link/8 :: - (inet:ip_address(), inet:port_number(), [gen_tcp:listen_option()], - mfargs(), mfargs(), mfargs(), integer(), string()) -> - rabbit_types:ok_pid_or_error()). - --endif. - -%%---------------------------------------------------------------------------- - -start_link(IPAddress, Port, SocketOpts, OnStartup, OnShutdown, - AcceptCallback, Label) -> - start_link(IPAddress, Port, SocketOpts, OnStartup, OnShutdown, - AcceptCallback, 1, Label). - -start_link(IPAddress, Port, SocketOpts, OnStartup, OnShutdown, - AcceptCallback, ConcurrentAcceptorCount, Label) -> - supervisor:start_link( - ?MODULE, {IPAddress, Port, SocketOpts, OnStartup, OnShutdown, - AcceptCallback, ConcurrentAcceptorCount, Label}). - -init({IPAddress, Port, SocketOpts, OnStartup, OnShutdown, - AcceptCallback, ConcurrentAcceptorCount, Label}) -> - %% This is gross. The tcp_listener needs to know about the - %% tcp_acceptor_sup, and the only way I can think of accomplishing - %% that without jumping through hoops is to register the - %% tcp_acceptor_sup. - Name = rabbit_misc:tcp_name(tcp_acceptor_sup, IPAddress, Port), - {ok, {{one_for_all, 10, 10}, - [{tcp_acceptor_sup, {tcp_acceptor_sup, start_link, - [Name, AcceptCallback]}, - transient, infinity, supervisor, [tcp_acceptor_sup]}, - {tcp_listener, {tcp_listener, start_link, - [IPAddress, Port, SocketOpts, - ConcurrentAcceptorCount, Name, - OnStartup, OnShutdown, Label]}, - transient, 16#ffffffff, worker, [tcp_listener]}]}}. diff --git a/src/truncate.erl b/src/truncate.erl deleted file mode 100644 index 820af1bf..00000000 --- a/src/truncate.erl +++ /dev/null @@ -1,194 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(truncate). - --define(ELLIPSIS_LENGTH, 3). - --record(params, {content, struct, content_dec, struct_dec}). - --export([log_event/2, term/2]). -%% exported for testing --export([test/0]). - -log_event({Type, GL, {Pid, Format, Args}}, Params) - when Type =:= error orelse - Type =:= info_msg orelse - Type =:= warning_msg -> - {Type, GL, {Pid, Format, [term(T, Params) || T <- Args]}}; -log_event({Type, GL, {Pid, ReportType, Report}}, Params) - when Type =:= error_report orelse - Type =:= info_report orelse - Type =:= warning_report -> - {Type, GL, {Pid, ReportType, report(Report, Params)}}; -log_event(Event, _Params) -> - Event. - -report([[Thing]], Params) -> report([Thing], Params); -report(List, Params) when is_list(List) -> [case Item of - {K, V} -> {K, term(V, Params)}; - _ -> term(Item, Params) - end || Item <- List]; -report(Other, Params) -> term(Other, Params). - -term(Thing, {Max, {Content, Struct, ContentDec, StructDec}}) -> - case term_limit(Thing, Max) of - true -> term(Thing, true, #params{content = Content, - struct = Struct, - content_dec = ContentDec, - struct_dec = StructDec}); - false -> Thing - end. - -term(Bin, _AllowPrintable, #params{content = N}) - when (is_binary(Bin) orelse is_bitstring(Bin)) - andalso size(Bin) > N - ?ELLIPSIS_LENGTH -> - Suffix = without_ellipsis(N), - <<Head:Suffix/binary, _/bitstring>> = Bin, - <<Head/binary, <<"...">>/binary>>; -term(L, AllowPrintable, #params{struct = N} = Params) when is_list(L) -> - case AllowPrintable andalso io_lib:printable_list(L) of - true -> N2 = without_ellipsis(N), - case length(L) > N2 of - true -> string:left(L, N2) ++ "..."; - false -> L - end; - false -> shrink_list(L, Params) - end; -term(T, _AllowPrintable, Params) when is_tuple(T) -> - list_to_tuple(shrink_list(tuple_to_list(T), Params)); -term(T, _, _) -> - T. - -without_ellipsis(N) -> erlang:max(N - ?ELLIPSIS_LENGTH, 0). - -shrink_list(_, #params{struct = N}) when N =< 0 -> - ['...']; -shrink_list([], _) -> - []; -shrink_list([H|T], #params{content = Content, - struct = Struct, - content_dec = ContentDec, - struct_dec = StructDec} = Params) -> - [term(H, true, Params#params{content = Content - ContentDec, - struct = Struct - StructDec}) - | term(T, false, Params#params{struct = Struct - 1})]. - -%%---------------------------------------------------------------------------- - -%% We don't use erts_debug:flat_size/1 because that ignores binary -%% sizes. This is all going to be rather approximate though, these -%% sizes are probably not very "fair" but we are just trying to see if -%% we reach a fairly arbitrary limit anyway though. -term_limit(Thing, Max) -> - case term_size(Thing, Max, erlang:system_info(wordsize)) of - limit_exceeded -> true; - _ -> false - end. - -term_size(B, M, _W) when is_bitstring(B) -> lim(M, size(B)); -term_size(A, M, W) when is_atom(A) -> lim(M, 2 * W); -term_size(N, M, W) when is_number(N) -> lim(M, 2 * W); -term_size(T, M, W) when is_tuple(T) -> tuple_term_size( - T, M, 1, tuple_size(T), W); -term_size([], M, _W) -> - M; -term_size([H|T], M, W) -> - case term_size(H, M, W) of - limit_exceeded -> limit_exceeded; - M2 -> lim(term_size(T, M2, W), 2 * W) - end; -term_size(X, M, W) -> - lim(M, erts_debug:flat_size(X) * W). - -lim(S, T) when is_number(S) andalso S > T -> S - T; -lim(_, _) -> limit_exceeded. - -tuple_term_size(_T, limit_exceeded, _I, _S, _W) -> - limit_exceeded; -tuple_term_size(_T, M, I, S, _W) when I > S -> - M; -tuple_term_size(T, M, I, S, W) -> - tuple_term_size(T, lim(term_size(element(I, T), M, W), 2 * W), I + 1, S, W). - -%%---------------------------------------------------------------------------- - -test() -> - test_short_examples_exactly(), - test_term_limit(), - test_large_examples_for_size(), - ok. - -test_short_examples_exactly() -> - F = fun (Term, Exp) -> - Exp = term(Term, {1, {10, 10, 5, 5}}), - Term = term(Term, {100000, {10, 10, 5, 5}}) - end, - FSmall = fun (Term, Exp) -> - Exp = term(Term, {1, {2, 2, 2, 2}}), - Term = term(Term, {100000, {2, 2, 2, 2}}) - end, - F([], []), - F("h", "h"), - F("hello world", "hello w..."), - F([[h,e,l,l,o,' ',w,o,r,l,d]], [[h,e,l,l,o,'...']]), - F([a|b], [a|b]), - F(<<"hello">>, <<"hello">>), - F([<<"hello world">>], [<<"he...">>]), - F(<<1:1>>, <<1:1>>), - F(<<1:81>>, <<0:56, "...">>), - F({{{{a}}},{b},c,d,e,f,g,h,i,j,k}, {{{'...'}},{b},c,d,e,f,g,h,i,j,'...'}), - FSmall({a,30,40,40,40,40}, {a,30,'...'}), - FSmall([a,30,40,40,40,40], [a,30,'...']), - P = spawn(fun() -> receive die -> ok end end), - F([0, 0.0, <<1:1>>, F, P], [0, 0.0, <<1:1>>, F, P]), - P ! die, - R = make_ref(), - F([R], [R]), - ok. - -test_term_limit() -> - W = erlang:system_info(wordsize), - S = <<"abc">>, - 1 = term_size(S, 4, W), - limit_exceeded = term_size(S, 3, W), - case 100 - term_size([S, S], 100, W) of - 22 -> ok; %% 32 bit - 38 -> ok %% 64 bit - end, - case 100 - term_size([S, [S]], 100, W) of - 30 -> ok; %% ditto - 54 -> ok - end, - limit_exceeded = term_size([S, S], 6, W), - ok. - -test_large_examples_for_size() -> - %% Real world values - Shrink = fun(Term) -> term(Term, {1, {1000, 100, 50, 5}}) end, - TestSize = fun(Term) -> - true = 5000000 < size(term_to_binary(Term)), - true = 500000 > size(term_to_binary(Shrink(Term))) - end, - TestSize(lists:seq(1, 5000000)), - TestSize(recursive_list(1000, 10)), - TestSize(recursive_list(5000, 20)), - TestSize(gb_sets:from_list([I || I <- lists:seq(1, 1000000)])), - TestSize(gb_trees:from_orddict([{I, I} || I <- lists:seq(1, 1000000)])), - ok. - -recursive_list(S, 0) -> lists:seq(1, S); -recursive_list(S, N) -> [recursive_list(S div N, N-1) || _ <- lists:seq(1, S)]. diff --git a/src/vm_memory_monitor.erl b/src/vm_memory_monitor.erl deleted file mode 100644 index 948956a3..00000000 --- a/src/vm_memory_monitor.erl +++ /dev/null @@ -1,388 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - -%% In practice Erlang shouldn't be allowed to grow to more than a half -%% of available memory. The pessimistic scenario is when the Erlang VM -%% has a single process that's consuming all memory. In such a case, -%% during garbage collection, Erlang tries to allocate a huge chunk of -%% continuous memory, which can result in a crash or heavy swapping. -%% -%% This module tries to warn Rabbit before such situations occur, so -%% that it has a higher chance to avoid running out of memory. - --module(vm_memory_monitor). - --behaviour(gen_server). - --export([start_link/1, start_link/3]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --export([get_total_memory/0, get_vm_limit/0, - get_check_interval/0, set_check_interval/1, - get_vm_memory_high_watermark/0, set_vm_memory_high_watermark/1, - get_memory_limit/0]). - -%% for tests --export([parse_line_linux/1]). - - --define(SERVER, ?MODULE). --define(DEFAULT_MEMORY_CHECK_INTERVAL, 1000). --define(ONE_MB, 1048576). - -%% For an unknown OS, we assume that we have 1GB of memory. It'll be -%% wrong. Scale by vm_memory_high_watermark in configuration to get a -%% sensible value. --define(MEMORY_SIZE_FOR_UNKNOWN_OS, 1073741824). - --record(state, {total_memory, - memory_limit, - memory_fraction, - timeout, - timer, - alarmed, - alarm_funs - }). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/1 :: (float()) -> rabbit_types:ok_pid_or_error()). --spec(start_link/3 :: (float(), fun ((any()) -> 'ok'), - fun ((any()) -> 'ok')) -> rabbit_types:ok_pid_or_error()). --spec(get_total_memory/0 :: () -> (non_neg_integer() | 'unknown')). --spec(get_vm_limit/0 :: () -> non_neg_integer()). --spec(get_check_interval/0 :: () -> non_neg_integer()). --spec(set_check_interval/1 :: (non_neg_integer()) -> 'ok'). --spec(get_vm_memory_high_watermark/0 :: () -> float()). --spec(set_vm_memory_high_watermark/1 :: (float()) -> 'ok'). --spec(get_memory_limit/0 :: () -> non_neg_integer()). - --endif. - -%%---------------------------------------------------------------------------- -%% Public API -%%---------------------------------------------------------------------------- - -get_total_memory() -> - try - get_total_memory(os:type()) - catch _:Error -> - rabbit_log:warning( - "Failed to get total system memory: ~n~p~n~p~n", - [Error, erlang:get_stacktrace()]), - unknown - end. - -get_vm_limit() -> get_vm_limit(os:type()). - -get_check_interval() -> - gen_server:call(?MODULE, get_check_interval, infinity). - -set_check_interval(Fraction) -> - gen_server:call(?MODULE, {set_check_interval, Fraction}, infinity). - -get_vm_memory_high_watermark() -> - gen_server:call(?MODULE, get_vm_memory_high_watermark, infinity). - -set_vm_memory_high_watermark(Fraction) -> - gen_server:call(?MODULE, {set_vm_memory_high_watermark, Fraction}, - infinity). - -get_memory_limit() -> - gen_server:call(?MODULE, get_memory_limit, infinity). - -%%---------------------------------------------------------------------------- -%% gen_server callbacks -%%---------------------------------------------------------------------------- - -start_link(MemFraction) -> - start_link(MemFraction, - fun alarm_handler:set_alarm/1, fun alarm_handler:clear_alarm/1). - -start_link(MemFraction, AlarmSet, AlarmClear) -> - gen_server:start_link({local, ?SERVER}, ?MODULE, - [MemFraction, {AlarmSet, AlarmClear}], []). - -init([MemFraction, AlarmFuns]) -> - TRef = start_timer(?DEFAULT_MEMORY_CHECK_INTERVAL), - State = #state { timeout = ?DEFAULT_MEMORY_CHECK_INTERVAL, - timer = TRef, - alarmed = false, - alarm_funs = AlarmFuns }, - {ok, set_mem_limits(State, MemFraction)}. - -handle_call(get_vm_memory_high_watermark, _From, State) -> - {reply, State#state.memory_fraction, State}; - -handle_call({set_vm_memory_high_watermark, MemFraction}, _From, State) -> - {reply, ok, set_mem_limits(State, MemFraction)}; - -handle_call(get_check_interval, _From, State) -> - {reply, State#state.timeout, State}; - -handle_call({set_check_interval, Timeout}, _From, State) -> - {ok, cancel} = timer:cancel(State#state.timer), - {reply, ok, State#state{timeout = Timeout, timer = start_timer(Timeout)}}; - -handle_call(get_memory_limit, _From, State) -> - {reply, State#state.memory_limit, State}; - -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_cast(_Request, State) -> - {noreply, State}. - -handle_info(update, State) -> - {noreply, internal_update(State)}; - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%---------------------------------------------------------------------------- -%% Server Internals -%%---------------------------------------------------------------------------- - -set_mem_limits(State, MemFraction) -> - TotalMemory = - case get_total_memory() of - unknown -> - case State of - #state { total_memory = undefined, - memory_limit = undefined } -> - error_logger:warning_msg( - "Unknown total memory size for your OS ~p. " - "Assuming memory size is ~pMB.~n", - [os:type(), - trunc(?MEMORY_SIZE_FOR_UNKNOWN_OS/?ONE_MB)]); - _ -> - ok - end, - ?MEMORY_SIZE_FOR_UNKNOWN_OS; - M -> M - end, - UsableMemory = - case get_vm_limit() of - Limit when Limit < TotalMemory -> - error_logger:warning_msg( - "Only ~pMB of ~pMB memory usable due to " - "limited address space.~n" - "Crashes due to memory exhaustion are possible - see~n" - "http://www.rabbitmq.com/memory.html#address-space~n", - [trunc(V/?ONE_MB) || V <- [Limit, TotalMemory]]), - Limit; - _ -> - TotalMemory - end, - MemLim = trunc(MemFraction * UsableMemory), - error_logger:info_msg("Memory limit set to ~pMB of ~pMB total.~n", - [trunc(MemLim/?ONE_MB), trunc(TotalMemory/?ONE_MB)]), - internal_update(State #state { total_memory = TotalMemory, - memory_limit = MemLim, - memory_fraction = MemFraction}). - -internal_update(State = #state { memory_limit = MemLimit, - alarmed = Alarmed, - alarm_funs = {AlarmSet, AlarmClear} }) -> - MemUsed = erlang:memory(total), - NewAlarmed = MemUsed > MemLimit, - case {Alarmed, NewAlarmed} of - {false, true} -> emit_update_info(set, MemUsed, MemLimit), - AlarmSet({{resource_limit, memory, node()}, []}); - {true, false} -> emit_update_info(clear, MemUsed, MemLimit), - AlarmClear({resource_limit, memory, node()}); - _ -> ok - end, - State #state {alarmed = NewAlarmed}. - -emit_update_info(AlarmState, MemUsed, MemLimit) -> - error_logger:info_msg( - "vm_memory_high_watermark ~p. Memory used:~p allowed:~p~n", - [AlarmState, MemUsed, MemLimit]). - -start_timer(Timeout) -> - {ok, TRef} = timer:send_interval(Timeout, update), - TRef. - -%% According to http://msdn.microsoft.com/en-us/library/aa366778(VS.85).aspx -%% Windows has 2GB and 8TB of address space for 32 and 64 bit accordingly. -get_vm_limit({win32,_OSname}) -> - case erlang:system_info(wordsize) of - 4 -> 2*1024*1024*1024; %% 2 GB for 32 bits 2^31 - 8 -> 8*1024*1024*1024*1024 %% 8 TB for 64 bits 2^42 - end; - -%% On a 32-bit machine, if you're using more than 2 gigs of RAM you're -%% in big trouble anyway. -get_vm_limit(_OsType) -> - case erlang:system_info(wordsize) of - 4 -> 2*1024*1024*1024; %% 2 GB for 32 bits 2^31 - 8 -> 256*1024*1024*1024*1024 %% 256 TB for 64 bits 2^48 - %%http://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details - end. - -%%---------------------------------------------------------------------------- -%% Internal Helpers -%%---------------------------------------------------------------------------- -cmd(Command) -> - Exec = hd(string:tokens(Command, " ")), - case os:find_executable(Exec) of - false -> throw({command_not_found, Exec}); - _ -> os:cmd(Command) - end. - -%% get_total_memory(OS) -> Total -%% Windows and Freebsd code based on: memsup:get_memory_usage/1 -%% Original code was part of OTP and released under "Erlang Public License". - -get_total_memory({unix,darwin}) -> - File = cmd("/usr/bin/vm_stat"), - Lines = string:tokens(File, "\n"), - Dict = dict:from_list(lists:map(fun parse_line_mach/1, Lines)), - [PageSize, Inactive, Active, Free, Wired] = - [dict:fetch(Key, Dict) || - Key <- [page_size, 'Pages inactive', 'Pages active', 'Pages free', - 'Pages wired down']], - PageSize * (Inactive + Active + Free + Wired); - -get_total_memory({unix,freebsd}) -> - PageSize = sysctl("vm.stats.vm.v_page_size"), - PageCount = sysctl("vm.stats.vm.v_page_count"), - PageCount * PageSize; - -get_total_memory({unix,openbsd}) -> - sysctl("hw.usermem"); - -get_total_memory({win32,_OSname}) -> - [Result|_] = os_mon_sysinfo:get_mem_info(), - {ok, [_MemLoad, TotPhys, _AvailPhys, _TotPage, _AvailPage, _TotV, _AvailV], - _RestStr} = - io_lib:fread("~d~d~d~d~d~d~d", Result), - TotPhys; - -get_total_memory({unix, linux}) -> - File = read_proc_file("/proc/meminfo"), - Lines = string:tokens(File, "\n"), - Dict = dict:from_list(lists:map(fun parse_line_linux/1, Lines)), - dict:fetch('MemTotal', Dict); - -get_total_memory({unix, sunos}) -> - File = cmd("/usr/sbin/prtconf"), - Lines = string:tokens(File, "\n"), - Dict = dict:from_list(lists:map(fun parse_line_sunos/1, Lines)), - dict:fetch('Memory size', Dict); - -get_total_memory({unix, aix}) -> - File = cmd("/usr/bin/vmstat -v"), - Lines = string:tokens(File, "\n"), - Dict = dict:from_list(lists:map(fun parse_line_aix/1, Lines)), - dict:fetch('memory pages', Dict) * 4096; - -get_total_memory(_OsType) -> - unknown. - -%% A line looks like "Foo bar: 123456." -parse_line_mach(Line) -> - [Name, RHS | _Rest] = string:tokens(Line, ":"), - case Name of - "Mach Virtual Memory Statistics" -> - ["(page", "size", "of", PageSize, "bytes)"] = - string:tokens(RHS, " "), - {page_size, list_to_integer(PageSize)}; - _ -> - [Value | _Rest1] = string:tokens(RHS, " ."), - {list_to_atom(Name), list_to_integer(Value)} - end. - -%% A line looks like "MemTotal: 502968 kB" -%% or (with broken OS/modules) "Readahead 123456 kB" -parse_line_linux(Line) -> - {Name, Value, UnitRest} = - case string:tokens(Line, ":") of - %% no colon in the line - [S] -> - [K, RHS] = re:split(S, "\s", [{parts, 2}, {return, list}]), - [V | Unit] = string:tokens(RHS, " "), - {K, V, Unit}; - [K, RHS | _Rest] -> - [V | Unit] = string:tokens(RHS, " "), - {K, V, Unit} - end, - Value1 = case UnitRest of - [] -> list_to_integer(Value); %% no units - ["kB"] -> list_to_integer(Value) * 1024 - end, - {list_to_atom(Name), Value1}. - -%% A line looks like "Memory size: 1024 Megabytes" -parse_line_sunos(Line) -> - case string:tokens(Line, ":") of - [Name, RHS | _Rest] -> - [Value1 | UnitsRest] = string:tokens(RHS, " "), - Value2 = case UnitsRest of - ["Gigabytes"] -> - list_to_integer(Value1) * ?ONE_MB * 1024; - ["Megabytes"] -> - list_to_integer(Value1) * ?ONE_MB; - ["Kilobytes"] -> - list_to_integer(Value1) * 1024; - _ -> - Value1 ++ UnitsRest %% no known units - end, - {list_to_atom(Name), Value2}; - [Name] -> {list_to_atom(Name), none} - end. - -%% Lines look like " 12345 memory pages" -%% or " 80.1 maxpin percentage" -parse_line_aix(Line) -> - [Value | NameWords] = string:tokens(Line, " "), - Name = string:join(NameWords, " "), - {list_to_atom(Name), - case lists:member($., Value) of - true -> trunc(list_to_float(Value)); - false -> list_to_integer(Value) - end}. - -sysctl(Def) -> - list_to_integer(cmd("/sbin/sysctl -n " ++ Def) -- "\n"). - -%% file:read_file does not work on files in /proc as it seems to get -%% the size of the file first and then read that many bytes. But files -%% in /proc always have length 0, we just have to read until we get -%% eof. -read_proc_file(File) -> - {ok, IoDevice} = file:open(File, [read, raw]), - Res = read_proc_file(IoDevice, []), - file:close(IoDevice), - lists:flatten(lists:reverse(Res)). - --define(BUFFER_SIZE, 1024). -read_proc_file(IoDevice, Acc) -> - case file:read(IoDevice, ?BUFFER_SIZE) of - {ok, Res} -> read_proc_file(IoDevice, [Res | Acc]); - eof -> Acc - end. diff --git a/src/worker_pool.erl b/src/worker_pool.erl deleted file mode 100644 index 608cea91..00000000 --- a/src/worker_pool.erl +++ /dev/null @@ -1,142 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(worker_pool). - -%% Generic worker pool manager. -%% -%% Supports nested submission of jobs (nested jobs always run -%% immediately in current worker process). -%% -%% Possible future enhancements: -%% -%% 1. Allow priorities (basically, change the pending queue to a -%% priority_queue). - --behaviour(gen_server2). - --export([start_link/0, submit/1, submit/2, submit_async/1, ready/1, - idle/1]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(mfargs() :: {atom(), atom(), [any()]}). - --spec(start_link/0 :: () -> {'ok', pid()} | {'error', any()}). --spec(submit/1 :: (fun (() -> A) | mfargs()) -> A). --spec(submit/2 :: (fun (() -> A) | mfargs(), 'reuse' | 'single') -> A). --spec(submit_async/1 :: (fun (() -> any()) | mfargs()) -> 'ok'). --spec(ready/1 :: (pid()) -> 'ok'). --spec(idle/1 :: (pid()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - --define(SERVER, ?MODULE). --define(HIBERNATE_AFTER_MIN, 1000). --define(DESIRED_HIBERNATE, 10000). - --record(state, { available, pending }). - -%%---------------------------------------------------------------------------- - -start_link() -> gen_server2:start_link({local, ?SERVER}, ?MODULE, [], - [{timeout, infinity}]). - -submit(Fun) -> - submit(Fun, reuse). - -%% ProcessModel =:= single is for working around the mnesia_locker bug. -submit(Fun, ProcessModel) -> - case get(worker_pool_worker) of - true -> worker_pool_worker:run(Fun); - _ -> Pid = gen_server2:call(?SERVER, {next_free, self()}, infinity), - worker_pool_worker:submit(Pid, Fun, ProcessModel) - end. - -submit_async(Fun) -> gen_server2:cast(?SERVER, {run_async, Fun}). - -ready(WPid) -> gen_server2:cast(?SERVER, {ready, WPid}). - -idle(WPid) -> gen_server2:cast(?SERVER, {idle, WPid}). - -%%---------------------------------------------------------------------------- - -init([]) -> - {ok, #state { pending = queue:new(), available = ordsets:new() }, hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. - -handle_call({next_free, CPid}, From, State = #state { available = [], - pending = Pending }) -> - {noreply, State#state{pending = queue:in({next_free, From, CPid}, Pending)}, - hibernate}; -handle_call({next_free, CPid}, _From, State = #state { available = - [WPid | Avail1] }) -> - worker_pool_worker:next_job_from(WPid, CPid), - {reply, WPid, State #state { available = Avail1 }, hibernate}; - -handle_call(Msg, _From, State) -> - {stop, {unexpected_call, Msg}, State}. - -handle_cast({ready, WPid}, State) -> - erlang:monitor(process, WPid), - handle_cast({idle, WPid}, State); - -handle_cast({idle, WPid}, State = #state { available = Avail, - pending = Pending }) -> - {noreply, - case queue:out(Pending) of - {empty, _Pending} -> - State #state { available = ordsets:add_element(WPid, Avail) }; - {{value, {next_free, From, CPid}}, Pending1} -> - worker_pool_worker:next_job_from(WPid, CPid), - gen_server2:reply(From, WPid), - State #state { pending = Pending1 }; - {{value, {run_async, Fun}}, Pending1} -> - worker_pool_worker:submit_async(WPid, Fun), - State #state { pending = Pending1 } - end, hibernate}; - -handle_cast({run_async, Fun}, State = #state { available = [], - pending = Pending }) -> - {noreply, State #state { pending = queue:in({run_async, Fun}, Pending)}, - hibernate}; -handle_cast({run_async, Fun}, State = #state { available = [WPid | Avail1] }) -> - worker_pool_worker:submit_async(WPid, Fun), - {noreply, State #state { available = Avail1 }, hibernate}; - -handle_cast(Msg, State) -> - {stop, {unexpected_cast, Msg}, State}. - -handle_info({'DOWN', _MRef, process, WPid, _Reason}, - State = #state { available = Avail }) -> - {noreply, State #state { available = ordsets:del_element(WPid, Avail) }, - hibernate}; - -handle_info(Msg, State) -> - {stop, {unexpected_info, Msg}, State}. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -terminate(_Reason, State) -> - State. diff --git a/src/worker_pool_sup.erl b/src/worker_pool_sup.erl deleted file mode 100644 index 89d2ed46..00000000 --- a/src/worker_pool_sup.erl +++ /dev/null @@ -1,53 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(worker_pool_sup). - --behaviour(supervisor). - --export([start_link/0, start_link/1]). - --export([init/1]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(start_link/1 :: (non_neg_integer()) -> rabbit_types:ok_pid_or_error()). - --endif. - -%%---------------------------------------------------------------------------- - --define(SERVER, ?MODULE). - -%%---------------------------------------------------------------------------- - -start_link() -> - start_link(erlang:system_info(schedulers)). - -start_link(WCount) -> - supervisor:start_link({local, ?SERVER}, ?MODULE, [WCount]). - -%%---------------------------------------------------------------------------- - -init([WCount]) -> - {ok, {{one_for_one, 10, 10}, - [{worker_pool, {worker_pool, start_link, []}, transient, - 16#ffffffff, worker, [worker_pool]} | - [{N, {worker_pool_worker, start_link, []}, transient, 16#ffffffff, - worker, [worker_pool_worker]} || N <- lists:seq(1, WCount)]]}}. diff --git a/src/worker_pool_worker.erl b/src/worker_pool_worker.erl deleted file mode 100644 index 819a6ae8..00000000 --- a/src/worker_pool_worker.erl +++ /dev/null @@ -1,143 +0,0 @@ -%% The contents of this file are subject to the Mozilla Public License -%% Version 1.1 (the "License"); you may not use this file except in -%% compliance with the License. You may obtain a copy of the License -%% at http://www.mozilla.org/MPL/ -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and -%% limitations under the License. -%% -%% The Original Code is RabbitMQ. -%% -%% The Initial Developer of the Original Code is GoPivotal, Inc. -%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved. -%% - --module(worker_pool_worker). - --behaviour(gen_server2). - --export([start_link/0, next_job_from/2, submit/3, submit_async/2, run/1]). - --export([set_maximum_since_use/2]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, prioritise_cast/3]). - -%%---------------------------------------------------------------------------- - --ifdef(use_specs). - --type(mfargs() :: {atom(), atom(), [any()]}). - --spec(start_link/0 :: () -> {'ok', pid()} | {'error', any()}). --spec(next_job_from/2 :: (pid(), pid()) -> 'ok'). --spec(submit/3 :: (pid(), fun (() -> A) | mfargs(), 'reuse' | 'single') -> A). --spec(submit_async/2 :: (pid(), fun (() -> any()) | mfargs()) -> 'ok'). --spec(run/1 :: (fun (() -> A)) -> A; (mfargs()) -> any()). --spec(set_maximum_since_use/2 :: (pid(), non_neg_integer()) -> 'ok'). - --endif. - -%%---------------------------------------------------------------------------- - --define(HIBERNATE_AFTER_MIN, 1000). --define(DESIRED_HIBERNATE, 10000). - -%%---------------------------------------------------------------------------- - -start_link() -> - gen_server2:start_link(?MODULE, [], [{timeout, infinity}]). - -next_job_from(Pid, CPid) -> - gen_server2:cast(Pid, {next_job_from, CPid}). - -submit(Pid, Fun, ProcessModel) -> - gen_server2:call(Pid, {submit, Fun, self(), ProcessModel}, infinity). - -submit_async(Pid, Fun) -> - gen_server2:cast(Pid, {submit_async, Fun}). - -set_maximum_since_use(Pid, Age) -> - gen_server2:cast(Pid, {set_maximum_since_use, Age}). - -run({M, F, A}) -> apply(M, F, A); -run(Fun) -> Fun(). - -run(Fun, reuse) -> - run(Fun); -run(Fun, single) -> - Self = self(), - Ref = make_ref(), - spawn_link(fun () -> - put(worker_pool_worker, true), - Self ! {Ref, run(Fun)}, - unlink(Self) - end), - receive - {Ref, Res} -> Res - end. - -%%---------------------------------------------------------------------------- - -init([]) -> - ok = file_handle_cache:register_callback(?MODULE, set_maximum_since_use, - [self()]), - ok = worker_pool:ready(self()), - put(worker_pool_worker, true), - {ok, undefined, hibernate, - {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. - -prioritise_cast({set_maximum_since_use, _Age}, _Len, _State) -> 8; -prioritise_cast({next_job_from, _CPid}, _Len, _State) -> 7; -prioritise_cast(_Msg, _Len, _State) -> 0. - -handle_call({submit, Fun, CPid, ProcessModel}, From, undefined) -> - {noreply, {job, CPid, From, Fun, ProcessModel}, hibernate}; - -handle_call({submit, Fun, CPid, ProcessModel}, From, {from, CPid, MRef}) -> - erlang:demonitor(MRef), - gen_server2:reply(From, run(Fun, ProcessModel)), - ok = worker_pool:idle(self()), - {noreply, undefined, hibernate}; - -handle_call(Msg, _From, State) -> - {stop, {unexpected_call, Msg}, State}. - -handle_cast({next_job_from, CPid}, undefined) -> - MRef = erlang:monitor(process, CPid), - {noreply, {from, CPid, MRef}, hibernate}; - -handle_cast({next_job_from, CPid}, {job, CPid, From, Fun, ProcessModel}) -> - gen_server2:reply(From, run(Fun, ProcessModel)), - ok = worker_pool:idle(self()), - {noreply, undefined, hibernate}; - -handle_cast({submit_async, Fun}, undefined) -> - run(Fun), - ok = worker_pool:idle(self()), - {noreply, undefined, hibernate}; - -handle_cast({set_maximum_since_use, Age}, State) -> - ok = file_handle_cache:set_maximum_since_use(Age), - {noreply, State, hibernate}; - -handle_cast(Msg, State) -> - {stop, {unexpected_cast, Msg}, State}. - -handle_info({'DOWN', MRef, process, CPid, _Reason}, {from, CPid, MRef}) -> - ok = worker_pool:idle(self()), - {noreply, undefined, hibernate}; - -handle_info({'DOWN', _MRef, process, _Pid, _Reason}, State) -> - {noreply, State, hibernate}; - -handle_info(Msg, State) -> - {stop, {unexpected_info, Msg}, State}. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -terminate(_Reason, State) -> - State. |