diff options
| author | Jean-Sébastien Pédron <jean-sebastien@rabbitmq.com> | 2015-08-10 11:50:24 +0200 |
|---|---|---|
| committer | Jean-Sébastien Pédron <jean-sebastien.pedron@dumbbell.fr> | 2015-10-20 11:10:48 +0200 |
| commit | 31c9fdf6080b42684b27d6e7805f7ba6bfa38601 (patch) | |
| tree | 3240641f70204db064ab7f7de8413349ef0c7ba5 /src | |
| parent | 7ce3113375c50b39cabe6fb24c71b25241f791d0 (diff) | |
| download | rabbitmq-server-git-31c9fdf6080b42684b27d6e7805f7ba6bfa38601.tar.gz | |
Move modules to rabbitmq-common
Diffstat (limited to 'src')
36 files changed, 0 insertions, 14655 deletions
diff --git a/src/app_utils.erl b/src/app_utils.erl deleted file mode 100644 index bab327eab6..0000000000 --- a/src/app_utils.erl +++ /dev/null @@ -1,127 +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-2015 Pivotal Software, 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, - fun application:stop/1, - 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/credit_flow.erl b/src/credit_flow.erl deleted file mode 100644 index 8c8d340601..0000000000 --- a/src/credit_flow.erl +++ /dev/null @@ -1,220 +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-2015 Pivotal Software, 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. -%% -%% Credit flows left to right when process send messags down the -%% chain, starting at the rabbit_reader, ending at the msg_store: -%% reader -> channel -> queue_process -> msg_store. -%% -%% If the message store has a back log, then it will block the -%% queue_process, which will block the channel, and finally the reader -%% will be blocked, throttling down publishers. -%% -%% Once a process is unblocked, it will grant credits up the chain, -%% possibly unblocking other processes: -%% reader <--grant channel <--grant queue_process <--grant msg_store. -%% -%% Grepping the project files for `credit_flow` will reveal the places -%% where this module is currently used, with extra comments on what's -%% going on at each instance. Note that credit flow between mirrors -%% synchronization has not been documented, since this doesn't affect -%% client publishes. - --define(DEFAULT_INITIAL_CREDIT, 200). --define(DEFAULT_MORE_CREDIT_AFTER, 50). - --define(DEFAULT_CREDIT, - case get(credit_flow_default_credit) of - undefined -> - Val = rabbit_misc:get_env(rabbit, credit_flow_default_credit, - {?DEFAULT_INITIAL_CREDIT, - ?DEFAULT_MORE_CREDIT_AFTER}), - put(credit_flow_default_credit, Val), - Val; - Val -> Val - end). - --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. - Var = case get(Key) of - undefined -> Default; - V -> V - end, - put(Key, Expr) - end). - -%% If current process was blocked by credit flow in the last -%% STATE_CHANGE_INTERVAL milliseconds, state/0 will report it as "in -%% flow". --define(STATE_CHANGE_INTERVAL, 1000000). - --ifdef(CREDIT_FLOW_TRACING). --define(TRACE_BLOCKED(SELF, FROM), rabbit_event:notify(credit_flow_blocked, - [{process, SELF}, - {process_info, erlang:process_info(SELF)}, - {from, FROM}, - {from_info, erlang:process_info(FROM)}, - {timestamp, - time_compat:os_system_time( - milliseconds)}])). --define(TRACE_UNBLOCKED(SELF, FROM), rabbit_event:notify(credit_flow_unblocked, - [{process, SELF}, - {from, FROM}, - {timestamp, - time_compat:os_system_time( - milliseconds)}])). --else. --define(TRACE_BLOCKED(SELF, FROM), ok). --define(TRACE_UNBLOCKED(SELF, FROM), ok). --endif. - -%%---------------------------------------------------------------------------- - -%% 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 -> Now = time_compat:monotonic_time(), - Diff = time_compat:convert_time_unit(Now - B, - native, - micro_seconds), - case Diff < ?STATE_CHANGE_INTERVAL 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) -> - ?TRACE_BLOCKED(self(), From), - case blocked() of - false -> put(credit_blocked_at, time_compat:monotonic_time()); - true -> ok - end, - ?UPDATE(credit_blocked, [], Blocks, [From | Blocks]). - -unblock(From) -> - ?TRACE_UNBLOCKED(self(), 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/gen_server2.erl b/src/gen_server2.erl deleted file mode 100644 index 23494399d2..0000000000 --- a/src/gen_server2.erl +++ /dev/null @@ -1,1364 +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(find_prioritisers( - 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, - {erlang:phash2([node()]), - time_compat:monotonic_time(), - time_compat:unique_integer()}}. - -%%%======================================================================== -%%% Internal functions -%%%======================================================================== -%%% --------------------------------------------------- -%%% The MAIN loop. -%%% --------------------------------------------------- -loop(GS2State = #gs2_state { time = hibernate, - timeout_state = undefined, - queue = Queue }) -> - case priority_queue:is_empty(Queue) of - true -> - pre_hibernate(GS2State); - false -> - process_next_msg(GS2State) - end; - -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, - time_compat:monotonic_time(), - 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, _, _, _, _} -> {time_compat:monotonic_time(), - 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 = time_compat:convert_time_unit(AwokeAt - SleptAt, - native, micro_seconds), - 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/mirrored_supervisor.erl b/src/mirrored_supervisor.erl deleted file mode 100644 index 96c1418791..0000000000 --- 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-2015 Pivotal Software, 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/mochijson2.erl b/src/mochijson2.erl deleted file mode 100644 index bddb52cc6f..0000000000 --- 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/pmon.erl b/src/pmon.erl deleted file mode 100644 index f42530022a..0000000000 --- a/src/pmon.erl +++ /dev/null @@ -1,109 +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-2015 Pivotal Software, Inc. All rights reserved. -%% - --module(pmon). - -%% Process Monitor -%% ================ -%% -%% This module monitors processes so that every process has at most -%% 1 monitor. -%% Processes monitored can be dynamically added and removed. -%% -%% Unlike erlang:[de]monitor* functions, this module -%% provides basic querying capability and avoids contacting down nodes. -%% -%% It is used to monitor nodes, queue mirrors, and by -%% the queue collector, among other things. - --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 88c69513d7..0000000000 --- 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-2015 Pivotal Software, 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_amqqueue.erl b/src/rabbit_amqqueue.erl deleted file mode 100644 index 65e4255a73..0000000000 --- a/src/rabbit_amqqueue.erl +++ /dev/null @@ -1,908 +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-2015 Pivotal Software, 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, 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(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 = [], - recoverable_slaves = [], - gm_pids = [], - state = live})), - - Node1 = case rabbit_queue_master_location_misc:get_location(Q) of - {ok, Node0} -> Node0; - {error, _} -> Node - end, - - Node1 = rabbit_mirror_queue_misc:initial_queue_node(Q, Node1), - gen_server2:call( - rabbit_amqqueue_sup_sup:start_queue_process(Node1, 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}, - {<<"x-max-priority">>, 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 = Dur}, _) -> Dur; -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(recoverable_slaves, #amqqueue{recoverable_slaves = RS}, _) -> RS; -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). - -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(Node, Q) || - #amqqueue{pid = Pid} = Q <- Qs, - node(Pid) =:= Node], - ok - end), - ok. - -%% Try to promote a slave while down - it should recover as a -%% master. We try to take the oldest slave here for best chance of -%% recovery. -forget_node_for_queue(DeadNode, Q = #amqqueue{recoverable_slaves = RS}) -> - forget_node_for_queue(DeadNode, RS, Q). - -forget_node_for_queue(_DeadNode, [], #amqqueue{name = Name}) -> - %% 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); - -%% Should not happen, but let's be conservative. -forget_node_for_queue(DeadNode, [DeadNode | T], Q) -> - forget_node_for_queue(DeadNode, T, Q); - -forget_node_for_queue(DeadNode, [H|T], Q) -> - case node_permits_offline_promotion(H) of - false -> forget_node_for_queue(DeadNode, T, Q); - true -> Q1 = Q#amqqueue{pid = rabbit_misc:node_to_fake_pid(H)}, - ok = mnesia:write(rabbit_durable_queue, Q1, write) - end. - -node_permits_offline_promotion(Node) -> - case node() of - Node -> not rabbit:is_running(); %% [1] - _ -> Running = rabbit_mnesia:cluster_nodes(running), - not lists:member(Node, Running) %% [2] - end. -%% [1] In this case if we are a real running node (i.e. rabbitmqctl -%% has RPCed into us) then we cannot allow promotion. If on the other -%% hand we *are* rabbitmqctl impersonating the node for offline -%% node-forgetting then we can. -%% -%% [2] This is simpler; as long as it's down that's OK - -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), - [maybe_clear_recoverable_node(Node, Q) || Q <- Qs], - ok - end). - -maybe_clear_recoverable_node(Node, - #amqqueue{sync_slave_pids = SPids, - recoverable_slaves = RSs} = Q) -> - case lists:member(Node, RSs) of - true -> - %% There is a race with - %% rabbit_mirror_queue_slave:record_synchronised/1 called - %% by the incoming slave node and this function, called - %% by the master node. If this function is executed after - %% record_synchronised/1, the node is erroneously removed - %% from the recoverable slaves list. - %% - %% We check if the slave node's queue PID is alive. If it is - %% the case, then this function is executed after. In this - %% situation, we don't touch the queue record, it is already - %% correct. - DoClearNode = - case [SP || SP <- SPids, node(SP) =:= Node] of - [SPid] -> not rabbit_misc:is_process_alive(SPid); - _ -> true - end, - if - DoClearNode -> RSs1 = RSs -- [Node], - store_queue( - Q#amqqueue{recoverable_slaves = RSs1}); - true -> ok - end; - false -> - 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, - recoverable_slaves = none, - gm_pids = none, - policy = none, - decorators = none, - state = none}. - -deliver([], _Delivery) -> - %% /dev/null optimisation - []; - -deliver(Qs, Delivery = #delivery{flow = Flow}) -> - {MPids, SPids} = qpids(Qs), - QPids = MPids ++ SPids, - %% We use up two credits to send to a slave since the message - %% arrives at the slave from two directions. We will ack one when - %% the slave receives the message direct from the channel, and the - %% other when it receives it via GM. - case Flow of - %% Here we are tracking messages sent by the rabbit_channel - %% process. We are accessing the rabbit_channel process - %% dictionary. - flow -> [credit_flow:send(QPid) || QPid <- QPids], - [credit_flow:send(QPid) || QPid <- SPids]; - 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}, - SMsg = {deliver, Delivery, true}, - 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_auth_mechanism.erl b/src/rabbit_auth_mechanism.erl deleted file mode 100644 index 78e3e7dd4b..0000000000 --- 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-2015 Pivotal Software, 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, Username, 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', rabbit_types:username() | none, 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_authn_backend.erl b/src/rabbit_authn_backend.erl deleted file mode 100644 index b9cb0d3669..0000000000 --- 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-2015 Pivotal Software, 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 495a79695d..0000000000 --- a/src/rabbit_authz_backend.erl +++ /dev/null @@ -1,76 +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-2015 Pivotal Software, 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} -%% {ok, Impl, Tags} -%% User authorisation succeeded, and here's the impl and potential extra tags fields. -%% {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()} | - {'ok', any(), 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_backing_queue.erl b/src/rabbit_backing_queue.erl deleted file mode 100644 index 2b808e206c..0000000000 --- a/src/rabbit_backing_queue.erl +++ /dev/null @@ -1,282 +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-2015 Pivotal Software, 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, head_message_timestamp, - disk_reads, disk_writes, 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(flow() :: 'flow' | 'noflow'). --type(msg_ids() :: [rabbit_types:msg_id()]). --type(publish() :: {rabbit_types:basic_message(), - rabbit_types:message_properties(), boolean()}). --type(delivered_publish() :: {rabbit_types:basic_message(), - rabbit_types:message_properties()}). --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(), flow(), - state()) -> state(). - -%% Like publish/6 but for batches of publishes. --callback batch_publish([publish()], pid(), flow(), 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(), flow(), - state()) - -> {ack(), state()}. - -%% Like publish_delivered/5 but for batches of publishes. --callback batch_publish_delivered([delivered_publish()], pid(), flow(), - 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(), flow(), 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}, {delete_crashed, 1}, {purge, 1}, - {purge_acks, 1}, {publish, 6}, {publish_delivered, 5}, - {batch_publish, 4}, {batch_publish_delivered, 4}, - {discard, 4}, {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}] ; -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 d70f4f379d..0000000000 --- a/src/rabbit_basic.erl +++ /dev/null @@ -1,326 +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-2015 Pivotal Software, 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, extract_timestamp/1, map_headers/2, delivery/4, - header_routes/1, parse_expiration/1, header/2, header/3]). --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(header() :: any()). --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(header/2 :: - (header(), headers()) -> 'undefined' | any()). --spec(header/3 :: - (header(), headers(), any()) -> 'undefined' | any()). - --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, flow = noflow}. - -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). - -header(_Header, undefined) -> - undefined; -header(_Header, []) -> - undefined; -header(Header, Headers) -> - header(Header, Headers, undefined). - -header(Header, Headers, Default) -> - case lists:keysearch(Header, 1, Headers) of - false -> Default; - {value, Val} -> Val - end. - -extract_headers(Content) -> - #content{properties = #'P_basic'{headers = Headers}} = - rabbit_binary_parser:ensure_content_decoded(Content), - Headers. - -extract_timestamp(Content) -> - #content{properties = #'P_basic'{timestamp = Timestamp}} = - rabbit_binary_parser:ensure_content_decoded(Content), - Timestamp. - -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 34f2d601aa..0000000000 --- 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-2015 Pivotal Software, 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 8b3bf3e6f5..0000000000 --- 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-2015 Pivotal Software, 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_channel.erl b/src/rabbit_channel.erl deleted file mode 100644 index f8ed9cae9f..0000000000 --- a/src/rabbit_channel.erl +++ /dev/null @@ -1,2017 +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-2015 Pivotal Software, Inc. All rights reserved. -%% - --module(rabbit_channel). - -%% rabbit_channel processes represent an AMQP 0-9-1 channels. -%% -%% Connections parse protocol frames coming from clients and -%% dispatch them to channel processes. -%% Channels are responsible for implementing the logic behind -%% the various protocol methods, involving other processes as -%% needed: -%% -%% * Routing messages (using functions in various exchange type -%% modules) to queue processes. -%% * Managing queues, exchanges, and bindings. -%% * Keeping track of consumers -%% * Keeping track of unacknowledged deliveries to consumers -%% * Keeping track of publisher confirms -%% * Keeping track of mandatory message routing confirmations -%% and returns -%% * Transaction management -%% * Authorisation (enforcing permissions) -%% * Publishing trace events if tracing is enabled -%% -%% Every channel has a number of dependent processes: -%% -%% * A writer which is responsible for sending frames to clients. -%% * A limiter which controls how many messages can be delivered -%% to consumers according to active QoS prefetch and internal -%% flow control logic. -%% -%% Channels are also aware of their connection's queue collector. -%% When a queue is declared as exclusive on a channel, the channel -%% will notify queue collector of that queue. - --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]). --export([get_vhost/1, get_user/1]). - --record(ch, { - %% starting | running | flow | closing - state, - %% same as reader's protocol. Used when instantiating - %% (protocol) exceptions. - protocol, - %% channel number - channel, - %% reader process - reader_pid, - %% writer process - writer_pid, - %% - conn_pid, - %% same as reader's name, see #v1.name - %% in rabbit_reader - conn_name, - %% limiter pid, see rabbit_limiter - limiter, - %% none | {Msgs, Acks} | committing | failed | - tx, - %% (consumer) delivery tag sequence - next_tag, - %% messages pending consumer acknowledgement - unacked_message_q, - %% same as #v1.user in the reader, used in - %% authorisation checks - user, - %% same as #v1.user in the reader - virtual_host, - %% when queue.bind's queue field is empty, - %% this name will be used instead - most_recently_declared_queue, - %% a dictionary of queue pid to queue name - queue_names, - %% queue processes are monitored to update - %% queue names - queue_monitors, - %% a dictionary of consumer tags to - %% consumer details: #amqqueue record, acknowledgement mode, - %% consumer exclusivity, etc - consumer_mapping, - %% a dictionary of queue pids to consumer tag lists - queue_consumers, - %% a set of pids of queues that have unacknowledged - %% deliveries - delivering_queues, - %% when a queue is declared as exclusive, queue - %% collector must be notified. - %% see rabbit_queue_collector for more info. - queue_collector_pid, - %% timer used to emit statistics - stats_timer, - %% are publisher confirms enabled for this channel? - confirm_enabled, - %% publisher confirm delivery tag sequence - publish_seqno, - %% a dtree used to track unconfirmed - %% (to publishers) messages - unconfirmed, - %% a list of tags for published messages that were - %% delivered but are yet to be confirmed to the client - confirmed, - %% a dtree used to track oustanding notifications - %% for messages published as mandatory - mandatory, - %% same as capabilities in the reader - capabilities, - %% tracing exchange resource if tracing is enabled, - %% 'none' otherwise - trace_state, - consumer_prefetch, - %% used by "one shot RPC" (amq. - reply_consumer, - %% flow | noflow, see rabbitmq-server#114 - delivery_flow, - interceptor_state -}). - - --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()). - --export_type([channel/0]). - --type(channel() :: #ch{}). - --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) -> - %% Here we are tracking messages sent by the rabbit_reader - %% process. We are accessing the rabbit_reader process dictionary. - 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()), - Flow = case rabbit_misc:get_env(rabbit, mirroring_flow_control, true) of - true -> flow; - false -> noflow - end, - 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, - delivery_flow = Flow, - interceptor_state = undefined}, - State1 = State#ch{ - interceptor_state = rabbit_channel_interceptor:init(State)}, - State2 = rabbit_event:init_stats_timer(State1, #ch.stats_timer), - rabbit_event:notify(channel_created, infos(?CREATION_EVENT_KEYS, State2)), - rabbit_event:if_enabled(State2, #ch.stats_timer, - fun() -> emit_stats(State2) end), - {ok, State2, 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, - interceptor_state = IState}) -> - case Flow of - %% We are going to process a message from the rabbit_reader - %% process, so here we ack it. In this case we are accessing - %% the rabbit_channel process dictionary. - flow -> credit_flow:ack(Reader); - noflow -> ok - end, - - try handle_method(rabbit_channel_interceptor:intercept_in( - expand_shortcuts(Method, State), Content, IState), - 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) -> - %% A rabbit_amqqueue_process is granting credit to our channel. If - %% our channel was being blocked by this process, and no other - %% process is blocking our channel, then this channel will be - %% unblocked. This means that any credit that was deferred will be - %% sent to rabbit_reader processs that might be blocked by this - %% particular channel. - 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), - %% A rabbit_amqqueue_process has died. If our channel was being - %% blocked by this process, and no other process is blocking our - %% channel, then this channel will be unblocked. This means that - %% any credit that was deferred will be sent to the rabbit_reader - %% processs that might be blocked by this particular channel. - 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, - time_compat:os_system_time(milli_seconds)}]) - 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). - -%%--------------------------------------------------------------------------- - -log(Level, Fmt, Args) -> rabbit_log:log(channel, Level, Fmt, Args). - -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} -> - 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.", _/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({Method, Content}, State) -> - handle_method(Method, Content, State). - -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, - delivery_flow = Flow}) -> - 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} -> - Delivery = rabbit_basic:delivery( - Mandatory, DoConfirm, Message, MsgSeqNo), - QNames = rabbit_exchange:route(Exchange, Delivery), - rabbit_trace:tap_in(Message, QNames, ConnName, ChannelNum, - Username, TraceState), - DQ = {Delivery#delivery{flow = Flow}, 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(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_partition_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_partition_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]. - -get_vhost(#ch{virtual_host = VHost}) -> VHost. - -get_user(#ch{user = User}) -> User. diff --git a/src/rabbit_channel_interceptor.erl b/src/rabbit_channel_interceptor.erl deleted file mode 100644 index 6a925b69fe..0000000000 --- a/src/rabbit_channel_interceptor.erl +++ /dev/null @@ -1,117 +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-2015 Pivotal Software, Inc. All rights reserved. -%% - --module(rabbit_channel_interceptor). - --include("rabbit_framing.hrl"). --include("rabbit.hrl"). - --export([init/1, intercept_in/3]). - --ifdef(use_specs). - --type(method_name() :: rabbit_framing:amqp_method_name()). --type(original_method() :: rabbit_framing:amqp_method_record()). --type(processed_method() :: rabbit_framing:amqp_method_record()). --type(original_content() :: rabbit_types:maybe(rabbit_types:content())). --type(processed_content() :: rabbit_types:maybe(rabbit_types:content())). --type(interceptor_state() :: term()). - --callback description() -> [proplists:property()]. -%% Derive some initial state from the channel. This will be passed back -%% as the third argument of intercept/3. --callback init(rabbit_channel:channel()) -> interceptor_state(). --callback intercept(original_method(), original_content(), - interceptor_state()) -> - {processed_method(), processed_content()} | - rabbit_misc:channel_or_connection_exit(). --callback applies_to() -> list(method_name()). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{description, 0}, {init, 1}, {intercept, 3}, {applies_to, 0}]; -behaviour_info(_Other) -> - undefined. - --endif. - -init(Ch) -> - Mods = [M || {_, M} <- rabbit_registry:lookup_all(channel_interceptor)], - check_no_overlap(Mods), - [{Mod, Mod:init(Ch)} || Mod <- Mods]. - -check_no_overlap(Mods) -> - check_no_overlap1([sets:from_list(Mod:applies_to()) || Mod <- Mods]). - -%% Check no non-empty pairwise intersection in a list of sets -check_no_overlap1(Sets) -> - lists:foldl(fun(Set, Union) -> - Is = sets:intersection(Set, Union), - case sets:size(Is) of - 0 -> ok; - _ -> - internal_error("Interceptor: more than one " - "module handles ~p~n", [Is]) - end, - sets:union(Set, Union) - end, - sets:new(), - Sets), - ok. - -intercept_in(M, C, Mods) -> - lists:foldl(fun({Mod, ModState}, {M1, C1}) -> - call_module(Mod, ModState, M1, C1) - end, - {M, C}, - Mods). - -call_module(Mod, St, M, C) -> - % this little dance is because Mod might be unloaded at any point - case (catch {ok, Mod:intercept(M, C, St)}) of - {ok, R} -> validate_response(Mod, M, C, R); - {'EXIT', {undef, [{Mod, intercept, _, _} | _]}} -> {M, C} - end. - -validate_response(Mod, M1, C1, R = {M2, C2}) -> - case {validate_method(M1, M2), validate_content(C1, C2)} of - {true, true} -> R; - {false, _} -> - internal_error("Interceptor: ~p expected to return " - "method: ~p but returned: ~p", - [Mod, rabbit_misc:method_record_type(M1), - rabbit_misc:method_record_type(M2)]); - {_, false} -> - internal_error("Interceptor: ~p expected to return " - "content iff content is provided but " - "content in = ~p; content out = ~p", - [Mod, C1, C2]) - end. - -validate_method(M, M2) -> - rabbit_misc:method_record_type(M) =:= rabbit_misc:method_record_type(M2). - -validate_content(none, none) -> true; -validate_content(#content{}, #content{}) -> true; -validate_content(_, _) -> false. - -%% 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_command_assembler.erl b/src/rabbit_command_assembler.erl deleted file mode 100644 index f93b85b122..0000000000 --- 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-2015 Pivotal Software, 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_event.erl b/src/rabbit_event.erl deleted file mode 100644 index 4d62dd079b..0000000000 --- a/src/rabbit_event.erl +++ /dev/null @@ -1,163 +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-2015 Pivotal Software, 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()). - --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 = time_compat:os_system_time(milli_seconds)}. - diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl deleted file mode 100644 index 7c5bfdf913..0000000000 --- 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-2015 Pivotal Software, 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 92c1de6c21..0000000000 --- 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-2015 Pivotal Software, 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_heartbeat.erl b/src/rabbit_heartbeat.erl deleted file mode 100644 index 993076770f..0000000000 --- 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-2015 Pivotal Software, 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_misc.erl b/src/rabbit_misc.erl deleted file mode 100644 index cfabf1ed5e..0000000000 --- a/src/rabbit_misc.erl +++ /dev/null @@ -1,1165 +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-2015 Pivotal Software, 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, - pid_change_node/2, 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([const/1]). --export([ntoa/1, ntoab/1]). --export([is_process_alive/1]). --export([pget/2, pget/3, pget_or_die/2, pmerge/3, pset/3, plmerge/2]). --export([format_message_queue/2]). --export([append_rpc_all_nodes/4]). --export([os_cmd/1]). --export([is_os_process_alive/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/5]). --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([get_env/3]). - -%% 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(pid_change_node/2 :: (pid(), node()) -> 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:graph(), - {'vertex', 'duplicate', digraph:vertex()} | - {'edge', ({bad_vertex, digraph:vertex()} | - {bad_edge, [digraph:vertex()]}), - digraph:vertex(), digraph:vertex()})). --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(pmerge/3 :: (term(), term(), [term()]) -> [term()]). --spec(plmerge/2 :: ([term()], [term()]) -> [term()]). --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(is_os_process_alive/1 :: (non_neg_integer()) -> boolean()). --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/5 :: - ({atom(), atom(), any()}, float(), non_neg_integer(), 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(get_env/3 :: (atom(), atom(), term()) -> term()). --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 -> file_handle_cache_stats:update( - mnesia_ram_tx), - Res; - false -> file_handle_cache_stats:update( - mnesia_disk_tx), - {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) -> - {Node, Cre, Id, Ser} = decompose_pid(Pid), - 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]} -> - [Cre, Id, Ser] = lists:map(fun list_to_integer/1, - [CreStr, IdStr, SerStr]), - compose_pid(list_to_atom(NodeStr), Cre, Id, Ser); - nomatch -> - throw(Err) - end. - -pid_change_node(Pid, NewNode) -> - {_OldNode, Cre, Id, Ser} = decompose_pid(Pid), - compose_pid(NewNode, Cre, Id, Ser). - -%% node(node_to_fake_pid(Node)) =:= Node. -node_to_fake_pid(Node) -> - compose_pid(Node, 0, 0, 0). - -decompose_pid(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>>), - {Node, Cre, Id, Ser}. - -compose_pid(Node, Cre, Id, Ser) -> - <<131,NodeEnc/binary>> = term_to_binary(Node), - binary_to_term(<<131,103,NodeEnc/binary,Id:32,Ser:32,Cre:8>>). - -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). - -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. - -%% property merge -pmerge(Key, Val, List) -> - case proplists:is_defined(Key, List) of - true -> List; - _ -> [{Key, Val} | List] - end. - -%% proplists merge -plmerge(P1, P2) -> - dict:to_list(dict:merge(fun(_, V, _) -> - V - end, - dict:from_list(P1), - dict:from_list(P2))). - -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. - -is_os_process_alive(Pid) -> - with_os([{unix, fun () -> - run_ps(Pid) =:= 0 - end}, - {win32, fun () -> - Cmd = "tasklist /nh /fi \"pid eq " ++ Pid ++ "\" ", - Res = 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. - -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. - -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 with a user specified max interval. Times are in millis. -interval_operation({M, F, A}, MaxRatio, MaxInterval, IdealInterval, LastInterval) -> - {Micros, Res} = timer:tc(M, F, A), - {Res, case {Micros > 1000 * (MaxRatio * IdealInterval), - Micros > 1000 * (MaxRatio * LastInterval)} of - {true, true} -> lists:min([MaxInterval, - 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). - -%% application:get_env/3 is only available in R16B01 or later. -get_env(Application, Key, Def) -> - case application:get_env(Application, Key) of - {ok, Val} -> Val; - undefined -> Def - end. - -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_msg_store_index.erl b/src/rabbit_msg_store_index.erl deleted file mode 100644 index 0c7a37bcd3..0000000000 --- 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-2015 Pivotal Software, 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 b6cf9842ff..0000000000 --- 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-2015 Pivotal Software, 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(), any()}])). --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_compat:connection_information(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 f95f8c5818..0000000000 --- a/src/rabbit_networking.erl +++ /dev/null @@ -1,608 +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-2015 Pivotal Software, 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) -> - %% Starting with ssl 4.0.1 in Erlang R14B, the verify_fun function - %% takes 3 arguments and returns a tuple. - {ok, SslAppVer} = application:get_key(ssl, vsn), - UseNewVerifyFun = rabbit_misc:version_compare(SslAppVer, "4.0.1", gte), - case rabbit_misc:pget(verify_fun, SslOptsConfig) of - {Module, Function, InitialUserState} -> - Fun = make_verify_fun(Module, Function, InitialUserState, - UseNewVerifyFun), - rabbit_misc:pset(verify_fun, Fun, SslOptsConfig); - {Module, Function} -> - Fun = make_verify_fun(Module, Function, none, - UseNewVerifyFun), - rabbit_misc:pset(verify_fun, Fun, SslOptsConfig); - undefined when UseNewVerifyFun -> - 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. - -make_verify_fun(Module, Function, InitialUserState, UseNewVerifyFun) -> - try - %% Preload the module: it is required to use - %% erlang:function_exported/3. - Module:module_info() - catch - _:Exception -> - rabbit_log:error("SSL verify_fun: module ~s missing: ~p~n", - [Module, Exception]), - throw({error, {invalid_verify_fun, missing_module}}) - end, - NewForm = erlang:function_exported(Module, Function, 3), - OldForm = erlang:function_exported(Module, Function, 1), - case {NewForm, OldForm} of - {true, _} when UseNewVerifyFun -> - %% This verify_fun is supported by Erlang R14B+ (ssl - %% 4.0.1 and later). - Fun = fun(OtpCert, Event, UserState) -> - Module:Function(OtpCert, Event, UserState) - end, - {Fun, InitialUserState}; - {_, true} -> - %% This verify_fun is supported by: - %% o Erlang up-to R13B; - %% o Erlang R14B+ for undocumented backward - %% compatibility. - %% - %% InitialUserState is ignored in this case. - fun(ErrorList) -> - Module:Function(ErrorList) - end; - {_, false} when not UseNewVerifyFun -> - rabbit_log:error("SSL verify_fun: ~s:~s/1 form required " - "for Erlang R13B~n", [Module, Function]), - throw({error, {invalid_verify_fun, old_form_required}}); - _ -> - Arity = case UseNewVerifyFun of true -> 3; _ -> 1 end, - rabbit_log:error("SSL verify_fun: no ~s:~s/~b exported~n", - [Module, Function, Arity]), - throw({error, {invalid_verify_fun, function_not_exported}}) - 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) -> - case lists:member(Node, nodes()) of - false -> ok = mnesia:dirty_delete(rabbit_listener, Node); - true -> rabbit_log:info( - "Keep ~s listeners: the node is already back~n", [Node]) - end. - -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, ConfigOpts} = application:get_env(rabbit, tcp_listen_options), - merge_essential_tcp_listen_options(ConfigOpts). - --define(ESSENTIAL_LISTEN_OPTIONS, - [binary, - {active, false}, - {packet, raw}, - {reuseaddr, true}, - {nodelay, true}]). - -merge_essential_tcp_listen_options(Opts) -> - lists:foldl(fun ({K, _} = Opt, Acc) -> - lists:keystore(K, 1, Acc, Opt); - (Opt, Acc) -> - [Opt | Acc] - end , Opts, ?ESSENTIAL_LISTEN_OPTIONS). - -%% 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_nodes.erl b/src/rabbit_nodes.erl deleted file mode 100644 index 57d971715b..0000000000 --- a/src/rabbit_nodes.erl +++ /dev/null @@ -1,221 +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-2015 Pivotal Software, 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, ensure_epmd/0, - all_running/0]). - --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'). --spec(ensure_epmd/0 :: () -> 'ok'). --spec(all_running/0 :: () -> [node()]). - --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?~n" - " * suggestion: is the Erlang distribution using TLS?", []}]; - {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). - -ensure_epmd() -> - {ok, Prog} = init:get_argument(progname), - ID = random:uniform(1000000000), - Port = open_port( - {spawn_executable, os:find_executable(Prog)}, - [{args, ["-sname", rabbit_misc:format("epmd-starter-~b", [ID]), - "-noshell", "-eval", "halt()."]}, - exit_status, stderr_to_stdout, use_stdio]), - port_shutdown_loop(Port). - -port_shutdown_loop(Port) -> - receive - {Port, {exit_status, _Rc}} -> ok; - {Port, _} -> port_shutdown_loop(Port) - end. - -all_running() -> rabbit_mnesia:cluster_nodes(running). diff --git a/src/rabbit_policy_validator.erl b/src/rabbit_policy_validator.erl deleted file mode 100644 index 7ebea83516..0000000000 --- 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-2015 Pivotal Software, 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_queue_collector.erl b/src/rabbit_queue_collector.erl deleted file mode 100644 index d4e1c71da2..0000000000 --- a/src/rabbit_queue_collector.erl +++ /dev/null @@ -1,95 +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-2015 Pivotal Software, Inc. All rights reserved. -%% - --module(rabbit_queue_collector). - -%% Queue collector keeps track of exclusive queues and cleans them -%% up e.g. when their connection is closed. - --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_decorator.erl b/src/rabbit_queue_decorator.erl deleted file mode 100644 index 0c6f0820c7..0000000000 --- a/src/rabbit_queue_decorator.erl +++ /dev/null @@ -1,80 +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-2015 Pivotal Software, Inc. All rights reserved. -%% - --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) -> - [{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_reader.erl b/src/rabbit_reader.erl deleted file mode 100644 index f66a80d811..0000000000 --- a/src/rabbit_reader.erl +++ /dev/null @@ -1,1412 +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-2015 Pivotal Software, Inc. All rights reserved. -%% - --module(rabbit_reader). - -%% This is an AMQP 0-9-1 connection implementation. If AMQP 1.0 plugin is enabled, -%% this module passes control of incoming AMQP 1.0 connections to it. -%% -%% Every connection (as in, a process using this module) -%% is a controlling process for a server socket. -%% -%% Connections have a number of responsibilities: -%% -%% * Performing protocol handshake -%% * Parsing incoming data and dispatching protocol methods -%% * Authenticating clients (with the help of authentication backends) -%% * Enforcing TCP backpressure (throttling clients) -%% * Enforcing connection limits, e.g. channel_max -%% * Channel management -%% * Setting up heartbeater and alarm notifications -%% * Emitting connection and network activity metric events -%% * Gracefully handling client disconnects, channel termination, etc -%% -%% and a few more. -%% -%% Every connection has -%% -%% * a queue collector which is responsible for keeping -%% track of exclusive queues on the connection and their cleanup. -%% * a heartbeater that's responsible for sending heartbeat frames to clients, -%% keeping track of the incoming ones and notifying connection about -%% heartbeat timeouts -%% * Stats timer, a timer that is used to periodically emit metric events -%% -%% Some dependencies are started under a separate supervisor to avoid deadlocks -%% during system shutdown. See rabbit_channel_sup:start_link/0 for details. -%% -%% Reader processes are special processes (in the OTP sense). - --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). -%% we wait for this many seconds before closing TCP connection -%% with a client that failed to log in. Provides some relief -%% from connection storms and DoS. --define(SILENT_CLOSE_DELAY, 3). --define(CHANNEL_MIN, 1). - -%%-------------------------------------------------------------------------- - --record(v1, { - %% parent process - parent, - %% socket - sock, - %% connection state, see connection record - connection, - callback, - recv_len, - pending_recv, - %% pre_init | securing | running | blocking | blocked | closing | closed | {become, F} - connection_state, - %% see comment in rabbit_connection_sup:start_link/0 - helper_sup, - %% takes care of cleaning up exclusive queues, - %% see rabbit_queue_collector - queue_collector, - %% sends and receives heartbeat frames, - %% see rabbit_heartbeat - heartbeater, - %% timer used to emit statistics - stats_timer, - %% channel supervisor - channel_sup_sup_pid, - %% how many channels this connection has - channel_count, - %% throttling state, for both - %% credit- and resource-driven flow control - throttle}). - --record(connection, { - %% e.g. <<"127.0.0.1:55054 -> 127.0.0.1:5672">> - name, - %% server host - host, - %% client host - peer_host, - %% server port - port, - %% client port - peer_port, - %% protocol framing implementation module, - %% e.g. rabbit_framing_amqp_0_9_1 - protocol, - user, - %% heartbeat timeout value used, 0 means - %% heartbeats are disabled - timeout_sec, - %% maximum allowed frame size, - %% see frame_max in the AMQP 0-9-1 spec - frame_max, - %% greatest channel number allowed, - %% see channel_max in the AMQP 0-9-1 spec - channel_max, - vhost, - %% client name, version, platform, etc - client_properties, - %% what lists protocol extensions - %% does this client support? - capabilities, - %% authentication mechanism used - %% as a pair of {Name, Module} - auth_mechanism, - %% authentication mechanism state, - %% initialised by rabbit_auth_mechanism:init/1 - %% implementations - auth_state, - %% time of connection - connected_at}). - --record(throttle, { - %% list of active alarms - alarmed_by, - %% flow | resource - last_blocked_by, - %% never | timestamp() - 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(AUTH_NOTIFICATION_INFO_KEYS, - [host, vhost, name, peer_host, peer_port, protocol, auth_mechanism, - ssl, ssl_protocol, ssl_cipher, peer_cert_issuer, peer_cert_subject, - peer_cert_validity]). - --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) -> - Level = - case Reason of - {ssl_upgrade_error, closed} -> - %% The socket was closed while upgrading to SSL. - %% This is presumably a TCP healthcheck, so don't log - %% it unless specified otherwise. - debug; - _ -> - error - end, - log(Level, "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, - {ok, HandshakeTimeout} = application:get_env(rabbit, handshake_timeout), - InitialFrameMax = application:get_env(rabbit, initial_frame_max, ?FRAME_MIN_SIZE), - 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 = InitialFrameMax, - vhost = none, - client_properties = none, - capabilities = [], - auth_mechanism = none, - auth_state = none, - connected_at = time_compat:os_system_time( - milli_seconds)}, - 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_connection_exception(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. - -log_connection_exception(Name, Ex) -> - Severity = case Ex of - connection_closed_with_no_data_received -> debug; - connection_closed_abruptly -> warning; - _ -> error - end, - log_connection_exception(Severity, Name, Ex). - -log_connection_exception(Severity, Name, {heartbeat_timeout, TimeoutSec}) -> - %% Long line to avoid extra spaces and line breaks in log - log(Severity, "closing AMQP connection ~p (~s):~nMissed heartbeats from client, timeout: ~ps~n", - [self(), Name, TimeoutSec]); -log_connection_exception(Severity, Name, Ex) -> - log(Severity, "closing AMQP connection ~p (~s):~n~p~n", - [self(), Name, Ex]). - -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, - connection_state = CS, - connection = #connection{ - name = ConnName}}) -> - Recv = rabbit_net:recv(Sock), - case CS of - pre_init when Buf =:= [] -> - %% We only log incoming connections when either the - %% first byte was received or there was an error (eg. a - %% timeout). - %% - %% The goal is to not log TCP healthchecks (a connection - %% with no data received) unless specified otherwise. - log(case Recv of - closed -> debug; - _ -> info - end, "accepting AMQP connection ~p (~s)~n", - [self(), ConnName]); - _ -> - ok - end, - case Recv of - {data, Data} -> - recvloop(Deb, [Data | Buf], BufLen + size(Data), - State#v1{pending_recv = false}); - closed when State#v1.connection_state =:= closed -> - ok; - closed when CS =:= pre_init andalso Buf =:= [] -> - stop(tcp_healthcheck, State); - 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(tcp_healthcheck, State) -> - %% The connection was closed before any packet was received. It's - %% probably a load-balancer healthcheck: don't consider this a - %% failure. - maybe_emit_stats(State), - throw(connection_closed_with_no_data_received); -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 = #connection{timeout_sec = T}}) -> - maybe_emit_stats(State), - throw({heartbeat_timeout, T}); -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) -> - %% Here we are receiving credit by some channel process. - 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 = - time_compat:monotonic_time()})}, - 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, Username, Msg, Args} -> - auth_fail(Username, Msg, Args, Name, State); - {protocol_error, Msg, Args} -> - notify_auth_result(none, user_authentication_failure, - [{error, rabbit_misc:format(Msg, Args)}], - State), - 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 -> - notify_auth_result(Username, user_authentication_success, - [], State); - not_allowed -> - auth_fail(Username, "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/5 :: - (rabbit_types:username() | none, string(), [any()], binary(), #v1{}) -> - no_return()). --endif. -auth_fail(Username, Msg, Args, AuthName, - State = #v1{connection = #connection{protocol = Protocol, - capabilities = Capabilities}}) -> - notify_auth_result(Username, user_authentication_failure, - [{error, rabbit_misc:format(Msg, Args)}], State), - 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). - -notify_auth_result(Username, AuthResult, ExtraProps, State) -> - EventProps = [{connection_type, network}, - {name, case Username of none -> ''; _ -> Username end}] ++ - [case Item of - name -> {connection_name, i(name, State)}; - _ -> {Item, i(Item, State)} - end || Item <- ?AUTH_NOTIFICATION_INFO_KEYS] ++ - ExtraProps, - rabbit_event:notify(AuthResult, [P || {_, V} = P <- EventProps, V =/= '']). - -%%-------------------------------------------------------------------------- - -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 - time_compat:convert_time_unit(time_compat:monotonic_time() - T, - native, - micro_seconds) < 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}) -> - case rabbit_net:ssl_info(Sock) of - nossl -> ''; - {error, _} -> ''; - {ok, Items} -> - P = proplists:get_value(protocol, Items), - CS = proplists:get_value(cipher_suite, Items), - %% The first form is R14. - %% The second is R13 - the extra term is exportability (by - %% inspection, the docs are wrong). - case CS of - {K, C, H} -> F({P, {K, C, H}}); - {K, C, H, _} -> F({P, {K, C, H}}) - end - end. - -cert_info(F, #v1{sock = Sock}) -> - case rabbit_net:peercert(Sock) of - nossl -> ''; - {error, _} -> ''; - {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_runtime_parameter.erl b/src/rabbit_runtime_parameter.erl deleted file mode 100644 index 1d4bc0b575..0000000000 --- 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-2015 Pivotal Software, 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_writer.erl b/src/rabbit_writer.erl deleted file mode 100644 index 897d937443..0000000000 --- a/src/rabbit_writer.erl +++ /dev/null @@ -1,390 +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-2015 Pivotal Software, Inc. All rights reserved. -%% - --module(rabbit_writer). - -%% This module backs writer processes ("writers"). The responsibility of -%% a writer is to serialise protocol methods and write them to the socket. -%% Every writer is associated with a channel and normally it's the channel -%% that delegates method delivery to it. However, rabbit_reader -%% (connection process) can use this module's functions to send data -%% on channel 0, which is only used for connection negotiation and -%% other "special" purposes. -%% -%% This module provides multiple functions that send protocol commands, -%% including some that are credit flow-aware. -%% -%% Writers perform internal buffering. When the amount of data -%% buffered exceeds a threshold, a socket flush is performed. -%% See FLUSH_THRESHOLD for details. -%% -%% When a socket write fails, writer will exit. - --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, { - %% socket (port) - sock, - %% channel number - channel, - %% connection-negotiated frame_max setting - frame_max, - %% see #connection.protocol in rabbit_reader - protocol, - %% connection (rabbit_reader) process - reader, - %% statistics emission timer - stats_timer, - %% data pending delivery (between socket - %% flushes) - 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]}). - -%% When the amount of protocol method data buffered exceeds -%% this threshold, a socket flush is performed. -%% -%% This magic number is the tcp-over-ethernet MSS (1460) minus the -%% minimum size of a AMQP 0-9-1 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/ssl_compat.erl b/src/ssl_compat.erl deleted file mode 100644 index fc83fbcfa6..0000000000 --- a/src/ssl_compat.erl +++ /dev/null @@ -1,75 +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-2015 Pivotal Software, Inc. All rights reserved. -%% - --module(ssl_compat). - -%% We don't want warnings about the use of erlang:now/0 in -%% this module. --compile(nowarn_deprecated_function). - --export([connection_information/1, - connection_information/2]). - -connection_information(SslSocket) -> - try - ssl:connection_information(SslSocket) - catch - error:undef -> - case ssl:connection_info(SslSocket) of - {ok, {ProtocolVersion, CipherSuite}} -> - {ok, [{protocol, ProtocolVersion}, - {cipher_suite, CipherSuite}]}; - {error, Reason} -> - {error, Reason} - end - end. - -connection_information(SslSocket, Items) -> - try - ssl:connection_information(SslSocket, Items) - catch - error:undef -> - WantProtocolVersion = lists:member(protocol, Items), - WantCipherSuite = lists:member(cipher_suite, Items), - if - WantProtocolVersion orelse WantCipherSuite -> - case ssl:connection_info(SslSocket) of - {ok, {ProtocolVersion, CipherSuite}} -> - filter_information_items(ProtocolVersion, - CipherSuite, - Items, - []); - {error, Reason} -> - {error, Reason} - end; - true -> - {ok, []} - end - end. - -filter_information_items(ProtocolVersion, CipherSuite, [protocol | Rest], - Result) -> - filter_information_items(ProtocolVersion, CipherSuite, Rest, - [{protocol, ProtocolVersion} | Result]); -filter_information_items(ProtocolVersion, CipherSuite, [cipher_suite | Rest], - Result) -> - filter_information_items(ProtocolVersion, CipherSuite, Rest, - [{cipher_suite, CipherSuite} | Result]); -filter_information_items(ProtocolVersion, CipherSuite, [_ | Rest], - Result) -> - filter_information_items(ProtocolVersion, CipherSuite, Rest, Result); -filter_information_items(_ProtocolVersion, _CipherSuite, [], Result) -> - {ok, lists:reverse(Result)}. diff --git a/src/supervisor2.erl b/src/supervisor2.erl deleted file mode 100644 index c8ffbb12ea..0000000000 --- a/src/supervisor2.erl +++ /dev/null @@ -1,1553 +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() | ?SETS:?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 = time_compat:monotonic_time(), - 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 time_compat:convert_time_unit(Now - Time, native, seconds) of - T when T > Period -> - false; - _ -> - true - end. - -%%% ------------------------------------------------------ -%%% 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/time_compat.erl b/src/time_compat.erl deleted file mode 100644 index b87c6cc550..0000000000 --- a/src/time_compat.erl +++ /dev/null @@ -1,305 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2014-2015. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - -%% -%% If your code need to be able to execute on ERTS versions both -%% earlier and later than 7.0, the best approach is to use the new -%% time API introduced in ERTS 7.0 and implement a fallback -%% solution using the old primitives to be used on old ERTS -%% versions. This way your code can automatically take advantage -%% of the improvements in the API when available. This is an -%% example of how to implement such an API, but it can be used -%% as is if you want to. Just add (a preferrably renamed version of) -%% this module to your project, and call the API via this module -%% instead of calling the BIFs directly. -%% - --module(time_compat). - -%% We don't want warnings about the use of erlang:now/0 in -%% this module. --compile(nowarn_deprecated_function). -%% -%% We don't use -%% -compile({nowarn_deprecated_function, [{erlang, now, 0}]}). -%% since this will produce warnings when compiled on systems -%% where it has not yet been deprecated. -%% - --export([monotonic_time/0, - monotonic_time/1, - erlang_system_time/0, - erlang_system_time/1, - os_system_time/0, - os_system_time/1, - time_offset/0, - time_offset/1, - convert_time_unit/3, - timestamp/0, - unique_integer/0, - unique_integer/1, - monitor/2, - system_info/1, - system_flag/2]). - -monotonic_time() -> - try - erlang:monotonic_time() - catch - error:undef -> - %% Use Erlang system time as monotonic time - erlang_system_time_fallback() - end. - -monotonic_time(Unit) -> - try - erlang:monotonic_time(Unit) - catch - error:badarg -> - erlang:error(badarg, [Unit]); - error:undef -> - %% Use Erlang system time as monotonic time - STime = erlang_system_time_fallback(), - try - convert_time_unit_fallback(STime, native, Unit) - catch - error:bad_time_unit -> erlang:error(badarg, [Unit]) - end - end. - -erlang_system_time() -> - try - erlang:system_time() - catch - error:undef -> - erlang_system_time_fallback() - end. - -erlang_system_time(Unit) -> - try - erlang:system_time(Unit) - catch - error:badarg -> - erlang:error(badarg, [Unit]); - error:undef -> - STime = erlang_system_time_fallback(), - try - convert_time_unit_fallback(STime, native, Unit) - catch - error:bad_time_unit -> erlang:error(badarg, [Unit]) - end - end. - -os_system_time() -> - try - os:system_time() - catch - error:undef -> - os_system_time_fallback() - end. - -os_system_time(Unit) -> - try - os:system_time(Unit) - catch - error:badarg -> - erlang:error(badarg, [Unit]); - error:undef -> - STime = os_system_time_fallback(), - try - convert_time_unit_fallback(STime, native, Unit) - catch - error:bad_time_unit -> erlang:error(badarg, [Unit]) - end - end. - -time_offset() -> - try - erlang:time_offset() - catch - error:undef -> - %% Erlang system time and Erlang monotonic - %% time are always aligned - 0 - end. - -time_offset(Unit) -> - try - erlang:time_offset(Unit) - catch - error:badarg -> - erlang:error(badarg, [Unit]); - error:undef -> - try - _ = integer_time_unit(Unit) - catch - error:bad_time_unit -> erlang:error(badarg, [Unit]) - end, - %% Erlang system time and Erlang monotonic - %% time are always aligned - 0 - end. - -convert_time_unit(Time, FromUnit, ToUnit) -> - try - erlang:convert_time_unit(Time, FromUnit, ToUnit) - catch - error:undef -> - try - convert_time_unit_fallback(Time, FromUnit, ToUnit) - catch - _:_ -> - erlang:error(badarg, [Time, FromUnit, ToUnit]) - end; - error:Error -> - erlang:error(Error, [Time, FromUnit, ToUnit]) - end. - -timestamp() -> - try - erlang:timestamp() - catch - error:undef -> - erlang:now() - end. - -unique_integer() -> - try - erlang:unique_integer() - catch - error:undef -> - {MS, S, US} = erlang:now(), - (MS*1000000+S)*1000000+US - end. - -unique_integer(Modifiers) -> - try - erlang:unique_integer(Modifiers) - catch - error:badarg -> - erlang:error(badarg, [Modifiers]); - error:undef -> - case is_valid_modifier_list(Modifiers) of - true -> - %% now() converted to an integer - %% fullfill the requirements of - %% all modifiers: unique, positive, - %% and monotonic... - {MS, S, US} = erlang:now(), - (MS*1000000+S)*1000000+US; - false -> - erlang:error(badarg, [Modifiers]) - end - end. - -monitor(Type, Item) -> - try - erlang:monitor(Type, Item) - catch - error:Error -> - case {Error, Type, Item} of - {badarg, time_offset, clock_service} -> - %% Time offset is final and will never change. - %% Return a dummy reference, there will never - %% be any need for 'CHANGE' messages... - make_ref(); - _ -> - erlang:error(Error, [Type, Item]) - end - end. - -system_info(Item) -> - try - erlang:system_info(Item) - catch - error:badarg -> - case Item of - time_correction -> - case erlang:system_info(tolerant_timeofday) of - enabled -> true; - disabled -> false - end; - time_warp_mode -> - no_time_warp; - time_offset -> - final; - NotSupArg when NotSupArg == os_monotonic_time_source; - NotSupArg == os_system_time_source; - NotSupArg == start_time; - NotSupArg == end_time -> - %% Cannot emulate this... - erlang:error(notsup, [NotSupArg]); - _ -> - erlang:error(badarg, [Item]) - end; - error:Error -> - erlang:error(Error, [Item]) - end. - -system_flag(Flag, Value) -> - try - erlang:system_flag(Flag, Value) - catch - error:Error -> - case {Error, Flag, Value} of - {badarg, time_offset, finalize} -> - %% Time offset is final - final; - _ -> - erlang:error(Error, [Flag, Value]) - end - end. - -%% -%% Internal functions -%% - -integer_time_unit(native) -> 1000*1000; -integer_time_unit(nano_seconds) -> 1000*1000*1000; -integer_time_unit(micro_seconds) -> 1000*1000; -integer_time_unit(milli_seconds) -> 1000; -integer_time_unit(seconds) -> 1; -integer_time_unit(I) when is_integer(I), I > 0 -> I; -integer_time_unit(BadRes) -> erlang:error(bad_time_unit, [BadRes]). - -erlang_system_time_fallback() -> - {MS, S, US} = erlang:now(), - (MS*1000000+S)*1000000+US. - -os_system_time_fallback() -> - {MS, S, US} = os:timestamp(), - (MS*1000000+S)*1000000+US. - -convert_time_unit_fallback(Time, FromUnit, ToUnit) -> - FU = integer_time_unit(FromUnit), - TU = integer_time_unit(ToUnit), - case Time < 0 of - true -> TU*Time - (FU - 1); - false -> TU*Time - end div FU. - -is_valid_modifier_list([positive|Ms]) -> - is_valid_modifier_list(Ms); -is_valid_modifier_list([monotonic|Ms]) -> - is_valid_modifier_list(Ms); -is_valid_modifier_list([]) -> - true; -is_valid_modifier_list(_) -> - false. |
