diff options
-rw-r--r-- | src/rabbit_amqqueue.erl | 66 | ||||
-rw-r--r-- | src/rabbit_amqqueue_process.erl | 7 | ||||
-rw-r--r-- | src/rabbit_basic.erl | 6 | ||||
-rw-r--r-- | src/rabbit_channel.erl | 3 | ||||
-rw-r--r-- | src/rabbit_mirror_queue_slave.erl | 9 | ||||
-rw-r--r-- | src/rabbit_router.erl | 60 | ||||
-rw-r--r-- | src/rabbit_tests.erl | 8 |
7 files changed, 67 insertions, 92 deletions
diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 96017df8..41e644f2 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -44,17 +44,17 @@ -ifdef(use_specs). --export_type([name/0, qmsg/0]). +-export_type([name/0, qmsg/0, routing_result/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(routing_result() :: 'routed' | 'unroutable' | 'not_delivered'). -type(queue_or_not_found() :: rabbit_types:amqqueue() | 'not_found'). -spec(start/0 :: () -> [name()]). @@ -69,7 +69,8 @@ -> queue_or_not_found() | rabbit_misc:thunk(queue_or_not_found())). -spec(lookup/1 :: (name()) -> rabbit_types:ok(rabbit_types:amqqueue()) | - rabbit_types:error('not_found')). + rabbit_types:error('not_found'); + ([name()]) -> [rabbit_types:amqqueue()]). -spec(with/2 :: (name(), qfun(A)) -> A | rabbit_types:error('not_found')). -spec(with_or_die/2 :: (name(), qfun(A)) -> A | rabbit_types:channel_exit()). @@ -117,12 +118,13 @@ rabbit_types:error('in_use') | rabbit_types:error('not_empty')). -spec(purge/1 :: (rabbit_types:amqqueue()) -> qlen()). --spec(deliver/2 :: (pid(), rabbit_types:delivery()) -> boolean()). +-spec(deliver/2 :: ([rabbit_types:amqqueue()], rabbit_types:delivery()) -> + {routing_result(), 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 :: ([pid()], pid()) -> ok_or_errors()). --spec(limit_all/3 :: ([pid()], pid(), rabbit_limiter:token()) -> +-spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). +-spec(limit_all/3 :: (qpids(), pid(), rabbit_limiter:token()) -> ok_or_errors()). -spec(basic_get/3 :: (rabbit_types:amqqueue(), pid(), boolean()) -> {'ok', non_neg_integer(), qmsg()} | 'empty'). @@ -134,7 +136,7 @@ (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), any()) -> 'ok'). -spec(notify_sent/2 :: (pid(), pid()) -> 'ok'). -spec(unblock/2 :: (pid(), pid()) -> 'ok'). --spec(flush_all/2 :: ([pid()], pid()) -> 'ok'). +-spec(flush_all/2 :: (qpids(), pid()) -> 'ok'). -spec(internal_delete/1 :: (name()) -> rabbit_types:ok_or_error('not_found') | rabbit_types:connection_exit() | @@ -264,6 +266,10 @@ add_default_binding(#amqqueue{name = QueueName}) -> key = RoutingKey, args = []}). +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}). @@ -419,14 +425,39 @@ delete(#amqqueue{ pid = QPid }, IfUnused, IfEmpty) -> purge(#amqqueue{ pid = QPid }) -> delegate_call(QPid, purge). -deliver(QPid, Delivery = #delivery{immediate = true}) -> - gen_server2:call(QPid, {deliver_immediately, Delivery}, infinity); -deliver(QPid, Delivery = #delivery{mandatory = true}) -> - gen_server2:call(QPid, {deliver, Delivery}, infinity), - true; -deliver(QPid, Delivery) -> - gen_server2:cast(QPid, {deliver, Delivery}), - true. +deliver([], #delivery{mandatory = false, immediate = false}) -> + %% /dev/null optimisation + {routed, []}; + +deliver(Qs, Delivery = #delivery{mandatory = false, immediate = false}) -> + %% optimisation: when Mandatory = false and Immediate = false, + %% rabbit_amqqueue:deliver will deliver the message to the queue + %% process asynchronously, and return true, which means all the + %% QPids will always be returned. It is therefore safe to use a + %% fire-and-forget cast here and return the QPids - the semantics + %% is preserved. This scales much better than the non-immediate + %% case below. + QPids = qpids(Qs), + delegate:invoke_no_result( + QPids, fun (QPid) -> gen_server2:cast(QPid, {deliver, Delivery}) end), + {routed, QPids}; + +deliver(Qs, Delivery = #delivery{mandatory = Mandatory, + immediate = Immediate}) -> + QPids = qpids(Qs), + {Success, _} = + delegate:invoke( + QPids, fun (QPid) -> + gen_server2:call(QPid, {deliver, Delivery}, infinity) + end), + case {Mandatory, Immediate, + lists:foldl(fun ({QPid, true}, {_, H}) -> {true, [QPid | H]}; + ({_, false}, {_, H}) -> {true, H} + end, {false, []}, Success)} of + {true, _ , {false, []}} -> {unroutable, []}; + {_ , true, {_ , []}} -> {not_delivered, []}; + {_ , _ , {_ , R}} -> {routed, R} + end. requeue(QPid, MsgIds, ChPid) -> delegate_call(QPid, {requeue, MsgIds, ChPid}). @@ -518,6 +549,9 @@ pseudo_queue(QueueName, Pid) -> slave_pids = [], mirror_nodes = undefined}. +qpids(Qs) -> lists:append([[QPid | SPids] || + #amqqueue{pid = QPid, slave_pids = SPids} <- Qs]). + safe_delegate_call_ok(F, Pids) -> case delegate:invoke(Pids, fun (Pid) -> rabbit_misc:with_exit_handler( diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index ba20b355..161f9787 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -877,9 +877,7 @@ handle_call({info, Items}, _From, State) -> handle_call(consumers, _From, State) -> reply(consumers(State), State); -handle_call({deliver_immediately, Delivery}, _From, State) -> - %% Synchronous, "immediate" delivery mode - %% +handle_call({deliver, Delivery = #delivery{immediate = true}}, _From, State) -> %% FIXME: Is this correct semantics? %% %% I'm worried in particular about the case where an exchange has @@ -897,8 +895,7 @@ handle_call({deliver_immediately, Delivery}, _From, State) -> false -> discard_delivery(Delivery, State1) end); -handle_call({deliver, Delivery}, From, State) -> - %% Synchronous, "mandatory" delivery mode. Reply asap. +handle_call({deliver, Delivery = #delivery{mandatory = true}}, From, State) -> gen_server2:reply(From, true), noreply(deliver_or_enqueue(Delivery, State)); diff --git a/src/rabbit_basic.erl b/src/rabbit_basic.erl index b266d366..b116821c 100644 --- a/src/rabbit_basic.erl +++ b/src/rabbit_basic.erl @@ -29,7 +29,7 @@ -type(properties_input() :: (rabbit_framing:amqp_property_record() | [{atom(), any()}])). -type(publish_result() :: - ({ok, rabbit_router:routing_result(), [pid()]} + ({ok, rabbit_amqqueue:routing_result(), [pid()]} | rabbit_types:error('not_found'))). -type(exchange_input() :: (rabbit_types:exchange() | rabbit_exchange:name())). @@ -88,8 +88,8 @@ publish(Delivery = #delivery{ end. publish(X, Delivery) -> - {RoutingRes, DeliveredQPids} = - rabbit_router:deliver(rabbit_exchange:route(X, Delivery), Delivery), + Qs = rabbit_amqqueue:lookup(rabbit_exchange:route(X, Delivery)), + {RoutingRes, DeliveredQPids} = rabbit_amqqueue:deliver(Qs, Delivery), {ok, RoutingRes, DeliveredQPids}. delivery(Mandatory, Immediate, Message, MsgSeqNo) -> diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 9b2fe28c..f14b2973 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1362,7 +1362,8 @@ deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{ exchange_name = XName}, msg_seq_no = MsgSeqNo}, QNames}, State) -> - {RoutingRes, DeliveredQPids} = rabbit_router:deliver(QNames, Delivery), + {RoutingRes, DeliveredQPids} = + rabbit_amqqueue:deliver(rabbit_amqqueue:lookup(QNames), Delivery), State1 = process_routing_result(RoutingRes, DeliveredQPids, XName, MsgSeqNo, Message, State), maybe_incr_stats([{XName, 1} | diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index d68063db..8d69a108 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -148,9 +148,8 @@ init([#amqqueue { name = QueueName } = Q]) -> {ok, State, hibernate, {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. -handle_call({deliver_immediately, Delivery = #delivery {}}, From, State) -> - %% Synchronous, "immediate" delivery mode - +handle_call({deliver, Delivery = #delivery { immediate = true }}, + From, State) -> %% It is safe to reply 'false' here even if a) we've not seen the %% msg via gm, or b) the master dies before we receive the msg via %% gm. In the case of (a), we will eventually receive the msg via @@ -166,8 +165,8 @@ handle_call({deliver_immediately, Delivery = #delivery {}}, From, State) -> gen_server2:reply(From, false), %% master may deliver it, not us noreply(maybe_enqueue_message(Delivery, false, State)); -handle_call({deliver, Delivery = #delivery {}}, From, State) -> - %% Synchronous, "mandatory" delivery mode +handle_call({deliver, Delivery = #delivery { mandatory = true }}, + From, State) -> gen_server2:reply(From, true), %% amqqueue throws away the result anyway noreply(maybe_enqueue_message(Delivery, true, State)); diff --git a/src/rabbit_router.erl b/src/rabbit_router.erl index 31f5ad14..219833b7 100644 --- a/src/rabbit_router.erl +++ b/src/rabbit_router.erl @@ -18,21 +18,17 @@ -include_lib("stdlib/include/qlc.hrl"). -include("rabbit.hrl"). --export([deliver/2, match_bindings/2, match_routing_key/2]). +-export([match_bindings/2, match_routing_key/2]). %%---------------------------------------------------------------------------- -ifdef(use_specs). --export_type([routing_key/0, routing_result/0, match_result/0]). +-export_type([routing_key/0, match_result/0]). -type(routing_key() :: binary()). --type(routing_result() :: 'routed' | 'unroutable' | 'not_delivered'). --type(qpids() :: [pid()]). -type(match_result() :: [rabbit_types:binding_destination()]). --spec(deliver/2 :: ([rabbit_amqqueue:name()], rabbit_types:delivery()) -> - {routing_result(), qpids()}). -spec(match_bindings/2 :: (rabbit_types:binding_source(), fun ((rabbit_types:binding()) -> boolean())) -> match_result()). @@ -44,38 +40,6 @@ %%---------------------------------------------------------------------------- -deliver([], #delivery{mandatory = false, - immediate = false}) -> - %% /dev/null optimisation - {routed, []}; - -deliver(QNames, Delivery = #delivery{mandatory = false, - immediate = false}) -> - %% optimisation: when Mandatory = false and Immediate = false, - %% rabbit_amqqueue:deliver will deliver the message to the queue - %% process asynchronously, and return true, which means all the - %% QPids will always be returned. It is therefore safe to use a - %% fire-and-forget cast here and return the QPids - the semantics - %% is preserved. This scales much better than the non-immediate - %% case below. - QPids = lookup_qpids(QNames), - delegate:invoke_no_result( - QPids, fun (Pid) -> rabbit_amqqueue:deliver(Pid, Delivery) end), - {routed, QPids}; - -deliver(QNames, Delivery = #delivery{mandatory = Mandatory, - immediate = Immediate}) -> - QPids = lookup_qpids(QNames), - {Success, _} = - delegate:invoke(QPids, - fun (Pid) -> - rabbit_amqqueue:deliver(Pid, Delivery) - end), - {Routed, Handled} = - lists:foldl(fun fold_deliveries/2, {false, []}, Success), - check_delivery(Mandatory, Immediate, {Routed, Handled}). - - %% TODO: Maybe this should be handled by a cursor instead. %% TODO: This causes a full scan for each entry with the same source match_bindings(SrcName, Match) -> @@ -104,26 +68,6 @@ match_routing_key(SrcName, [_|_] = RoutingKeys) -> %%-------------------------------------------------------------------- -fold_deliveries({Pid, true},{_, Handled}) -> {true, [Pid|Handled]}; -fold_deliveries({_, false},{_, Handled}) -> {true, Handled}. - -%% check_delivery(Mandatory, Immediate, {WasRouted, QPids}) -check_delivery(true, _ , {false, []}) -> {unroutable, []}; -check_delivery(_ , true, {_ , []}) -> {not_delivered, []}; -check_delivery(_ , _ , {_ , Qs}) -> {routed, Qs}. - -%% Normally we'd call mnesia:dirty_read/1 here, but that is quite -%% expensive for the reasons explained in rabbit_misc:dirty_read/1. -lookup_qpids(QNames) -> - lists:foldl(fun (QName, QPids) -> - case ets:lookup(rabbit_queue, QName) of - [#amqqueue{pid = QPid, slave_pids = SPids}] -> - [QPid | SPids ++ QPids]; - [] -> - QPids - end - end, [], QNames). - %% Normally we'd call mnesia:dirty_select/2 here, but that is quite %% expensive for the same reasons as above, and, additionally, due to %% mnesia 'fixing' the table with ets:safe_fixtable/2, which is wholly diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 00d46f5a..9afb95b9 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2232,7 +2232,7 @@ with_fresh_variable_queue(Fun) -> _ = rabbit_variable_queue:delete_and_terminate(shutdown, Fun(VQ)), passed. -publish_and_confirm(QPid, Payload, Count) -> +publish_and_confirm(Q, Payload, Count) -> Seqs = lists:seq(1, Count), [begin Msg = rabbit_basic:message(rabbit_misc:r(<<>>, exchange, <<>>), @@ -2240,7 +2240,7 @@ publish_and_confirm(QPid, Payload, Count) -> Payload), Delivery = #delivery{mandatory = false, immediate = false, sender = self(), message = Msg, msg_seq_no = Seq}, - true = rabbit_amqqueue:deliver(QPid, Delivery) + {routed, _} = rabbit_amqqueue:deliver([Q], Delivery) end || Seq <- Seqs], wait_for_confirms(gb_sets:from_list(Seqs)). @@ -2477,7 +2477,7 @@ test_queue_recover() -> Count = 2 * rabbit_queue_index:next_segment_boundary(0), {new, #amqqueue { pid = QPid, name = QName } = Q} = rabbit_amqqueue:declare(test_queue(), true, false, [], none), - publish_and_confirm(QPid, <<>>, Count), + publish_and_confirm(Q, <<>>, Count), exit(QPid, kill), MRef = erlang:monitor(process, QPid), @@ -2507,7 +2507,7 @@ test_variable_queue_delete_msg_store_files_callback() -> rabbit_amqqueue:declare(test_queue(), true, false, [], none), Payload = <<0:8388608>>, %% 1MB Count = 30, - publish_and_confirm(QPid, Payload, Count), + publish_and_confirm(Q, Payload, Count), rabbit_amqqueue:set_ram_duration_target(QPid, 0), |