From 1fefb9ca7a1aa99da5c6a910fecc2bb0c901c51e Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 6 Aug 2008 10:16:03 +0100 Subject: Special-case global=true: we don't implement it --- src/rabbit_channel.erl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index ec1d1fba..fa39ecf7 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -444,6 +444,12 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, end end; +handle_method(#'basic.qos'{global = true}, _, State) -> + rabbit_misc:protocol_error(not_implemented, + "Basic.Qos global (per-connection) setting not implemented", + [], + 'basic.qos'); + handle_method(#'basic.qos'{}, _, State) -> %% FIXME: Need to implement QOS {reply, #'basic.qos_ok'{}, State}; -- cgit v1.2.1 From f85a177a9b1c58076455b660660e07ca379d211d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 6 Aug 2008 12:00:21 +0100 Subject: Remove unnecessary method name from protocol_error --- src/rabbit_channel.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index fa39ecf7..d4034358 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -447,8 +447,7 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, handle_method(#'basic.qos'{global = true}, _, State) -> rabbit_misc:protocol_error(not_implemented, "Basic.Qos global (per-connection) setting not implemented", - [], - 'basic.qos'); + []); handle_method(#'basic.qos'{}, _, State) -> %% FIXME: Need to implement QOS -- cgit v1.2.1 From b50e4f85ee18b5351e4d9c4cfe7305e8147c0239 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Tue, 18 Nov 2008 19:40:02 +0000 Subject: First stab at basic.qos --- src/rabbit_amqqueue.erl | 12 +++--- src/rabbit_amqqueue_process.erl | 82 ++++++++++++++++++++++++++++------------- src/rabbit_channel.erl | 12 ++++-- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 56d2c35d..938182da 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -30,7 +30,7 @@ -export([lookup/1, with/2, with_or_die/2, list_vhost_queues/1, stat/1, stat_all/0, deliver/5, redeliver/2, requeue/3, ack/4]). -export([claim_queue/2]). --export([basic_get/3, basic_consume/7, basic_cancel/4]). +-export([basic_get/3, basic_consume/8, basic_cancel/4]). -export([notify_sent/2]). -export([commit_all/2, rollback_all/2, notify_down_all/2]). -export([on_node_down/1]). @@ -82,8 +82,8 @@ -spec(claim_queue/2 :: (amqqueue(), pid()) -> 'ok' | 'locked'). -spec(basic_get/3 :: (amqqueue(), pid(), bool()) -> {'ok', non_neg_integer(), msg()} | 'empty'). --spec(basic_consume/7 :: - (amqqueue(), bool(), pid(), pid(), ctag(), bool(), any()) -> +-spec(basic_consume/8 :: + (amqqueue(), bool(), pid(), pid(), pid(), ctag(), bool(), any()) -> 'ok' | {'error', 'queue_owned_by_another_connection' | 'exclusive_consume_unavailable'}). -spec(basic_cancel/4 :: (amqqueue(), pid(), ctag(), any()) -> 'ok'). @@ -238,10 +238,10 @@ claim_queue(#amqqueue{pid = QPid}, ReaderPid) -> basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> gen_server:call(QPid, {basic_get, ChPid, NoAck}). -basic_consume(#amqqueue{pid = QPid}, NoAck, ReaderPid, ChPid, +basic_consume(#amqqueue{pid = QPid}, NoAck, ReaderPid, ChPid, LimiterPid, ConsumerTag, ExclusiveConsume, OkMsg) -> - gen_server:call(QPid, {basic_consume, NoAck, ReaderPid, ChPid, - ConsumerTag, ExclusiveConsume, OkMsg}). + gen_server:call(QPid, {basic_consume, NoAck, ReaderPid, ChPid, + LimiterPid, ConsumerTag, ExclusiveConsume, OkMsg}). basic_cancel(#amqqueue{pid = QPid}, ChPid, ConsumerTag, OkMsg) -> ok = gen_server:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}). diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index f8964e34..43355a5a 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -43,6 +43,7 @@ % Queue's state -record(q, {q, owner, + limiter_mapping, exclusive_consumer, has_had_consumers, next_msg_id, @@ -75,6 +76,7 @@ init(Q) -> exclusive_consumer = none, has_had_consumers = false, next_msg_id = 1, + limiter_mapping = dict:new(), message_buffer = queue:new(), round_robin = queue:new()}, ?HIBERNATE_AFTER}. @@ -141,34 +143,61 @@ update_store_and_maybe_block_ch( deliver_immediately(Message, Delivered, State = #q{q = #amqqueue{name = QName}, round_robin = RoundRobin, + limiter_mapping = LimiterMapping, next_msg_id = NextId}) -> ?LOGDEBUG("AMQQUEUE ~p DELIVERY:~n~p~n", [QName, Message]), case queue:out(RoundRobin) of - {{value, QEntry = {ChPid, #consumer{tag = ConsumerTag, - ack_required = AckRequired}}}, + {{value, QEntry = {ChPid, + #consumer{tag = ConsumerTag, + ack_required = AckRequired = true}}}, RoundRobinTail} -> - rabbit_channel:deliver( - ChPid, ConsumerTag, AckRequired, - {QName, self(), NextId, Delivered, Message}), - C = #cr{unsent_message_count = Count, - unacked_messages = UAM} = ch_record(ChPid), - NewUAM = case AckRequired of - true -> dict:store(NextId, Message, UAM); - false -> UAM - end, - NewConsumers = - case update_store_and_maybe_block_ch( - C#cr{unsent_message_count = Count + 1, - unacked_messages = NewUAM}) of - ok -> queue:in(QEntry, RoundRobinTail); - block_ch -> block_consumers(ChPid, RoundRobinTail) - end, - {offered, AckRequired, State#q{round_robin = NewConsumers, - next_msg_id = NextId +1}}; + % Use Qos Limits if an ack is required + % Query the limiter to find out if a limit has been breached + LimiterPid = dict:fetch(ChPid, LimiterMapping), + case rabbit_limiter:can_send(LimiterPid, self()) of + true -> + really_deliver(AckRequired, ChPid, ConsumerTag, + Delivered, Message, NextId, QName, + QEntry, RoundRobinTail, State); + false -> + % Have another go by cycling through the consumer + % queue + NewConsumers = block_consumers(ChPid, RoundRobinTail), + deliver_immediately(Message, Delivered, + State#q{round_robin = NewConsumers}) + end; + {{value, QEntry = {ChPid, + #consumer{tag = ConsumerTag, + ack_required = AckRequired = false}}}, + RoundRobinTail} -> + really_deliver(AckRequired, ChPid, ConsumerTag, + Delivered, Message, NextId, QName, + QEntry, RoundRobinTail, State); {empty, _} -> not_offered end. +% TODO The arity of this function seems a bit large :-( +really_deliver(AckRequired, ChPid, ConsumerTag, Delivered, Message, NextId, + QName, QEntry, RoundRobinTail, State) -> + rabbit_channel:deliver(ChPid, ConsumerTag, AckRequired, + {QName, self(), NextId, Delivered, Message}), + C = #cr{unsent_message_count = Count, + unacked_messages = UAM} = ch_record(ChPid), + NewUAM = case AckRequired of + true -> dict:store(NextId, Message, UAM); + false -> UAM + end, + NewConsumers = + case update_store_and_maybe_block_ch( + C#cr{unsent_message_count = Count + 1, + unacked_messages = NewUAM}) of + ok -> queue:in(QEntry, RoundRobinTail); + block_ch -> block_consumers(ChPid, RoundRobinTail) + end, + {offered, AckRequired, State#q{round_robin = NewConsumers, + next_msg_id = NextId +1}}. + attempt_delivery(none, Message, State) -> case deliver_immediately(Message, false, State) of {offered, false, State1} -> @@ -519,11 +548,14 @@ handle_call({basic_get, ChPid, NoAck}, _From, reply(empty, State) end; -handle_call({basic_consume, NoAck, ReaderPid, ChPid, ConsumerTag, - ExclusiveConsume, OkMsg}, - _From, State = #q{owner = Owner, - exclusive_consumer = ExistingHolder, - round_robin = RoundRobin}) -> +handle_call({basic_consume, NoAck, ReaderPid, ChPid, LimiterPid, + ConsumerTag, ExclusiveConsume, OkMsg}, + _From, _State = #q{owner = Owner, + limiter_mapping = Mapping, + exclusive_consumer = ExistingHolder, + round_robin = RoundRobin}) -> + % TODO Remove the underscore in front of the first State variable + State = _State#q{limiter_mapping = dict:store(ChPid, LimiterPid, Mapping)}, case check_queue_owner(Owner, ReaderPid) of mismatch -> reply({error, queue_owned_by_another_connection}, State); diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 1eb421ca..7331a34b 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -36,7 +36,7 @@ -record(ch, {state, proxy_pid, reader_pid, writer_pid, transaction_id, tx_participants, next_tag, uncommitted_ack_q, unacked_message_q, - username, virtual_host, + username, virtual_host, limiter, most_recently_declared_queue, consumer_mapping}). %%---------------------------------------------------------------------------- @@ -102,6 +102,8 @@ init(ProxyPid, [ReaderPid, WriterPid, Username, VHost]) -> username = Username, virtual_host = VHost, most_recently_declared_queue = <<>>, + % TODO See point 3.1.1 of the design - start the limiter lazily + limiter = rabbit_limiter:start_link(), consumer_mapping = dict:new()}. handle_message({method, Method, Content}, State) -> @@ -323,6 +325,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, nowait = NoWait}, _, State = #ch{ proxy_pid = ProxyPid, reader_pid = ReaderPid, + limiter = LimiterPid, consumer_mapping = ConsumerMapping }) -> case dict:find(ConsumerTag, ConsumerMapping) of error -> @@ -340,7 +343,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, QueueName, fun (Q) -> rabbit_amqqueue:basic_consume( - Q, NoAck, ReaderPid, ProxyPid, + Q, NoAck, ReaderPid, ProxyPid, LimiterPid, ActualConsumerTag, ExclusiveConsume, ok_msg(NoWait, #'basic.consume_ok'{ consumer_tag = ActualConsumerTag})) @@ -405,8 +408,9 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, end end; -handle_method(#'basic.qos'{}, _, State) -> - %% FIXME: Need to implement QOS +handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, + _, State = #ch{limiter = Limiter}) -> + Limiter ! {prefetch_count, PrefetchCount}, {reply, #'basic.qos_ok'{}, State}; handle_method(#'basic.recover'{requeue = true}, -- cgit v1.2.1 From f2243fc3cfa0560e13667a0c503901d94d7268b8 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Wed, 19 Nov 2008 11:33:35 +0000 Subject: Moved limiter out of the queue state into the process dict --- src/rabbit_amqqueue_process.erl | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 43355a5a..9bb41b6b 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -43,7 +43,6 @@ % Queue's state -record(q, {q, owner, - limiter_mapping, exclusive_consumer, has_had_consumers, next_msg_id, @@ -57,6 +56,7 @@ %% These are held in our process dictionary -record(cr, {consumers, ch_pid, + limiter_pid, monitor_ref, unacked_messages, is_overload_protection_active, @@ -76,7 +76,6 @@ init(Q) -> exclusive_consumer = none, has_had_consumers = false, next_msg_id = 1, - limiter_mapping = dict:new(), message_buffer = queue:new(), round_robin = queue:new()}, ?HIBERNATE_AFTER}. @@ -143,7 +142,6 @@ update_store_and_maybe_block_ch( deliver_immediately(Message, Delivered, State = #q{q = #amqqueue{name = QName}, round_robin = RoundRobin, - limiter_mapping = LimiterMapping, next_msg_id = NextId}) -> ?LOGDEBUG("AMQQUEUE ~p DELIVERY:~n~p~n", [QName, Message]), case queue:out(RoundRobin) of @@ -153,7 +151,7 @@ deliver_immediately(Message, Delivered, RoundRobinTail} -> % Use Qos Limits if an ack is required % Query the limiter to find out if a limit has been breached - LimiterPid = dict:fetch(ChPid, LimiterMapping), + #cr{limiter_pid = LimiterPid} = ch_record(ChPid), case rabbit_limiter:can_send(LimiterPid, self()) of true -> really_deliver(AckRequired, ChPid, ConsumerTag, @@ -550,12 +548,9 @@ handle_call({basic_get, ChPid, NoAck}, _From, handle_call({basic_consume, NoAck, ReaderPid, ChPid, LimiterPid, ConsumerTag, ExclusiveConsume, OkMsg}, - _From, _State = #q{owner = Owner, - limiter_mapping = Mapping, - exclusive_consumer = ExistingHolder, - round_robin = RoundRobin}) -> - % TODO Remove the underscore in front of the first State variable - State = _State#q{limiter_mapping = dict:store(ChPid, LimiterPid, Mapping)}, + _From, State = #q{owner = Owner, + exclusive_consumer = ExistingHolder, + round_robin = RoundRobin}) -> case check_queue_owner(Owner, ReaderPid) of mismatch -> reply({error, queue_owned_by_another_connection}, State); @@ -566,7 +561,8 @@ handle_call({basic_consume, NoAck, ReaderPid, ChPid, LimiterPid, ok -> C = #cr{consumers = Consumers} = ch_record(ChPid), Consumer = #consumer{tag = ConsumerTag, ack_required = not(NoAck)}, - C1 = C#cr{consumers = [Consumer | Consumers]}, + C1 = C#cr{consumers = [Consumer | Consumers], + limiter_pid = LimiterPid}, store_ch_record(C1), State1 = State#q{has_had_consumers = true, exclusive_consumer = -- cgit v1.2.1 From 92bce20b237ff4bf96dc49077f83d300f4d8b125 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Wed, 19 Nov 2008 16:31:51 +0000 Subject: Savepoint --- src/rabbit_amqqueue_process.erl | 5 +- src/rabbit_channel.erl | 2 +- src/rabbit_limiter.erl | 155 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 src/rabbit_limiter.erl diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 9bb41b6b..c5a6a343 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -307,7 +307,7 @@ handle_ch_down(DownPid, State = #q{exclusive_consumer = Holder, {stop, normal, NewState} end end. - + cancel_holder(ChPid, ConsumerTag, {ChPid, ConsumerTag}) -> none; cancel_holder(_ChPid, _ConsumerTag, Holder) -> @@ -658,7 +658,8 @@ handle_cast({ack, Txn, MsgIds, ChPid}, State) -> case lookup_ch(ChPid) of not_found -> noreply(State); - C = #cr{unacked_messages = UAM} -> + C = #cr{unacked_messages = UAM, limiter_pid = LimiterPid} -> + rabbit_limiter:decrement_capacity(LimiterPid, qname(State)), {Acked, Remaining} = collect_messages(MsgIds, UAM), persist_acks(Txn, qname(State), Acked), case Txn of diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 7331a34b..ac186cfa 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -103,7 +103,7 @@ init(ProxyPid, [ReaderPid, WriterPid, Username, VHost]) -> virtual_host = VHost, most_recently_declared_queue = <<>>, % TODO See point 3.1.1 of the design - start the limiter lazily - limiter = rabbit_limiter:start_link(), + limiter = rabbit_limiter:start_link(self()), consumer_mapping = dict:new()}. handle_message({method, Method, Content}, State) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl new file mode 100644 index 00000000..3dfeb5fe --- /dev/null +++ b/src/rabbit_limiter.erl @@ -0,0 +1,155 @@ +-module(rabbit_limiter). + + +% I'm starting out with a gen_server because of the synchronous query +% that the queue process makes +-behviour(gen_server). + +-export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, + handle_info/2]). +-export([start_link/1]). +-export([can_send/2, decrement_capacity/2]). + +-record(lim, {prefetch_count = 1, + ch_pid, + blocked = false, + in_use = dict:new()}). + +%--------------------------------------------------------------------------- +% API +%--------------------------------------------------------------------------- + +% Kicks this pig +start_link(ChPid) -> + {ok, Pid} = gen_server:start_link(?MODULE, [ChPid], []), + Pid. + +% Queries the limiter to ask whether the queue can deliver a message +% without breaching a limit +can_send(LimiterPid, QPid) -> + gen_server:call(LimiterPid, {can_send, QPid}). + +% Lets the limiter know that a queue has received an ack from a consumer +% and hence can reduce the in-use-by-that queue capcity information +decrement_capacity(LimiterPid, QPid) -> + gen_server:cast(LimiterPid, {decrement_capacity, QPid}). + +%--------------------------------------------------------------------------- +% gen_server callbacks +%--------------------------------------------------------------------------- + +init([ChPid]) -> + {ok, #lim{ch_pid = ChPid} }. + +% This queuries the limiter to ask if it is possible to send a message without +% breaching a limit for this queue process +handle_call({can_send, QPid}, _From, State) -> + {CanSend, NewState} = maybe_can_send(QPid, State), + {reply, CanSend, NewState}. + +% This is an asynchronous ack from a queue that it has received an ack from +% a queue. This allows the limiter to update the the in-use-by-that queue +% capacity infromation. +handle_cast({decrement_capacity, QPid}, State) -> + NewState = decrement_in_use(QPid, State), + maybe_notify_queues(NewState), + {noreply, NewState}. + +% When the prefetch count has not been set, +% e.g. when the channel has not yet been issued a basic.qos +handle_info({prefetch_count, PrefetchCount}, + State = #lim{prefetch_count = 0}) -> + {noreply, State#lim{prefetch_count = PrefetchCount}}; + +% When the new limit is larger than the existing limit, +% notify all queues and forget about queues with an in-use +% capcity of zero +handle_info({prefetch_count, PrefetchCount}, + State = #lim{prefetch_count = CurrentLimit}) + when PrefetchCount > CurrentLimit -> + % TODO implement this requirement + {noreply, State#lim{prefetch_count = PrefetchCount}}; + +% Default setter of the prefetch count +handle_info({prefetch_count, PrefetchCount}, State) -> + {noreply, State#lim{prefetch_count = PrefetchCount}}. + +terminate(_, _) -> + ok. + +code_change(_, State, _) -> + State. + +%--------------------------------------------------------------------------- +% Internal plumbing +%--------------------------------------------------------------------------- + +decrement_in_use(QPid, State = #lim{in_use = InUse}) -> + case dict:find(QPid, InUse) of + {ok, Capacity} -> + io:format("capacity ~p~n",[Capacity]), + if + % Is there a lower bound on capacity? + % i.e. what is the zero mark, how much is unlimited? + Capacity > 0 -> + NewInUse = dict:store(QPid, Capacity - 1, InUse), + State#lim{in_use = NewInUse}; + true -> + % TODO How should this be handled? + State + end; + error -> + % TODO How should this case be handled? + State + end. + +maybe_notify_queues(State = #lim{ch_pid = ChPid, in_use = InUse}) -> + Capacity = current_capcity(State), + case should_notify(Capacity, State) of + true -> + dict:map(fun(Q,_) -> + rabbit_amqqueue:notify_sent(Q, ChPid) + end, InUse), + State#lim{blocked = false}; + false -> + ok + end. + +current_capcity(#lim{in_use = InUse}) -> + % TODO This *seems* expensive to compute this on the fly + dict:fold(fun(_, PerQ, Acc) -> PerQ + Acc end, 0, InUse). + + +% This is a very naive way of deciding wether to unblock or not, +% it *might* be better to wait for a time or volume threshold +% instead of broadcasting notifications +should_notify(Capacity, #lim{prefetch_count = Limit, blocked = true}) + when Capacity < Limit -> + true; + +should_notify(_,_) -> false. + +maybe_can_send(_, State = #lim{blocked = true}) -> + {false, State}; + +maybe_can_send(QPid, State = #lim{prefetch_count = Limit, + in_use = InUse, + blocked = false}) -> + Capacity = current_capcity(State), + io:format("Limit was ~p, capacity ~p~n",[Limit, Capacity]), + if + Capacity < Limit -> + NewInUse = update_in_use_capacity(QPid, InUse), + { true, State#lim{in_use = NewInUse} }; + true -> + { false, State#lim{blocked = true}} + end. + +update_in_use_capacity(QPid, InUse) -> + case dict:find(QPid, InUse) of + {ok, Capacity} -> + dict:store(QPid, Capacity + 1, InUse); + error -> + dict:store(QPid, 0, InUse) + end. + -- cgit v1.2.1 From d42a48dfea34f0cf3047ab1336216b95c6a1d586 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Thu, 20 Nov 2008 14:29:11 +0000 Subject: First cut with some actual load balancing working --- src/rabbit_amqqueue.erl | 5 +++++ src/rabbit_amqqueue_process.erl | 39 ++++++++++++++++++++++++++++++--------- src/rabbit_channel.erl | 2 +- src/rabbit_limiter.erl | 28 ++++++++++++++++++---------- 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 938182da..4e524e3c 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -32,6 +32,7 @@ -export([claim_queue/2]). -export([basic_get/3, basic_consume/8, basic_cancel/4]). -export([notify_sent/2]). +-export([unblock/2]). -export([commit_all/2, rollback_all/2, notify_down_all/2]). -export([on_node_down/1]). @@ -88,6 +89,7 @@ 'exclusive_consume_unavailable'}). -spec(basic_cancel/4 :: (amqqueue(), pid(), ctag(), any()) -> 'ok'). -spec(notify_sent/2 :: (pid(), pid()) -> 'ok'). +-spec(unblock/2 :: (pid(), pid()) -> 'ok'). -spec(internal_delete/1 :: (queue_name()) -> 'ok' | not_found()). -spec(on_node_down/1 :: (node()) -> 'ok'). -spec(pseudo_queue/2 :: (binary(), pid()) -> amqqueue()). @@ -249,6 +251,9 @@ basic_cancel(#amqqueue{pid = QPid}, ChPid, ConsumerTag, OkMsg) -> notify_sent(QPid, ChPid) -> gen_server:cast(QPid, {notify_sent, ChPid}). +unblock(QPid, ChPid) -> + gen_server:cast(QPid, {unblock, ChPid}). + internal_delete(QueueName) -> rabbit_misc:execute_mnesia_transaction( fun () -> diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c5a6a343..6ef5f970 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -59,6 +59,7 @@ limiter_pid, monitor_ref, unacked_messages, + is_limit_active, is_overload_protection_active, unsent_message_count}). @@ -125,18 +126,22 @@ all_ch_record() -> [C || {{ch, _}, C} <- get()]. update_store_and_maybe_block_ch( - C = #cr{is_overload_protection_active = Active, + C = #cr{is_overload_protection_active = Overloaded, + is_limit_active = Limited, unsent_message_count = Count}) -> - {Result, NewActive} = + {Result, NewOverloaded, NewLimited} = if - not(Active) and (Count > ?UNSENT_MESSAGE_LIMIT) -> - {block_ch, true}; - Active and (Count == 0) -> - {unblock_ch, false}; + not(Overloaded) and (Count > ?UNSENT_MESSAGE_LIMIT) -> + {block_ch, true, Limited}; + Overloaded and (Count == 0) -> + {unblock_ch, false, Limited}; + Limited and (Count < ?UNSENT_MESSAGE_LIMIT) -> + {unblock_ch, Overloaded, false}; true -> - {ok, Active} + {ok, Overloaded, Limited} end, - store_ch_record(C#cr{is_overload_protection_active = NewActive}), + store_ch_record(C#cr{is_overload_protection_active = NewOverloaded, + is_limit_active = NewLimited}), Result. deliver_immediately(Message, Delivered, @@ -160,6 +165,8 @@ deliver_immediately(Message, Delivered, false -> % Have another go by cycling through the consumer % queue + C = ch_record(ChPid), + store_ch_record(C#cr{is_limit_active = true}), NewConsumers = block_consumers(ChPid, RoundRobinTail), deliver_immediately(Message, Delivered, State#q{round_robin = NewConsumers}) @@ -659,7 +666,7 @@ handle_cast({ack, Txn, MsgIds, ChPid}, State) -> not_found -> noreply(State); C = #cr{unacked_messages = UAM, limiter_pid = LimiterPid} -> - rabbit_limiter:decrement_capacity(LimiterPid, qname(State)), + rabbit_limiter:decrement_capacity(LimiterPid, self()), {Acked, Remaining} = collect_messages(MsgIds, UAM), persist_acks(Txn, qname(State), Acked), case Txn of @@ -692,6 +699,20 @@ handle_cast({requeue, MsgIds, ChPid}, State) -> [{Message, true} || Message <- Messages], State)) end; +handle_cast({unblock, ChPid}, State) -> + % TODO Refactor the code duplication + % between this an the notify_sent cast handler + case lookup_ch(ChPid) of + not_found -> + noreply(State); + C = #cr{is_limit_active = true} -> + noreply(possibly_unblock(C, State)); + C -> + rabbit_log:warning("Ignoring unblock for an active ch: ~p~n", + [C]), + noreply(State) + end; + handle_cast({notify_sent, ChPid}, State) -> case lookup_ch(ChPid) of not_found -> noreply(State); diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index ac186cfa..3306d6f6 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -103,7 +103,7 @@ init(ProxyPid, [ReaderPid, WriterPid, Username, VHost]) -> virtual_host = VHost, most_recently_declared_queue = <<>>, % TODO See point 3.1.1 of the design - start the limiter lazily - limiter = rabbit_limiter:start_link(self()), + limiter = rabbit_limiter:start_link(ProxyPid), consumer_mapping = dict:new()}. handle_message({method, Method, Content}, State) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 3dfeb5fe..1973d358 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -51,9 +51,9 @@ handle_call({can_send, QPid}, _From, State) -> % a queue. This allows the limiter to update the the in-use-by-that queue % capacity infromation. handle_cast({decrement_capacity, QPid}, State) -> - NewState = decrement_in_use(QPid, State), - maybe_notify_queues(NewState), - {noreply, NewState}. + State1 = decrement_in_use(QPid, State), + State2 = maybe_notify_queues(State1), + {noreply, State2}. % When the prefetch count has not been set, % e.g. when the channel has not yet been issued a basic.qos @@ -84,10 +84,10 @@ code_change(_, State, _) -> % Internal plumbing %--------------------------------------------------------------------------- +% Reduces the in-use-count of the queue by one decrement_in_use(QPid, State = #lim{in_use = InUse}) -> case dict:find(QPid, InUse) of {ok, Capacity} -> - io:format("capacity ~p~n",[Capacity]), if % Is there a lower bound on capacity? % i.e. what is the zero mark, how much is unlimited? @@ -96,26 +96,35 @@ decrement_in_use(QPid, State = #lim{in_use = InUse}) -> State#lim{in_use = NewInUse}; true -> % TODO How should this be handled? + rabbit_log:warning( + "Ignoring decrement for zero capacity: ~p~n", + [QPid]), State end; error -> % TODO How should this case be handled? + rabbit_log:warning("Ignoring decrement for unknown queue: ~p~n", + [QPid]), State end. +% Works out whether any queues should be notified +% If any notification is required, it propagates a transition +% of the blocked state maybe_notify_queues(State = #lim{ch_pid = ChPid, in_use = InUse}) -> - Capacity = current_capcity(State), + Capacity = current_capacity(State), case should_notify(Capacity, State) of true -> dict:map(fun(Q,_) -> - rabbit_amqqueue:notify_sent(Q, ChPid) + rabbit_amqqueue:unblock(Q, ChPid) end, InUse), State#lim{blocked = false}; false -> - ok + State end. -current_capcity(#lim{in_use = InUse}) -> +% Computes the current aggregrate capacity of all of the in-use queues +current_capacity(#lim{in_use = InUse}) -> % TODO This *seems* expensive to compute this on the fly dict:fold(fun(_, PerQ, Acc) -> PerQ + Acc end, 0, InUse). @@ -135,8 +144,7 @@ maybe_can_send(_, State = #lim{blocked = true}) -> maybe_can_send(QPid, State = #lim{prefetch_count = Limit, in_use = InUse, blocked = false}) -> - Capacity = current_capcity(State), - io:format("Limit was ~p, capacity ~p~n",[Limit, Capacity]), + Capacity = current_capacity(State), if Capacity < Limit -> NewInUse = update_in_use_capacity(QPid, InUse), -- cgit v1.2.1 From 77249f371e49a9bf21869d027d89c21ec5a7c554 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Thu, 20 Nov 2008 17:15:52 +0000 Subject: Refactored the internal structure of the limiter --- src/rabbit_limiter.erl | 112 +++++++++++++++---------------------------------- 1 file changed, 34 insertions(+), 78 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 1973d358..adf4cd4b 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -1,3 +1,5 @@ +%% TODO Decide what to do with the license statement now that Cohesive have +%% bailed. -module(rabbit_limiter). @@ -10,9 +12,8 @@ -export([start_link/1]). -export([can_send/2, decrement_capacity/2]). --record(lim, {prefetch_count = 1, +-record(lim, {prefetch_count = 0, ch_pid, - blocked = false, in_use = dict:new()}). %--------------------------------------------------------------------------- @@ -44,22 +45,22 @@ init([ChPid]) -> % This queuries the limiter to ask if it is possible to send a message without % breaching a limit for this queue process handle_call({can_send, QPid}, _From, State) -> - {CanSend, NewState} = maybe_can_send(QPid, State), - {reply, CanSend, NewState}. + case limit_reached(State) of + true -> {reply, false, State}; + false -> {reply, true, update_in_use_capacity(QPid, State)} + end. % This is an asynchronous ack from a queue that it has received an ack from % a queue. This allows the limiter to update the the in-use-by-that queue % capacity infromation. handle_cast({decrement_capacity, QPid}, State) -> - State1 = decrement_in_use(QPid, State), - State2 = maybe_notify_queues(State1), - {noreply, State2}. - -% When the prefetch count has not been set, -% e.g. when the channel has not yet been issued a basic.qos -handle_info({prefetch_count, PrefetchCount}, - State = #lim{prefetch_count = 0}) -> - {noreply, State#lim{prefetch_count = PrefetchCount}}; + NewState = decrement_in_use(QPid, State), + ShouldNotify = limit_reached(State) and not(limit_reached(State)), + if + ShouldNotify -> notify_queues(NewState); + true -> ok + end, + {noreply, NewState}. % When the new limit is larger than the existing limit, % notify all queues and forget about queues with an in-use @@ -86,78 +87,33 @@ code_change(_, State, _) -> % Reduces the in-use-count of the queue by one decrement_in_use(QPid, State = #lim{in_use = InUse}) -> - case dict:find(QPid, InUse) of - {ok, Capacity} -> - if - % Is there a lower bound on capacity? - % i.e. what is the zero mark, how much is unlimited? - Capacity > 0 -> - NewInUse = dict:store(QPid, Capacity - 1, InUse), - State#lim{in_use = NewInUse}; - true -> - % TODO How should this be handled? - rabbit_log:warning( - "Ignoring decrement for zero capacity: ~p~n", - [QPid]), - State - end; - error -> - % TODO How should this case be handled? - rabbit_log:warning("Ignoring decrement for unknown queue: ~p~n", - [QPid]), - State + NewInUse = dict:update_counter(QPid, -1, InUse), + Count = dict:fetch(QPid, NewInUse), + if + Count < 1 -> + State#lim{in_use = dict:erase(QPid, NewInUse)}; + true -> + State#lim{in_use = NewInUse} end. -% Works out whether any queues should be notified -% If any notification is required, it propagates a transition -% of the blocked state -maybe_notify_queues(State = #lim{ch_pid = ChPid, in_use = InUse}) -> - Capacity = current_capacity(State), - case should_notify(Capacity, State) of - true -> - dict:map(fun(Q,_) -> - rabbit_amqqueue:unblock(Q, ChPid) - end, InUse), - State#lim{blocked = false}; - false -> - State - end. +% Unblocks every queue that this limiter knows about +notify_queues(#lim{ch_pid = ChPid, in_use = InUse}) -> + dict:map(fun(Q,_) -> rabbit_amqqueue:unblock(Q, ChPid) end, InUse). % Computes the current aggregrate capacity of all of the in-use queues current_capacity(#lim{in_use = InUse}) -> - % TODO This *seems* expensive to compute this on the fly + % TODO It *seems* expensive to compute this on the fly dict:fold(fun(_, PerQ, Acc) -> PerQ + Acc end, 0, InUse). +% A prefetch limit of zero means unlimited +limit_reached(#lim{prefetch_count = 0}) -> + false; -% This is a very naive way of deciding wether to unblock or not, -% it *might* be better to wait for a time or volume threshold -% instead of broadcasting notifications -should_notify(Capacity, #lim{prefetch_count = Limit, blocked = true}) - when Capacity < Limit -> - true; +% Works out whether the limit is breached for the current limiter state +limit_reached(State = #lim{prefetch_count = Limit}) -> + current_capacity(State) == Limit. -should_notify(_,_) -> false. - -maybe_can_send(_, State = #lim{blocked = true}) -> - {false, State}; - -maybe_can_send(QPid, State = #lim{prefetch_count = Limit, - in_use = InUse, - blocked = false}) -> - Capacity = current_capacity(State), - if - Capacity < Limit -> - NewInUse = update_in_use_capacity(QPid, InUse), - { true, State#lim{in_use = NewInUse} }; - true -> - { false, State#lim{blocked = true}} - end. - -update_in_use_capacity(QPid, InUse) -> - case dict:find(QPid, InUse) of - {ok, Capacity} -> - dict:store(QPid, Capacity + 1, InUse); - error -> - dict:store(QPid, 0, InUse) - end. +% Increments the counter for the in-use-capacity of a particular queue +update_in_use_capacity(QPid, State = #lim{in_use = InUse}) -> + State#lim{in_use = dict:update_counter(QPid, 1, InUse)}. -- cgit v1.2.1 From d0bc61aef26fb7f1d9d97e1ca2e0e3d71fd6a4d7 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Fri, 21 Nov 2008 14:08:11 +0000 Subject: Fixed bug in limiter --- src/rabbit_limiter.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index adf4cd4b..abca7ce1 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -5,7 +5,7 @@ % I'm starting out with a gen_server because of the synchronous query % that the queue process makes --behviour(gen_server). +-behaviour(gen_server). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). @@ -55,9 +55,9 @@ handle_call({can_send, QPid}, _From, State) -> % capacity infromation. handle_cast({decrement_capacity, QPid}, State) -> NewState = decrement_in_use(QPid, State), - ShouldNotify = limit_reached(State) and not(limit_reached(State)), + ShouldNotify = limit_reached(State) and not(limit_reached(NewState)), if - ShouldNotify -> notify_queues(NewState); + ShouldNotify -> notify_queues(State); true -> ok end, {noreply, NewState}. -- cgit v1.2.1 From d990241d9b9bf2b492788a3b3d2b82c8fd0dd88d Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Fri, 21 Nov 2008 16:44:00 +0000 Subject: Made set_prefetch_count into a proper gen_server call --- src/rabbit_channel.erl | 2 +- src/rabbit_limiter.erl | 34 ++++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 3306d6f6..c6108489 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -410,7 +410,7 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{limiter = Limiter}) -> - Limiter ! {prefetch_count, PrefetchCount}, + rabbit_limiter:set_prefetch_count(Limiter, PrefetchCount), {reply, #'basic.qos_ok'{}, State}; handle_method(#'basic.recover'{requeue = true}, diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index abca7ce1..fbce5ea4 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -10,7 +10,7 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -export([start_link/1]). --export([can_send/2, decrement_capacity/2]). +-export([set_prefetch_count/2, can_send/2, decrement_capacity/2]). -record(lim, {prefetch_count = 0, ch_pid, @@ -25,6 +25,9 @@ start_link(ChPid) -> {ok, Pid} = gen_server:start_link(?MODULE, [ChPid], []), Pid. +set_prefetch_count(LimiterPid, PrefetchCount) -> + gen_server:call(LimiterPid, {prefetch_count, PrefetchCount}). + % Queries the limiter to ask whether the queue can deliver a message % without breaching a limit can_send(LimiterPid, QPid) -> @@ -48,7 +51,20 @@ handle_call({can_send, QPid}, _From, State) -> case limit_reached(State) of true -> {reply, false, State}; false -> {reply, true, update_in_use_capacity(QPid, State)} - end. + end; + +% When the new limit is larger than the existing limit, +% notify all queues and forget about queues with an in-use +% capcity of zero +handle_call({prefetch_count, PrefetchCount}, _From, + State = #lim{prefetch_count = CurrentLimit}) + when PrefetchCount > CurrentLimit -> + % TODO implement this requirement + {reply, ok, State#lim{prefetch_count = PrefetchCount}}; + +% Default setter of the prefetch count +handle_call({prefetch_count, PrefetchCount}, _From, State) -> + {reply, ok, State#lim{prefetch_count = PrefetchCount}}. % This is an asynchronous ack from a queue that it has received an ack from % a queue. This allows the limiter to update the the in-use-by-that queue @@ -62,18 +78,8 @@ handle_cast({decrement_capacity, QPid}, State) -> end, {noreply, NewState}. -% When the new limit is larger than the existing limit, -% notify all queues and forget about queues with an in-use -% capcity of zero -handle_info({prefetch_count, PrefetchCount}, - State = #lim{prefetch_count = CurrentLimit}) - when PrefetchCount > CurrentLimit -> - % TODO implement this requirement - {noreply, State#lim{prefetch_count = PrefetchCount}}; - -% Default setter of the prefetch count -handle_info({prefetch_count, PrefetchCount}, State) -> - {noreply, State#lim{prefetch_count = PrefetchCount}}. +handle_info(_, State) -> + {noreply, State}. terminate(_, _) -> ok. -- cgit v1.2.1 From a826871c0142034ddbf48af719139e8af1516a26 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Fri, 21 Nov 2008 17:34:01 +0000 Subject: Got rid o the per-queue in-use capacity --- src/rabbit_amqqueue_process.erl | 2 +- src/rabbit_limiter.erl | 51 +++++++++++++++++------------------------ 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 6ef5f970..c0f48ad1 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -666,7 +666,7 @@ handle_cast({ack, Txn, MsgIds, ChPid}, State) -> not_found -> noreply(State); C = #cr{unacked_messages = UAM, limiter_pid = LimiterPid} -> - rabbit_limiter:decrement_capacity(LimiterPid, self()), + rabbit_limiter:decrement_capacity(LimiterPid), {Acked, Remaining} = collect_messages(MsgIds, UAM), persist_acks(Txn, qname(State), Acked), case Txn of diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index fbce5ea4..3f194b31 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -10,11 +10,12 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -export([start_link/1]). --export([set_prefetch_count/2, can_send/2, decrement_capacity/2]). +-export([set_prefetch_count/2, can_send/2, decrement_capacity/1]). -record(lim, {prefetch_count = 0, ch_pid, - in_use = dict:new()}). + queues = sets:new(), + in_use = 0}). %--------------------------------------------------------------------------- % API @@ -35,8 +36,8 @@ can_send(LimiterPid, QPid) -> % Lets the limiter know that a queue has received an ack from a consumer % and hence can reduce the in-use-by-that queue capcity information -decrement_capacity(LimiterPid, QPid) -> - gen_server:cast(LimiterPid, {decrement_capacity, QPid}). +decrement_capacity(LimiterPid) -> + gen_server:cast(LimiterPid, decrement_capacity). %--------------------------------------------------------------------------- % gen_server callbacks @@ -47,10 +48,13 @@ init([ChPid]) -> % This queuries the limiter to ask if it is possible to send a message without % breaching a limit for this queue process -handle_call({can_send, QPid}, _From, State) -> +handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse, + queues = Queues}) -> case limit_reached(State) of true -> {reply, false, State}; - false -> {reply, true, update_in_use_capacity(QPid, State)} + false -> + NewQueues = sets:add_element(QPid, Queues), + {reply, true, State#lim{in_use = InUse + 1, queues = NewQueues}} end; % When the new limit is larger than the existing limit, @@ -69,8 +73,8 @@ handle_call({prefetch_count, PrefetchCount}, _From, State) -> % This is an asynchronous ack from a queue that it has received an ack from % a queue. This allows the limiter to update the the in-use-by-that queue % capacity infromation. -handle_cast({decrement_capacity, QPid}, State) -> - NewState = decrement_in_use(QPid, State), +handle_cast(decrement_capacity, State) -> + NewState = decrement_in_use(State), ShouldNotify = limit_reached(State) and not(limit_reached(NewState)), if ShouldNotify -> notify_queues(State); @@ -92,34 +96,21 @@ code_change(_, State, _) -> %--------------------------------------------------------------------------- % Reduces the in-use-count of the queue by one -decrement_in_use(QPid, State = #lim{in_use = InUse}) -> - NewInUse = dict:update_counter(QPid, -1, InUse), - Count = dict:fetch(QPid, NewInUse), - if - Count < 1 -> - State#lim{in_use = dict:erase(QPid, NewInUse)}; - true -> - State#lim{in_use = NewInUse} - end. +decrement_in_use(State = #lim{in_use = 0}) -> + State#lim{in_use = 0}; -% Unblocks every queue that this limiter knows about -notify_queues(#lim{ch_pid = ChPid, in_use = InUse}) -> - dict:map(fun(Q,_) -> rabbit_amqqueue:unblock(Q, ChPid) end, InUse). +decrement_in_use(State = #lim{in_use = InUse}) -> + State#lim{in_use = InUse - 1}. -% Computes the current aggregrate capacity of all of the in-use queues -current_capacity(#lim{in_use = InUse}) -> - % TODO It *seems* expensive to compute this on the fly - dict:fold(fun(_, PerQ, Acc) -> PerQ + Acc end, 0, InUse). +% Unblocks every queue that this limiter knows about +notify_queues(#lim{ch_pid = ChPid, queues = Queues}) -> + sets:fold(fun(Q,_) -> rabbit_amqqueue:unblock(Q, ChPid) end, [], Queues). % A prefetch limit of zero means unlimited limit_reached(#lim{prefetch_count = 0}) -> false; % Works out whether the limit is breached for the current limiter state -limit_reached(State = #lim{prefetch_count = Limit}) -> - current_capacity(State) == Limit. - -% Increments the counter for the in-use-capacity of a particular queue -update_in_use_capacity(QPid, State = #lim{in_use = InUse}) -> - State#lim{in_use = dict:update_counter(QPid, 1, InUse)}. +limit_reached(#lim{prefetch_count = Limit, in_use = InUse}) -> + InUse == Limit. -- cgit v1.2.1 From 284ee2c626e3a55545496681997c395684d2c3f0 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Fri, 21 Nov 2008 17:40:25 +0000 Subject: Changed prefetch from call to cast --- src/rabbit_limiter.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 3f194b31..e38843c9 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -27,7 +27,7 @@ start_link(ChPid) -> Pid. set_prefetch_count(LimiterPid, PrefetchCount) -> - gen_server:call(LimiterPid, {prefetch_count, PrefetchCount}). + gen_server:cast(LimiterPid, {prefetch_count, PrefetchCount}). % Queries the limiter to ask whether the queue can deliver a message % without breaching a limit @@ -55,20 +55,20 @@ handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse, false -> NewQueues = sets:add_element(QPid, Queues), {reply, true, State#lim{in_use = InUse + 1, queues = NewQueues}} - end; + end. % When the new limit is larger than the existing limit, % notify all queues and forget about queues with an in-use % capcity of zero -handle_call({prefetch_count, PrefetchCount}, _From, +handle_cast({prefetch_count, PrefetchCount}, State = #lim{prefetch_count = CurrentLimit}) when PrefetchCount > CurrentLimit -> % TODO implement this requirement - {reply, ok, State#lim{prefetch_count = PrefetchCount}}; + {noreply, State#lim{prefetch_count = PrefetchCount}}; % Default setter of the prefetch count -handle_call({prefetch_count, PrefetchCount}, _From, State) -> - {reply, ok, State#lim{prefetch_count = PrefetchCount}}. +handle_cast({prefetch_count, PrefetchCount}, State) -> + {noreply, State#lim{prefetch_count = PrefetchCount}}; % This is an asynchronous ack from a queue that it has received an ack from % a queue. This allows the limiter to update the the in-use-by-that queue -- cgit v1.2.1 From 3410d55dcbb1ad78ec2d7ca600bb0bda4c6cb502 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Fri, 21 Nov 2008 18:14:12 +0000 Subject: Minor fixes --- src/rabbit_limiter.erl | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index e38843c9..b83af0c9 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -50,11 +50,11 @@ init([ChPid]) -> % breaching a limit for this queue process handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse, queues = Queues}) -> - case limit_reached(State) of - true -> {reply, false, State}; + NewState = State#lim{queues = sets:add_element(QPid, Queues)}, + case limit_reached(NewState) of + true -> {reply, false, NewState}; false -> - NewQueues = sets:add_element(QPid, Queues), - {reply, true, State#lim{in_use = InUse + 1, queues = NewQueues}} + {reply, true, NewState#lim{in_use = InUse + 1}} end. % When the new limit is larger than the existing limit, @@ -63,8 +63,10 @@ handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse, handle_cast({prefetch_count, PrefetchCount}, State = #lim{prefetch_count = CurrentLimit}) when PrefetchCount > CurrentLimit -> - % TODO implement this requirement - {noreply, State#lim{prefetch_count = PrefetchCount}}; + notify_queues(State), + {noreply, State#lim{prefetch_count = PrefetchCount, + queues = sets:new(), + in_use = 0}}; % Default setter of the prefetch count handle_cast({prefetch_count, PrefetchCount}, State) -> @@ -73,14 +75,16 @@ handle_cast({prefetch_count, PrefetchCount}, State) -> % This is an asynchronous ack from a queue that it has received an ack from % a queue. This allows the limiter to update the the in-use-by-that queue % capacity infromation. -handle_cast(decrement_capacity, State) -> +handle_cast(decrement_capacity, State = #lim{in_use = InUse}) -> NewState = decrement_in_use(State), ShouldNotify = limit_reached(State) and not(limit_reached(NewState)), if - ShouldNotify -> notify_queues(State); - true -> ok - end, - {noreply, NewState}. + ShouldNotify -> + notify_queues(State), + {noreply, State#lim{queues = sets:new(), in_use = InUse - 1}}; + true -> + {noreply, NewState} + end. handle_info(_, State) -> {noreply, State}. -- cgit v1.2.1 From 1b8c0ff6a1f6a305945afacdb3e5d223ae1221f9 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Mon, 24 Nov 2008 00:52:28 +0000 Subject: Now the channel sends the ack directly to the limiter instead of via the queue --- src/rabbit_amqqueue_process.erl | 1 - src/rabbit_channel.erl | 3 +++ src/rabbit_limiter.erl | 18 +++++++++--------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c0f48ad1..b4d0d52d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -666,7 +666,6 @@ handle_cast({ack, Txn, MsgIds, ChPid}, State) -> not_found -> noreply(State); C = #cr{unacked_messages = UAM, limiter_pid = LimiterPid} -> - rabbit_limiter:decrement_capacity(LimiterPid), {Acked, Remaining} = collect_messages(MsgIds, UAM), persist_acks(Txn, qname(State), Acked), case Txn of diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index c6108489..4abc3494 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -271,6 +271,7 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, handle_method(#'basic.ack'{delivery_tag = DeliveryTag, multiple = Multiple}, _, State = #ch{transaction_id = TxnKey, + limiter = Limiter, next_tag = NextDeliveryTag, unacked_message_q = UAMQ}) -> if DeliveryTag >= NextDeliveryTag -> @@ -279,6 +280,8 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, true -> ok end, {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), + % CC the limiter on the number of acks that have been received + rabbit_limiter:decrement_capacity(Limiter, queue:len(Acked)), Participants = ack(State#ch.proxy_pid, TxnKey, Acked), {noreply, case TxnKey of none -> State#ch{unacked_message_q = Remaining}; diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index b83af0c9..4e130ea0 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -10,7 +10,7 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -export([start_link/1]). --export([set_prefetch_count/2, can_send/2, decrement_capacity/1]). +-export([set_prefetch_count/2, can_send/2, decrement_capacity/2]). -record(lim, {prefetch_count = 0, ch_pid, @@ -36,8 +36,8 @@ can_send(LimiterPid, QPid) -> % Lets the limiter know that a queue has received an ack from a consumer % and hence can reduce the in-use-by-that queue capcity information -decrement_capacity(LimiterPid) -> - gen_server:cast(LimiterPid, decrement_capacity). +decrement_capacity(LimiterPid, Magnitude) -> + gen_server:cast(LimiterPid, {decrement_capacity, Magnitude}). %--------------------------------------------------------------------------- % gen_server callbacks @@ -75,8 +75,8 @@ handle_cast({prefetch_count, PrefetchCount}, State) -> % This is an asynchronous ack from a queue that it has received an ack from % a queue. This allows the limiter to update the the in-use-by-that queue % capacity infromation. -handle_cast(decrement_capacity, State = #lim{in_use = InUse}) -> - NewState = decrement_in_use(State), +handle_cast({decrement_capacity, Magnitude}, State = #lim{in_use = InUse}) -> + NewState = decrement_in_use(Magnitude, State), ShouldNotify = limit_reached(State) and not(limit_reached(NewState)), if ShouldNotify -> @@ -99,12 +99,12 @@ code_change(_, State, _) -> % Internal plumbing %--------------------------------------------------------------------------- -% Reduces the in-use-count of the queue by one -decrement_in_use(State = #lim{in_use = 0}) -> +% Reduces the in-use-count of the queue by a specific magnitude +decrement_in_use(_, State = #lim{in_use = 0}) -> State#lim{in_use = 0}; -decrement_in_use(State = #lim{in_use = InUse}) -> - State#lim{in_use = InUse - 1}. +decrement_in_use(Magnitude, State = #lim{in_use = InUse}) -> + State#lim{in_use = InUse - Magnitude}. % Unblocks every queue that this limiter knows about notify_queues(#lim{ch_pid = ChPid, queues = Queues}) -> -- cgit v1.2.1 From c4f8ddb5cb914b0f825a5c8fc30df594f92c8703 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Mon, 24 Nov 2008 01:14:38 +0000 Subject: Differentiate between acks for basic.get and basic.consume --- src/rabbit_channel.erl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 4abc3494..f9f92959 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -281,7 +281,19 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, end, {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), % CC the limiter on the number of acks that have been received - rabbit_limiter:decrement_capacity(Limiter, queue:len(Acked)), + % but don't include any acks from a basic.get bottom half + % (hence the differentiation between tags set to none and other tags) + % TODO - this is quite crude and is probably more expensive than it should + % be - according to the OTP documentation, len/1 runs in O(n), probably + % not so cool for a queuing system + NotBasicGet = queue:filter( + fun({_CurrentDeliveryTag, ConsumerTag, _Msg}) -> + case ConsumerTag of + none -> false; + _ -> true + end + end, Acked), + rabbit_limiter:decrement_capacity(Limiter, queue:len(NotBasicGet)), Participants = ack(State#ch.proxy_pid, TxnKey, Acked), {noreply, case TxnKey of none -> State#ch{unacked_message_q = Remaining}; -- cgit v1.2.1 From 36c7a93b0e05496c85d2fdbbfea178584feec9ac Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Sun, 7 Dec 2008 01:26:36 +0000 Subject: Dead queue informs limiter --- src/rabbit_amqqueue_process.erl | 5 ++++- src/rabbit_channel.erl | 1 + src/rabbit_limiter.erl | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b4d0d52d..2000a11c 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -81,6 +81,9 @@ init(Q) -> round_robin = queue:new()}, ?HIBERNATE_AFTER}. terminate(_Reason, State) -> + %% Inform all limiters that we're dying + [ rabbit_limiter:unregister_queue(LimiterPid, self()) + || #cr{limiter_pid = LimiterPid} <- all_ch_record()], %% FIXME: How do we cancel active subscriptions? QName = qname(State), lists:foreach(fun (Txn) -> ok = rollback_work(Txn, QName) end, @@ -665,7 +668,7 @@ handle_cast({ack, Txn, MsgIds, ChPid}, State) -> case lookup_ch(ChPid) of not_found -> noreply(State); - C = #cr{unacked_messages = UAM, limiter_pid = LimiterPid} -> + C = #cr{unacked_messages = UAM} -> {Acked, Remaining} = collect_messages(MsgIds, UAM), persist_acks(Txn, qname(State), Acked), case Txn of diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index f9f92959..240ee3d3 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -293,6 +293,7 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, _ -> true end end, Acked), + % TODO Optimization: Probably don't need to send this if len = 0 rabbit_limiter:decrement_capacity(Limiter, queue:len(NotBasicGet)), Participants = ack(State#ch.proxy_pid, TxnKey, Acked), {noreply, case TxnKey of diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 4e130ea0..adc2c721 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -11,6 +11,7 @@ handle_info/2]). -export([start_link/1]). -export([set_prefetch_count/2, can_send/2, decrement_capacity/2]). +-export([unregister_queue/2]). -record(lim, {prefetch_count = 0, ch_pid, @@ -38,6 +39,11 @@ can_send(LimiterPid, QPid) -> % and hence can reduce the in-use-by-that queue capcity information decrement_capacity(LimiterPid, Magnitude) -> gen_server:cast(LimiterPid, {decrement_capacity, Magnitude}). + +% This is called to tell the limiter that the queue is probably dead and +% it should be forgotten about +unregister_queue(LimiterPid, QPid) -> + gen_server:cast(LimiterPid, {unregister_queue, QPid}). %--------------------------------------------------------------------------- % gen_server callbacks @@ -68,6 +74,11 @@ handle_cast({prefetch_count, PrefetchCount}, queues = sets:new(), in_use = 0}}; +% Removes the queue process from the set of monitored queues +handle_cast({unregister_queue, QPid}, State= #lim{queues = Queues}) -> + NewState = decrement_in_use(1, State), + {noreply, NewState#lim{queues = sets:del_element(QPid, Queues)}}; + % Default setter of the prefetch count handle_cast({prefetch_count, PrefetchCount}, State) -> {noreply, State#lim{prefetch_count = PrefetchCount}}; -- cgit v1.2.1 From ee627645fcf03b9d562dcb73f61987d265dd2869 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Sun, 7 Dec 2008 01:34:42 +0000 Subject: Added match for setting the global flag --- src/rabbit_channel.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 240ee3d3..55617abb 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -424,6 +424,10 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, end end; +handle_method(#'basic.qos'{global = true}, _, _State) -> + rabbit_misc:protocol_error(not_implemented, + "Global flag for basic.qos not implementented"); + handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{limiter = Limiter}) -> rabbit_limiter:set_prefetch_count(Limiter, PrefetchCount), -- cgit v1.2.1 From 9c8e90bde666981f5cc716abbeb7d55d4d10bdce Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Sun, 7 Dec 2008 01:40:09 +0000 Subject: Added catch for pre-fetch size --- src/rabbit_channel.erl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 55617abb..a8db5b3f 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -428,6 +428,11 @@ handle_method(#'basic.qos'{global = true}, _, _State) -> rabbit_misc:protocol_error(not_implemented, "Global flag for basic.qos not implementented"); +handle_method(#'basic.qos'{prefetch_size = Size}, + _, _State) when Size /= 0 -> + rabbit_misc:protocol_error(not_implemented, + "Pre-fetch size for basic.qos not implementented"); + handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{limiter = Limiter}) -> rabbit_limiter:set_prefetch_count(Limiter, PrefetchCount), -- cgit v1.2.1 From ff2368fc2e3606248709ef3309eaf51245edcad2 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Sun, 7 Dec 2008 02:01:59 +0000 Subject: Shutting dialyzer up --- src/rabbit_channel.erl | 7 ++++--- src/rabbit_limiter.erl | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a8db5b3f..b17a2b06 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -424,14 +424,15 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, end end; -handle_method(#'basic.qos'{global = true}, _, _State) -> +handle_method(#'basic.qos'{global = Flag = true}, _, _State) -> rabbit_misc:protocol_error(not_implemented, - "Global flag for basic.qos not implementented"); + "Global flag (~s) for basic.qos not implementented", [Flag]); handle_method(#'basic.qos'{prefetch_size = Size}, _, _State) when Size /= 0 -> rabbit_misc:protocol_error(not_implemented, - "Pre-fetch size for basic.qos not implementented"); + "Pre-fetch size (~s) for basic.qos not implementented", + [Size]); handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{limiter = Limiter}) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index adc2c721..8509eab8 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -2,9 +2,6 @@ %% bailed. -module(rabbit_limiter). - -% I'm starting out with a gen_server because of the synchronous query -% that the queue process makes -behaviour(gen_server). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, @@ -13,6 +10,19 @@ -export([set_prefetch_count/2, can_send/2, decrement_capacity/2]). -export([unregister_queue/2]). +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(set_prefetch_count/2 :: (pid(), non_neg_integer()) -> 'ok'). +-spec(can_send/2 :: (pid(), pid()) -> bool()). +-spec(decrement_capacity/2 :: (pid(), non_neg_integer()) -> 'ok'). +-spec(unregister_queue/2 :: (pid(), pid()) -> 'ok'). + +-endif. + +%%---------------------------------------------------------------------------- + -record(lim, {prefetch_count = 0, ch_pid, queues = sets:new(), -- cgit v1.2.1 From 26ef7ae0cf92b5f95f8cd0ed0ec5d6e0560fd352 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Dec 2008 14:10:10 +0000 Subject: add limiter to module list --- ebin/rabbit.app | 1 + 1 file changed, 1 insertion(+) diff --git a/ebin/rabbit.app b/ebin/rabbit.app index 93abd456..217bb27d 100644 --- a/ebin/rabbit.app +++ b/ebin/rabbit.app @@ -17,6 +17,7 @@ rabbit_framing, rabbit_framing_channel, rabbit_heartbeat, + rabbit_limiter, rabbit_load, rabbit_log, rabbit_misc, -- cgit v1.2.1 From 13f6e4555d9d919af78042a17695fd48c1ecfd1e Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Thu, 18 Dec 2008 17:26:56 +0000 Subject: Fix for multi ack bug --- src/rabbit_limiter.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index d91893b0..20f54359 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -131,7 +131,7 @@ handle_cast({decrement_capacity, Magnitude}, State = #lim{in_use = InUse}) -> if ShouldNotify -> notify_queues(State), - {noreply, State#lim{queues = sets:new(), in_use = InUse - 1}}; + {noreply, State#lim{queues = sets:new(), in_use = InUse - Magnitude}}; true -> {noreply, NewState} end. -- cgit v1.2.1 From ad32ba9a6e7f63beafdf997df473cf9b279c6209 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Thu, 18 Dec 2008 17:37:45 +0000 Subject: Got rid of superfluous PD read --- src/rabbit_amqqueue_process.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b2c619d3..d26b0bb4 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -180,7 +180,7 @@ deliver_immediately(Message, Delivered, RoundRobinTail} -> % Use Qos Limits if an ack is required % Query the limiter to find out if a limit has been breached - #cr{limiter_pid = LimiterPid} = ch_record(ChPid), + C = #cr{limiter_pid = LimiterPid} = ch_record(ChPid), case rabbit_limiter:can_send(LimiterPid, self()) of true -> really_deliver(AckRequired, ChPid, ConsumerTag, @@ -189,7 +189,6 @@ deliver_immediately(Message, Delivered, false -> % Have another go by cycling through the consumer % queue - C = ch_record(ChPid), store_ch_record(C#cr{is_limit_active = true}), NewConsumers = block_consumers(ChPid, RoundRobinTail), deliver_immediately(Message, Delivered, -- cgit v1.2.1 From 7498967e007fdf8693aad6dd636e57aa5bf6d531 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Thu, 18 Dec 2008 18:25:26 +0000 Subject: Put some monitors in --- src/rabbit_amqqueue_process.erl | 3 --- src/rabbit_limiter.erl | 43 +++++++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index d26b0bb4..702a8aee 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -102,9 +102,6 @@ init(Q) -> round_robin = queue:new()}, ?HIBERNATE_AFTER}. terminate(_Reason, State) -> - %% Inform all limiters that we're dying - [ rabbit_limiter:unregister_queue(LimiterPid, self()) - || #cr{limiter_pid = LimiterPid} <- all_ch_record()], %% FIXME: How do we cancel active subscriptions? QName = qname(State), lists:foreach(fun (Txn) -> ok = rollback_work(Txn, QName) end, diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 20f54359..6cc170f9 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -37,7 +37,6 @@ handle_info/2]). -export([start_link/1]). -export([set_prefetch_count/2, can_send/2, decrement_capacity/2]). --export([unregister_queue/2]). %%---------------------------------------------------------------------------- @@ -46,7 +45,6 @@ -spec(set_prefetch_count/2 :: (pid(), non_neg_integer()) -> 'ok'). -spec(can_send/2 :: (pid(), pid()) -> bool()). -spec(decrement_capacity/2 :: (pid(), non_neg_integer()) -> 'ok'). --spec(unregister_queue/2 :: (pid(), pid()) -> 'ok'). -endif. @@ -54,7 +52,7 @@ -record(lim, {prefetch_count = 0, ch_pid, - queues = sets:new(), + queues = dict:new(), in_use = 0}). %--------------------------------------------------------------------------- @@ -78,11 +76,6 @@ can_send(LimiterPid, QPid) -> % and hence can reduce the in-use-by-that queue capcity information decrement_capacity(LimiterPid, Magnitude) -> gen_server:cast(LimiterPid, {decrement_capacity, Magnitude}). - -% This is called to tell the limiter that the queue is probably dead and -% it should be forgotten about -unregister_queue(LimiterPid, QPid) -> - gen_server:cast(LimiterPid, {unregister_queue, QPid}). %--------------------------------------------------------------------------- % gen_server callbacks @@ -93,9 +86,8 @@ init([ChPid]) -> % This queuries the limiter to ask if it is possible to send a message without % breaching a limit for this queue process -handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse, - queues = Queues}) -> - NewState = State#lim{queues = sets:add_element(QPid, Queues)}, +handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> + NewState = monitor_queue(QPid, State), case limit_reached(NewState) of true -> {reply, false, NewState}; false -> @@ -109,14 +101,14 @@ handle_cast({prefetch_count, PrefetchCount}, State = #lim{prefetch_count = CurrentLimit}) when PrefetchCount > CurrentLimit -> notify_queues(State), - {noreply, State#lim{prefetch_count = PrefetchCount, - queues = sets:new(), + NewState = demonitor_all(State), + {noreply, NewState#lim{prefetch_count = PrefetchCount, in_use = 0}}; % Removes the queue process from the set of monitored queues -handle_cast({unregister_queue, QPid}, State= #lim{queues = Queues}) -> +handle_cast({unregister_queue, QPid}, State = #lim{}) -> NewState = decrement_in_use(1, State), - {noreply, NewState#lim{queues = sets:del_element(QPid, Queues)}}; + {noreply, demonitor_queue(QPid, NewState)}; % Default setter of the prefetch count handle_cast({prefetch_count, PrefetchCount}, State) -> @@ -131,7 +123,8 @@ handle_cast({decrement_capacity, Magnitude}, State = #lim{in_use = InUse}) -> if ShouldNotify -> notify_queues(State), - {noreply, State#lim{queues = sets:new(), in_use = InUse - Magnitude}}; + NextState = demonitor_all(State), + {noreply, NextState#lim{in_use = InUse - Magnitude}}; true -> {noreply, NewState} end. @@ -149,6 +142,22 @@ code_change(_, State, _) -> % Internal plumbing %--------------------------------------------------------------------------- +% Starts to monitor a particular queue +monitor_queue(QPid, State = #lim{queues = Queues}) -> + MonitorRef = erlang:monitor(process, QPid), + State#lim{queues = dict:store(QPid, MonitorRef, Queues)}. + +% Stops monitoring a particular queue +demonitor_queue(QPid, State = #lim{queues = Queues}) -> + MonitorRef = dict:fetch(QPid, Queues), + true = erlang:demonitor(MonitorRef), + State#lim{queues = dict:erase(QPid, Queues)}. + +% Stops monitoring all queues +demonitor_all(State = #lim{queues = Queues}) -> + dict:map(fun(_, Ref) -> true = erlang:demonitor(Ref) end, Queues), + State#lim{queues = dict:new()}. + % Reduces the in-use-count of the queue by a specific magnitude decrement_in_use(_, State = #lim{in_use = 0}) -> State#lim{in_use = 0}; @@ -158,7 +167,7 @@ decrement_in_use(Magnitude, State = #lim{in_use = InUse}) -> % Unblocks every queue that this limiter knows about notify_queues(#lim{ch_pid = ChPid, queues = Queues}) -> - sets:fold(fun(Q,_) -> rabbit_amqqueue:unblock(Q, ChPid) end, [], Queues). + dict:map(fun(Q, _) -> rabbit_amqqueue:unblock(Q, ChPid) end, Queues). % A prefetch limit of zero means unlimited limit_reached(#lim{prefetch_count = 0}) -> -- cgit v1.2.1 From 6c25cdcea5b0ab405d3ab160446784221533e802 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Thu, 18 Dec 2008 18:29:58 +0000 Subject: Don't double monitor anything --- src/rabbit_limiter.erl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 6cc170f9..0d938580 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -144,8 +144,11 @@ code_change(_, State, _) -> % Starts to monitor a particular queue monitor_queue(QPid, State = #lim{queues = Queues}) -> - MonitorRef = erlang:monitor(process, QPid), - State#lim{queues = dict:store(QPid, MonitorRef, Queues)}. + case dict:is_key(QPid, Queues) of + false -> MonitorRef = erlang:monitor(process, QPid), + State#lim{queues = dict:store(QPid, MonitorRef, Queues)}; + true -> State + end. % Stops monitoring a particular queue demonitor_queue(QPid, State = #lim{queues = Queues}) -> -- cgit v1.2.1 From 742fcd66dd6b4a56a9481ba17f4d052389cc8386 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Thu, 18 Dec 2008 18:46:05 +0000 Subject: Added handler for monitor notifications --- src/rabbit_limiter.erl | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 0d938580..9f23724e 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -95,20 +95,13 @@ handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> end. % When the new limit is larger than the existing limit, -% notify all queues and forget about queues with an in-use -% capcity of zero +% notify all queues and forget about all queues handle_cast({prefetch_count, PrefetchCount}, State = #lim{prefetch_count = CurrentLimit}) when PrefetchCount > CurrentLimit -> notify_queues(State), NewState = demonitor_all(State), - {noreply, NewState#lim{prefetch_count = PrefetchCount, - in_use = 0}}; - -% Removes the queue process from the set of monitored queues -handle_cast({unregister_queue, QPid}, State = #lim{}) -> - NewState = decrement_in_use(1, State), - {noreply, demonitor_queue(QPid, NewState)}; + {noreply, NewState#lim{prefetch_count = PrefetchCount, in_use = 0}}; % Default setter of the prefetch count handle_cast({prefetch_count, PrefetchCount}, State) -> @@ -129,8 +122,10 @@ handle_cast({decrement_capacity, Magnitude}, State = #lim{in_use = InUse}) -> {noreply, NewState} end. -handle_info(_, State) -> - {noreply, State}. +%% This is received when a queue dies +handle_info({'DOWN', _MonitorRef, _Type, QPid, _Info}, + State = #lim{queues = Queues}) -> + {noreply, State#lim{queues = dict:erase(QPid, Queues)}}. terminate(_, _) -> ok. @@ -150,12 +145,6 @@ monitor_queue(QPid, State = #lim{queues = Queues}) -> true -> State end. -% Stops monitoring a particular queue -demonitor_queue(QPid, State = #lim{queues = Queues}) -> - MonitorRef = dict:fetch(QPid, Queues), - true = erlang:demonitor(MonitorRef), - State#lim{queues = dict:erase(QPid, Queues)}. - % Stops monitoring all queues demonitor_all(State = #lim{queues = Queues}) -> dict:map(fun(_, Ref) -> true = erlang:demonitor(Ref) end, Queues), -- cgit v1.2.1 From 39478fac4b45ab2f710f68f66f5e656f903fda0f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Dec 2008 20:33:31 +0000 Subject: fix typo --- src/rabbit_limiter.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 9f23724e..b939b4bb 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -84,8 +84,8 @@ decrement_capacity(LimiterPid, Magnitude) -> init([ChPid]) -> {ok, #lim{ch_pid = ChPid} }. -% This queuries the limiter to ask if it is possible to send a message without -% breaching a limit for this queue process +% This queries the limiter to ask if it is possible to send a message +% without breaching a limit for this queue process handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> NewState = monitor_queue(QPid, State), case limit_reached(NewState) of -- cgit v1.2.1 From c2c4195b7d9c1261e0e7a775ae4e9e5782d85f39 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Dec 2008 20:58:51 +0000 Subject: lots of tweaks and fixes - remove superfluous (or wrong) comments - notification and demonitoring always go together - don't change the in_use count when limit is altered - fix the limit_reached condition --- src/rabbit_limiter.erl | 68 +++++++++++++++----------------------------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index b939b4bb..e5e54563 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -84,45 +84,26 @@ decrement_capacity(LimiterPid, Magnitude) -> init([ChPid]) -> {ok, #lim{ch_pid = ChPid} }. -% This queries the limiter to ask if it is possible to send a message -% without breaching a limit for this queue process handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> - NewState = monitor_queue(QPid, State), - case limit_reached(NewState) of - true -> {reply, false, NewState}; - false -> - {reply, true, NewState#lim{in_use = InUse + 1}} + case limit_reached(State) of + true -> {reply, false, remember_queue(QPid, State)}; + false -> {reply, true, State#lim{in_use = InUse + 1}} end. -% When the new limit is larger than the existing limit, -% notify all queues and forget about all queues handle_cast({prefetch_count, PrefetchCount}, - State = #lim{prefetch_count = CurrentLimit}) - when PrefetchCount > CurrentLimit -> - notify_queues(State), - NewState = demonitor_all(State), - {noreply, NewState#lim{prefetch_count = PrefetchCount, in_use = 0}}; - -% Default setter of the prefetch count -handle_cast({prefetch_count, PrefetchCount}, State) -> - {noreply, State#lim{prefetch_count = PrefetchCount}}; - -% This is an asynchronous ack from a queue that it has received an ack from -% a queue. This allows the limiter to update the the in-use-by-that queue -% capacity infromation. -handle_cast({decrement_capacity, Magnitude}, State = #lim{in_use = InUse}) -> + State = #lim{prefetch_count = CurrentLimit}) -> + NewState = State#lim{prefetch_count = PrefetchCount}, + {noreply, if PrefetchCount > CurrentLimit -> forget_queues(NewState); + true -> NewState + end}; + +handle_cast({decrement_capacity, Magnitude}, State) -> NewState = decrement_in_use(Magnitude, State), ShouldNotify = limit_reached(State) and not(limit_reached(NewState)), - if - ShouldNotify -> - notify_queues(State), - NextState = demonitor_all(State), - {noreply, NextState#lim{in_use = InUse - Magnitude}}; - true -> - {noreply, NewState} - end. + {noreply, if ShouldNotify -> forget_queues(NewState); + true -> NewState + end}. -%% This is received when a queue dies handle_info({'DOWN', _MonitorRef, _Type, QPid, _Info}, State = #lim{queues = Queues}) -> {noreply, State#lim{queues = dict:erase(QPid, Queues)}}. @@ -137,35 +118,26 @@ code_change(_, State, _) -> % Internal plumbing %--------------------------------------------------------------------------- -% Starts to monitor a particular queue -monitor_queue(QPid, State = #lim{queues = Queues}) -> +remember_queue(QPid, State = #lim{queues = Queues}) -> case dict:is_key(QPid, Queues) of false -> MonitorRef = erlang:monitor(process, QPid), State#lim{queues = dict:store(QPid, MonitorRef, Queues)}; true -> State end. -% Stops monitoring all queues -demonitor_all(State = #lim{queues = Queues}) -> - dict:map(fun(_, Ref) -> true = erlang:demonitor(Ref) end, Queues), +forget_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> + ok = dict:fold(fun(Q, Ref, ok) -> + true = erlang:demonitor(Ref), + rabbit_amqqueue:unblock(Q, ChPid) + end, ok, Queues), State#lim{queues = dict:new()}. -% Reduces the in-use-count of the queue by a specific magnitude decrement_in_use(_, State = #lim{in_use = 0}) -> State#lim{in_use = 0}; - decrement_in_use(Magnitude, State = #lim{in_use = InUse}) -> State#lim{in_use = InUse - Magnitude}. -% Unblocks every queue that this limiter knows about -notify_queues(#lim{ch_pid = ChPid, queues = Queues}) -> - dict:map(fun(Q, _) -> rabbit_amqqueue:unblock(Q, ChPid) end, Queues). - -% A prefetch limit of zero means unlimited limit_reached(#lim{prefetch_count = 0}) -> false; - -% Works out whether the limit is breached for the current limiter state limit_reached(#lim{prefetch_count = Limit, in_use = InUse}) -> - InUse == Limit. - + InUse >= Limit. -- cgit v1.2.1 From fb718f3812c48d6021480d9ca578c9a47005e2eb Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Dec 2008 21:05:20 +0000 Subject: tidy comments --- src/rabbit_limiter.erl | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index e5e54563..e225e827 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -55,11 +55,10 @@ queues = dict:new(), in_use = 0}). -%--------------------------------------------------------------------------- -% API -%--------------------------------------------------------------------------- +%%---------------------------------------------------------------------------- +%% API +%-%--------------------------------------------------------------------------- -% Kicks this pig start_link(ChPid) -> {ok, Pid} = gen_server:start_link(?MODULE, [ChPid], []), Pid. @@ -67,19 +66,19 @@ start_link(ChPid) -> set_prefetch_count(LimiterPid, PrefetchCount) -> gen_server:cast(LimiterPid, {prefetch_count, PrefetchCount}). -% Queries the limiter to ask whether the queue can deliver a message -% without breaching a limit +%% Ask the limiter whether the queue can deliver a message without +%% breaching a limit can_send(LimiterPid, QPid) -> gen_server:call(LimiterPid, {can_send, QPid}). -% Lets the limiter know that a queue has received an ack from a consumer -% and hence can reduce the in-use-by-that queue capcity information +%% Let the limiter know that the channel has received some acks from a +%% consumer decrement_capacity(LimiterPid, Magnitude) -> gen_server:cast(LimiterPid, {decrement_capacity, Magnitude}). -%--------------------------------------------------------------------------- -% gen_server callbacks -%--------------------------------------------------------------------------- +%%---------------------------------------------------------------------------- +%% gen_server callbacks +%%---------------------------------------------------------------------------- init([ChPid]) -> {ok, #lim{ch_pid = ChPid} }. -- cgit v1.2.1 From 31b713c6de94dae6329fbbf12aa1f3d4af0f2ba4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Dec 2008 21:11:10 +0000 Subject: tidy some more comments --- src/rabbit_limiter.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index e225e827..cd8f7734 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -113,9 +113,9 @@ terminate(_, _) -> code_change(_, State, _) -> State. -%--------------------------------------------------------------------------- -% Internal plumbing -%--------------------------------------------------------------------------- +%%---------------------------------------------------------------------------- +%% Internal plumbing +%%---------------------------------------------------------------------------- remember_queue(QPid, State = #lim{queues = Queues}) -> case dict:is_key(QPid, Queues) of -- cgit v1.2.1 From 13cc87c8935cdabf65fded6015f02b5991f37204 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Dec 2008 21:28:57 +0000 Subject: refactoring --- src/rabbit_limiter.erl | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index cd8f7734..6388c360 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -89,19 +89,14 @@ handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> false -> {reply, true, State#lim{in_use = InUse + 1}} end. -handle_cast({prefetch_count, PrefetchCount}, - State = #lim{prefetch_count = CurrentLimit}) -> - NewState = State#lim{prefetch_count = PrefetchCount}, - {noreply, if PrefetchCount > CurrentLimit -> forget_queues(NewState); - true -> NewState - end}; - -handle_cast({decrement_capacity, Magnitude}, State) -> - NewState = decrement_in_use(Magnitude, State), - ShouldNotify = limit_reached(State) and not(limit_reached(NewState)), - {noreply, if ShouldNotify -> forget_queues(NewState); - true -> NewState - end}. +handle_cast({prefetch_count, PrefetchCount}, State) -> + {noreply, maybe_notify(State, State#lim{prefetch_count = PrefetchCount})}; + +handle_cast({decrement_capacity, Magnitude}, State = #lim{in_use = InUse}) -> + NewInUse = if InUse == 0 -> 0; + true -> InUse - Magnitude + end, + {noreply, maybe_notify(State, State#lim{in_use = NewInUse})}. handle_info({'DOWN', _MonitorRef, _Type, QPid, _Info}, State = #lim{queues = Queues}) -> @@ -117,6 +112,12 @@ code_change(_, State, _) -> %% Internal plumbing %%---------------------------------------------------------------------------- +maybe_notify(OldState, NewState) -> + case limit_reached(OldState) and not(limit_reached(NewState)) of + true -> forget_queues(NewState); + false -> NewState + end. + remember_queue(QPid, State = #lim{queues = Queues}) -> case dict:is_key(QPid, Queues) of false -> MonitorRef = erlang:monitor(process, QPid), @@ -131,11 +132,6 @@ forget_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> end, ok, Queues), State#lim{queues = dict:new()}. -decrement_in_use(_, State = #lim{in_use = 0}) -> - State#lim{in_use = 0}; -decrement_in_use(Magnitude, State = #lim{in_use = InUse}) -> - State#lim{in_use = InUse - Magnitude}. - limit_reached(#lim{prefetch_count = 0}) -> false; limit_reached(#lim{prefetch_count = Limit, in_use = InUse}) -> -- cgit v1.2.1 From 6fe2881e4013895ef6b2f43d6b118a221f023a98 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Dec 2008 21:34:13 +0000 Subject: oops --- src/rabbit_limiter.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 6388c360..6e9b10a2 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -57,7 +57,7 @@ %%---------------------------------------------------------------------------- %% API -%-%--------------------------------------------------------------------------- +%%---------------------------------------------------------------------------- start_link(ChPid) -> {ok, Pid} = gen_server:start_link(?MODULE, [ChPid], []), -- cgit v1.2.1 From bb75417a9173744f32d5a312293cf47555d71c85 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Dec 2008 21:43:36 +0000 Subject: minor simplifications --- src/rabbit_limiter.erl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 6e9b10a2..257950b3 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -113,11 +113,14 @@ code_change(_, State, _) -> %%---------------------------------------------------------------------------- maybe_notify(OldState, NewState) -> - case limit_reached(OldState) and not(limit_reached(NewState)) of + case limit_reached(OldState) andalso not(limit_reached(NewState)) of true -> forget_queues(NewState); false -> NewState end. +limit_reached(#lim{prefetch_count = Limit, in_use = InUse}) -> + Limit =/= 0 andalso InUse >= Limit. + remember_queue(QPid, State = #lim{queues = Queues}) -> case dict:is_key(QPid, Queues) of false -> MonitorRef = erlang:monitor(process, QPid), @@ -131,8 +134,3 @@ forget_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> rabbit_amqqueue:unblock(Q, ChPid) end, ok, Queues), State#lim{queues = dict:new()}. - -limit_reached(#lim{prefetch_count = 0}) -> - false; -limit_reached(#lim{prefetch_count = Limit, in_use = InUse}) -> - InUse >= Limit. -- cgit v1.2.1 From 69d95872d3e87d0fdda7ed408d4bfdae4d37d9a9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 19 Dec 2008 00:10:31 +0000 Subject: handle transactional acks also: - simplify and optimise non-basic-get ack counting - don't talk to the limiter when there is nothing to tell, i.e. the non-basic-get ack count is zero --- src/rabbit_channel.erl | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index b77d26a0..c586df02 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -277,7 +277,6 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, handle_method(#'basic.ack'{delivery_tag = DeliveryTag, multiple = Multiple}, _, State = #ch{transaction_id = TxnKey, - limiter = Limiter, next_tag = NextDeliveryTag, unacked_message_q = UAMQ}) -> if DeliveryTag >= NextDeliveryTag -> @@ -286,24 +285,10 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, true -> ok end, {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), - % CC the limiter on the number of acks that have been received - % but don't include any acks from a basic.get bottom half - % (hence the differentiation between tags set to none and other tags) - % TODO - this is quite crude and is probably more expensive than it should - % be - according to the OTP documentation, len/1 runs in O(n), probably - % not so cool for a queuing system - NotBasicGet = queue:filter( - fun({_CurrentDeliveryTag, ConsumerTag, _Msg}) -> - case ConsumerTag of - none -> false; - _ -> true - end - end, Acked), - % TODO Optimization: Probably don't need to send this if len = 0 - rabbit_limiter:decrement_capacity(Limiter, queue:len(NotBasicGet)), Participants = ack(State#ch.proxy_pid, TxnKey, Acked), {noreply, case TxnKey of - none -> State#ch{unacked_message_q = Remaining}; + none -> ok = notify_limiter(State#ch.limiter, Acked), + State#ch{unacked_message_q = Remaining}; _ -> NewUAQ = queue:join(State#ch.uncommitted_ack_q, Acked), add_tx_participants( @@ -789,7 +774,9 @@ internal_commit(State = #ch{transaction_id = TxnKey, tx_participants = Participants}) -> case rabbit_amqqueue:commit_all(sets:to_list(Participants), TxnKey) of - ok -> new_tx(State); + ok -> ok = notify_limiter(State#ch.limiter, + State#ch.uncommitted_ack_q), + new_tx(State); {error, Errors} -> rabbit_misc:protocol_error( internal_error, "commit failed: ~w", [Errors]) end. @@ -840,6 +827,17 @@ notify_queues(#ch{proxy_pid = ProxyPid, consumer_mapping = Consumers}) -> end], ProxyPid). +%% tell the limiter about the number of acks that have been received +%% for messages delivered to subscribed consumers, rather than those +%% for messages sent in a response to a basic.get +notify_limiter(Limiter, Acked) -> + case lists:foldl(fun ({_, none, _}, Acc) -> Acc; + ({_, _, _}, Acc) -> Acc + 1 + end, 0, queue:to_list(Acked)) of + 0 -> ok; + Count -> rabbit_limiter:decrement_capacity(Limiter, Count) + end. + is_message_persistent(#content{properties = #'P_basic'{ delivery_mode = Mode}}) -> case Mode of -- cgit v1.2.1 From 2bfbd6d7e52eff0c5fe65dcd467811904dc61107 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 19 Dec 2008 01:36:00 +0000 Subject: fix bug: make sure consumers blocked due to limit are dropped from State --- src/rabbit_amqqueue_process.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 702a8aee..b03887b8 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -199,7 +199,7 @@ deliver_immediately(Message, Delivered, Delivered, Message, NextId, QName, QEntry, RoundRobinTail, State); {empty, _} -> - not_offered + {not_offered, State} end. % TODO The arity of this function seems a bit large :-( @@ -231,8 +231,8 @@ attempt_delivery(none, Message, State) -> persist_message(none, qname(State), Message), persist_delivery(qname(State), Message, false), {true, State1}; - not_offered -> - {false, State} + {not_offered, State1} -> + {false, State1} end; attempt_delivery(Txn, Message, State) -> persist_message(Txn, qname(State), Message), @@ -367,8 +367,8 @@ run_poke_burst(MessageBuffer, State) -> {offered, false, NewState} -> persist_auto_ack(qname(State), Message), run_poke_burst(BufferTail, NewState); - not_offered -> - State#q{message_buffer = MessageBuffer} + {not_offered, NewState} -> + NewState#q{message_buffer = MessageBuffer} end; {empty, _} -> State#q{message_buffer = MessageBuffer} -- cgit v1.2.1 From eab60583f79a3f689be30403b7f8a56aad99bd8b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 19 Dec 2008 16:53:51 +0000 Subject: saner state transition handling and assorted bug fixes --- src/rabbit_amqqueue_process.erl | 91 ++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b03887b8..53b569b4 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -66,7 +66,6 @@ monitor_ref, unacked_messages, is_limit_active, - is_overload_protection_active, unsent_message_count}). -define(INFO_KEYS, @@ -133,7 +132,7 @@ ch_record(ChPid) -> ch_pid = ChPid, monitor_ref = MonitorRef, unacked_messages = dict:new(), - is_overload_protection_active = false, + is_limit_active = false, unsent_message_count = 0}, put(Key, C), C; @@ -146,24 +145,16 @@ store_ch_record(C = #cr{ch_pid = ChPid}) -> all_ch_record() -> [C || {{ch, _}, C} <- get()]. -update_store_and_maybe_block_ch( - C = #cr{is_overload_protection_active = Overloaded, - is_limit_active = Limited, - unsent_message_count = Count}) -> - {Result, NewOverloaded, NewLimited} = - if - not(Overloaded) and (Count > ?UNSENT_MESSAGE_LIMIT) -> - {block_ch, true, Limited}; - Overloaded and (Count == 0) -> - {unblock_ch, false, Limited}; - Limited and (Count < ?UNSENT_MESSAGE_LIMIT) -> - {unblock_ch, Overloaded, false}; - true -> - {ok, Overloaded, Limited} - end, - store_ch_record(C#cr{is_overload_protection_active = NewOverloaded, - is_limit_active = NewLimited}), - Result. +is_ch_blocked(#cr{unsent_message_count = Count, is_limit_active = Limited}) -> + Limited orelse Count > ?UNSENT_MESSAGE_LIMIT. + +ch_record_state_transition(OldCR, NewCR) -> + BlockedOld = is_ch_blocked(OldCR), + BlockedNew = is_ch_blocked(NewCR), + if BlockedOld andalso not(BlockedNew) -> unblock; + BlockedNew andalso not(BlockedOld) -> block; + true -> ok + end. deliver_immediately(Message, Delivered, State = #q{q = #amqqueue{name = QName}, @@ -213,12 +204,13 @@ really_deliver(AckRequired, ChPid, ConsumerTag, Delivered, Message, NextId, true -> dict:store(NextId, Message, UAM); false -> UAM end, + NewC = C#cr{unsent_message_count = Count + 1, + unacked_messages = NewUAM}, + store_ch_record(NewC), NewConsumers = - case update_store_and_maybe_block_ch( - C#cr{unsent_message_count = Count + 1, - unacked_messages = NewUAM}) of - ok -> queue:in(QEntry, RoundRobinTail); - block_ch -> block_consumers(ChPid, RoundRobinTail) + case ch_record_state_transition(C, NewC) of + ok -> queue:in(QEntry, RoundRobinTail); + block -> block_consumers(ChPid, RoundRobinTail) end, {offered, AckRequired, State#q{round_robin = NewConsumers, next_msg_id = NextId +1}}. @@ -270,16 +262,22 @@ block_consumer(ChPid, ConsumerTag, RoundRobin) -> (CP /= ChPid) or (CT /= ConsumerTag) end, queue:to_list(RoundRobin))). -possibly_unblock(C = #cr{consumers = Consumers, ch_pid = ChPid}, - State = #q{round_robin = RoundRobin}) -> - case update_store_and_maybe_block_ch(C) of - ok -> +possibly_unblock(State, ChPid, Update) -> + case lookup_ch(ChPid) of + not_found -> State; - unblock_ch -> - run_poke_burst(State#q{round_robin = - unblock_consumers(ChPid, Consumers, RoundRobin)}) + C -> + NewC = Update(C), + store_ch_record(NewC), + case ch_record_state_transition(C, NewC) of + ok -> State; + unblock -> NewRR = unblock_consumers(ChPid, + NewC#cr.consumers, + State#q.round_robin), + run_poke_burst(State#q{round_robin = NewRR}) + end end. - + check_auto_delete(State = #q{q = #amqqueue{auto_delete = false}}) -> {continue, State}; check_auto_delete(State = #q{has_had_consumers = false}) -> @@ -764,27 +762,16 @@ handle_cast({requeue, MsgIds, ChPid}, State) -> end; handle_cast({unblock, ChPid}, State) -> - % TODO Refactor the code duplication - % between this an the notify_sent cast handler - case lookup_ch(ChPid) of - not_found -> - noreply(State); - C = #cr{is_limit_active = true} -> - noreply(possibly_unblock(C, State)); - C -> - rabbit_log:warning("Ignoring unblock for an active ch: ~p~n", - [C]), - noreply(State) - end; + noreply( + possibly_unblock(State, ChPid, + fun (C) -> C#cr{is_limit_active = false} end)); handle_cast({notify_sent, ChPid}, State) -> - case lookup_ch(ChPid) of - not_found -> noreply(State); - T = #cr{unsent_message_count =Count} -> - noreply(possibly_unblock( - T#cr{unsent_message_count = Count - 1}, - State)) - end. + noreply( + possibly_unblock(State, ChPid, + fun (C = #cr{unsent_message_count = Count}) -> + C#cr{unsent_message_count = Count - 1} + end)). handle_info({'DOWN', MonitorRef, process, DownPid, _Reason}, State = #q{owner = {DownPid, MonitorRef}}) -> -- cgit v1.2.1 From 8154121e282bab4951a11daacb0428ad76c2db3e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 10:26:23 +0000 Subject: ensure fairness --- src/rabbit_limiter.erl | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 257950b3..7ca9772b 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -129,8 +129,17 @@ remember_queue(QPid, State = #lim{queues = Queues}) -> end. forget_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> - ok = dict:fold(fun(Q, Ref, ok) -> - true = erlang:demonitor(Ref), - rabbit_amqqueue:unblock(Q, ChPid) - end, ok, Queues), + QList = dict:to_list(Queues), + case length(QList) of + 0 -> ok; + L -> + %% We randomly vary the position in which each queue + %% appears in the list, thus ensuring that each queue has + %% an equal chance of being notified first. + {L1, L2} = lists:split(random:uniform(L), QList), + [begin + true = erlang:demonitor(Ref), + ok = rabbit_amqqueue:unblock(Q, ChPid) + end || {Q, Ref} <- L2 ++ L1] + end, State#lim{queues = dict:new()}. -- cgit v1.2.1 From 05fa30a345228631d8c7002252ad133ef38e0e9e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 15:02:07 +0000 Subject: cosmetic --- src/rabbit_channel.erl | 4 ++-- src/rabbit_limiter.erl | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 4b0cf6d5..36888f33 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -430,7 +430,7 @@ handle_method(#'basic.qos'{prefetch_size = Size}, handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{limiter = Limiter}) -> - rabbit_limiter:set_prefetch_count(Limiter, PrefetchCount), + ok = rabbit_limiter:limit(Limiter, PrefetchCount), {reply, #'basic.qos_ok'{}, State}; handle_method(#'basic.recover'{requeue = true}, @@ -838,7 +838,7 @@ notify_limiter(Limiter, Acked) -> ({_, _, _}, Acc) -> Acc + 1 end, 0, queue:to_list(Acked)) of 0 -> ok; - Count -> rabbit_limiter:decrement_capacity(Limiter, Count) + Count -> rabbit_limiter:ack(Limiter, Count) end. is_message_persistent(#content{properties = #'P_basic'{ diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 7ca9772b..12632625 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -36,15 +36,15 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -export([start_link/1]). --export([set_prefetch_count/2, can_send/2, decrement_capacity/2]). +-export([limit/2, can_send/2, ack/2]). %%---------------------------------------------------------------------------- -ifdef(use_specs). --spec(set_prefetch_count/2 :: (pid(), non_neg_integer()) -> 'ok'). +-spec(limit/2 :: (pid(), non_neg_integer()) -> 'ok'). -spec(can_send/2 :: (pid(), pid()) -> bool()). --spec(decrement_capacity/2 :: (pid(), non_neg_integer()) -> 'ok'). +-spec(ack/2 :: (pid(), non_neg_integer()) -> 'ok'). -endif. @@ -63,8 +63,8 @@ start_link(ChPid) -> {ok, Pid} = gen_server:start_link(?MODULE, [ChPid], []), Pid. -set_prefetch_count(LimiterPid, PrefetchCount) -> - gen_server:cast(LimiterPid, {prefetch_count, PrefetchCount}). +limit(LimiterPid, PrefetchCount) -> + gen_server:cast(LimiterPid, {limit, PrefetchCount}). %% Ask the limiter whether the queue can deliver a message without %% breaching a limit @@ -73,8 +73,8 @@ can_send(LimiterPid, QPid) -> %% Let the limiter know that the channel has received some acks from a %% consumer -decrement_capacity(LimiterPid, Magnitude) -> - gen_server:cast(LimiterPid, {decrement_capacity, Magnitude}). +ack(LimiterPid, Count) -> + gen_server:cast(LimiterPid, {ack, Count}). %%---------------------------------------------------------------------------- %% gen_server callbacks @@ -89,12 +89,12 @@ handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> false -> {reply, true, State#lim{in_use = InUse + 1}} end. -handle_cast({prefetch_count, PrefetchCount}, State) -> +handle_cast({limit, PrefetchCount}, State) -> {noreply, maybe_notify(State, State#lim{prefetch_count = PrefetchCount})}; -handle_cast({decrement_capacity, Magnitude}, State = #lim{in_use = InUse}) -> +handle_cast({ack, Count}, State = #lim{in_use = InUse}) -> NewInUse = if InUse == 0 -> 0; - true -> InUse - Magnitude + true -> InUse - Count end, {noreply, maybe_notify(State, State#lim{in_use = NewInUse})}. -- cgit v1.2.1 From 62874e89b1cd969e12ffc288fb43f46f1b72e05d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 16:00:34 +0000 Subject: make limiter keep track of all queues with subscriptions This is more efficient since it avoids the repeated (de)monitoring and updates to the limiter state. --- src/rabbit_amqqueue_process.erl | 12 ++++++++- src/rabbit_limiter.erl | 58 +++++++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 53b569b4..c01f08df 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -634,6 +634,11 @@ handle_call({basic_consume, NoAck, ReaderPid, ChPid, LimiterPid, C1 = C#cr{consumers = [Consumer | Consumers], limiter_pid = LimiterPid}, store_ch_record(C1), + if Consumers == [] -> + ok = rabbit_limiter:register(LimiterPid, self()); + true -> + ok + end, State1 = State#q{has_had_consumers = true, exclusive_consumer = if @@ -653,12 +658,17 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, _From, not_found -> ok = maybe_send_reply(ChPid, OkMsg), reply(ok, State); - C = #cr{consumers = Consumers} -> + C = #cr{consumers = Consumers, limiter_pid = LimiterPid} -> NewConsumers = lists:filter (fun (#consumer{tag = CT}) -> CT /= ConsumerTag end, Consumers), C1 = C#cr{consumers = NewConsumers}, store_ch_record(C1), + if NewConsumers == [] -> + ok = rabbit_limiter:unregister(LimiterPid, self()); + true -> + ok + end, ok = maybe_send_reply(ChPid, OkMsg), case check_auto_delete( State#q{exclusive_consumer = cancel_holder(ChPid, diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 12632625..f1a45415 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -36,7 +36,7 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -export([start_link/1]). --export([limit/2, can_send/2, ack/2]). +-export([limit/2, can_send/2, ack/2, register/2, unregister/2]). %%---------------------------------------------------------------------------- @@ -45,6 +45,8 @@ -spec(limit/2 :: (pid(), non_neg_integer()) -> 'ok'). -spec(can_send/2 :: (pid(), pid()) -> bool()). -spec(ack/2 :: (pid(), non_neg_integer()) -> 'ok'). +-spec(register/2 :: (pid(), pid()) -> 'ok'). +-spec(unregister/2 :: (pid(), pid()) -> 'ok'). -endif. @@ -76,6 +78,12 @@ can_send(LimiterPid, QPid) -> ack(LimiterPid, Count) -> gen_server:cast(LimiterPid, {ack, Count}). +register(LimiterPid, QPid) -> + gen_server:cast(LimiterPid, {register, QPid}). + +unregister(LimiterPid, QPid) -> + gen_server:cast(LimiterPid, {unregister, QPid}). + %%---------------------------------------------------------------------------- %% gen_server callbacks %%---------------------------------------------------------------------------- @@ -83,9 +91,13 @@ ack(LimiterPid, Count) -> init([ChPid]) -> {ok, #lim{ch_pid = ChPid} }. -handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> +handle_call({can_send, _QPid}, _From, State = #lim{in_use = InUse}) -> case limit_reached(State) of - true -> {reply, false, remember_queue(QPid, State)}; + true -> + %% TODO: keep track of the fact that the specific QPid has + %% had a can_send request rejected, so we can restrict the + %% notifications to these QPids only. + {reply, false, State}; false -> {reply, true, State#lim{in_use = InUse + 1}} end. @@ -96,11 +108,16 @@ handle_cast({ack, Count}, State = #lim{in_use = InUse}) -> NewInUse = if InUse == 0 -> 0; true -> InUse - Count end, - {noreply, maybe_notify(State, State#lim{in_use = NewInUse})}. + {noreply, maybe_notify(State, State#lim{in_use = NewInUse})}; + +handle_cast({register, QPid}, State) -> + {noreply, remember_queue(QPid, State)}; -handle_info({'DOWN', _MonitorRef, _Type, QPid, _Info}, - State = #lim{queues = Queues}) -> - {noreply, State#lim{queues = dict:erase(QPid, Queues)}}. +handle_cast({unregister, QPid}, State) -> + {noreply, forget_queue(QPid, State)}. + +handle_info({'DOWN', _MonitorRef, _Type, QPid, _Info}, State) -> + {noreply, forget_queue(QPid, State)}. terminate(_, _) -> ok. @@ -114,9 +131,10 @@ code_change(_, State, _) -> maybe_notify(OldState, NewState) -> case limit_reached(OldState) andalso not(limit_reached(NewState)) of - true -> forget_queues(NewState); - false -> NewState - end. + true -> ok = notify_queues(NewState#lim.ch_pid, NewState#lim.queues); + false -> ok + end, + NewState. limit_reached(#lim{prefetch_count = Limit, in_use = InUse}) -> Limit =/= 0 andalso InUse >= Limit. @@ -128,7 +146,16 @@ remember_queue(QPid, State = #lim{queues = Queues}) -> true -> State end. -forget_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> +forget_queue(QPid, State = #lim{ch_pid = ChPid, queues = Queues}) -> + case dict:find(QPid, Queues) of + {ok, MonitorRef} -> + true = erlang:demonitor(MonitorRef), + ok = rabbit_amqqueue:unblock(QPid, ChPid), + State#lim{queues = dict:erase(QPid, Queues)}; + error -> State + end. + +notify_queues(ChPid, Queues) -> QList = dict:to_list(Queues), case length(QList) of 0 -> ok; @@ -137,9 +164,6 @@ forget_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> %% appears in the list, thus ensuring that each queue has %% an equal chance of being notified first. {L1, L2} = lists:split(random:uniform(L), QList), - [begin - true = erlang:demonitor(Ref), - ok = rabbit_amqqueue:unblock(Q, ChPid) - end || {Q, Ref} <- L2 ++ L1] - end, - State#lim{queues = dict:new()}. + [ok = rabbit_amqqueue:unblock(Q, ChPid) || {Q, _} <- L2 ++ L1], + ok + end. -- cgit v1.2.1 From ef5e395acb15c1ffd2ecd5b82cc3ee7208890ea5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 16:28:22 +0000 Subject: create limiter lazily This makes an 'unlimited' channel as efficient as it used to be --- src/rabbit_channel.erl | 25 ++++++++++++++++++++----- src/rabbit_limiter.erl | 2 ++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index af1923a7..001fa4af 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -108,8 +108,7 @@ init(ProxyPid, [ReaderPid, WriterPid, Username, VHost]) -> username = Username, virtual_host = VHost, most_recently_declared_queue = <<>>, - % TODO See point 3.1.1 of the design - start the limiter lazily - limiter = rabbit_limiter:start_link(ProxyPid), + limiter = undefined, consumer_mapping = dict:new()}. handle_message({method, Method, Content}, State) -> @@ -430,11 +429,25 @@ handle_method(#'basic.qos'{prefetch_size = Size}, "Pre-fetch size (~s) for basic.qos not implementented", [Size]); -handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, - _, State = #ch{limiter = Limiter}) -> - ok = rabbit_limiter:limit(Limiter, PrefetchCount), +handle_method(#'basic.qos'{prefetch_count = 0}, + _, State = #ch{ limiter = undefined }) -> {reply, #'basic.qos_ok'{}, State}; +handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, + _, State = #ch{ limiter = Limiter, + proxy_pid = ProxyPid }) -> + %% TODO: terminate limiter when transitioning to 'unlimited' + NewLimiter = case Limiter of + undefined -> + %% TODO: tell queues with subscribers about + %% the limiter + rabbit_limiter:start_link(ProxyPid); + Pid -> + Pid + end, + ok = rabbit_limiter:limit(NewLimiter, PrefetchCount), + {reply, #'basic.qos_ok'{}, State#ch{limiter = NewLimiter}}; + handle_method(#'basic.recover'{requeue = true}, _, State = #ch{ transaction_id = none, proxy_pid = ProxyPid, @@ -835,6 +848,8 @@ notify_queues(#ch{proxy_pid = ProxyPid, consumer_mapping = Consumers}) -> %% tell the limiter about the number of acks that have been received %% for messages delivered to subscribed consumers, rather than those %% for messages sent in a response to a basic.get +notify_limiter(undefined, _Acked) -> + ok; notify_limiter(Limiter, Acked) -> case lists:foldl(fun ({_, none, _}, Acc) -> Acc; ({_, _, _}, Acc) -> Acc + 1 diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index f1a45415..824de072 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -70,6 +70,8 @@ limit(LimiterPid, PrefetchCount) -> %% Ask the limiter whether the queue can deliver a message without %% breaching a limit +can_send(undefined, _QPid) -> + true; can_send(LimiterPid, QPid) -> gen_server:call(LimiterPid, {can_send, QPid}). -- cgit v1.2.1 From cd234a9d8c95c5ef501554935d879b020a9678fa Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 20:47:16 +0000 Subject: deal with limiting after consumer subscription --- src/rabbit_amqqueue.erl | 9 ++++++++- src/rabbit_amqqueue_process.erl | 15 ++++++++++++++- src/rabbit_channel.erl | 38 +++++++++++++++++++++----------------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 24ded98c..a345f5ab 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -40,7 +40,7 @@ -export([basic_get/3, basic_consume/8, basic_cancel/4]). -export([notify_sent/2]). -export([unblock/2]). --export([commit_all/2, rollback_all/2, notify_down_all/2]). +-export([commit_all/2, rollback_all/2, notify_down_all/2, limit_all/3]). -export([on_node_down/1]). -import(mnesia). @@ -92,6 +92,7 @@ -spec(commit_all/2 :: ([pid()], txn()) -> ok_or_errors()). -spec(rollback_all/2 :: ([pid()], txn()) -> ok_or_errors()). -spec(notify_down_all/2 :: ([pid()], pid()) -> ok_or_errors()). +-spec(limit_all/3 :: ([pid()], pid(), pid()) -> ok_or_errors()). -spec(claim_queue/2 :: (amqqueue(), pid()) -> 'ok' | 'locked'). -spec(basic_get/3 :: (amqqueue(), pid(), bool()) -> {'ok', non_neg_integer(), msg()} | 'empty'). @@ -261,6 +262,12 @@ notify_down_all(QPids, ChPid) -> fun (QPid) -> gen_server:call(QPid, {notify_down, ChPid}, Timeout) end, QPids). +limit_all(QPids, ChPid, LimiterPid) -> + safe_pmap_ok( + fun (_) -> ok end, + fun (QPid) -> gen_server:cast(QPid, {limit, ChPid, LimiterPid}) end, + QPids). + claim_queue(#amqqueue{pid = QPid}, ReaderPid) -> gen_server:call(QPid, {claim_queue, ReaderPid}). diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c01f08df..c6bb0502 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -781,7 +781,20 @@ handle_cast({notify_sent, ChPid}, State) -> possibly_unblock(State, ChPid, fun (C = #cr{unsent_message_count = Count}) -> C#cr{unsent_message_count = Count - 1} - end)). + end)); + +handle_cast({limit, ChPid, LimiterPid}, State) -> + case lookup_ch(ChPid) of + not_found -> + ok; + C = #cr{consumers = Consumers} -> + if Consumers =/= [] -> + ok = rabbit_limiter:register(LimiterPid, self()); + true -> ok + end, + store_ch_record(C#cr{limiter_pid = LimiterPid}) + end, + noreply(State). handle_info({'DOWN', MonitorRef, process, DownPid, _Reason}, State = #q{owner = {DownPid, MonitorRef}}) -> diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 001fa4af..51e550ed 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -439,11 +439,11 @@ handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, %% TODO: terminate limiter when transitioning to 'unlimited' NewLimiter = case Limiter of undefined -> - %% TODO: tell queues with subscribers about - %% the limiter - rabbit_limiter:start_link(ProxyPid); - Pid -> - Pid + LPid = rabbit_limiter:start_link(ProxyPid), + ok = limit_queues(LPid, State), + LPid; + LPid -> + LPid end, ok = rabbit_limiter:limit(NewLimiter, PrefetchCount), {reply, #'basic.qos_ok'{}, State#ch{limiter = NewLimiter}}; @@ -832,18 +832,22 @@ fold_per_queue(F, Acc0, UAQ) -> Acc0, D). notify_queues(#ch{proxy_pid = ProxyPid, consumer_mapping = Consumers}) -> - rabbit_amqqueue:notify_down_all( - [QPid || QueueName <- - sets:to_list( - dict:fold(fun (_ConsumerTag, QueueName, S) -> - sets:add_element(QueueName, S) - end, sets:new(), Consumers)), - case rabbit_amqqueue:lookup(QueueName) of - {ok, Q} -> QPid = Q#amqqueue.pid, true; - %% queue has been deleted in the meantime - {error, not_found} -> QPid = none, false - end], - ProxyPid). + rabbit_amqqueue:notify_down_all(consumer_queues(Consumers), ProxyPid). + +limit_queues(LPid, #ch{proxy_pid = ProxyPid, consumer_mapping = Consumers}) -> + rabbit_amqqueue:limit_all(consumer_queues(Consumers), ProxyPid, LPid). + +consumer_queues(Consumers) -> + [QPid || QueueName <- + sets:to_list( + dict:fold(fun (_ConsumerTag, QueueName, S) -> + sets:add_element(QueueName, S) + end, sets:new(), Consumers)), + case rabbit_amqqueue:lookup(QueueName) of + {ok, Q} -> QPid = Q#amqqueue.pid, true; + %% queue has been deleted in the meantime + {error, not_found} -> QPid = none, false + end]. %% tell the limiter about the number of acks that have been received %% for messages delivered to subscribed consumers, rather than those -- cgit v1.2.1 From 7b6e96533e2d572740818c801cef6f69a1824d9f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 21:14:46 +0000 Subject: cosmetic --- src/rabbit_channel.erl | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 51e550ed..71009747 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -39,10 +39,10 @@ %% callbacks -export([init/2, handle_message/2]). --record(ch, {state, proxy_pid, reader_pid, writer_pid, +-record(ch, {state, proxy_pid, reader_pid, writer_pid, limiter_pid, transaction_id, tx_participants, next_tag, uncommitted_ack_q, unacked_message_q, - username, virtual_host, limiter, + username, virtual_host, most_recently_declared_queue, consumer_mapping}). %%---------------------------------------------------------------------------- @@ -100,6 +100,7 @@ init(ProxyPid, [ReaderPid, WriterPid, Username, VHost]) -> proxy_pid = ProxyPid, reader_pid = ReaderPid, writer_pid = WriterPid, + limiter_pid = undefined, transaction_id = none, tx_participants = sets:new(), next_tag = 1, @@ -108,7 +109,6 @@ init(ProxyPid, [ReaderPid, WriterPid, Username, VHost]) -> username = Username, virtual_host = VHost, most_recently_declared_queue = <<>>, - limiter = undefined, consumer_mapping = dict:new()}. handle_message({method, Method, Content}, State) -> @@ -291,7 +291,7 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), Participants = ack(State#ch.proxy_pid, TxnKey, Acked), {noreply, case TxnKey of - none -> ok = notify_limiter(State#ch.limiter, Acked), + none -> ok = notify_limiter(State#ch.limiter_pid, Acked), State#ch{unacked_message_q = Remaining}; _ -> NewUAQ = queue:join(State#ch.uncommitted_ack_q, Acked), @@ -336,7 +336,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, nowait = NoWait}, _, State = #ch{ proxy_pid = ProxyPid, reader_pid = ReaderPid, - limiter = LimiterPid, + limiter_pid = LimiterPid, consumer_mapping = ConsumerMapping }) -> case dict:find(ConsumerTag, ConsumerMapping) of error -> @@ -430,23 +430,23 @@ handle_method(#'basic.qos'{prefetch_size = Size}, [Size]); handle_method(#'basic.qos'{prefetch_count = 0}, - _, State = #ch{ limiter = undefined }) -> + _, State = #ch{ limiter_pid = undefined }) -> {reply, #'basic.qos_ok'{}, State}; handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, - _, State = #ch{ limiter = Limiter, + _, State = #ch{ limiter_pid = LimiterPid, proxy_pid = ProxyPid }) -> %% TODO: terminate limiter when transitioning to 'unlimited' - NewLimiter = case Limiter of - undefined -> - LPid = rabbit_limiter:start_link(ProxyPid), - ok = limit_queues(LPid, State), - LPid; - LPid -> - LPid - end, - ok = rabbit_limiter:limit(NewLimiter, PrefetchCount), - {reply, #'basic.qos_ok'{}, State#ch{limiter = NewLimiter}}; + NewLimiterPid = case LimiterPid of + undefined -> + LPid = rabbit_limiter:start_link(ProxyPid), + ok = limit_queues(LPid, State), + LPid; + LPid -> + LPid + end, + ok = rabbit_limiter:limit(NewLimiterPid, PrefetchCount), + {reply, #'basic.qos_ok'{}, State#ch{limiter_pid = NewLimiterPid}}; handle_method(#'basic.recover'{requeue = true}, _, State = #ch{ transaction_id = none, @@ -792,7 +792,7 @@ internal_commit(State = #ch{transaction_id = TxnKey, tx_participants = Participants}) -> case rabbit_amqqueue:commit_all(sets:to_list(Participants), TxnKey) of - ok -> ok = notify_limiter(State#ch.limiter, + ok -> ok = notify_limiter(State#ch.limiter_pid, State#ch.uncommitted_ack_q), new_tx(State); {error, Errors} -> rabbit_misc:protocol_error( @@ -854,12 +854,12 @@ consumer_queues(Consumers) -> %% for messages sent in a response to a basic.get notify_limiter(undefined, _Acked) -> ok; -notify_limiter(Limiter, Acked) -> +notify_limiter(LimiterPid, Acked) -> case lists:foldl(fun ({_, none, _}, Acc) -> Acc; ({_, _, _}, Acc) -> Acc + 1 end, 0, queue:to_list(Acked)) of 0 -> ok; - Count -> rabbit_limiter:ack(Limiter, Count) + Count -> rabbit_limiter:ack(LimiterPid, Count) end. is_message_persistent(#content{properties = #'P_basic'{ -- cgit v1.2.1 From 20c86095dd1440f27f51763c932c71034d3fdde4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 21:39:11 +0000 Subject: handle the "no limiter" case more obviously This is no semantic change since gen_server:cast(undefined, ...) returns 'ok'. However, it only does so because it catches the 'badarg' error thrown by erlang:send. It is probably more efficient to not attempt the send in the first place. Plus for documentation purposes, and to keep dialyzer happy, it is useful to state explicitly which functions are expected to be called on an 'undefined' limiter. --- src/rabbit_limiter.erl | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 824de072..e02f77b1 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -70,21 +70,19 @@ limit(LimiterPid, PrefetchCount) -> %% Ask the limiter whether the queue can deliver a message without %% breaching a limit -can_send(undefined, _QPid) -> - true; -can_send(LimiterPid, QPid) -> - gen_server:call(LimiterPid, {can_send, QPid}). +can_send(undefined, _QPid) -> true; +can_send(LimiterPid, QPid) -> gen_server:call(LimiterPid, {can_send, QPid}). %% Let the limiter know that the channel has received some acks from a %% consumer -ack(LimiterPid, Count) -> - gen_server:cast(LimiterPid, {ack, Count}). +ack(undefined, _Count) -> ok; +ack(LimiterPid, Count) -> gen_server:cast(LimiterPid, {ack, Count}). -register(LimiterPid, QPid) -> - gen_server:cast(LimiterPid, {register, QPid}). +register(undefined, _QPid) -> ok; +register(LimiterPid, QPid) -> gen_server:cast(LimiterPid, {register, QPid}). -unregister(LimiterPid, QPid) -> - gen_server:cast(LimiterPid, {unregister, QPid}). +unregister(undefined, _QPid) -> ok; +unregister(LimiterPid, QPid) -> gen_server:cast(LimiterPid, {unregister, QPid}). %%---------------------------------------------------------------------------- %% gen_server callbacks -- cgit v1.2.1 From 01b464088eaf595b0c1a84d289f470a67ea99071 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 21:44:52 +0000 Subject: add type spec for start_link --- src/rabbit_limiter.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index e02f77b1..a9ec9e10 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -42,6 +42,7 @@ -ifdef(use_specs). +-spec(start_link/1 :: (pid()) -> pid()). -spec(limit/2 :: (pid(), non_neg_integer()) -> 'ok'). -spec(can_send/2 :: (pid(), pid()) -> bool()). -spec(ack/2 :: (pid(), non_neg_integer()) -> 'ok'). -- cgit v1.2.1 From 8d7ad7391c0edc4052b193b696b7dce449ae0c39 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 23 Dec 2008 21:54:26 +0000 Subject: don't leave the limiter behind when the channel terminates --- src/rabbit_channel.erl | 4 +++- src/rabbit_limiter.erl | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 71009747..e7678cdf 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -162,13 +162,15 @@ handle_message(Other, State) -> %%--------------------------------------------------------------------------- -terminate(Reason, State = #ch{writer_pid = WriterPid}) -> +terminate(Reason, State = #ch{writer_pid = WriterPid, + limiter_pid = LimiterPid}) -> Res = notify_queues(internal_rollback(State)), case Reason of normal -> ok = Res; _ -> ok end, rabbit_writer:shutdown(WriterPid), + rabbit_limiter:shutdown(LimiterPid), exit(Reason). return_ok(State, true, _Msg) -> {noreply, State}; diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index a9ec9e10..6ffa8c23 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -35,7 +35,7 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). --export([start_link/1]). +-export([start_link/1, shutdown/1]). -export([limit/2, can_send/2, ack/2, register/2, unregister/2]). %%---------------------------------------------------------------------------- @@ -43,6 +43,7 @@ -ifdef(use_specs). -spec(start_link/1 :: (pid()) -> pid()). +-spec(shutdown/1 :: (pid()) -> 'ok'). -spec(limit/2 :: (pid(), non_neg_integer()) -> 'ok'). -spec(can_send/2 :: (pid(), pid()) -> bool()). -spec(ack/2 :: (pid(), non_neg_integer()) -> 'ok'). @@ -66,6 +67,11 @@ start_link(ChPid) -> {ok, Pid} = gen_server:start_link(?MODULE, [ChPid], []), Pid. +shutdown(undefined) -> + ok; +shutdown(LimiterPid) -> + gen_server:cast(LimiterPid, shutdown). + limit(LimiterPid, PrefetchCount) -> gen_server:cast(LimiterPid, {limit, PrefetchCount}). @@ -102,6 +108,9 @@ handle_call({can_send, _QPid}, _From, State = #lim{in_use = InUse}) -> false -> {reply, true, State#lim{in_use = InUse + 1}} end. +handle_cast(shutdown, State) -> + {stop, normal, State}; + handle_cast({limit, PrefetchCount}, State) -> {noreply, maybe_notify(State, State#lim{prefetch_count = PrefetchCount})}; -- cgit v1.2.1 From 7de6e196f14ca8d83815b7e8e023535dda98647e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 24 Dec 2008 14:57:47 +0000 Subject: destroy limiter when a channel becomes unlimited which results in far more efficient handling of subsequent deliveries --- src/rabbit_amqqueue_process.erl | 25 ++++++++++++++----------- src/rabbit_channel.erl | 25 ++++++++++++++++--------- src/rabbit_limiter.erl | 10 ++++++++-- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c6bb0502..c49b06e5 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -784,17 +784,20 @@ handle_cast({notify_sent, ChPid}, State) -> end)); handle_cast({limit, ChPid, LimiterPid}, State) -> - case lookup_ch(ChPid) of - not_found -> - ok; - C = #cr{consumers = Consumers} -> - if Consumers =/= [] -> - ok = rabbit_limiter:register(LimiterPid, self()); - true -> ok - end, - store_ch_record(C#cr{limiter_pid = LimiterPid}) - end, - noreply(State). + noreply( + possibly_unblock( + State, ChPid, + fun (C = #cr{consumers = Consumers, + limiter_pid = OldLimiterPid, + is_limit_active = Limited}) -> + if Consumers =/= [] andalso OldLimiterPid == undefined -> + ok = rabbit_limiter:register(LimiterPid, self()); + true -> + ok + end, + NewLimited = Limited andalso LimiterPid =/= undefined, + C#cr{limiter_pid = LimiterPid, is_limit_active = NewLimited} + end)). handle_info({'DOWN', MonitorRef, process, DownPid, _Reason}, State = #q{owner = {DownPid, MonitorRef}}) -> diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index e7678cdf..a4bfacbb 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -154,6 +154,12 @@ handle_message({conserve_memory, Conserve}, State) -> State#ch.writer_pid, #'channel.flow'{active = not(Conserve)}), State; +handle_message({'EXIT', Pid, Reason}, State = #ch{proxy_pid = Pid}) -> + terminate(Reason, State); + +handle_message({'EXIT', _Pid, normal}, State) -> + State; + handle_message({'EXIT', _Pid, Reason}, State) -> terminate(Reason, State); @@ -431,21 +437,22 @@ handle_method(#'basic.qos'{prefetch_size = Size}, "Pre-fetch size (~s) for basic.qos not implementented", [Size]); -handle_method(#'basic.qos'{prefetch_count = 0}, - _, State = #ch{ limiter_pid = undefined }) -> - {reply, #'basic.qos_ok'{}, State}; - handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{ limiter_pid = LimiterPid, proxy_pid = ProxyPid }) -> - %% TODO: terminate limiter when transitioning to 'unlimited' - NewLimiterPid = case LimiterPid of - undefined -> + NewLimiterPid = case {LimiterPid, PrefetchCount} of + {undefined, 0} -> + undefined; + {undefined, _} -> LPid = rabbit_limiter:start_link(ProxyPid), ok = limit_queues(LPid, State), LPid; - LPid -> - LPid + {_, 0} -> + ok = rabbit_limiter:shutdown(LimiterPid), + ok = limit_queues(undefined, State), + undefined; + {_, _} -> + LimiterPid end, ok = rabbit_limiter:limit(NewLimiterPid, PrefetchCount), {reply, #'basic.qos_ok'{}, State#ch{limiter_pid = NewLimiterPid}}; diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 6ffa8c23..3e09bb37 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -72,13 +72,19 @@ shutdown(undefined) -> shutdown(LimiterPid) -> gen_server:cast(LimiterPid, shutdown). +limit(undefined, 0) -> + ok; limit(LimiterPid, PrefetchCount) -> gen_server:cast(LimiterPid, {limit, PrefetchCount}). %% Ask the limiter whether the queue can deliver a message without %% breaching a limit -can_send(undefined, _QPid) -> true; -can_send(LimiterPid, QPid) -> gen_server:call(LimiterPid, {can_send, QPid}). +can_send(undefined, _QPid) -> + true; +can_send(LimiterPid, QPid) -> + rabbit_misc:with_exit_handler( + fun () -> true end, + fun () -> gen_server:call(LimiterPid, {can_send, QPid}) end). %% Let the limiter know that the channel has received some acks from a %% consumer -- cgit v1.2.1 From 4882f8c367da4a7f608d1b7be5ab941a4f592441 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 25 Dec 2008 19:46:49 +0000 Subject: optimisation: only notify queues that have had can_send requests rejected --- src/rabbit_limiter.erl | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 3e09bb37..38bf4cd4 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -104,13 +104,9 @@ unregister(LimiterPid, QPid) -> gen_server:cast(LimiterPid, {unregister, QPid}). init([ChPid]) -> {ok, #lim{ch_pid = ChPid} }. -handle_call({can_send, _QPid}, _From, State = #lim{in_use = InUse}) -> +handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> case limit_reached(State) of - true -> - %% TODO: keep track of the fact that the specific QPid has - %% had a can_send request rejected, so we can restrict the - %% notifications to these QPids only. - {reply, false, State}; + true -> {reply, false, limit_queue(QPid, State)}; false -> {reply, true, State#lim{in_use = InUse + 1}} end. @@ -147,32 +143,39 @@ code_change(_, State, _) -> maybe_notify(OldState, NewState) -> case limit_reached(OldState) andalso not(limit_reached(NewState)) of - true -> ok = notify_queues(NewState#lim.ch_pid, NewState#lim.queues); - false -> ok - end, - NewState. + true -> notify_queues(NewState); + false -> NewState + end. limit_reached(#lim{prefetch_count = Limit, in_use = InUse}) -> Limit =/= 0 andalso InUse >= Limit. remember_queue(QPid, State = #lim{queues = Queues}) -> case dict:is_key(QPid, Queues) of - false -> MonitorRef = erlang:monitor(process, QPid), - State#lim{queues = dict:store(QPid, MonitorRef, Queues)}; + false -> MRef = erlang:monitor(process, QPid), + State#lim{queues = dict:store(QPid, {MRef, false}, Queues)}; true -> State end. forget_queue(QPid, State = #lim{ch_pid = ChPid, queues = Queues}) -> case dict:find(QPid, Queues) of - {ok, MonitorRef} -> - true = erlang:demonitor(MonitorRef), + {ok, {MRef, _}} -> + true = erlang:demonitor(MRef), ok = rabbit_amqqueue:unblock(QPid, ChPid), State#lim{queues = dict:erase(QPid, Queues)}; error -> State end. -notify_queues(ChPid, Queues) -> - QList = dict:to_list(Queues), +limit_queue(QPid, State = #lim{queues = Queues}) -> + UpdateFun = fun ({MRef, _}) -> {MRef, true} end, + State#lim{queues = dict:update(QPid, UpdateFun, Queues)}. + +notify_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> + {QList, NewQueues} = + dict:fold(fun (_QPid, {_, false}, Acc) -> Acc; + (QPid, {MRef, true}, {L, D}) -> + {[QPid | L], dict:store(QPid, {MRef, false}, D)} + end, {[], Queues}, Queues), case length(QList) of 0 -> ok; L -> @@ -180,6 +183,7 @@ notify_queues(ChPid, Queues) -> %% appears in the list, thus ensuring that each queue has %% an equal chance of being notified first. {L1, L2} = lists:split(random:uniform(L), QList), - [ok = rabbit_amqqueue:unblock(Q, ChPid) || {Q, _} <- L2 ++ L1], + [ok = rabbit_amqqueue:unblock(Q, ChPid) || Q <- L2 ++ L1], ok - end. + end, + State#lim{queues = NewQueues}. -- cgit v1.2.1 From 0d6e4923d6d5f88ba6742983065b39ef296b6c8b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 25 Dec 2008 20:10:34 +0000 Subject: cosmetic: rename 'in_use' to 'volume' --- src/rabbit_limiter.erl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 38bf4cd4..62c6c73c 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -57,7 +57,7 @@ -record(lim, {prefetch_count = 0, ch_pid, queues = dict:new(), - in_use = 0}). + volume = 0}). %%---------------------------------------------------------------------------- %% API @@ -104,10 +104,10 @@ unregister(LimiterPid, QPid) -> gen_server:cast(LimiterPid, {unregister, QPid}). init([ChPid]) -> {ok, #lim{ch_pid = ChPid} }. -handle_call({can_send, QPid}, _From, State = #lim{in_use = InUse}) -> +handle_call({can_send, QPid}, _From, State = #lim{volume = Volume}) -> case limit_reached(State) of true -> {reply, false, limit_queue(QPid, State)}; - false -> {reply, true, State#lim{in_use = InUse + 1}} + false -> {reply, true, State#lim{volume = Volume + 1}} end. handle_cast(shutdown, State) -> @@ -116,11 +116,11 @@ handle_cast(shutdown, State) -> handle_cast({limit, PrefetchCount}, State) -> {noreply, maybe_notify(State, State#lim{prefetch_count = PrefetchCount})}; -handle_cast({ack, Count}, State = #lim{in_use = InUse}) -> - NewInUse = if InUse == 0 -> 0; - true -> InUse - Count - end, - {noreply, maybe_notify(State, State#lim{in_use = NewInUse})}; +handle_cast({ack, Count}, State = #lim{volume = Volume}) -> + NewVolume = if Volume == 0 -> 0; + true -> Volume - Count + end, + {noreply, maybe_notify(State, State#lim{volume = NewVolume})}; handle_cast({register, QPid}, State) -> {noreply, remember_queue(QPid, State)}; @@ -147,8 +147,8 @@ maybe_notify(OldState, NewState) -> false -> NewState end. -limit_reached(#lim{prefetch_count = Limit, in_use = InUse}) -> - Limit =/= 0 andalso InUse >= Limit. +limit_reached(#lim{prefetch_count = Limit, volume = Volume}) -> + Limit =/= 0 andalso Volume >= Limit. remember_queue(QPid, State = #lim{queues = Queues}) -> case dict:is_key(QPid, Queues) of -- cgit v1.2.1 From 6d7792714815e494cb63aec8ee86894e12f5e4d7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 26 Dec 2008 09:09:25 +0000 Subject: tidying up, refactoring and some cosmetic changes --- src/rabbit_amqqueue.erl | 5 ++- src/rabbit_amqqueue_process.erl | 73 +++++++++++++++-------------------------- src/rabbit_channel.erl | 15 ++++----- src/rabbit_limiter.erl | 6 ++-- 4 files changed, 38 insertions(+), 61 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index a345f5ab..3c8bd99e 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -38,8 +38,7 @@ -export([list/1, info/1, info/2, info_all/1, info_all/2]). -export([claim_queue/2]). -export([basic_get/3, basic_consume/8, basic_cancel/4]). --export([notify_sent/2]). --export([unblock/2]). +-export([notify_sent/2, unblock/2]). -export([commit_all/2, rollback_all/2, notify_down_all/2, limit_all/3]). -export([on_node_down/1]). @@ -97,7 +96,7 @@ -spec(basic_get/3 :: (amqqueue(), pid(), bool()) -> {'ok', non_neg_integer(), msg()} | 'empty'). -spec(basic_consume/8 :: - (amqqueue(), bool(), pid(), pid(), pid(), ctag(), bool(), any()) -> + (amqqueue(), bool(), pid(), pid(), pid(), ctag(), bool(), any()) -> 'ok' | {'error', 'queue_owned_by_another_connection' | 'exclusive_consume_unavailable'}). -spec(basic_cancel/4 :: (amqqueue(), pid(), ctag(), any()) -> 'ok'). diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c49b06e5..5199fb87 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -162,59 +162,42 @@ deliver_immediately(Message, Delivered, next_msg_id = NextId}) -> ?LOGDEBUG("AMQQUEUE ~p DELIVERY:~n~p~n", [QName, Message]), case queue:out(RoundRobin) of - {{value, QEntry = {ChPid, - #consumer{tag = ConsumerTag, - ack_required = AckRequired = true}}}, + {{value, QEntry = {ChPid, #consumer{tag = ConsumerTag, + ack_required = AckRequired}}}, RoundRobinTail} -> - % Use Qos Limits if an ack is required - % Query the limiter to find out if a limit has been breached - C = #cr{limiter_pid = LimiterPid} = ch_record(ChPid), - case rabbit_limiter:can_send(LimiterPid, self()) of + C = #cr{limiter_pid = LimiterPid, + unsent_message_count = Count, + unacked_messages = UAM} = ch_record(ChPid), + case not(AckRequired) orelse rabbit_limiter:can_send( + LimiterPid, self()) of true -> - really_deliver(AckRequired, ChPid, ConsumerTag, - Delivered, Message, NextId, QName, - QEntry, RoundRobinTail, State); + rabbit_channel:deliver( + ChPid, ConsumerTag, AckRequired, + {QName, self(), NextId, Delivered, Message}), + NewUAM = case AckRequired of + true -> dict:store(NextId, Message, UAM); + false -> UAM + end, + NewC = C#cr{unsent_message_count = Count + 1, + unacked_messages = NewUAM}, + store_ch_record(NewC), + NewConsumers = + case ch_record_state_transition(C, NewC) of + ok -> queue:in(QEntry, RoundRobinTail); + block -> block_consumers(ChPid, RoundRobinTail) + end, + {offered, AckRequired, State#q{round_robin = NewConsumers, + next_msg_id = NextId + 1}}; false -> - % Have another go by cycling through the consumer - % queue store_ch_record(C#cr{is_limit_active = true}), NewConsumers = block_consumers(ChPid, RoundRobinTail), deliver_immediately(Message, Delivered, State#q{round_robin = NewConsumers}) end; - {{value, QEntry = {ChPid, - #consumer{tag = ConsumerTag, - ack_required = AckRequired = false}}}, - RoundRobinTail} -> - really_deliver(AckRequired, ChPid, ConsumerTag, - Delivered, Message, NextId, QName, - QEntry, RoundRobinTail, State); {empty, _} -> {not_offered, State} end. -% TODO The arity of this function seems a bit large :-( -really_deliver(AckRequired, ChPid, ConsumerTag, Delivered, Message, NextId, - QName, QEntry, RoundRobinTail, State) -> - rabbit_channel:deliver(ChPid, ConsumerTag, AckRequired, - {QName, self(), NextId, Delivered, Message}), - C = #cr{unsent_message_count = Count, - unacked_messages = UAM} = ch_record(ChPid), - NewUAM = case AckRequired of - true -> dict:store(NextId, Message, UAM); - false -> UAM - end, - NewC = C#cr{unsent_message_count = Count + 1, - unacked_messages = NewUAM}, - store_ch_record(NewC), - NewConsumers = - case ch_record_state_transition(C, NewC) of - ok -> queue:in(QEntry, RoundRobinTail); - block -> block_consumers(ChPid, RoundRobinTail) - end, - {offered, AckRequired, State#q{round_robin = NewConsumers, - next_msg_id = NextId +1}}. - attempt_delivery(none, Message, State) -> case deliver_immediately(Message, false, State) of {offered, false, State1} -> @@ -631,9 +614,8 @@ handle_call({basic_consume, NoAck, ReaderPid, ChPid, LimiterPid, ok -> C = #cr{consumers = Consumers} = ch_record(ChPid), Consumer = #consumer{tag = ConsumerTag, ack_required = not(NoAck)}, - C1 = C#cr{consumers = [Consumer | Consumers], - limiter_pid = LimiterPid}, - store_ch_record(C1), + store_ch_record(C#cr{consumers = [Consumer | Consumers], + limiter_pid = LimiterPid}), if Consumers == [] -> ok = rabbit_limiter:register(LimiterPid, self()); true -> @@ -662,8 +644,7 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, _From, NewConsumers = lists:filter (fun (#consumer{tag = CT}) -> CT /= ConsumerTag end, Consumers), - C1 = C#cr{consumers = NewConsumers}, - store_ch_record(C1), + store_ch_record(C#cr{consumers = NewConsumers}), if NewConsumers == [] -> ok = rabbit_limiter:unregister(LimiterPid, self()); true -> diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a4bfacbb..304275c4 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -427,15 +427,12 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, end end; -handle_method(#'basic.qos'{global = Flag = true}, _, _State) -> - rabbit_misc:protocol_error(not_implemented, - "Global flag (~s) for basic.qos not implementented", [Flag]); +handle_method(#'basic.qos'{global = true}, _, _State) -> + rabbit_misc:protocol_error(not_implemented, "global=true", []); -handle_method(#'basic.qos'{prefetch_size = Size}, - _, _State) when Size /= 0 -> +handle_method(#'basic.qos'{prefetch_size = Size}, _, _State) when Size /= 0 -> rabbit_misc:protocol_error(not_implemented, - "Pre-fetch size (~s) for basic.qos not implementented", - [Size]); + "prefetch_size!=0 (~w)", [Size]); handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{ limiter_pid = LimiterPid, @@ -859,8 +856,8 @@ consumer_queues(Consumers) -> end]. %% tell the limiter about the number of acks that have been received -%% for messages delivered to subscribed consumers, rather than those -%% for messages sent in a response to a basic.get +%% for messages delivered to subscribed consumers, but not acks for +%% messages sent in a response to a basic.get. notify_limiter(undefined, _Acked) -> ok; notify_limiter(LimiterPid, Acked) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 62c6c73c..3776edd0 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -179,9 +179,9 @@ notify_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> case length(QList) of 0 -> ok; L -> - %% We randomly vary the position in which each queue - %% appears in the list, thus ensuring that each queue has - %% an equal chance of being notified first. + %% We randomly vary the position of queues in the list, + %% thus ensuring that each queue has an equal chance of + %% being notified first. {L1, L2} = lists:split(random:uniform(L), QList), [ok = rabbit_amqqueue:unblock(Q, ChPid) || Q <- L2 ++ L1], ok -- cgit v1.2.1 From b794768e9815bbc3c9c2bfcc04c8c03e9dd29013 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 6 Jan 2009 20:01:45 +0000 Subject: initial version of gen_server2 This is the same as the R11B-5 version, except for the introductory comment and change in module name --- src/gen_server2.erl | 820 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 820 insertions(+) create mode 100644 src/gen_server2.erl diff --git a/src/gen_server2.erl b/src/gen_server2.erl new file mode 100644 index 00000000..be0ed136 --- /dev/null +++ b/src/gen_server2.erl @@ -0,0 +1,820 @@ +%% This file is a copy of gen_server.erl from the R11B-5 Erlang/OTP +%% distribution, with the following modifications: +%% +%% 1) the module name is gen_server2 +%% +%% All modifications are (C) 2009 LShift Ltd. + +%% ``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} +%%% 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 +%%% always called when server terminates +%%% +%%% ==> ok +%%% +%%% +%%% 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, + enter_loop/3, enter_loop/4, enter_loop/5]). + +-export([behaviour_info/1]). + +%% System exports +-export([system_continue/3, + system_terminate/4, + system_code_change/4, + format_status/2]). + +%% Internal exports +-export([init_it/6, print_event/3]). + +-import(error_logger, [format/2]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +behaviour_info(callbacks) -> + [{init,1},{handle_call,3},{handle_cast,2},{handle_info,2}, + {terminate,2},{code_change,3}]; +behaviour_info(_Other) -> + undefined. + +%%% ----------------------------------------------------------------- +%%% 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 prey +%%----------------------------------------------------------------- +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). + + +%%----------------------------------------------------------------- +%% enter_loop(Mod, Options, State, , ) ->_ +%% +%% 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). + +enter_loop(Mod, Options, State, ServerName = {_, _}) -> + enter_loop(Mod, Options, State, ServerName, infinity); + +enter_loop(Mod, Options, State, Timeout) -> + enter_loop(Mod, Options, State, self(), Timeout). + +enter_loop(Mod, Options, State, ServerName, Timeout) -> + Name = get_proc_name(ServerName), + Parent = get_parent(), + Debug = debug_options(Name, Options), + loop(Parent, Name, State, Mod, Timeout, 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, Name, Mod, Args, Options) -> + Debug = debug_options(Name, Options), + case catch Mod:init(Args) of + {ok, State} -> + proc_lib:init_ack(Starter, {ok, self()}), + loop(Parent, Name, State, Mod, infinity, Debug); + {ok, State, Timeout} -> + proc_lib:init_ack(Starter, {ok, self()}), + loop(Parent, Name, State, Mod, Timeout, Debug); + {stop, Reason} -> + proc_lib:init_ack(Starter, {error, Reason}), + exit(Reason); + ignore -> + proc_lib:init_ack(Starter, ignore), + exit(normal); + {'EXIT', Reason} -> + 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. + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== +%%% --------------------------------------------------- +%%% The MAIN loop. +%%% --------------------------------------------------- +loop(Parent, Name, State, Mod, Time, Debug) -> + Msg = receive + Input -> + Input + after Time -> + timeout + end, + case Msg of + {system, From, Req} -> + sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, + [Name, State, Mod, Time]); + {'EXIT', Parent, Reason} -> + terminate(Reason, Name, Msg, Mod, State, Debug); + _Msg when Debug =:= [] -> + handle_msg(Msg, Parent, Name, State, Mod, Time); + _Msg -> + Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, + Name, {in, Msg}), + handle_msg(Msg, Parent, Name, State, Mod, Time, Debug1) + end. + +%%% --------------------------------------------------- +%%% Send/recive functions +%%% --------------------------------------------------- +do_send(Dest, Msg) -> + case catch erlang:send(Dest, Msg, [noconnect]) of + noconnect -> + spawn(erlang, send, [Dest,Msg]); + Other -> + Other + end. + +do_multi_call(Nodes, Name, Req, infinity) -> + Tag = make_ref(), + Monitors = send_nodes(Nodes, Name, Tag, Req), + rec_nodes(Tag, Monitors, Name, undefined); +do_multi_call(Nodes, Name, Req, Timeout) -> + Tag = make_ref(), + Caller = self(), + Receiver = + spawn( + fun() -> + %% Middleman process. Should be unsensitive to regular + %% exit signals. The sychronization is needed in case + %% the receiver would exit before the caller started + %% the monitor. + process_flag(trap_exit, true), + Mref = erlang:monitor(process, Caller), + receive + {Caller,Tag} -> + Monitors = send_nodes(Nodes, Name, Tag, Req), + TimerId = erlang:start_timer(Timeout, self(), ok), + Result = rec_nodes(Tag, Monitors, Name, TimerId), + exit({self(),Tag,Result}); + {'DOWN',Mref,_,_,_} -> + %% Caller died before sending us the go-ahead. + %% Give up silently. + exit(normal) + end + end), + Mref = erlang:monitor(process, Receiver), + Receiver ! {self(),Tag}, + receive + {'DOWN',Mref,_,_,{Receiver,Tag,Result}} -> + Result; + {'DOWN',Mref,_,_,Reason} -> + %% The middleman code failed. Or someone did + %% exit(_, kill) on the middleman process => Reason==killed + exit(Reason) + end. + +send_nodes(Nodes, Name, Tag, Req) -> + send_nodes(Nodes, Name, Tag, Req, []). + +send_nodes([Node|Tail], Name, Tag, Req, Monitors) + when is_atom(Node) -> + Monitor = start_monitor(Node, Name), + %% Handle non-existing names in rec_nodes. + catch {Name, Node} ! {'$gen_call', {self(), {Tag, Node}}, Req}, + send_nodes(Tail, Name, Tag, Req, [Monitor | Monitors]); +send_nodes([_Node|Tail], Name, Tag, Req, Monitors) -> + %% Skip non-atom Node + send_nodes(Tail, Name, Tag, Req, Monitors); +send_nodes([], _Name, _Tag, _Req, Monitors) -> + Monitors. + +%% Against old nodes: +%% If no reply has been delivered within 2 secs. (per node) check that +%% the server really exists and wait for ever for the answer. +%% +%% Against contemporary nodes: +%% Wait for reply, server 'DOWN', or timeout from TimerId. + +rec_nodes(Tag, Nodes, Name, TimerId) -> + rec_nodes(Tag, Nodes, Name, [], [], 2000, TimerId). + +rec_nodes(Tag, [{N,R}|Tail], Name, Badnodes, Replies, Time, TimerId ) -> + receive + {'DOWN', R, _, _, _} -> + rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, Time, TimerId); + {{Tag, N}, Reply} -> %% Tag is bound !!! + 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). + +handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, _Time) -> + case catch Mod:handle_call(Msg, From, State) of + {reply, Reply, NState} -> + reply(From, Reply), + loop(Parent, Name, NState, Mod, infinity, []); + {reply, Reply, NState, Time1} -> + reply(From, Reply), + loop(Parent, Name, NState, Mod, Time1, []); + {noreply, NState} -> + loop(Parent, Name, NState, Mod, infinity, []); + {noreply, NState, Time1} -> + loop(Parent, Name, NState, Mod, Time1, []); + {stop, Reason, Reply, NState} -> + {'EXIT', R} = + (catch terminate(Reason, Name, Msg, Mod, NState, [])), + reply(From, Reply), + exit(R); + Other -> handle_common_reply(Other, Parent, Name, Msg, Mod, State) + end; +handle_msg(Msg, Parent, Name, State, Mod, _Time) -> + Reply = (catch dispatch(Msg, Mod, State)), + handle_common_reply(Reply, Parent, Name, Msg, Mod, State). + +handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, _Time, Debug) -> + case catch Mod:handle_call(Msg, From, State) of + {reply, Reply, NState} -> + Debug1 = reply(Name, From, Reply, NState, Debug), + loop(Parent, Name, NState, Mod, infinity, Debug1); + {reply, Reply, NState, Time1} -> + Debug1 = reply(Name, From, Reply, NState, Debug), + loop(Parent, Name, NState, Mod, Time1, Debug1); + {noreply, NState} -> + Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, + {noreply, NState}), + loop(Parent, Name, NState, Mod, infinity, Debug1); + {noreply, NState, Time1} -> + Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, + {noreply, NState}), + loop(Parent, Name, NState, Mod, Time1, Debug1); + {stop, Reason, Reply, NState} -> + {'EXIT', R} = + (catch terminate(Reason, Name, Msg, Mod, NState, Debug)), + reply(Name, From, Reply, NState, Debug), + exit(R); + Other -> + handle_common_reply(Other, Parent, Name, Msg, Mod, State, Debug) + end; +handle_msg(Msg, Parent, Name, State, Mod, _Time, Debug) -> + Reply = (catch dispatch(Msg, Mod, State)), + handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug). + +handle_common_reply(Reply, Parent, Name, Msg, Mod, State) -> + case Reply of + {noreply, NState} -> + loop(Parent, Name, NState, Mod, infinity, []); + {noreply, NState, Time1} -> + loop(Parent, Name, NState, Mod, Time1, []); + {stop, Reason, NState} -> + terminate(Reason, Name, Msg, Mod, NState, []); + {'EXIT', What} -> + terminate(What, Name, Msg, Mod, State, []); + _ -> + terminate({bad_return_value, Reply}, Name, Msg, Mod, State, []) + end. + +handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) -> + case Reply of + {noreply, NState} -> + Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, + {noreply, NState}), + loop(Parent, Name, NState, Mod, infinity, Debug1); + {noreply, NState, Time1} -> + Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, + {noreply, NState}), + loop(Parent, Name, NState, Mod, Time1, Debug1); + {stop, Reason, NState} -> + terminate(Reason, Name, Msg, Mod, NState, Debug); + {'EXIT', What} -> + terminate(What, Name, Msg, Mod, State, Debug); + _ -> + terminate({bad_return_value, Reply}, Name, Msg, Mod, State, Debug) + end. + +reply(Name, {To, Tag}, Reply, State, Debug) -> + reply({To, Tag}, Reply), + sys:handle_debug(Debug, {?MODULE, print_event}, Name, + {out, Reply, To, State} ). + + +%%----------------------------------------------------------------- +%% Callback functions for system messages handling. +%%----------------------------------------------------------------- +system_continue(Parent, Debug, [Name, State, Mod, Time]) -> + loop(Parent, Name, State, Mod, Time, Debug). + +system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time]) -> + terminate(Reason, Name, [], Mod, State, Debug). + +system_code_change([Name, State, Mod, Time], _Module, OldVsn, Extra) -> + case catch Mod:code_change(OldVsn, State, Extra) of + {ok, NewState} -> {ok, [Name, NewState, Mod, Time]}; + 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, Name, Msg, Mod, State, Debug) -> + case catch Mod:terminate(Reason, State) of + {'EXIT', R} -> + error_info(R, Name, Msg, State, Debug), + exit(R); + _ -> + case Reason of + normal -> + exit(normal); + shutdown -> + exit(shutdown); + _ -> + error_info(Reason, Name, Msg, State, Debug), + exit(Reason) + end + end. + +error_info(_Reason, 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, Name, Msg, State, Debug) -> + Reason1 = + case Reason of + {undef,[{M,F,A}|MFAs]} -> + 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; + _ -> + Reason + end, + format("** Generic server ~p terminating \n" + "** Last message in was ~p~n" + "** When Server state == ~p~n" + "** Reason for termination == ~n** ~p~n", + [Name, Msg, State, Reason1]), + sys:print_log(Debug), + ok. + +%%% --------------------------------------------------- +%%% 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 global:safe_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 global:safe_whereis_name(Name) of + undefined -> + exit(could_not_find_registerd_name); + Pid -> + Pid + end; + Pid -> + Pid + end. + +%%----------------------------------------------------------------- +%% Status information +%%----------------------------------------------------------------- +format_status(Opt, StatusData) -> + [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time]] = 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 = + case erlang:function_exported(Mod, format_status, 2) of + true -> + case catch Mod:format_status(Opt, [PDict, State]) of + {'EXIT', _} -> [{data, [{"State", State}]}]; + Else -> Else + end; + _ -> + [{data, [{"State", State}]}] + end, + [{header, Header}, + {data, [{"Status", SysState}, + {"Parent", Parent}, + {"Logged events", Log}]} | + Specfic]. -- cgit v1.2.1 From 50e03766a0f34747f6ea7c28ebc3a2760b66cdeb Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 7 Jan 2009 13:39:36 +0000 Subject: reduce impact of long message queues on selective receives --- src/gen_server2.erl | 120 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 42 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index be0ed136..ef14813f 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -3,6 +3,16 @@ %% %% 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. +%% %% All modifications are (C) 2009 LShift Ltd. %% ``The contents of this file are subject to the Erlang Public License, @@ -263,7 +273,8 @@ enter_loop(Mod, Options, State, ServerName, Timeout) -> Name = get_proc_name(ServerName), Parent = get_parent(), Debug = debug_options(Name, Options), - loop(Parent, Name, State, Mod, Timeout, Debug). + Queue = queue:new(), + loop(Parent, Name, State, Mod, Timeout, Queue, Debug). %%%======================================================================== %%% Gen-callback functions @@ -280,13 +291,14 @@ init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name, Mod, Args, Options) -> Debug = debug_options(Name, Options), + Queue = queue:new(), case catch Mod:init(Args) of {ok, State} -> proc_lib:init_ack(Starter, {ok, self()}), - loop(Parent, Name, State, Mod, infinity, Debug); + loop(Parent, Name, State, Mod, infinity, Queue, Debug); {ok, State, Timeout} -> proc_lib:init_ack(Starter, {ok, self()}), - loop(Parent, Name, State, Mod, Timeout, Debug); + loop(Parent, Name, State, Mod, Timeout, Queue, Debug); {stop, Reason} -> proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); @@ -308,25 +320,40 @@ init_it(Starter, Parent, Name, Mod, Args, Options) -> %%% --------------------------------------------------- %%% The MAIN loop. %%% --------------------------------------------------- -loop(Parent, Name, State, Mod, Time, Debug) -> - Msg = receive - Input -> - Input - after Time -> - timeout - end, +loop(Parent, Name, State, Mod, Time, Queue, Debug) -> + receive + Input -> loop(Parent, Name, State, Mod, + Time, queue:in(Input, Queue), Debug) + after 0 -> + case queue:out(Queue) of + {{value, Msg}, Queue1} -> + process_msg(Parent, Name, State, Mod, + Time, Queue1, Debug, Msg); + {empty, Queue1} -> + receive + Input -> + loop(Parent, Name, State, Mod, + Time, queue:in(Input, Queue1), Debug) + after Time -> + process_msg(Parent, Name, State, Mod, + Time, Queue1, Debug, timeout) + end + end + end. + +process_msg(Parent, Name, State, Mod, Time, Queue, Debug, Msg) -> case Msg of {system, From, Req} -> sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, - [Name, State, Mod, Time]); + [Name, State, Mod, Time, Queue]); {'EXIT', Parent, Reason} -> terminate(Reason, Name, Msg, Mod, State, Debug); _Msg when Debug =:= [] -> - handle_msg(Msg, Parent, Name, State, Mod, Time); + handle_msg(Msg, Parent, Name, State, Mod, Time, Queue); _Msg -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {in, Msg}), - handle_msg(Msg, Parent, Name, State, Mod, Time, Debug1) + handle_msg(Msg, Parent, Name, State, Mod, Time, Queue, Debug1) end. %%% --------------------------------------------------- @@ -528,63 +555,70 @@ dispatch({'$gen_cast', Msg}, Mod, State) -> dispatch(Info, Mod, State) -> Mod:handle_info(Info, State). -handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, _Time) -> +handle_msg({'$gen_call', From, Msg}, + Parent, Name, State, Mod, _Time, Queue) -> case catch Mod:handle_call(Msg, From, State) of {reply, Reply, NState} -> reply(From, Reply), - loop(Parent, Name, NState, Mod, infinity, []); + loop(Parent, Name, NState, Mod, infinity, Queue, []); {reply, Reply, NState, Time1} -> reply(From, Reply), - loop(Parent, Name, NState, Mod, Time1, []); + loop(Parent, Name, NState, Mod, Time1, Queue, []); {noreply, NState} -> - loop(Parent, Name, NState, Mod, infinity, []); + loop(Parent, Name, NState, Mod, infinity, Queue, []); {noreply, NState, Time1} -> - loop(Parent, Name, NState, Mod, Time1, []); + loop(Parent, Name, NState, Mod, Time1, Queue, []); {stop, Reason, Reply, NState} -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, [])), reply(From, Reply), exit(R); - Other -> handle_common_reply(Other, Parent, Name, Msg, Mod, State) + Other -> handle_common_reply(Other, + Parent, Name, Msg, Mod, State, Queue) end; -handle_msg(Msg, Parent, Name, State, Mod, _Time) -> +handle_msg(Msg, + Parent, Name, State, Mod, _Time, Queue) -> Reply = (catch dispatch(Msg, Mod, State)), - handle_common_reply(Reply, Parent, Name, Msg, Mod, State). + handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue). -handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, _Time, Debug) -> +handle_msg({'$gen_call', From, Msg}, + Parent, Name, State, Mod, _Time, Queue, Debug) -> case catch Mod:handle_call(Msg, From, State) of {reply, Reply, NState} -> Debug1 = reply(Name, From, Reply, NState, Debug), - loop(Parent, Name, NState, Mod, infinity, Debug1); + loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); {reply, Reply, NState, Time1} -> Debug1 = reply(Name, From, Reply, NState, Debug), - loop(Parent, Name, NState, Mod, Time1, Debug1); + loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); {noreply, NState} -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {noreply, NState}), - loop(Parent, Name, NState, Mod, infinity, Debug1); + loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); {noreply, NState, Time1} -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {noreply, NState}), - loop(Parent, Name, NState, Mod, Time1, Debug1); + loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); {stop, Reason, Reply, NState} -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, Debug)), reply(Name, From, Reply, NState, Debug), exit(R); Other -> - handle_common_reply(Other, Parent, Name, Msg, Mod, State, Debug) + handle_common_reply(Other, + Parent, Name, Msg, Mod, State, Queue, Debug) end; -handle_msg(Msg, Parent, Name, State, Mod, _Time, Debug) -> +handle_msg(Msg, + Parent, Name, State, Mod, _Time, Queue, Debug) -> Reply = (catch dispatch(Msg, Mod, State)), - handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug). + handle_common_reply(Reply, + Parent, Name, Msg, Mod, State, Queue, Debug). -handle_common_reply(Reply, Parent, Name, Msg, Mod, State) -> +handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue) -> case Reply of {noreply, NState} -> - loop(Parent, Name, NState, Mod, infinity, []); + loop(Parent, Name, NState, Mod, infinity, Queue, []); {noreply, NState, Time1} -> - loop(Parent, Name, NState, Mod, Time1, []); + loop(Parent, Name, NState, Mod, Time1, Queue, []); {stop, Reason, NState} -> terminate(Reason, Name, Msg, Mod, NState, []); {'EXIT', What} -> @@ -593,16 +627,16 @@ handle_common_reply(Reply, Parent, Name, Msg, Mod, State) -> terminate({bad_return_value, Reply}, Name, Msg, Mod, State, []) end. -handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) -> +handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue, Debug) -> case Reply of {noreply, NState} -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {noreply, NState}), - loop(Parent, Name, NState, Mod, infinity, Debug1); + loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); {noreply, NState, Time1} -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {noreply, NState}), - loop(Parent, Name, NState, Mod, Time1, Debug1); + loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); {stop, Reason, NState} -> terminate(Reason, Name, Msg, Mod, NState, Debug); {'EXIT', What} -> @@ -620,15 +654,15 @@ reply(Name, {To, Tag}, Reply, State, Debug) -> %%----------------------------------------------------------------- %% Callback functions for system messages handling. %%----------------------------------------------------------------- -system_continue(Parent, Debug, [Name, State, Mod, Time]) -> - loop(Parent, Name, State, Mod, Time, Debug). +system_continue(Parent, Debug, [Name, State, Mod, Time, Queue]) -> + loop(Parent, Name, State, Mod, Time, Queue, Debug). -system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time]) -> +system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time, _Queue]) -> terminate(Reason, Name, [], Mod, State, Debug). -system_code_change([Name, State, Mod, Time], _Module, OldVsn, Extra) -> +system_code_change([Name, State, Mod, Time, Queue], _Module, OldVsn, Extra) -> case catch Mod:code_change(OldVsn, State, Extra) of - {ok, NewState} -> {ok, [Name, NewState, Mod, Time]}; + {ok, NewState} -> {ok, [Name, NewState, Mod, Time, Queue]}; Else -> Else end. @@ -795,7 +829,8 @@ name_to_pid(Name) -> %% Status information %%----------------------------------------------------------------- format_status(Opt, StatusData) -> - [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time]] = StatusData, + [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, Queue]] = + StatusData, NameTag = if is_pid(Name) -> pid_to_list(Name); is_atom(Name) -> @@ -816,5 +851,6 @@ format_status(Opt, StatusData) -> [{header, Header}, {data, [{"Status", SysState}, {"Parent", Parent}, - {"Logged events", Log}]} | + {"Logged events", Log}, + {"Queued messages", queue:to_list(Queue)}]} | Specfic]. -- cgit v1.2.1 From 157e2c3ad2467d9829f2b81c15b76a8013e0b890 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 7 Jan 2009 16:41:08 +0000 Subject: order-preserving cast --- src/gen_server2.erl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index ef14813f..11bb66d7 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -3,7 +3,6 @@ %% %% 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 @@ -13,6 +12,10 @@ %% 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. +%% %% All modifications are (C) 2009 LShift Ltd. %% ``The contents of this file are subject to the Erlang Public License, @@ -360,12 +363,7 @@ process_msg(Parent, Name, State, Mod, Time, Queue, Debug, Msg) -> %%% Send/recive functions %%% --------------------------------------------------- do_send(Dest, Msg) -> - case catch erlang:send(Dest, Msg, [noconnect]) of - noconnect -> - spawn(erlang, send, [Dest,Msg]); - Other -> - Other - end. + catch erlang:send(Dest, Msg). do_multi_call(Nodes, Name, Req, infinity) -> Tag = make_ref(), -- cgit v1.2.1 From a48c3dd7d69115f780b7efc81aebf7ff6f77f1fc Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 7 Jan 2009 19:31:41 +0000 Subject: minor refactoring export {start,stop}_applications from rabbit_misc, so that other code, notably the Erlang client, can call them. --- src/rabbit.erl | 32 ++------------------------------ src/rabbit_misc.erl | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 41064c77..30b8c394 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -75,14 +75,14 @@ start() -> try ok = ensure_working_log_handlers(), ok = rabbit_mnesia:ensure_mnesia_dir(), - ok = start_applications(?APPS) + ok = rabbit_misc:start_applications(?APPS) after %%give the error loggers some time to catch up timer:sleep(100) end. stop() -> - ok = stop_applications(?APPS). + ok = rabbit_misc:stop_applications(?APPS). stop_and_halt() -> spawn(fun () -> @@ -109,34 +109,6 @@ rotate_logs(BinarySuffix) -> %%-------------------------------------------------------------------- -manage_applications(Iterate, Do, Undo, SkipError, ErrorTag, Apps) -> - Iterate(fun (App, Acc) -> - case Do(App) of - ok -> [App | Acc]; - {error, {SkipError, _}} -> Acc; - {error, Reason} -> - lists:foreach(Undo, Acc), - throw({error, {ErrorTag, App, Reason}}) - end - end, [], Apps), - ok. - -start_applications(Apps) -> - manage_applications(fun lists:foldl/3, - fun application:start/1, - fun application:stop/1, - already_started, - cannot_start_application, - Apps). - -stop_applications(Apps) -> - manage_applications(fun lists:foldr/3, - fun application:stop/1, - fun application:start/1, - not_started, - cannot_stop_application, - Apps). - start(normal, []) -> {ok, SupPid} = rabbit_sup:start_link(), diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 973e163b..85db50d7 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -50,6 +50,7 @@ -export([dirty_read_all/1, dirty_foreach_key/2, dirty_dump_log/1]). -export([append_file/2, ensure_parent_dirs_exist/1]). -export([format_stderr/2]). +-export([start_applications/1, stop_applications/1]). -import(mnesia). -import(lists). @@ -108,6 +109,8 @@ -spec(append_file/2 :: (string(), string()) -> 'ok' | {'error', any()}). -spec(ensure_parent_dirs_exist/1 :: (string()) -> 'ok'). -spec(format_stderr/2 :: (string(), [any()]) -> 'true'). +-spec(start_applications/1 :: ([atom()]) -> 'ok'). +-spec(stop_applications/1 :: ([atom()]) -> 'ok'). -endif. @@ -398,3 +401,32 @@ format_stderr(Fmt, Args) -> Port = open_port({fd, 0, 2}, [out]), port_command(Port, io_lib:format(Fmt, Args)), port_close(Port). + +manage_applications(Iterate, Do, Undo, SkipError, ErrorTag, Apps) -> + Iterate(fun (App, Acc) -> + case Do(App) of + ok -> [App | Acc]; + {error, {SkipError, _}} -> Acc; + {error, Reason} -> + lists:foreach(Undo, Acc), + throw({error, {ErrorTag, App, Reason}}) + end + end, [], Apps), + ok. + +start_applications(Apps) -> + manage_applications(fun lists:foldl/3, + fun application:start/1, + fun application:stop/1, + already_started, + cannot_start_application, + Apps). + +stop_applications(Apps) -> + manage_applications(fun lists:foldr/3, + fun application:stop/1, + fun application:start/1, + not_started, + cannot_stop_application, + Apps). + -- cgit v1.2.1 From 61e98a9eb103a4d51a2c682a01ac9bdbee6d3385 Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Thu, 8 Jan 2009 13:36:11 +0000 Subject: Cosmetic --- ebin/rabbit.app | 4 ++-- src/rabbit_alarm.erl | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ebin/rabbit.app b/ebin/rabbit.app index 0d714fdf..5ecd247b 100644 --- a/ebin/rabbit.app +++ b/ebin/rabbit.app @@ -50,8 +50,8 @@ {applications, [kernel, stdlib, sasl, mnesia, os_mon]}, {mod, {rabbit, []}}, {env, [{tcp_listeners, [{"0.0.0.0", 5672}]}, - {extra_startup_steps, []}, + {extra_startup_steps, []}, {default_user, <<"guest">>}, {default_pass, <<"guest">>}, {default_vhost, <<"/">>}, - {memory_alarms, auto}]}]}. + {memory_alarms, auto}]}]}. diff --git a/src/rabbit_alarm.erl b/src/rabbit_alarm.erl index dee71d23..875624ba 100644 --- a/src/rabbit_alarm.erl +++ b/src/rabbit_alarm.erl @@ -53,7 +53,7 @@ -spec(start/1 :: (bool() | 'auto') -> 'ok'). -spec(stop/0 :: () -> 'ok'). -spec(register/2 :: (pid(), mfa_tuple()) -> 'ok'). - + -endif. %%---------------------------------------------------------------------------- @@ -101,7 +101,7 @@ handle_call({register, Pid, HighMemMFA}, end, NewAlertees = dict:store(Pid, HighMemMFA, Alertess), {ok, ok, State#alarms{alertees = NewAlertees}}; - + handle_call(_Request, State) -> {ok, not_understood, State}. @@ -135,7 +135,7 @@ code_change(_OldVsn, State, _Extra) -> %%---------------------------------------------------------------------------- start_memsup() -> - Mod = case os:type() of + Mod = case os:type() of %% memsup doesn't take account of buffers or cache when %% considering "free" memory - therefore on Linux we can %% get memory alarms very easily without any pressure @@ -143,7 +143,7 @@ start_memsup() -> %% our own simple memory monitor. %% {unix, linux} -> rabbit_memsup_linux; - + %% Start memsup programmatically rather than via the %% rabbitmq-server script. This is not quite the right %% thing to do as os_mon checks to see if memsup is -- cgit v1.2.1 From 6ebf31bd90a044cec95301d0bb9008267ca9c3ed Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 8 Jan 2009 14:48:36 +0000 Subject: cosmetic: use multi-line strings --- src/rabbit_error_logger_file_h.erl | 2 +- src/rabbit_mnesia.erl | 4 ++-- src/rabbit_sasl_report_file_h.erl | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rabbit_error_logger_file_h.erl b/src/rabbit_error_logger_file_h.erl index 9a9220b5..183b6984 100644 --- a/src/rabbit_error_logger_file_h.erl +++ b/src/rabbit_error_logger_file_h.erl @@ -46,7 +46,7 @@ init({{File, Suffix}, []}) -> case rabbit_misc:append_file(File, Suffix) of ok -> ok; {error, Error} -> - rabbit_log:error("Failed to append contents of " ++ + rabbit_log:error("Failed to append contents of " "log file '~s' to '~s':~n~p~n", [File, [File, Suffix], Error]) end, diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index d19c37cb..eebb38fa 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -243,8 +243,8 @@ init_db(ClusterNodes) -> %% NB: we cannot use rabbit_log here since %% it may not have been started yet error_logger:warning_msg( - "schema integrity check failed: ~p~n" ++ - "moving database to backup location " ++ + "schema integrity check failed: ~p~n" + "moving database to backup location " "and recreating schema from scratch~n", [Reason]), ok = move_db(), diff --git a/src/rabbit_sasl_report_file_h.erl b/src/rabbit_sasl_report_file_h.erl index 9e4c9c8a..2a365ce1 100644 --- a/src/rabbit_sasl_report_file_h.erl +++ b/src/rabbit_sasl_report_file_h.erl @@ -47,7 +47,7 @@ init({{File, Suffix}, []}) -> case rabbit_misc:append_file(File, Suffix) of ok -> ok; {error, Error} -> - rabbit_log:error("Failed to append contents of " ++ + rabbit_log:error("Failed to append contents of " "sasl log file '~s' to '~s':~n~p~n", [File, [File, Suffix], Error]) end, -- cgit v1.2.1 From de3b777fcce0a02e0a490daa21cb485c27086943 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 8 Jan 2009 15:09:01 +0000 Subject: gen_server2.beam is needed at compile time ...so the compiler can check that any uses of the behaviour implement all the callbacks --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b441fcab..b8fa2cfa 100644 --- a/Makefile +++ b/Makefile @@ -39,9 +39,12 @@ ERL_CALL=erl_call -sname $(RABBITMQ_NODENAME) -e #all: $(EBIN_DIR)/rabbit.boot all: $(TARGETS) -$(EBIN_DIR)/%.beam: $(SOURCE_DIR)/%.erl $(INCLUDE_DIR)/rabbit_framing.hrl $(INCLUDE_DIR)/rabbit.hrl +$(EBIN_DIR)/gen_server2.beam: $(SOURCE_DIR)/gen_server2.erl erlc $(ERLC_OPTS) $< -# ERLC_EMULATOR="erl -smp" erlc $(ERLC_OPTS) $< + +$(EBIN_DIR)/%.beam: $(SOURCE_DIR)/%.erl $(INCLUDE_DIR)/rabbit_framing.hrl $(INCLUDE_DIR)/rabbit.hrl $(EBIN_DIR)/gen_server2.beam + erlc $(ERLC_OPTS) -pa $(EBIN_DIR) $< +# ERLC_EMULATOR="erl -smp" erlc $(ERLC_OPTS) -pa $(EBIN_DIR) $< $(INCLUDE_DIR)/rabbit_framing.hrl: codegen.py $(AMQP_CODEGEN_DIR)/amqp_codegen.py $(AMQP_SPEC_JSON_PATH) $(PYTHON) codegen.py header $(AMQP_SPEC_JSON_PATH) > $@ -- cgit v1.2.1 From f8777b9d1fd08ff4107875d1005eb458398cb764 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 8 Jan 2009 15:45:04 +0000 Subject: turn queue processes into custom gen_servers to avoid long message queues that impact the performance of selective receive --- src/rabbit_amqqueue_process.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 709e355e..ee012dee 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -33,7 +33,7 @@ -include("rabbit.hrl"). -include("rabbit_framing.hrl"). --behaviour(gen_server). +-behaviour(gen_server2). -define(UNSENT_MESSAGE_LIMIT, 100). -define(HIBERNATE_AFTER, 1000). @@ -85,7 +85,7 @@ %%---------------------------------------------------------------------------- start_link(Q) -> - gen_server:start_link(?MODULE, Q, []). + gen_server2:start_link(?MODULE, Q, []). %%---------------------------------------------------------------------------- @@ -551,14 +551,14 @@ handle_call({deliver, Txn, Message}, _From, State) -> handle_call({commit, Txn}, From, State) -> ok = commit_work(Txn, qname(State)), %% optimisation: we reply straight away so the sender can continue - gen_server:reply(From, ok), + gen_server2:reply(From, ok), NewState = process_pending(Txn, State), erase_tx(Txn), noreply(NewState); handle_call({notify_down, ChPid}, From, State) -> %% optimisation: we reply straight away so the sender can continue - gen_server:reply(From, ok), + gen_server2:reply(From, ok), handle_ch_down(ChPid, State); handle_call({basic_get, ChPid, NoAck}, _From, @@ -757,7 +757,7 @@ handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, State) -> handle_info(timeout, State) -> %% TODO: Once we drop support for R11B-5, we can change this to %% {noreply, State, hibernate}; - proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]); + proc_lib:hibernate(gen_server2, enter_loop, [?MODULE, [], State]); handle_info(Info, State) -> ?LOGDEBUG("Info in queue: ~p~n", [Info]), -- cgit v1.2.1 From aa8401fe1ae1a020e62afcc8c43d1ee3f2142602 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 8 Jan 2009 22:04:25 +0000 Subject: replace all cross-node gen_server:casts with gen_server2:casts The latter is order-preserving, whereas the former isn't. Rather than just replacing the casts, I replaced all uses of gen_server with gen_server2 in the affected modules. That way we don't mix modules when talking to a given process. --- src/rabbit_amqqueue.erl | 40 ++++++++++++++++++++-------------------- src/rabbit_router.erl | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 2b9abb29..cf4c324d 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -43,7 +43,7 @@ -export([on_node_down/1]). -import(mnesia). --import(gen_server). +-import(gen_server2). -import(lists). -import(queue). @@ -197,10 +197,10 @@ list(VHostPath) -> map(VHostPath, F) -> rabbit_misc:filter_exit_map(F, list(VHostPath)). info(#amqqueue{ pid = QPid }) -> - gen_server:call(QPid, info). + gen_server2:call(QPid, info). info(#amqqueue{ pid = QPid }, Items) -> - case gen_server:call(QPid, {info, Items}) of + case gen_server2:call(QPid, {info, Items}) of {ok, Res} -> Res; {error, Error} -> throw(Error) end. @@ -209,45 +209,45 @@ info_all(VHostPath) -> map(VHostPath, fun (Q) -> info(Q) end). info_all(VHostPath, Items) -> map(VHostPath, fun (Q) -> info(Q, Items) end). -stat(#amqqueue{pid = QPid}) -> gen_server:call(QPid, stat). +stat(#amqqueue{pid = QPid}) -> gen_server2:call(QPid, stat). stat_all() -> lists:map(fun stat/1, rabbit_misc:dirty_read_all(amqqueue)). delete(#amqqueue{ pid = QPid }, IfUnused, IfEmpty) -> - gen_server:call(QPid, {delete, IfUnused, IfEmpty}). + gen_server2:call(QPid, {delete, IfUnused, IfEmpty}). -purge(#amqqueue{ pid = QPid }) -> gen_server:call(QPid, purge). +purge(#amqqueue{ pid = QPid }) -> gen_server2:call(QPid, purge). deliver(_IsMandatory, true, Txn, Message, QPid) -> - gen_server:call(QPid, {deliver_immediately, Txn, Message}); + gen_server2:call(QPid, {deliver_immediately, Txn, Message}); deliver(true, _IsImmediate, Txn, Message, QPid) -> - gen_server:call(QPid, {deliver, Txn, Message}), + gen_server2:call(QPid, {deliver, Txn, Message}), true; deliver(false, _IsImmediate, Txn, Message, QPid) -> - gen_server:cast(QPid, {deliver, Txn, Message}), + gen_server2:cast(QPid, {deliver, Txn, Message}), true. redeliver(QPid, Messages) -> - gen_server:cast(QPid, {redeliver, Messages}). + gen_server2:cast(QPid, {redeliver, Messages}). requeue(QPid, MsgIds, ChPid) -> - gen_server:cast(QPid, {requeue, MsgIds, ChPid}). + gen_server2:cast(QPid, {requeue, MsgIds, ChPid}). ack(QPid, Txn, MsgIds, ChPid) -> - gen_server:cast(QPid, {ack, Txn, MsgIds, ChPid}). + gen_server2:cast(QPid, {ack, Txn, MsgIds, ChPid}). commit_all(QPids, Txn) -> Timeout = length(QPids) * ?CALL_TIMEOUT, safe_pmap_ok( fun (QPid) -> exit({queue_disappeared, QPid}) end, - fun (QPid) -> gen_server:call(QPid, {commit, Txn}, Timeout) end, + fun (QPid) -> gen_server2:call(QPid, {commit, Txn}, Timeout) end, QPids). rollback_all(QPids, Txn) -> safe_pmap_ok( fun (QPid) -> exit({queue_disappeared, QPid}) end, - fun (QPid) -> gen_server:cast(QPid, {rollback, Txn}) end, + fun (QPid) -> gen_server2:cast(QPid, {rollback, Txn}) end, QPids). notify_down_all(QPids, ChPid) -> @@ -256,25 +256,25 @@ notify_down_all(QPids, ChPid) -> %% we don't care if the queue process has terminated in the %% meantime fun (_) -> ok end, - fun (QPid) -> gen_server:call(QPid, {notify_down, ChPid}, Timeout) end, + fun (QPid) -> gen_server2:call(QPid, {notify_down, ChPid}, Timeout) end, QPids). claim_queue(#amqqueue{pid = QPid}, ReaderPid) -> - gen_server:call(QPid, {claim_queue, ReaderPid}). + gen_server2:call(QPid, {claim_queue, ReaderPid}). basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> - gen_server:call(QPid, {basic_get, ChPid, NoAck}). + gen_server2:call(QPid, {basic_get, ChPid, NoAck}). basic_consume(#amqqueue{pid = QPid}, NoAck, ReaderPid, ChPid, ConsumerTag, ExclusiveConsume, OkMsg) -> - gen_server:call(QPid, {basic_consume, NoAck, ReaderPid, ChPid, + gen_server2:call(QPid, {basic_consume, NoAck, ReaderPid, ChPid, ConsumerTag, ExclusiveConsume, OkMsg}). basic_cancel(#amqqueue{pid = QPid}, ChPid, ConsumerTag, OkMsg) -> - ok = gen_server:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}). + ok = gen_server2:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}). notify_sent(QPid, ChPid) -> - gen_server:cast(QPid, {notify_sent, ChPid}). + gen_server2:cast(QPid, {notify_sent, ChPid}). internal_delete(QueueName) -> rabbit_misc:execute_mnesia_transaction( diff --git a/src/rabbit_router.erl b/src/rabbit_router.erl index ad653a2f..0b36a53c 100644 --- a/src/rabbit_router.erl +++ b/src/rabbit_router.erl @@ -100,7 +100,7 @@ deliver_per_node(NodeQPids, Mandatory = false, Immediate = false, %% than the non-immediate case below. {ok, lists:flatmap( fun ({Node, QPids}) -> - gen_server:cast( + gen_server2:cast( {?SERVER, Node}, {deliver, QPids, Mandatory, Immediate, Txn, Message}), QPids -- cgit v1.2.1 From b53ab4e9ad895f1fe0830cd153482693a2b18af2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 8 Jan 2009 22:53:14 +0000 Subject: replace gen_server:cast with gen_server2:cast in rabbit_limiter The limiter is co-located with channels but interacts with queues which may be on different nodes. It is therefore subject to the message reordering problem of cross-node gen_server:cast - see bug 19749. For consistency I replaced gen_server:call as well, though I left the gen_server:start_link alone in order to avoid the (admittedly small) penalty introduced by gen_server2's modified main loop. --- src/rabbit_limiter.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 3776edd0..e1d92f72 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -31,7 +31,7 @@ -module(rabbit_limiter). --behaviour(gen_server). +-behaviour(gen_server2). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). @@ -70,12 +70,12 @@ start_link(ChPid) -> shutdown(undefined) -> ok; shutdown(LimiterPid) -> - gen_server:cast(LimiterPid, shutdown). + gen_server2:cast(LimiterPid, shutdown). limit(undefined, 0) -> ok; limit(LimiterPid, PrefetchCount) -> - gen_server:cast(LimiterPid, {limit, PrefetchCount}). + gen_server2:cast(LimiterPid, {limit, PrefetchCount}). %% Ask the limiter whether the queue can deliver a message without %% breaching a limit @@ -84,18 +84,18 @@ can_send(undefined, _QPid) -> can_send(LimiterPid, QPid) -> rabbit_misc:with_exit_handler( fun () -> true end, - fun () -> gen_server:call(LimiterPid, {can_send, QPid}) end). + fun () -> gen_server2:call(LimiterPid, {can_send, QPid}) end). %% Let the limiter know that the channel has received some acks from a %% consumer ack(undefined, _Count) -> ok; -ack(LimiterPid, Count) -> gen_server:cast(LimiterPid, {ack, Count}). +ack(LimiterPid, Count) -> gen_server2:cast(LimiterPid, {ack, Count}). register(undefined, _QPid) -> ok; -register(LimiterPid, QPid) -> gen_server:cast(LimiterPid, {register, QPid}). +register(LimiterPid, QPid) -> gen_server2:cast(LimiterPid, {register, QPid}). unregister(undefined, _QPid) -> ok; -unregister(LimiterPid, QPid) -> gen_server:cast(LimiterPid, {unregister, QPid}). +unregister(LimiterPid, QPid) -> gen_server2:cast(LimiterPid, {unregister, QPid}). %%---------------------------------------------------------------------------- %% gen_server callbacks -- cgit v1.2.1 From fbd08ac50a6418da6dc6ec04e048e9bcfd25378e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 8 Jan 2009 22:57:02 +0000 Subject: oops, meant to advertise this as an ordinary gen_server which it still is, since I left the gen_server:start_link in place. --- src/rabbit_limiter.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index e1d92f72..7ecdb6fb 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -31,7 +31,7 @@ -module(rabbit_limiter). --behaviour(gen_server2). +-behaviour(gen_server). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -- cgit v1.2.1 From 7b62fa15b69499ed5b67c39f51126521d3e20879 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 9 Jan 2009 12:12:38 +0000 Subject: first cut of turning rabbit_channel into a gen_server2 --- src/rabbit_channel.erl | 119 ++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index ca2782c7..454701ea 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -33,11 +33,12 @@ -include("rabbit_framing.hrl"). -include("rabbit.hrl"). +-behaviour(gen_server2). + -export([start_link/4, do/2, do/3, shutdown/1]). -export([send_command/2, deliver/4, conserve_memory/2]). -%% callbacks --export([init/2, handle_message/2]). +-export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -record(ch, {state, proxy_pid, reader_pid, writer_pid, transaction_id, tx_participants, next_tag, @@ -62,102 +63,96 @@ %%---------------------------------------------------------------------------- start_link(ReaderPid, WriterPid, Username, VHost) -> - buffering_proxy:start_link(?MODULE, [ReaderPid, WriterPid, - Username, VHost]). + {ok, Pid} = gen_server2:start_link( + ?MODULE, [ReaderPid, WriterPid, Username, VHost], []), + Pid. do(Pid, Method) -> do(Pid, Method, none). do(Pid, Method, Content) -> - Pid ! {method, Method, Content}, - ok. + gen_server2:cast(Pid, {method, Method, Content}). shutdown(Pid) -> - Pid ! terminate, - ok. + gen_server2:cast(Pid, terminate). send_command(Pid, Msg) -> - Pid ! {command, Msg}, - ok. + gen_server2:cast(Pid, {command, Msg}). deliver(Pid, ConsumerTag, AckRequired, Msg) -> - Pid ! {deliver, ConsumerTag, AckRequired, Msg}, - ok. + gen_server2:cast(Pid, {deliver, ConsumerTag, AckRequired, Msg}). conserve_memory(Pid, Conserve) -> - Pid ! {conserve_memory, Conserve}, - ok. + gen_server2:cast(Pid, {conserve_memory, Conserve}). %%--------------------------------------------------------------------------- -init(ProxyPid, [ReaderPid, WriterPid, Username, VHost]) -> +init([ReaderPid, WriterPid, Username, VHost]) -> process_flag(trap_exit, true), link(WriterPid), - %% this is bypassing the proxy so alarms can "jump the queue" and - %% be handled promptly rabbit_alarm:register(self(), {?MODULE, conserve_memory, []}), - #ch{state = starting, - proxy_pid = ProxyPid, - reader_pid = ReaderPid, - writer_pid = WriterPid, - transaction_id = none, - tx_participants = sets:new(), - next_tag = 1, - uncommitted_ack_q = queue:new(), - unacked_message_q = queue:new(), - username = Username, - virtual_host = VHost, - most_recently_declared_queue = <<>>, - consumer_mapping = dict:new()}. - -handle_message({method, Method, Content}, State) -> + {ok, #ch{state = starting, + proxy_pid = self(), + reader_pid = ReaderPid, + writer_pid = WriterPid, + transaction_id = none, + tx_participants = sets:new(), + next_tag = 1, + uncommitted_ack_q = queue:new(), + unacked_message_q = queue:new(), + username = Username, + virtual_host = VHost, + most_recently_declared_queue = <<>>, + consumer_mapping = dict:new()}}. + +handle_call(_Request, _From, State) -> + {noreply, State}. + +handle_cast({method, Method, Content}, State) -> try handle_method(Method, Content, State) of {reply, Reply, NewState} -> ok = rabbit_writer:send_command(NewState#ch.writer_pid, Reply), - NewState; + {noreply, NewState}; {noreply, NewState} -> - NewState; + {noreply, NewState}; stop -> - exit(normal) + %% TODO: this isn't quite right; it results in queues + %% being notified twice and rabbit_writer:shutdown being + %% called twice. + {stop, normal, State} catch exit:{amqp, Error, Explanation, none} -> - terminate({amqp, Error, Explanation, - rabbit_misc:method_record_type(Method)}, - State); + {stop, {amqp, Error, Explanation, + rabbit_misc:method_record_type(Method)}, State}; exit:normal -> - terminate(normal, State); + {stop, normal, State}; _:Reason -> - terminate({Reason, erlang:get_stacktrace()}, State) + {stop, {Reason, erlang:get_stacktrace()}, State} end; -handle_message(terminate, State) -> - terminate(normal, State); +handle_cast(terminate, State) -> + {stop, normal, State}; -handle_message({command, Msg}, State = #ch{writer_pid = WriterPid}) -> +handle_cast({command, Msg}, State = #ch{writer_pid = WriterPid}) -> ok = rabbit_writer:send_command(WriterPid, Msg), - State; + {noreply, State}; -handle_message({deliver, ConsumerTag, AckRequired, Msg}, - State = #ch{proxy_pid = ProxyPid, - writer_pid = WriterPid, - next_tag = DeliveryTag}) -> +handle_cast({deliver, ConsumerTag, AckRequired, Msg}, + State = #ch{proxy_pid = ProxyPid, + writer_pid = WriterPid, + next_tag = DeliveryTag}) -> State1 = lock_message(AckRequired, {DeliveryTag, ConsumerTag, Msg}, State), ok = internal_deliver(WriterPid, ProxyPid, true, ConsumerTag, DeliveryTag, Msg), - State1#ch{next_tag = DeliveryTag + 1}; + {noreply, State1#ch{next_tag = DeliveryTag + 1}}; -handle_message({conserve_memory, Conserve}, State) -> +handle_cast({conserve_memory, Conserve}, State) -> ok = rabbit_writer:send_command( State#ch.writer_pid, #'channel.flow'{active = not(Conserve)}), - State; - -handle_message({'EXIT', _Pid, Reason}, State) -> - terminate(Reason, State); - -handle_message(Other, State) -> - terminate({unexpected_channel_message, Other}, State). + {noreply, State}. -%%--------------------------------------------------------------------------- +handle_info({'EXIT', _Pid, Reason}, State) -> + {noreply, Reason, State}. terminate(Reason, State = #ch{writer_pid = WriterPid}) -> Res = notify_queues(internal_rollback(State)), @@ -165,8 +160,12 @@ terminate(Reason, State = #ch{writer_pid = WriterPid}) -> normal -> ok = Res; _ -> ok end, - rabbit_writer:shutdown(WriterPid), - exit(Reason). + rabbit_writer:shutdown(WriterPid). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%--------------------------------------------------------------------------- return_ok(State, true, _Msg) -> {noreply, State}; return_ok(State, false, Msg) -> {reply, Msg, State}. -- cgit v1.2.1 From 216a2b42ddbd8e05d5a81336e8edaa190ea94c77 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 9 Jan 2009 12:24:15 +0000 Subject: fix normal termination case --- src/rabbit_channel.erl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 454701ea..dae0a96e 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -116,10 +116,7 @@ handle_cast({method, Method, Content}, State) -> {noreply, NewState} -> {noreply, NewState}; stop -> - %% TODO: this isn't quite right; it results in queues - %% being notified twice and rabbit_writer:shutdown being - %% called twice. - {stop, normal, State} + {stop, normal, State#ch{state = terminating}} catch exit:{amqp, Error, Explanation, none} -> {stop, {amqp, Error, Explanation, @@ -154,6 +151,9 @@ handle_cast({conserve_memory, Conserve}, State) -> handle_info({'EXIT', _Pid, Reason}, State) -> {noreply, Reason, State}. +terminate(_Reason, #ch{writer_pid = WriterPid, state = terminating}) -> + rabbit_writer:shutdown(WriterPid); + terminate(Reason, State = #ch{writer_pid = WriterPid}) -> Res = notify_queues(internal_rollback(State)), case Reason of @@ -247,7 +247,6 @@ handle_method(_Method, _, #ch{state = starting}) -> handle_method(#'channel.close'{}, _, State = #ch{writer_pid = WriterPid}) -> ok = notify_queues(internal_rollback(State)), ok = rabbit_writer:send_command(WriterPid, #'channel.close_ok'{}), - ok = rabbit_writer:shutdown(WriterPid), stop; handle_method(#'access.request'{},_, State) -> -- cgit v1.2.1 From 951ad69f21ab300db6de2f94e4d608b4a59ccd8c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 9 Jan 2009 12:26:45 +0000 Subject: get rid of buffering_proxy --- ebin/rabbit.app | 3 +- src/buffering_proxy.erl | 108 ------------------------------------------------ 2 files changed, 1 insertion(+), 110 deletions(-) delete mode 100644 src/buffering_proxy.erl diff --git a/ebin/rabbit.app b/ebin/rabbit.app index 5ecd247b..ca5aec6f 100644 --- a/ebin/rabbit.app +++ b/ebin/rabbit.app @@ -2,8 +2,7 @@ [{description, "RabbitMQ"}, {id, "RabbitMQ"}, {vsn, "%%VERSION%%"}, - {modules, [buffering_proxy, - rabbit_access_control, + {modules, [rabbit_access_control, rabbit_alarm, rabbit_amqqueue, rabbit_amqqueue_process, diff --git a/src/buffering_proxy.erl b/src/buffering_proxy.erl deleted file mode 100644 index 344b719a..00000000 --- a/src/buffering_proxy.erl +++ /dev/null @@ -1,108 +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 Developers of the Original Code are LShift Ltd, -%% Cohesive Financial Technologies LLC, and Rabbit Technologies Ltd. -%% -%% Portions created before 22-Nov-2008 00:00:00 GMT by LShift Ltd, -%% Cohesive Financial Technologies LLC, or Rabbit Technologies Ltd -%% are Copyright (C) 2007-2008 LShift Ltd, Cohesive Financial -%% Technologies LLC, and Rabbit Technologies Ltd. -%% -%% Portions created by LShift Ltd are Copyright (C) 2007-2009 LShift -%% Ltd. Portions created by Cohesive Financial Technologies LLC are -%% Copyright (C) 2007-2009 Cohesive Financial Technologies -%% LLC. Portions created by Rabbit Technologies Ltd are Copyright -%% (C) 2007-2009 Rabbit Technologies Ltd. -%% -%% All Rights Reserved. -%% -%% Contributor(s): ______________________________________. -%% - --module(buffering_proxy). - --export([start_link/2]). - -%% internal - --export([mainloop/4, drain/2]). --export([proxy_loop/3]). - --define(HIBERNATE_AFTER, 5000). - -%%---------------------------------------------------------------------------- - -start_link(M, A) -> - spawn_link( - fun () -> process_flag(trap_exit, true), - ProxyPid = self(), - Ref = make_ref(), - Pid = spawn_link( - fun () -> ProxyPid ! Ref, - mainloop(ProxyPid, Ref, M, - M:init(ProxyPid, A)) end), - proxy_loop(Ref, Pid, empty) - end). - -%%---------------------------------------------------------------------------- - -mainloop(ProxyPid, Ref, M, State) -> - NewState = - receive - {Ref, Messages} -> - NewSt = - lists:foldl(fun (Msg, S) -> - drain(M, M:handle_message(Msg, S)) - end, State, lists:reverse(Messages)), - ProxyPid ! Ref, - NewSt; - Msg -> M:handle_message(Msg, State) - after ?HIBERNATE_AFTER -> - erlang:hibernate(?MODULE, mainloop, - [ProxyPid, Ref, M, State]) - end, - ?MODULE:mainloop(ProxyPid, Ref, M, NewState). - -drain(M, State) -> - receive - Msg -> ?MODULE:drain(M, M:handle_message(Msg, State)) - after 0 -> - State - end. - -proxy_loop(Ref, Pid, State) -> - receive - Ref -> - ?MODULE:proxy_loop( - Ref, Pid, - case State of - empty -> waiting; - waiting -> exit(duplicate_next); - Messages -> Pid ! {Ref, Messages}, empty - end); - {'EXIT', Pid, Reason} -> - exit(Reason); - {'EXIT', _, Reason} -> - exit(Pid, Reason), - ?MODULE:proxy_loop(Ref, Pid, State); - Msg -> - ?MODULE:proxy_loop( - Ref, Pid, - case State of - empty -> [Msg]; - waiting -> Pid ! {Ref, [Msg]}, empty; - Messages -> [Msg | Messages] - end) - after ?HIBERNATE_AFTER -> - erlang:hibernate(?MODULE, proxy_loop, [Ref, Pid, State]) - end. -- cgit v1.2.1 From cc6c502811e9656137431fa3a81ed87de66591db Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 9 Jan 2009 13:39:41 +0000 Subject: get rid of now-superfluous proxy_pid in channel state --- src/rabbit_channel.erl | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index dae0a96e..6abca523 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -40,7 +40,7 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). --record(ch, {state, proxy_pid, reader_pid, writer_pid, +-record(ch, {state, reader_pid, writer_pid, transaction_id, tx_participants, next_tag, uncommitted_ack_q, unacked_message_q, username, virtual_host, @@ -92,7 +92,6 @@ init([ReaderPid, WriterPid, Username, VHost]) -> link(WriterPid), rabbit_alarm:register(self(), {?MODULE, conserve_memory, []}), {ok, #ch{state = starting, - proxy_pid = self(), reader_pid = ReaderPid, writer_pid = WriterPid, transaction_id = none, @@ -135,12 +134,10 @@ handle_cast({command, Msg}, State = #ch{writer_pid = WriterPid}) -> {noreply, State}; handle_cast({deliver, ConsumerTag, AckRequired, Msg}, - State = #ch{proxy_pid = ProxyPid, - writer_pid = WriterPid, + State = #ch{writer_pid = WriterPid, next_tag = DeliveryTag}) -> State1 = lock_message(AckRequired, {DeliveryTag, ConsumerTag, Msg}, State), - ok = internal_deliver(WriterPid, ProxyPid, - true, ConsumerTag, DeliveryTag, Msg), + ok = internal_deliver(WriterPid, true, ConsumerTag, DeliveryTag, Msg), {noreply, State1#ch{next_tag = DeliveryTag + 1}}; handle_cast({conserve_memory, Conserve}, State) -> @@ -284,7 +281,7 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, true -> ok end, {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), - Participants = ack(State#ch.proxy_pid, TxnKey, Acked), + Participants = ack(TxnKey, Acked), {noreply, case TxnKey of none -> State#ch{unacked_message_q = Remaining}; _ -> NewUAQ = queue:join(State#ch.uncommitted_ack_q, @@ -297,12 +294,12 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, handle_method(#'basic.get'{queue = QueueNameBin, no_ack = NoAck}, - _, State = #ch{ proxy_pid = ProxyPid, writer_pid = WriterPid, + _, State = #ch{ writer_pid = WriterPid, next_tag = DeliveryTag }) -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), case rabbit_amqqueue:with_or_die( QueueName, - fun (Q) -> rabbit_amqqueue:basic_get(Q, ProxyPid, NoAck) end) of + fun (Q) -> rabbit_amqqueue:basic_get(Q, self(), NoAck) end) of {ok, MessageCount, Msg = {_QName, _QPid, _MsgId, Redelivered, #basic_message{exchange_name = ExchangeName, @@ -328,8 +325,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, no_ack = NoAck, exclusive = ExclusiveConsume, nowait = NoWait}, - _, State = #ch{ proxy_pid = ProxyPid, - reader_pid = ReaderPid, + _, State = #ch{ reader_pid = ReaderPid, consumer_mapping = ConsumerMapping }) -> case dict:find(ConsumerTag, ConsumerMapping) of error -> @@ -347,7 +343,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, QueueName, fun (Q) -> rabbit_amqqueue:basic_consume( - Q, NoAck, ReaderPid, ProxyPid, + Q, NoAck, ReaderPid, self(), ActualConsumerTag, ExclusiveConsume, ok_msg(NoWait, #'basic.consume_ok'{ consumer_tag = ActualConsumerTag})) @@ -378,8 +374,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, nowait = NoWait}, - _, State = #ch{ proxy_pid = ProxyPid, - consumer_mapping = ConsumerMapping }) -> + _, State = #ch{consumer_mapping = ConsumerMapping }) -> OkMsg = #'basic.cancel_ok'{consumer_tag = ConsumerTag}, case dict:find(ConsumerTag, ConsumerMapping) of error -> @@ -400,7 +395,7 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, %% cancel_ok ourselves it might overtake a %% message sent previously by the queue. rabbit_amqqueue:basic_cancel( - Q, ProxyPid, ConsumerTag, + Q, self(), ConsumerTag, ok_msg(NoWait, #'basic.cancel_ok'{ consumer_tag = ConsumerTag})) end) of @@ -418,7 +413,6 @@ handle_method(#'basic.qos'{}, _, State) -> handle_method(#'basic.recover'{requeue = true}, _, State = #ch{ transaction_id = none, - proxy_pid = ProxyPid, unacked_message_q = UAMQ }) -> ok = fold_per_queue( fun (QPid, MsgIds, ok) -> @@ -427,14 +421,13 @@ handle_method(#'basic.recover'{requeue = true}, %% order. To keep it happy we reverse the id list %% since we are given them in reverse order. rabbit_amqqueue:requeue( - QPid, lists:reverse(MsgIds), ProxyPid) + QPid, lists:reverse(MsgIds), self()) end, ok, UAMQ), %% No answer required, apparently! {noreply, State#ch{unacked_message_q = queue:new()}}; handle_method(#'basic.recover'{requeue = false}, _, State = #ch{ transaction_id = none, - proxy_pid = ProxyPid, writer_pid = WriterPid, unacked_message_q = UAMQ }) -> lists:foreach( @@ -452,8 +445,7 @@ handle_method(#'basic.recover'{requeue = false}, %% %% FIXME: should we allocate a fresh DeliveryTag? ok = internal_deliver( - WriterPid, ProxyPid, - false, ConsumerTag, DeliveryTag, + WriterPid, false, ConsumerTag, DeliveryTag, {QName, QPid, MsgId, true, Message}) end, queue:to_list(UAMQ)), %% No answer required, apparently! @@ -742,10 +734,10 @@ add_tx_participants(MoreP, State = #ch{tx_participants = Participants}) -> State#ch{tx_participants = sets:union(Participants, sets:from_list(MoreP))}. -ack(ProxyPid, TxnKey, UAQ) -> +ack(TxnKey, UAQ) -> fold_per_queue( fun (QPid, MsgIds, L) -> - ok = rabbit_amqqueue:ack(QPid, TxnKey, MsgIds, ProxyPid), + ok = rabbit_amqqueue:ack(QPid, TxnKey, MsgIds, self()), [QPid | L] end, [], UAQ). @@ -797,7 +789,7 @@ fold_per_queue(F, Acc0, UAQ) -> dict:fold(fun (QPid, MsgIds, Acc) -> F(QPid, MsgIds, Acc) end, Acc0, D). -notify_queues(#ch{proxy_pid = ProxyPid, consumer_mapping = Consumers}) -> +notify_queues(#ch{consumer_mapping = Consumers}) -> rabbit_amqqueue:notify_down_all( [QPid || QueueName <- sets:to_list( @@ -809,7 +801,7 @@ notify_queues(#ch{proxy_pid = ProxyPid, consumer_mapping = Consumers}) -> %% queue has been deleted in the meantime {error, not_found} -> QPid = none, false end], - ProxyPid). + self()). is_message_persistent(#content{properties = #'P_basic'{ delivery_mode = Mode}}) -> @@ -827,7 +819,7 @@ lock_message(true, MsgStruct, State = #ch{unacked_message_q = UAMQ}) -> lock_message(false, _MsgStruct, State) -> State. -internal_deliver(WriterPid, ChPid, Notify, ConsumerTag, DeliveryTag, +internal_deliver(WriterPid, Notify, ConsumerTag, DeliveryTag, {_QName, QPid, _MsgId, Redelivered, #basic_message{exchange_name = ExchangeName, routing_key = RoutingKey, @@ -839,6 +831,6 @@ internal_deliver(WriterPid, ChPid, Notify, ConsumerTag, DeliveryTag, routing_key = RoutingKey}, ok = case Notify of true -> rabbit_writer:send_command_and_notify( - WriterPid, QPid, ChPid, M, Content); + WriterPid, QPid, self(), M, Content); false -> rabbit_writer:send_command(WriterPid, M, Content) end. -- cgit v1.2.1 From b5f4ceb5b42314c3d71710006f57e34fb512abf2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 13 Jan 2009 19:00:50 +0000 Subject: auto-generate rabbit.app module list --- Makefile | 10 +++++++--- ebin/rabbit.app | 57 ------------------------------------------------------ ebin/rabbit_app.in | 20 +++++++++++++++++++ generate_app | 10 ++++++++++ 4 files changed, 37 insertions(+), 60 deletions(-) delete mode 100644 ebin/rabbit.app create mode 100644 ebin/rabbit_app.in create mode 100644 generate_app diff --git a/Makefile b/Makefile index 84bb3dfe..e75f2d28 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,8 @@ SOURCE_DIR=src EBIN_DIR=ebin INCLUDE_DIR=include SOURCES=$(wildcard $(SOURCE_DIR)/*.erl) -TARGETS=$(EBIN_DIR)/rabbit_framing.beam $(patsubst $(SOURCE_DIR)/%.erl, $(EBIN_DIR)/%.beam,$(SOURCES)) +BEAM_TARGETS=$(EBIN_DIR)/rabbit_framing.beam $(patsubst $(SOURCE_DIR)/%.erl, $(EBIN_DIR)/%.beam,$(SOURCES)) +TARGETS=$(EBIN_DIR)/rabbit.app $(BEAM_TARGETS) WEB_URL=http://stage.rabbitmq.com/ MANPAGES=$(patsubst %.pod, %.gz, $(wildcard docs/*.[0-9].pod)) @@ -39,6 +40,9 @@ ERL_CALL=erl_call -sname $(RABBITMQ_NODENAME) -e #all: $(EBIN_DIR)/rabbit.boot all: $(TARGETS) +$(EBIN_DIR)/rabbit.app: $(EBIN_DIR)/rabbit_app.in $(BEAM_TARGETS) + escript generate_app $(EBIN_DIR) < $< > $@ + $(EBIN_DIR)/%.beam: $(SOURCE_DIR)/%.erl $(INCLUDE_DIR)/rabbit_framing.hrl $(INCLUDE_DIR)/rabbit.hrl erlc $(ERLC_OPTS) $< # ERLC_EMULATOR="erl -smp" erlc $(ERLC_OPTS) $< @@ -57,7 +61,7 @@ dialyze: $(TARGETS) clean: cleandb rm -f $(EBIN_DIR)/*.beam - rm -f $(EBIN_DIR)/rabbit.boot $(EBIN_DIR)/rabbit.script + rm -f $(EBIN_DIR)/rabbit.app $(EBIN_DIR)/rabbit.boot $(EBIN_DIR)/rabbit.script rm -f $(INCLUDE_DIR)/rabbit_framing.hrl $(SOURCE_DIR)/rabbit_framing.erl codegen.pyc rm -f docs/*.[0-9].gz @@ -123,7 +127,7 @@ srcdist: distclean cp BUILD.in $(TARGET_SRC_DIR)/BUILD elinks -dump -no-references -no-numbering $(WEB_URL)build-server.html \ >> $(TARGET_SRC_DIR)/BUILD - sed -i 's/%%VERSION%%/$(VERSION)/' $(TARGET_SRC_DIR)/ebin/rabbit.app + sed -i 's/%%VERSION%%/$(VERSION)/' $(TARGET_SRC_DIR)/ebin/rabbit_app.in cp -r $(AMQP_CODEGEN_DIR)/* $(TARGET_SRC_DIR)/codegen/ cp codegen.py Makefile $(TARGET_SRC_DIR) diff --git a/ebin/rabbit.app b/ebin/rabbit.app deleted file mode 100644 index 5ecd247b..00000000 --- a/ebin/rabbit.app +++ /dev/null @@ -1,57 +0,0 @@ -{application, rabbit, %% -*- erlang -*- - [{description, "RabbitMQ"}, - {id, "RabbitMQ"}, - {vsn, "%%VERSION%%"}, - {modules, [buffering_proxy, - rabbit_access_control, - rabbit_alarm, - rabbit_amqqueue, - rabbit_amqqueue_process, - rabbit_amqqueue_sup, - rabbit_binary_generator, - rabbit_binary_parser, - rabbit_channel, - rabbit_control, - rabbit, - rabbit_error_logger, - rabbit_error_logger_file_h, - rabbit_exchange, - rabbit_framing_channel, - rabbit_framing, - rabbit_heartbeat, - rabbit_load, - rabbit_log, - rabbit_memsup_linux, - rabbit_misc, - rabbit_mnesia, - rabbit_multi, - rabbit_networking, - rabbit_node_monitor, - rabbit_persister, - rabbit_reader, - rabbit_router, - rabbit_sasl_report_file_h, - rabbit_sup, - rabbit_tests, - rabbit_tracer, - rabbit_writer, - tcp_acceptor, - tcp_acceptor_sup, - tcp_client_sup, - tcp_listener, - tcp_listener_sup]}, - {registered, [rabbit_amqqueue_sup, - rabbit_log, - rabbit_node_monitor, - rabbit_persister, - rabbit_router, - rabbit_sup, - rabbit_tcp_client_sup]}, - {applications, [kernel, stdlib, sasl, mnesia, os_mon]}, - {mod, {rabbit, []}}, - {env, [{tcp_listeners, [{"0.0.0.0", 5672}]}, - {extra_startup_steps, []}, - {default_user, <<"guest">>}, - {default_pass, <<"guest">>}, - {default_vhost, <<"/">>}, - {memory_alarms, auto}]}]}. diff --git a/ebin/rabbit_app.in b/ebin/rabbit_app.in new file mode 100644 index 00000000..e2f36c0f --- /dev/null +++ b/ebin/rabbit_app.in @@ -0,0 +1,20 @@ +{application, rabbit, %% -*- erlang -*- + [{description, "RabbitMQ"}, + {id, "RabbitMQ"}, + {vsn, "%%VERSION%%"}, + {modules, []}, + {registered, [rabbit_amqqueue_sup, + rabbit_log, + rabbit_node_monitor, + rabbit_persister, + rabbit_router, + rabbit_sup, + rabbit_tcp_client_sup]}, + {applications, [kernel, stdlib, sasl, mnesia, os_mon]}, + {mod, {rabbit, []}}, + {env, [{tcp_listeners, [{"0.0.0.0", 5672}]}, + {extra_startup_steps, []}, + {default_user, <<"guest">>}, + {default_pass, <<"guest">>}, + {default_vhost, <<"/">>}, + {memory_alarms, auto}]}]}. diff --git a/generate_app b/generate_app new file mode 100644 index 00000000..1d75e83c --- /dev/null +++ b/generate_app @@ -0,0 +1,10 @@ +#!/usr/bin/env escript +%% -*- erlang -*- + +main([BeamDir]) -> + Modules = [list_to_atom(filename:basename(F, ".beam")) || + F <- filelib:wildcard("*.beam", BeamDir)], + {ok, {application, Application, Properties}} = io:read(""), + NewProperties = lists:keyreplace(modules, 1, Properties, + {modules, Modules}), + io:format("~p", [{application, Application, NewProperties}]). -- cgit v1.2.1 From 0b61669747325219a672c43bd182f7e539341118 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 13 Jan 2009 19:46:18 +0000 Subject: oops --- generate_app | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_app b/generate_app index 1d75e83c..bb6f7516 100644 --- a/generate_app +++ b/generate_app @@ -7,4 +7,4 @@ main([BeamDir]) -> {ok, {application, Application, Properties}} = io:read(""), NewProperties = lists:keyreplace(modules, 1, Properties, {modules, Modules}), - io:format("~p", [{application, Application, NewProperties}]). + io:format("~p.", [{application, Application, NewProperties}]). -- cgit v1.2.1 From a8505e2361de1d8d758d7bc2b7aa1b7c5690a573 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 14 Jan 2009 12:03:38 +0000 Subject: make the channel process hibernate after 1s of idleness to conserve resources --- src/rabbit_channel.erl | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 6abca523..9659d080 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -46,6 +46,8 @@ username, virtual_host, most_recently_declared_queue, consumer_mapping}). +-define(HIBERNATE_AFTER, 1000). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -105,15 +107,15 @@ init([ReaderPid, WriterPid, Username, VHost]) -> consumer_mapping = dict:new()}}. handle_call(_Request, _From, State) -> - {noreply, State}. + noreply(State). handle_cast({method, Method, Content}, State) -> try handle_method(Method, Content, State) of {reply, Reply, NewState} -> ok = rabbit_writer:send_command(NewState#ch.writer_pid, Reply), - {noreply, NewState}; + noreply(NewState); {noreply, NewState} -> - {noreply, NewState}; + noreply(NewState); stop -> {stop, normal, State#ch{state = terminating}} catch @@ -131,22 +133,27 @@ handle_cast(terminate, State) -> handle_cast({command, Msg}, State = #ch{writer_pid = WriterPid}) -> ok = rabbit_writer:send_command(WriterPid, Msg), - {noreply, State}; + noreply(State); handle_cast({deliver, ConsumerTag, AckRequired, Msg}, State = #ch{writer_pid = WriterPid, next_tag = DeliveryTag}) -> State1 = lock_message(AckRequired, {DeliveryTag, ConsumerTag, Msg}, State), ok = internal_deliver(WriterPid, true, ConsumerTag, DeliveryTag, Msg), - {noreply, State1#ch{next_tag = DeliveryTag + 1}}; + noreply(State1#ch{next_tag = DeliveryTag + 1}); handle_cast({conserve_memory, Conserve}, State) -> ok = rabbit_writer:send_command( State#ch.writer_pid, #'channel.flow'{active = not(Conserve)}), - {noreply, State}. + noreply(State). handle_info({'EXIT', _Pid, Reason}, State) -> - {noreply, Reason, State}. + {noreply, Reason, State}; + +handle_info(timeout, State) -> + %% TODO: Once we drop support for R11B-5, we can change this to + %% {noreply, State, hibernate}; + proc_lib:hibernate(gen_server2, enter_loop, [?MODULE, [], State]). terminate(_Reason, #ch{writer_pid = WriterPid, state = terminating}) -> rabbit_writer:shutdown(WriterPid); @@ -164,6 +171,8 @@ code_change(_OldVsn, State, _Extra) -> %%--------------------------------------------------------------------------- +noreply(NewState) -> {noreply, NewState, ?HIBERNATE_AFTER}. + return_ok(State, true, _Msg) -> {noreply, State}; return_ok(State, false, Msg) -> {reply, Msg, State}. -- cgit v1.2.1 From 00c7b0249193201cf4e7eb209895802595c32684 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 14 Jan 2009 12:09:31 +0000 Subject: fix bug in handling of writer exit --- src/rabbit_channel.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 9659d080..d508aa81 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -148,7 +148,7 @@ handle_cast({conserve_memory, Conserve}, State) -> noreply(State). handle_info({'EXIT', _Pid, Reason}, State) -> - {noreply, Reason, State}; + {stop, Reason, State}; handle_info(timeout, State) -> %% TODO: Once we drop support for R11B-5, we can change this to -- cgit v1.2.1 From 3111476762c0206c18d77ffc56745fcdeb5da26b Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 15 Jan 2009 14:13:25 +1300 Subject: Add "amq.match" builtin exchange; accept "headers" in exchange.declare; and stub out routing implementation for headers exchanges. --- src/rabbit_access_control.erl | 1 + src/rabbit_exchange.erl | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index b73090fc..e2d96c71 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -186,6 +186,7 @@ add_vhost(VHostPath) -> [{<<"">>, direct}, {<<"amq.direct">>, direct}, {<<"amq.topic">>, topic}, + {<<"amq.match">>, headers}, {<<"amq.fanout">>, fanout}]], ok; [_] -> diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 925c335c..03478a4d 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -145,6 +145,8 @@ check_type(<<"direct">>) -> direct; check_type(<<"topic">>) -> topic; +check_type(<<"headers">>) -> + headers; check_type(T) -> rabbit_misc:protocol_error( command_invalid, "invalid exchange type '~s'", [T]). @@ -252,6 +254,9 @@ route(#exchange{name = Name, type = topic}, RoutingKey) -> topic_matches(BindingKey, RoutingKey)] end); +route(X = #exchange{type = headers}, _RoutingKey) -> + exit(headers_unimplemented); + route(X = #exchange{type = fanout}, _) -> route_internal(X, '_'); -- cgit v1.2.1 From 28333003d191d6a487f31704004fe671a9cf1813 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 15 Jan 2009 14:23:29 +1300 Subject: Depend on generate_app when deciding whether to rebuild rabbit.app. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e75f2d28..cc070d9f 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ ERL_CALL=erl_call -sname $(RABBITMQ_NODENAME) -e #all: $(EBIN_DIR)/rabbit.boot all: $(TARGETS) -$(EBIN_DIR)/rabbit.app: $(EBIN_DIR)/rabbit_app.in $(BEAM_TARGETS) +$(EBIN_DIR)/rabbit.app: $(EBIN_DIR)/rabbit_app.in $(BEAM_TARGETS) generate_app escript generate_app $(EBIN_DIR) < $< > $@ $(EBIN_DIR)/%.beam: $(SOURCE_DIR)/%.erl $(INCLUDE_DIR)/rabbit_framing.hrl $(INCLUDE_DIR)/rabbit.hrl -- cgit v1.2.1 From 6727fa1875d711c41abaa68b27663db4a2b59ce5 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 15 Jan 2009 14:23:59 +1300 Subject: Use io:read('') instead of io:read("") to avoid a "prompt" of "[]" ending up in the output. --- generate_app | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_app b/generate_app index bb6f7516..62301292 100644 --- a/generate_app +++ b/generate_app @@ -4,7 +4,7 @@ main([BeamDir]) -> Modules = [list_to_atom(filename:basename(F, ".beam")) || F <- filelib:wildcard("*.beam", BeamDir)], - {ok, {application, Application, Properties}} = io:read(""), + {ok, {application, Application, Properties}} = io:read(''), NewProperties = lists:keyreplace(modules, 1, Properties, {modules, Modules}), io:format("~p.", [{application, Application, NewProperties}]). -- cgit v1.2.1 From 100a9502fac186848d47cf51c11cbb4a72d63cd5 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 15 Jan 2009 16:13:58 +1300 Subject: Implement routing for headers exchange. --- src/rabbit_channel.erl | 2 +- src/rabbit_exchange.erl | 111 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 101 insertions(+), 12 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index ca2782c7..cbdc9e48 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -273,7 +273,7 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, routing_key = RoutingKey, content = DecodedContent, persistent_key = PersistentKey}, - rabbit_exchange:route(Exchange, RoutingKey), State)}; + rabbit_exchange:route(Exchange, RoutingKey, DecodedContent), State)}; handle_method(#'basic.ack'{delivery_tag = DeliveryTag, multiple = Multiple}, diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 03478a4d..a4e6d219 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -37,11 +37,11 @@ -export([recover/0, declare/5, lookup/1, lookup_or_die/1, list/1, info/1, info/2, info_all/1, info_all/2, simple_publish/6, simple_publish/3, - route/2]). + route/3]). -export([add_binding/4, delete_binding/4, list_bindings/1]). -export([delete/2]). -export([delete_bindings_for_queue/1]). --export([check_type/1, assert_type/2, topic_matches/2]). +-export([check_type/1, assert_type/2, topic_matches/2, headers_match/2]). %% EXTENDED API -export([list_exchange_bindings/1]). @@ -77,7 +77,7 @@ (bool(), bool(), exchange_name(), routing_key(), binary(), binary()) -> publish_res()). -spec(simple_publish/3 :: (bool(), bool(), message()) -> publish_res()). --spec(route/2 :: (exchange(), routing_key()) -> [pid()]). +-spec(route/3 :: (exchange(), routing_key(), decoded_content()) -> [pid()]). -spec(add_binding/4 :: (exchange_name(), queue_name(), routing_key(), amqp_table()) -> bind_res() | {'error', 'durability_settings_incompatible'}). @@ -88,6 +88,7 @@ [{exchange_name(), queue_name(), routing_key(), amqp_table()}]). -spec(delete_bindings_for_queue/1 :: (queue_name()) -> 'ok'). -spec(topic_matches/2 :: (binary(), binary()) -> bool()). +-spec(headers_match/2 :: (amqp_table(), amqp_table()) -> bool()). -spec(delete/2 :: (exchange_name(), bool()) -> 'ok' | not_found() | {'error', 'in_use'}). -spec(list_queue_bindings/1 :: (queue_name()) -> @@ -213,15 +214,19 @@ simple_publish(Mandatory, Immediate, ExchangeName, RoutingKeyBin, %% Usable by Erlang code that wants to publish messages. simple_publish(Mandatory, Immediate, Message = #basic_message{exchange_name = ExchangeName, - routing_key = RoutingKey}) -> + routing_key = RoutingKey, + content = Content}) -> case lookup(ExchangeName) of {ok, Exchange} -> - QPids = route(Exchange, RoutingKey), + QPids = route(Exchange, RoutingKey, Content), rabbit_router:deliver(QPids, Mandatory, Immediate, none, Message); {error, Error} -> {error, Error} end. +sort_arguments(Arguments) -> + lists:keysort(1, Arguments). + %% return the list of qpids to which a message with a given routing %% key, sent to a particular exchange, should be delivered. %% @@ -230,7 +235,7 @@ simple_publish(Mandatory, Immediate, %% current exchange types that is at most once. %% %% TODO: Maybe this should be handled by a cursor instead. -route(#exchange{name = Name, type = topic}, RoutingKey) -> +route(#exchange{name = Name, type = topic}, RoutingKey, _Content) -> Query = qlc:q([QName || #route{binding = #binding{ exchange_name = ExchangeName, @@ -254,13 +259,37 @@ route(#exchange{name = Name, type = topic}, RoutingKey) -> topic_matches(BindingKey, RoutingKey)] end); -route(X = #exchange{type = headers}, _RoutingKey) -> - exit(headers_unimplemented); +route(#exchange{name = Name, type = headers}, + _RoutingKey, + #content{properties = #'P_basic'{headers = Headers0}}) -> + Headers = case Headers0 of + undefined -> []; + _ -> sort_arguments(Headers0) + end, + Query = qlc:q([QName || + #route{binding = #binding{exchange_name = ExchangeName, + queue_name = QName, + args = Spec}} + <- mnesia:table(route), + ExchangeName == Name, + headers_match(Spec, Headers)]), + lookup_qpids( + try + mnesia:async_dirty(fun qlc:e/1, [Query]) + catch exit:{aborted, {badarg, _}} -> + %% work around OTP-7025, which was fixed in R12B-1, by + %% falling back on a less efficient method + [QName || #route{binding = #binding{queue_name = QName, args = Spec}} + <- mnesia:dirty_match_object( + #route{binding = #binding{exchange_name = Name, + _ = '_'}}), + headers_match(Spec, Headers)] + end); -route(X = #exchange{type = fanout}, _) -> +route(X = #exchange{type = fanout}, _RoutingKey, _Content) -> route_internal(X, '_'); -route(X = #exchange{type = direct}, RoutingKey) -> +route(X = #exchange{type = direct}, RoutingKey, _Content) -> route_internal(X, RoutingKey). route_internal(#exchange{name = Name}, RoutingKey) -> @@ -382,7 +411,7 @@ sync_binding(ExchangeName, QueueName, RoutingKey, Arguments, Durable, Fun) -> Binding = #binding{exchange_name = ExchangeName, queue_name = QueueName, key = RoutingKey, - args = Arguments}, + args = sort_arguments(Arguments)}, ok = case Durable of true -> Fun(durable_routes, #route{binding = Binding}, write); false -> ok @@ -434,6 +463,66 @@ reverse_binding(#binding{exchange_name = Exchange, key = Key, args = Args}. +default_headers_match_kind() -> all. + +parse_x_match(<<"all">>) -> all; +parse_x_match(<<"any">>) -> any; +parse_x_match(Other) -> + rabbit_log:warning("Invalid x-match field value ~p; expected all or any", [Other]), + default_headers_match_kind(). + +%% Horrendous matching algorithm. Depends for its merge-like +%% (linear-time) behaviour on the lists:keysort (sort_arguments) that +%% route/3 and sync_binding/6 do. +%% +%% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +%% In other words: REQUIRES BOTH PATTERN AND DATA TO BE SORTED ASCENDING BY KEY. +%% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +%% +headers_match(Pattern, Data) -> + MatchKind = case lists:keysearch(<<"x-match">>, 1, Pattern) of + {value, {_, longstr, MK}} -> parse_x_match(MK); + {value, {_, Type, MK}} -> + rabbit_log:warning("Invalid x-match field type ~p (value ~p); expected longstr", + [Type, MK]), + default_headers_match_kind(); + _ -> default_headers_match_kind() + end, + headers_match(Pattern, Data, true, false, MatchKind). + +headers_match([], _Data, AllMatch, _AnyMatch, all) -> + AllMatch; +headers_match([], _Data, _AllMatch, AnyMatch, any) -> + AnyMatch; +headers_match([{<<"x-", _/binary>>, _PT, _PV} | PRest], D, AllMatch, AnyMatch, MatchKind) -> + headers_match(PRest, D, AllMatch, AnyMatch, MatchKind); +headers_match(_Pattern, [], _AllMatch, AnyMatch, MatchKind) -> + headers_match([], [], false, AnyMatch, MatchKind); +headers_match(P = [{PK, _PT, _PV} | _], [{DK, _DT, _DV} | DRest], AllMatch, AnyMatch, MatchKind) + when PK > DK -> + headers_match(P, DRest, AllMatch, AnyMatch, MatchKind); +headers_match([{PK, _PT, _PV} | PRest], D = [{DK, _DT, _DV} | _], _AllMatch, AnyMatch, MatchKind) + when PK < DK -> + headers_match(PRest, D, false, AnyMatch, MatchKind); +headers_match([{PK, PT, PV} | PRest], [{DK, DT, DV} | DRest], AllMatch, AnyMatch, MatchKind) + when PK == DK -> + if + PT == void -> + %% It's not properly specified, but a "no value" in a + %% pattern field is supposed to mean simple presence of + %% the corresponding data field. I've interpreted that to + %% mean a type of "void" for the pattern field. + headers_match(PRest, DRest, AllMatch, true, MatchKind); + PT =/= DT -> + %% Similarly, it's not specified, but I assume that a + %% mismatched type causes a mismatched value. + headers_match(PRest, DRest, false, AnyMatch, MatchKind); + PV == DV -> + headers_match(PRest, DRest, AllMatch, true, MatchKind); + true -> + headers_match(PRest, DRest, false, AnyMatch, MatchKind) + end. + split_topic_key(Key) -> {ok, KeySplit} = regexp:split(binary_to_list(Key), "\\."), KeySplit. -- cgit v1.2.1 From 99a94debffc6fc924a6fec58c92b461959ad653d Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 15 Jan 2009 16:51:58 +1300 Subject: The PDF requires amq.match. The XML requires amq.headers. Create both. --- src/rabbit_access_control.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index e2d96c71..36270efd 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -186,7 +186,8 @@ add_vhost(VHostPath) -> [{<<"">>, direct}, {<<"amq.direct">>, direct}, {<<"amq.topic">>, topic}, - {<<"amq.match">>, headers}, + {<<"amq.match">>, headers}, %% per 0-9-1 pdf + {<<"amq.headers">>, headers}, %% per 0-9-1 xml {<<"amq.fanout">>, fanout}]], ok; [_] -> -- cgit v1.2.1 From ff5698ef609c369c4cc4a64a15ab08009b86233e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 15 Jan 2009 13:43:06 +0000 Subject: cosmetic and minor refactoring --- src/rabbit_exchange.erl | 72 +++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index a4e6d219..c83dd38b 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -264,13 +264,13 @@ route(#exchange{name = Name, type = headers}, #content{properties = #'P_basic'{headers = Headers0}}) -> Headers = case Headers0 of undefined -> []; - _ -> sort_arguments(Headers0) + _ -> sort_arguments(Headers0) end, Query = qlc:q([QName || - #route{binding = #binding{exchange_name = ExchangeName, - queue_name = QName, - args = Spec}} - <- mnesia:table(route), + #route{binding = #binding{ + exchange_name = ExchangeName, + queue_name = QName, + args = Spec}} <- mnesia:table(route), ExchangeName == Name, headers_match(Spec, Headers)]), lookup_qpids( @@ -279,10 +279,11 @@ route(#exchange{name = Name, type = headers}, catch exit:{aborted, {badarg, _}} -> %% work around OTP-7025, which was fixed in R12B-1, by %% falling back on a less efficient method - [QName || #route{binding = #binding{queue_name = QName, args = Spec}} - <- mnesia:dirty_match_object( - #route{binding = #binding{exchange_name = Name, - _ = '_'}}), + [QName || #route{binding = #binding{queue_name = QName, + args = Spec}} <- + mnesia:dirty_match_object( + #route{binding = #binding{exchange_name = Name, + _ = '_'}}), headers_match(Spec, Headers)] end); @@ -468,7 +469,8 @@ default_headers_match_kind() -> all. parse_x_match(<<"all">>) -> all; parse_x_match(<<"any">>) -> any; parse_x_match(Other) -> - rabbit_log:warning("Invalid x-match field value ~p; expected all or any", [Other]), + rabbit_log:warning("Invalid x-match field value ~p; expected all or any", + [Other]), default_headers_match_kind(). %% Horrendous matching algorithm. Depends for its merge-like @@ -483,7 +485,8 @@ headers_match(Pattern, Data) -> MatchKind = case lists:keysearch(<<"x-match">>, 1, Pattern) of {value, {_, longstr, MK}} -> parse_x_match(MK); {value, {_, Type, MK}} -> - rabbit_log:warning("Invalid x-match field type ~p (value ~p); expected longstr", + rabbit_log:warning("Invalid x-match field type ~p " + "(value ~p); expected longstr", [Type, MK]), default_headers_match_kind(); _ -> default_headers_match_kind() @@ -494,34 +497,33 @@ headers_match([], _Data, AllMatch, _AnyMatch, all) -> AllMatch; headers_match([], _Data, _AllMatch, AnyMatch, any) -> AnyMatch; -headers_match([{<<"x-", _/binary>>, _PT, _PV} | PRest], D, AllMatch, AnyMatch, MatchKind) -> - headers_match(PRest, D, AllMatch, AnyMatch, MatchKind); +headers_match([{<<"x-", _/binary>>, _PT, _PV} | PRest], Data, + AllMatch, AnyMatch, MatchKind) -> + headers_match(PRest, Data, AllMatch, AnyMatch, MatchKind); headers_match(_Pattern, [], _AllMatch, AnyMatch, MatchKind) -> headers_match([], [], false, AnyMatch, MatchKind); -headers_match(P = [{PK, _PT, _PV} | _], [{DK, _DT, _DV} | DRest], AllMatch, AnyMatch, MatchKind) - when PK > DK -> - headers_match(P, DRest, AllMatch, AnyMatch, MatchKind); -headers_match([{PK, _PT, _PV} | PRest], D = [{DK, _DT, _DV} | _], _AllMatch, AnyMatch, MatchKind) - when PK < DK -> - headers_match(PRest, D, false, AnyMatch, MatchKind); -headers_match([{PK, PT, PV} | PRest], [{DK, DT, DV} | DRest], AllMatch, AnyMatch, MatchKind) - when PK == DK -> - if - PT == void -> - %% It's not properly specified, but a "no value" in a - %% pattern field is supposed to mean simple presence of - %% the corresponding data field. I've interpreted that to - %% mean a type of "void" for the pattern field. - headers_match(PRest, DRest, AllMatch, true, MatchKind); - PT =/= DT -> +headers_match(Pattern = [{PK, _PT, _PV} | _], [{DK, _DT, _DV} | DRest], + AllMatch, AnyMatch, MatchKind) when PK > DK -> + headers_match(Pattern, DRest, AllMatch, AnyMatch, MatchKind); +headers_match([{PK, _PT, _PV} | PRest], Data = [{DK, _DT, _DV} | _], + _AllMatch, AnyMatch, MatchKind) when PK < DK -> + headers_match(PRest, Data, false, AnyMatch, MatchKind); +headers_match([{PK, PT, PV} | PRest], [{DK, DT, DV} | DRest], + AllMatch, AnyMatch, MatchKind) when PK == DK -> + {AllMatch1, AnyMatch1} = + if + %% It's not properly specified, but a "no value" in a + %% pattern field is supposed to mean simple presence of + %% the corresponding data field. I've interpreted that to + %% mean a type of "void" for the pattern field. + PT == void -> {AllMatch, true}; %% Similarly, it's not specified, but I assume that a %% mismatched type causes a mismatched value. - headers_match(PRest, DRest, false, AnyMatch, MatchKind); - PV == DV -> - headers_match(PRest, DRest, AllMatch, true, MatchKind); - true -> - headers_match(PRest, DRest, false, AnyMatch, MatchKind) - end. + PT =/= DT -> {false, AnyMatch}; + PV == DV -> {AllMatch, true}; + true -> {false, AnyMatch} + end, + headers_match(PRest, DRest, AllMatch1, AnyMatch1, MatchKind). split_topic_key(Key) -> {ok, KeySplit} = regexp:split(binary_to_list(Key), "\\."), -- cgit v1.2.1 From d4fb4e425aa33ae59b920c7793e2a3065ad0ab52 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 15 Jan 2009 14:04:03 +0000 Subject: refactoring to eliminate code duplication ...between topic and headers exchange routing --- src/rabbit_exchange.erl | 79 +++++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index c83dd38b..2b5d9763 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -233,65 +233,48 @@ sort_arguments(Arguments) -> %% The function ensures that a qpid appears in the return list exactly %% as many times as a message should be delivered to it. With the %% current exchange types that is at most once. -%% -%% TODO: Maybe this should be handled by a cursor instead. route(#exchange{name = Name, type = topic}, RoutingKey, _Content) -> - Query = qlc:q([QName || - #route{binding = #binding{ - exchange_name = ExchangeName, - queue_name = QName, - key = BindingKey}} <- mnesia:table(route), - ExchangeName == Name, - %% TODO: This causes a full scan for each entry - %% with the same exchange (see bug 19336) - topic_matches(BindingKey, RoutingKey)]), - lookup_qpids( - try - mnesia:async_dirty(fun qlc:e/1, [Query]) - catch exit:{aborted, {badarg, _}} -> - %% work around OTP-7025, which was fixed in R12B-1, by - %% falling back on a less efficient method - [QName || #route{binding = #binding{queue_name = QName, - key = BindingKey}} <- - mnesia:dirty_match_object( - #route{binding = #binding{exchange_name = Name, - _ = '_'}}), - topic_matches(BindingKey, RoutingKey)] - end); + match_bindings(Name, fun (#binding{key = BindingKey}) -> + topic_matches(BindingKey, RoutingKey) + end); -route(#exchange{name = Name, type = headers}, - _RoutingKey, - #content{properties = #'P_basic'{headers = Headers0}}) -> - Headers = case Headers0 of +route(#exchange{name = Name, type = headers}, _RoutingKey, Content) -> + Headers = case (Content#content.properties)#'P_basic'.headers of undefined -> []; - _ -> sort_arguments(Headers0) + H -> sort_arguments(H) end, - Query = qlc:q([QName || - #route{binding = #binding{ - exchange_name = ExchangeName, - queue_name = QName, - args = Spec}} <- mnesia:table(route), - ExchangeName == Name, - headers_match(Spec, Headers)]), + match_bindings(Name, fun (#binding{args = Spec}) -> + headers_match(Spec, Headers) + end); + +route(X = #exchange{type = fanout}, _RoutingKey, _Content) -> + route_internal(X, '_'); + +route(X = #exchange{type = direct}, RoutingKey, _Content) -> + route_internal(X, RoutingKey). + +%% TODO: Maybe this should be handled by a cursor instead. +%% TODO: This causes a full scan for each entry with the same exchange +match_bindings(XName, Match) -> + Query = qlc:q([QName || #route{binding = Binding = #binding{ + exchange_name = ExchangeName, + queue_name = QName}} <- + mnesia:table(route), + ExchangeName == XName, + Match(Binding)]), lookup_qpids( try mnesia:async_dirty(fun qlc:e/1, [Query]) catch exit:{aborted, {badarg, _}} -> %% work around OTP-7025, which was fixed in R12B-1, by %% falling back on a less efficient method - [QName || #route{binding = #binding{queue_name = QName, - args = Spec}} <- - mnesia:dirty_match_object( - #route{binding = #binding{exchange_name = Name, + [QName || #route{binding = Binding = #binding{ + queue_name = QName}} <- + mnesia:dirty_match_object( + #route{binding = #binding{exchange_name = XName, _ = '_'}}), - headers_match(Spec, Headers)] - end); - -route(X = #exchange{type = fanout}, _RoutingKey, _Content) -> - route_internal(X, '_'); - -route(X = #exchange{type = direct}, RoutingKey, _Content) -> - route_internal(X, RoutingKey). + Match(Binding)] + end). route_internal(#exchange{name = Name}, RoutingKey) -> MatchHead = #route{binding = #binding{exchange_name = Name, -- cgit v1.2.1 From a59216a046c0495b79e067e8fa0ed638d3f29f3d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 15 Jan 2009 14:14:12 +0000 Subject: minor refactoring: more consistent signatures/matching in routing functions --- src/rabbit_exchange.erl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 2b5d9763..960e4945 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -233,34 +233,34 @@ sort_arguments(Arguments) -> %% The function ensures that a qpid appears in the return list exactly %% as many times as a message should be delivered to it. With the %% current exchange types that is at most once. -route(#exchange{name = Name, type = topic}, RoutingKey, _Content) -> - match_bindings(Name, fun (#binding{key = BindingKey}) -> - topic_matches(BindingKey, RoutingKey) - end); +route(X = #exchange{type = topic}, RoutingKey, _Content) -> + match_bindings(X, fun (#binding{key = BindingKey}) -> + topic_matches(BindingKey, RoutingKey) + end); -route(#exchange{name = Name, type = headers}, _RoutingKey, Content) -> +route(X = #exchange{type = headers}, _RoutingKey, Content) -> Headers = case (Content#content.properties)#'P_basic'.headers of undefined -> []; H -> sort_arguments(H) end, - match_bindings(Name, fun (#binding{args = Spec}) -> - headers_match(Spec, Headers) - end); + match_bindings(X, fun (#binding{args = Spec}) -> + headers_match(Spec, Headers) + end); route(X = #exchange{type = fanout}, _RoutingKey, _Content) -> - route_internal(X, '_'); + match_routing_key(X, '_'); route(X = #exchange{type = direct}, RoutingKey, _Content) -> - route_internal(X, RoutingKey). + match_routing_key(X, RoutingKey). %% TODO: Maybe this should be handled by a cursor instead. %% TODO: This causes a full scan for each entry with the same exchange -match_bindings(XName, Match) -> +match_bindings(#exchange{name = Name}, Match) -> Query = qlc:q([QName || #route{binding = Binding = #binding{ exchange_name = ExchangeName, queue_name = QName}} <- mnesia:table(route), - ExchangeName == XName, + ExchangeName == Name, Match(Binding)]), lookup_qpids( try @@ -271,12 +271,12 @@ match_bindings(XName, Match) -> [QName || #route{binding = Binding = #binding{ queue_name = QName}} <- mnesia:dirty_match_object( - #route{binding = #binding{exchange_name = XName, + #route{binding = #binding{exchange_name = Name, _ = '_'}}), Match(Binding)] end). -route_internal(#exchange{name = Name}, RoutingKey) -> +match_routing_key(#exchange{name = Name}, RoutingKey) -> MatchHead = #route{binding = #binding{exchange_name = Name, queue_name = '$1', key = RoutingKey, -- cgit v1.2.1 From c8a10816690982de04073eec6239fbeb33335e3b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 15 Jan 2009 14:33:28 +0000 Subject: don't try to dialyze rabbit.app ...it won't work --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 962b6f4b..fede89e1 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ $(SOURCE_DIR)/rabbit_framing.erl: codegen.py $(AMQP_CODEGEN_DIR)/amqp_codegen.py $(EBIN_DIR)/rabbit.boot $(EBIN_DIR)/rabbit.script: $(EBIN_DIR)/rabbit.app $(EBIN_DIR)/rabbit.rel $(TARGETS) erl -noshell -eval 'systools:make_script("ebin/rabbit", [{path, ["ebin"]}]), halt().' -dialyze: $(TARGETS) +dialyze: $(BEAM_TARGETS) dialyzer -c $? clean: cleandb -- cgit v1.2.1 From 5246d7b643d6be6fdb29992b1becb6d96f20ff9b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 15 Jan 2009 14:34:29 +0000 Subject: ignore rabbit.app --- .hgignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgignore b/.hgignore index 28f9cfd8..35607765 100644 --- a/.hgignore +++ b/.hgignore @@ -9,6 +9,7 @@ syntax: regexp ^include/rabbit_framing.hrl$ ^src/rabbit_framing.erl$ ^rabbit.plt$ +^ebin/rabbit.app$ ^packaging/RPMS/Fedora/(BUILD|RPMS|SOURCES|SPECS|SRPMS)$ ^packaging/debs/Debian/rabbitmq-server_.*\.(dsc|(diff|tar)\.gz|deb|changes)$ -- cgit v1.2.1 From 6e0e3fcdc9b2760ace7f16537cbc6ad1cd94497e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 15 Jan 2009 15:57:43 +0000 Subject: add some comments --- src/rabbit_channel.erl | 3 ++- src/rabbit_limiter.erl | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 513d3050..8ef0f7e1 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -855,7 +855,8 @@ consumer_queues(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. +%% messages sent in a response to a basic.get (identified by their +%% 'none' consumer tag) notify_limiter(undefined, _Acked) -> ok; notify_limiter(LimiterPid, Acked) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 3776edd0..49a9c195 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -56,8 +56,11 @@ -record(lim, {prefetch_count = 0, ch_pid, - queues = dict:new(), + queues = dict:new(), % QPid -> {MonitorRef, Notify} volume = 0}). +%% 'Notify' is a boolean that indicates whether a queue should be +%% notified of a change in the limit or volume that may allow it to +%% deliver more messages via the limiter's channel. %%---------------------------------------------------------------------------- %% API -- cgit v1.2.1 From cfe54cf2cbd058a099d8f5d79b3706894df1d6d5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 16 Jan 2009 13:46:15 +0000 Subject: modify table structure in preparation for permissions --- include/rabbit.hrl | 3 ++- src/rabbit_access_control.erl | 33 ++++++++++++++++++++------------- src/rabbit_mnesia.erl | 8 ++++---- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/include/rabbit.hrl b/include/rabbit.hrl index d07aeaf8..02f1ec21 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -30,7 +30,8 @@ %% -record(user, {username, password}). --record(user_vhost, {username, virtual_host}). +-record(permission, {configuration, messaging}). +-record(user_permission, {username, virtual_host, permission}). -record(vhost, {virtual_host, dummy}). diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 36270efd..e248575b 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -113,8 +113,9 @@ internal_lookup_vhost_access(Username, VHostPath) -> rabbit_misc:execute_mnesia_transaction( fun () -> case mnesia:match_object( - #user_vhost{username = Username, - virtual_host = VHostPath}) of + #user_permission{username = Username, + virtual_host = VHostPath, + _ = '_'}) of [] -> not_found; [R] -> {ok, R} end @@ -151,7 +152,7 @@ delete_user(Username) -> Username, fun () -> ok = mnesia:delete({user, Username}), - ok = mnesia:delete({user_vhost, Username}) + ok = mnesia:delete({user_permission, Username}) end)), rabbit_log:info("Deleted user ~p~n", [Username]), R. @@ -232,23 +233,23 @@ list_vhosts() -> list_vhost_users(VHostPath) -> [Username || - #user_vhost{username = Username} <- + #user_permission{username = Username} <- %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction( rabbit_misc:with_vhost( VHostPath, - fun () -> mnesia:index_read(user_vhost, VHostPath, - #user_vhost.virtual_host) + fun () -> mnesia:index_read(user_permission, VHostPath, + #user_permission.virtual_host) end))]. list_user_vhosts(Username) -> [VHostPath || - #user_vhost{virtual_host = VHostPath} <- + #user_permission{virtual_host = VHostPath} <- %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction( rabbit_misc:with_user( Username, - fun () -> mnesia:read({user_vhost, Username}) end))]. + fun () -> mnesia:read({user_permission, Username}) end))]. map_user_vhost(Username, VHostPath) -> rabbit_misc:execute_mnesia_transaction( @@ -256,8 +257,11 @@ map_user_vhost(Username, VHostPath) -> Username, VHostPath, fun () -> ok = mnesia:write( - #user_vhost{username = Username, - virtual_host = VHostPath}) + #user_permission{username = Username, + virtual_host = VHostPath, + permission = #permission{ + configuration = ".*", + messaging = ".*"}}) end)). unmap_user_vhost(Username, VHostPath) -> @@ -265,8 +269,11 @@ unmap_user_vhost(Username, VHostPath) -> rabbit_misc:with_user_and_vhost( Username, VHostPath, fun () -> - ok = mnesia:delete_object( - #user_vhost{username = Username, - virtual_host = VHostPath}) + [ok = mnesia:delete_object(R) || + R <- mnesia:match_object( + #user_permission{username = Username, + virtual_host = VHostPath, + _ = '_'})], + ok end)). diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index eebb38fa..31c0efbe 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -102,10 +102,10 @@ force_reset() -> reset(true). table_definitions() -> [{user, [{disc_copies, [node()]}, {attributes, record_info(fields, user)}]}, - {user_vhost, [{type, bag}, - {disc_copies, [node()]}, - {attributes, record_info(fields, user_vhost)}, - {index, [virtual_host]}]}, + {user_permission, [{type, bag}, + {disc_copies, [node()]}, + {attributes, record_info(fields, user_permission)}, + {index, [virtual_host]}]}, {vhost, [{disc_copies, [node()]}, {attributes, record_info(fields, vhost)}]}, {rabbit_config, [{disc_copies, [node()]}]}, -- cgit v1.2.1 From b11db7b1c427a2196289e2498b28aeac4bd56c9a Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 16 Jan 2009 16:24:34 +0000 Subject: beginnings of permission admin I changed the table structure (again). It's a choice between two of efficiency, integrity and no data duplication - I picked the last two. We pass the permission regexps around as binaries and store them like that too, just as we do for all other string-y datums. I was toying with the idea of making set_permissions take a #permission record instead of two separate regexp params, so that it is easy to add more permissions later. But that is inconsistent with the rest of the rabbit_access_control API, which is "flat", and complicates the code in rabbit_control and the rabbit_tests. --- ebin/rabbit_app.in | 1 + include/rabbit.hrl | 7 +++- src/rabbit.erl | 6 +++- src/rabbit_access_control.erl | 78 +++++++++++++++++++++++++++---------------- src/rabbit_control.erl | 42 +++++++++++++---------- src/rabbit_mnesia.erl | 6 ++-- src/rabbit_tests.erl | 26 +++++++++------ 7 files changed, 103 insertions(+), 63 deletions(-) diff --git a/ebin/rabbit_app.in b/ebin/rabbit_app.in index e2f36c0f..77f9d299 100644 --- a/ebin/rabbit_app.in +++ b/ebin/rabbit_app.in @@ -17,4 +17,5 @@ {default_user, <<"guest">>}, {default_pass, <<"guest">>}, {default_vhost, <<"/">>}, + {default_permissions, [<<".*">>, <<".*">>]}, {memory_alarms, auto}]}]}. diff --git a/include/rabbit.hrl b/include/rabbit.hrl index 02f1ec21..8aba8a6f 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -31,7 +31,8 @@ -record(user, {username, password}). -record(permission, {configuration, messaging}). --record(user_permission, {username, virtual_host, permission}). +-record(user_vhost, {username, virtual_host}). +-record(user_permission, {user_vhost, permission}). -record(vhost, {virtual_host, dummy}). @@ -75,6 +76,7 @@ -type(thunk(T) :: fun(() -> T)). -type(info_key() :: atom()). -type(info() :: {info_key(), any()}). +-type(regexp() :: binary()). %% this is really an abstract type, but dialyzer does not support them -type(guid() :: any()). @@ -89,6 +91,9 @@ -type(user() :: #user{username :: username(), password :: password()}). +-type(permission() :: + #permission{configuration :: regexp(), + messaging :: regexp()}). -type(amqqueue() :: #amqqueue{name :: queue_name(), durable :: bool(), diff --git a/src/rabbit.erl b/src/rabbit.erl index 30b8c394..7ad13a7d 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -264,9 +264,13 @@ insert_default_data() -> {ok, DefaultUser} = application:get_env(default_user), {ok, DefaultPass} = application:get_env(default_pass), {ok, DefaultVHost} = application:get_env(default_vhost), + {ok, [DefaultConfigurationPerm, DefaultMessagingPerm]} = + application:get_env(default_permissions), ok = rabbit_access_control:add_vhost(DefaultVHost), ok = rabbit_access_control:add_user(DefaultUser, DefaultPass), - ok = rabbit_access_control:map_user_vhost(DefaultUser, DefaultVHost), + ok = rabbit_access_control:set_permissions(DefaultUser, DefaultVHost, + DefaultConfigurationPerm, + DefaultMessagingPerm), ok. start_builtin_amq_applications() -> diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index e248575b..177e6f20 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -38,7 +38,7 @@ -export([add_user/2, delete_user/1, change_password/2, list_users/0, lookup_user/1]). -export([add_vhost/1, delete_vhost/1, list_vhosts/0, list_vhost_users/1]). --export([list_user_vhosts/1, map_user_vhost/2, unmap_user_vhost/2]). +-export([list_user_vhosts/1, set_permissions/4, clear_permissions/2]). %%---------------------------------------------------------------------------- @@ -57,8 +57,8 @@ -spec(list_vhosts/0 :: () -> [vhost()]). -spec(list_vhost_users/1 :: (vhost()) -> [username()]). -spec(list_user_vhosts/1 :: (username()) -> [vhost()]). --spec(map_user_vhost/2 :: (username(), vhost()) -> 'ok'). --spec(unmap_user_vhost/2 :: (username(), vhost()) -> 'ok'). +-spec(set_permissions/4 :: (username(), vhost(), regexp(), regexp()) -> 'ok'). +-spec(clear_permissions/2 :: (username(), vhost()) -> 'ok'). -endif. @@ -112,10 +112,9 @@ internal_lookup_vhost_access(Username, VHostPath) -> %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:match_object( - #user_permission{username = Username, - virtual_host = VHostPath, - _ = '_'}) of + case mnesia:read({user_permission, + #user_vhost{username = Username, + virtual_host = VHostPath}}) of [] -> not_found; [R] -> {ok, R} end @@ -152,7 +151,13 @@ delete_user(Username) -> Username, fun () -> ok = mnesia:delete({user, Username}), - ok = mnesia:delete({user_permission, Username}) + [ok = mnesia:delete_object(R) || + R <- mnesia:match_object( + #user_permission{user_vhost = #user_vhost{ + username = Username, + virtual_host = '_'}, + permission = '_'})], + ok end)), rabbit_log:info("Deleted user ~p~n", [Username]), R. @@ -222,7 +227,7 @@ internal_delete_vhost(VHostPath) -> end, rabbit_exchange:list(VHostPath)), lists:foreach(fun (Username) -> - ok = unmap_user_vhost(Username, VHostPath) + ok = clear_permissions(Username, VHostPath) end, list_vhost_users(VHostPath)), ok = mnesia:delete({vhost, VHostPath}), @@ -233,47 +238,62 @@ list_vhosts() -> list_vhost_users(VHostPath) -> [Username || - #user_permission{username = Username} <- + #user_permission{user_vhost = #user_vhost{username = Username}} <- %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction( rabbit_misc:with_vhost( VHostPath, - fun () -> mnesia:index_read(user_permission, VHostPath, - #user_permission.virtual_host) + fun () -> mnesia:match_object( + #user_permission{user_vhost = #user_vhost{ + username = '_', + virtual_host = VHostPath}, + permission = '_'}) end))]. list_user_vhosts(Username) -> [VHostPath || - #user_permission{virtual_host = VHostPath} <- + #user_permission{user_vhost = #user_vhost{virtual_host = VHostPath}} <- %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction( rabbit_misc:with_user( Username, - fun () -> mnesia:read({user_permission, Username}) end))]. + fun () -> mnesia:match_object( + #user_permission{user_vhost = #user_vhost{ + username = Username, + virtual_host = '_'}, + permission = '_'}) + end))]. + +validate_regexp(RegexpBin) -> + Regexp = binary_to_list(RegexpBin), + case regexp:parse(Regexp) of + {ok, _} -> ok; + {error, Reason} -> throw({error, {invalid_regexp, Regexp, Reason}}) + end. -map_user_vhost(Username, VHostPath) -> +set_permissions(Username, VHostPath, ConfigurationPerm, MessagingPerm) -> + validate_regexp(ConfigurationPerm), + validate_regexp(MessagingPerm), rabbit_misc:execute_mnesia_transaction( rabbit_misc:with_user_and_vhost( Username, VHostPath, - fun () -> - ok = mnesia:write( - #user_permission{username = Username, - virtual_host = VHostPath, - permission = #permission{ - configuration = ".*", - messaging = ".*"}}) + fun () -> ok = mnesia:write( + #user_permission{user_vhost = #user_vhost{ + username = Username, + virtual_host = VHostPath}, + permission = #permission{ + configuration = ConfigurationPerm, + messaging = MessagingPerm}}) end)). -unmap_user_vhost(Username, VHostPath) -> + +clear_permissions(Username, VHostPath) -> rabbit_misc:execute_mnesia_transaction( rabbit_misc:with_user_and_vhost( Username, VHostPath, fun () -> - [ok = mnesia:delete_object(R) || - R <- mnesia:match_object( - #user_permission{username = Username, - virtual_host = VHostPath, - _ = '_'})], - ok + ok = mnesia:delete({user_permission, + #user_vhost{username = Username, + virtual_host = VHostPath}}) end)). diff --git a/src/rabbit_control.erl b/src/rabbit_control.erl index cbc11b40..d897b3e9 100644 --- a/src/rabbit_control.erl +++ b/src/rabbit_control.erl @@ -114,8 +114,8 @@ Available commands: delete_vhost list_vhosts - map_user_vhost - unmap_user_vhost + set_permissions [-p ] + clear_permissions [-p ] list_user_vhosts list_vhost_users @@ -223,13 +223,17 @@ action(list_vhosts, Node, [], Inform) -> Inform("Listing vhosts", []), display_list(call(Node, {rabbit_access_control, list_vhosts, []})); -action(map_user_vhost, Node, Args = [_Username, _VHostPath], Inform) -> - Inform("Mapping user ~p to vhost ~p", Args), - call(Node, {rabbit_access_control, map_user_vhost, Args}); +action(set_permissions, Node, Args, Inform) -> + {VHost, [Username, ConfigurationPerm, MessagingPerm]} = + parse_vhost_flag(Args), + Inform("Setting permissions for user ~p in vhost ~p", [Username, VHost]), + call(Node, {rabbit_access_control, set_permissions, + [Username, VHost, ConfigurationPerm, MessagingPerm]}); -action(unmap_user_vhost, Node, Args = [_Username, _VHostPath], Inform) -> - Inform("Unmapping user ~p from vhost ~p", Args), - call(Node, {rabbit_access_control, unmap_user_vhost, Args}); +action(clear_permissions, Node, Args, Inform) -> + {VHost, [Username]} = parse_vhost_flag(Args), + Inform("Clearing permissions for user ~p in vhost ~p", [Username, VHost]), + call(Node, {rabbit_access_control, clear_permissions, [Username, VHost]}); action(list_user_vhosts, Node, Args = [_Username], Inform) -> Inform("Listing vhosts for user ~p", Args), @@ -241,7 +245,7 @@ action(list_vhost_users, Node, Args = [_VHostPath], Inform) -> action(list_queues, Node, Args, Inform) -> Inform("Listing queues", []), - {VHostArg, RemainingArgs} = parse_vhost_flag(Args), + {VHostArg, RemainingArgs} = parse_vhost_flag_bin(Args), ArgAtoms = list_replace(node, pid, default_if_empty(RemainingArgs, [name, messages])), display_info_list(rpc_call(Node, rabbit_amqqueue, info_all, @@ -250,7 +254,7 @@ action(list_queues, Node, Args, Inform) -> action(list_exchanges, Node, Args, Inform) -> Inform("Listing exchanges", []), - {VHostArg, RemainingArgs} = parse_vhost_flag(Args), + {VHostArg, RemainingArgs} = parse_vhost_flag_bin(Args), ArgAtoms = default_if_empty(RemainingArgs, [name, type]), display_info_list(rpc_call(Node, rabbit_exchange, info_all, [VHostArg, ArgAtoms]), @@ -258,7 +262,7 @@ action(list_exchanges, Node, Args, Inform) -> action(list_bindings, Node, Args, Inform) -> Inform("Listing bindings", []), - {VHostArg, _} = parse_vhost_flag(Args), + {VHostArg, _} = parse_vhost_flag_bin(Args), InfoKeys = [exchange_name, routing_key, queue_name, args], display_info_list( [lists:zip(InfoKeys, tuple_to_list(X)) || @@ -275,12 +279,16 @@ action(list_connections, Node, Args, Inform) -> ArgAtoms). parse_vhost_flag(Args) when is_list(Args) -> - case Args of - ["-p", VHost | RemainingArgs] -> - {list_to_binary(VHost), RemainingArgs}; - RemainingArgs -> - {<<"/">>, RemainingArgs} - end. + case Args of + ["-p", VHost | RemainingArgs] -> + {VHost, RemainingArgs}; + RemainingArgs -> + {"/", RemainingArgs} + end. + +parse_vhost_flag_bin(Args) -> + {VHost, RemainingArgs} = parse_vhost_flag(Args), + {list_to_binary(VHost), RemainingArgs}. default_if_empty(List, Default) when is_list(List) -> if List == [] -> diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 31c0efbe..b7f3dd0a 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -102,10 +102,8 @@ force_reset() -> reset(true). table_definitions() -> [{user, [{disc_copies, [node()]}, {attributes, record_info(fields, user)}]}, - {user_permission, [{type, bag}, - {disc_copies, [node()]}, - {attributes, record_info(fields, user_permission)}, - {index, [virtual_host]}]}, + {user_permission, [{disc_copies, [node()]}, + {attributes, record_info(fields, user_permission)}]}, {vhost, [{disc_copies, [node()]}, {attributes, record_info(fields, vhost)}]}, {rabbit_config, [{disc_copies, [node()]}]}, diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index df2e71d9..26398f9b 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -444,17 +444,18 @@ test_user_management() -> {error, {no_such_vhost, _}} = control_action(delete_vhost, ["/testhost"]), {error, {no_such_user, _}} = - control_action(map_user_vhost, ["foo", "/"]), + control_action(set_permissions, ["foo", ".*", ".*"]), {error, {no_such_user, _}} = - control_action(unmap_user_vhost, ["foo", "/"]), + control_action(clear_permissions, ["foo"]), {error, {no_such_user, _}} = control_action(list_user_vhosts, ["foo"]), - {error, {no_such_vhost, _}} = - control_action(map_user_vhost, ["guest", "/testhost"]), - {error, {no_such_vhost, _}} = - control_action(unmap_user_vhost, ["guest", "/testhost"]), {error, {no_such_vhost, _}} = control_action(list_vhost_users, ["/testhost"]), + {error, {invalid_regexp, _, _}} = + control_action(set_permissions, ["guest", "+foo", ".*"]), + {error, {invalid_regexp, _, _}} = + control_action(set_permissions, ["guest", ".*", "+foo"]), + %% user creation ok = control_action(add_user, ["foo", "bar"]), {error, {user_already_exists, _}} = @@ -469,13 +470,15 @@ test_user_management() -> ok = control_action(list_vhosts, []), %% user/vhost mapping - ok = control_action(map_user_vhost, ["foo", "/testhost"]), - ok = control_action(map_user_vhost, ["foo", "/testhost"]), + ok = control_action(set_permissions, ["-p", "/testhost", + "foo", ".*", ".*"]), + ok = control_action(set_permissions, ["-p", "/testhost", + "foo", ".*", ".*"]), ok = control_action(list_user_vhosts, ["foo"]), %% user/vhost unmapping - ok = control_action(unmap_user_vhost, ["foo", "/testhost"]), - ok = control_action(unmap_user_vhost, ["foo", "/testhost"]), + ok = control_action(clear_permissions, ["-p", "/testhost", "foo"]), + ok = control_action(clear_permissions, ["-p", "/testhost", "foo"]), %% vhost deletion ok = control_action(delete_vhost, ["/testhost"]), @@ -484,7 +487,8 @@ test_user_management() -> %% deleting a populated vhost ok = control_action(add_vhost, ["/testhost"]), - ok = control_action(map_user_vhost, ["foo", "/testhost"]), + ok = control_action(set_permissions, ["-p", "/testhost", + "foo", ".*", ".*"]), ok = control_action(delete_vhost, ["/testhost"]), %% user deletion -- cgit v1.2.1 From 679f35d6a9ca187198e3f658cbb6db20fa52e914 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 16 Jan 2009 17:45:49 +0000 Subject: hook in permission checks --- src/rabbit_access_control.erl | 33 ++++++++++++++++++++++++++++++++- src/rabbit_channel.erl | 24 +++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 177e6f20..d75f1b29 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -34,7 +34,7 @@ -include("rabbit.hrl"). -export([check_login/2, user_pass_login/2, - check_vhost_access/2]). + check_vhost_access/2, check_resource_access/3]). -export([add_user/2, delete_user/1, change_password/2, list_users/0, lookup_user/1]). -export([add_vhost/1, delete_vhost/1, list_vhosts/0, list_vhost_users/1]). @@ -47,6 +47,8 @@ -spec(check_login/2 :: (binary(), binary()) -> user()). -spec(user_pass_login/2 :: (username(), password()) -> user()). -spec(check_vhost_access/2 :: (user(), vhost()) -> 'ok'). +-spec(check_resource_access/3 :: + (username(), r(atom()), non_neg_integer()) -> 'ok'). -spec(add_user/2 :: (username(), password()) -> 'ok'). -spec(delete_user/1 :: (username()) -> 'ok'). -spec(change_password/2 :: (username(), password()) -> 'ok'). @@ -131,6 +133,35 @@ check_vhost_access(#user{username = Username}, VHostPath) -> [VHostPath, Username]) end. +check_resource_access(Username, + R = #resource{kind = exchange, name = <<"">>}, + Permission) -> + check_resource_access(Username, + R#resource{name = <<"amq.default">>}, + Permission); +check_resource_access(Username, + R = #resource{virtual_host = VHostPath, name = Name}, + Permission) -> + %% TODO: cache the results + Res = case mnesia:dirty_read({user_permission, + #user_vhost{username = Username, + virtual_host = VHostPath}}) of + [] -> + false; + [#user_permission{permission = P}] -> + case regexp:match( + binary_to_list(Name), + binary_to_list(element(Permission, P))) of + {match, _, _} -> true; + nomatch -> false + end + end, + if Res -> ok; + true -> rabbit_misc:protocol_error( + access_refused, "access to ~s refused for user '~s'", + [rabbit_misc:rs(R), Username]) + end. + add_user(Username, Password) -> R = rabbit_misc:execute_mnesia_transaction( fun () -> diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index ef97daa4..056ae649 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -199,6 +199,14 @@ return_queue_declare_ok(State, NoWait, Q) -> {reply, Reply, NewState} end. +check_configuration_permitted(Resource, #ch{ username = Username}) -> + rabbit_access_control:check_resource_access( + Username, Resource, #permission.configuration). + +check_messaging_permitted(Resource, #ch{ username = Username}) -> + rabbit_access_control:check_resource_access( + Username, Resource, #permission.messaging). + expand_queue_name_shortcut(<<>>, #ch{ most_recently_declared_queue = <<>> }) -> rabbit_misc:protocol_error( not_allowed, "no previously declared queue", []); @@ -269,6 +277,7 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, immediate = Immediate}, Content, State = #ch{ virtual_host = VHostPath}) -> ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + check_messaging_permitted(ExchangeName, State), Exchange = rabbit_exchange:lookup_or_die(ExchangeName), %% We decode the content's properties here because we're almost %% certain to want to look at delivery-mode and priority. @@ -312,6 +321,7 @@ handle_method(#'basic.get'{queue = QueueNameBin, _, State = #ch{ proxy_pid = ProxyPid, writer_pid = WriterPid, next_tag = DeliveryTag }) -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_messaging_permitted(QueueName, State), case rabbit_amqqueue:with_or_die( QueueName, fun (Q) -> rabbit_amqqueue:basic_get(Q, ProxyPid, NoAck) end) of @@ -347,6 +357,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, case dict:find(ConsumerTag, ConsumerMapping) of error -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_messaging_permitted(QueueName, State), ActualConsumerTag = case ConsumerTag of <<>> -> rabbit_misc:binstring_guid("amq.ctag"); @@ -510,6 +521,7 @@ handle_method(#'exchange.declare'{exchange = ExchangeNameBin, _, State = #ch{ virtual_host = VHostPath }) -> CheckedType = rabbit_exchange:check_type(TypeNameBin), ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + check_configuration_permitted(ExchangeName, State), X = case rabbit_exchange:lookup(ExchangeName) of {ok, FoundX} -> FoundX; {error, not_found} -> @@ -529,6 +541,7 @@ handle_method(#'exchange.declare'{exchange = ExchangeNameBin, nowait = NoWait}, _, State = #ch{ virtual_host = VHostPath }) -> ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + check_configuration_permitted(ExchangeName, State), X = rabbit_exchange:lookup_or_die(ExchangeName), ok = rabbit_exchange:assert_type(X, rabbit_exchange:check_type(TypeNameBin)), return_ok(State, NoWait, #'exchange.declare_ok'{}); @@ -538,6 +551,7 @@ handle_method(#'exchange.delete'{exchange = ExchangeNameBin, nowait = NoWait}, _, State = #ch { virtual_host = VHostPath }) -> ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + check_configuration_permitted(ExchangeName, State), case rabbit_exchange:delete(ExchangeName, IfUnused) of {error, not_found} -> rabbit_misc:protocol_error( @@ -588,9 +602,12 @@ handle_method(#'queue.declare'{queue = QueueNameBin, Other -> check_name('queue', Other) end, QueueName = rabbit_misc:r(VHostPath, queue, ActualNameBin), + check_configuration_permitted(QueueName, State), Finish(rabbit_amqqueue:declare(QueueName, Durable, AutoDelete, Args)); - Other -> Other + Other = #amqqueue{name = QueueName} -> + check_configuration_permitted(QueueName, State), + Other end, return_queue_declare_ok(State, NoWait, Q); @@ -599,6 +616,7 @@ handle_method(#'queue.declare'{queue = QueueNameBin, nowait = NoWait}, _, State = #ch{ virtual_host = VHostPath }) -> QueueName = rabbit_misc:r(VHostPath, queue, QueueNameBin), + check_configuration_permitted(QueueName, State), Q = rabbit_amqqueue:with_or_die(QueueName, fun (Q) -> Q end), return_queue_declare_ok(State, NoWait, Q); @@ -609,6 +627,7 @@ handle_method(#'queue.delete'{queue = QueueNameBin, }, _, State) -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_configuration_permitted(QueueName, State), case rabbit_amqqueue:with_or_die( QueueName, fun (Q) -> rabbit_amqqueue:delete(Q, IfUnused, IfEmpty) end) of @@ -645,6 +664,7 @@ handle_method(#'queue.purge'{queue = QueueNameBin, nowait = NoWait}, _, State) -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_messaging_permitted(QueueName, State), {ok, PurgedMessageCount} = rabbit_amqqueue:with_or_die( QueueName, fun (Q) -> rabbit_amqqueue:purge(Q) end), @@ -694,9 +714,11 @@ binding_action(Fun, ExchangeNameBin, QueueNameBin, RoutingKey, Arguments, %% FIXME: don't allow binding to internal exchanges - %% including the one named "" ! QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_configuration_permitted(QueueName, State), ActualRoutingKey = expand_routing_key_shortcut(QueueNameBin, RoutingKey, State), ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + check_configuration_permitted(ExchangeName, State), case Fun(ExchangeName, QueueName, ActualRoutingKey, Arguments) of {error, queue_not_found} -> rabbit_misc:protocol_error( -- cgit v1.2.1 From becaa37f9b803ac61ba84c7c72fdb080864176b4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 16 Jan 2009 18:07:58 +0000 Subject: change a gen_server:call to a gen_server2:call for consistency with gen_server2:cast --- src/rabbit_router.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_router.erl b/src/rabbit_router.erl index 0b36a53c..26d857be 100644 --- a/src/rabbit_router.erl +++ b/src/rabbit_router.erl @@ -110,7 +110,7 @@ deliver_per_node(NodeQPids, Mandatory, Immediate, Txn, Message) -> R = rabbit_misc:upmap( fun ({Node, QPids}) -> - try gen_server:call( + try gen_server2:call( {?SERVER, Node}, {deliver, QPids, Mandatory, Immediate, Txn, Message}) catch -- cgit v1.2.1 From a68ed73fdcfeef691de4f51cc2a4cef1c6134bf2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 16 Jan 2009 21:29:52 +0000 Subject: better docs and error handling --- src/rabbit_control.erl | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/rabbit_control.erl b/src/rabbit_control.erl index d897b3e9..4d771871 100644 --- a/src/rabbit_control.erl +++ b/src/rabbit_control.erl @@ -114,7 +114,7 @@ Available commands: delete_vhost list_vhosts - set_permissions [-p ] + set_permissions [-p ] clear_permissions [-p ] list_user_vhosts list_vhost_users @@ -223,18 +223,6 @@ action(list_vhosts, Node, [], Inform) -> Inform("Listing vhosts", []), display_list(call(Node, {rabbit_access_control, list_vhosts, []})); -action(set_permissions, Node, Args, Inform) -> - {VHost, [Username, ConfigurationPerm, MessagingPerm]} = - parse_vhost_flag(Args), - Inform("Setting permissions for user ~p in vhost ~p", [Username, VHost]), - call(Node, {rabbit_access_control, set_permissions, - [Username, VHost, ConfigurationPerm, MessagingPerm]}); - -action(clear_permissions, Node, Args, Inform) -> - {VHost, [Username]} = parse_vhost_flag(Args), - Inform("Clearing permissions for user ~p in vhost ~p", [Username, VHost]), - call(Node, {rabbit_access_control, clear_permissions, [Username, VHost]}); - action(list_user_vhosts, Node, Args = [_Username], Inform) -> Inform("Listing vhosts for user ~p", Args), display_list(call(Node, {rabbit_access_control, list_user_vhosts, Args})); @@ -276,7 +264,20 @@ action(list_connections, Node, Args, Inform) -> default_if_empty(Args, [user, peer_address, peer_port])), display_info_list(rpc_call(Node, rabbit_networking, connection_info_all, [ArgAtoms]), - ArgAtoms). + ArgAtoms); + +action(Command, Node, Args, Inform) -> + {VHost, RemainingArgs} = parse_vhost_flag(Args), + action(Command, Node, VHost, RemainingArgs, Inform). + +action(set_permissions, Node, VHost, [Username, CPerm, MPerm], Inform) -> + Inform("Setting permissions for user ~p in vhost ~p", [Username, VHost]), + call(Node, {rabbit_access_control, set_permissions, + [Username, VHost, CPerm, MPerm]}); + +action(clear_permissions, Node, VHost, [Username], Inform) -> + Inform("Clearing permissions for user ~p in vhost ~p", [Username, VHost]), + call(Node, {rabbit_access_control, clear_permissions, [Username, VHost]}). parse_vhost_flag(Args) when is_list(Args) -> case Args of -- cgit v1.2.1 From 43bbca54383c4df4998c9f8aff29c21d21b1d5b9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 18 Jan 2009 09:09:24 +0000 Subject: cosmetic: correct indentation --- src/rabbit_amqqueue_process.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b4fd3fcc..c390b2b7 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -514,8 +514,8 @@ i(messages_uncommitted, _) -> #tx{pending_messages = Pending} <- all_tx_record()]); i(messages, State) -> lists:sum([i(Item, State) || Item <- [messages_ready, - messages_unacknowledged, - messages_uncommitted]]); + messages_unacknowledged, + messages_uncommitted]]); i(acks_uncommitted, _) -> lists:sum([length(Pending) || #tx{pending_acks = Pending} <- all_tx_record()]); -- cgit v1.2.1 From ce518286c8ab8bdfd9403ae81d7ac5f1f4ba45f6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 18 Jan 2009 09:53:18 +0000 Subject: better documentation of message counts --- docs/rabbitmqctl.1.pod | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/rabbitmqctl.1.pod b/docs/rabbitmqctl.1.pod index b9edd584..692c4dc5 100644 --- a/docs/rabbitmqctl.1.pod +++ b/docs/rabbitmqctl.1.pod @@ -150,19 +150,17 @@ pid Erlang process identifier associated with the queue messages_ready - number of ready messages + number of messages ready to be delivered to clients messages_unacknowledged - number of unacknowledged messages + number of messages delivered to clients but not yet acknowledged messages_uncommitted - number of uncommitted messages - -messages - sum of ready, unacknowledged and uncommitted messages + number of messages published in as yet uncommitted transactions acks_uncommitted - number of uncommitted acknowledgements + number of acknowledgements received in as yet uncommitted + transactions consumers number of consumers -- cgit v1.2.1 -- cgit v1.2.1 From daa45bbe58905c43baa04ab9efb17cec03de1282 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 19 Jan 2009 18:01:34 +0000 Subject: replace list_{vhost_users,user_vhosts} with list_{vhost,user}_permissions --- src/rabbit_access_control.erl | 79 ++++++++++++++++++++++++------------------- src/rabbit_control.erl | 46 ++++++++++++------------- src/rabbit_tests.erl | 7 ++-- 3 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index d75f1b29..ed88263c 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -37,8 +37,9 @@ check_vhost_access/2, check_resource_access/3]). -export([add_user/2, delete_user/1, change_password/2, list_users/0, lookup_user/1]). --export([add_vhost/1, delete_vhost/1, list_vhosts/0, list_vhost_users/1]). --export([list_user_vhosts/1, set_permissions/4, clear_permissions/2]). +-export([add_vhost/1, delete_vhost/1, list_vhosts/0]). +-export([set_permissions/4, clear_permissions/2, + list_vhost_permissions/1, list_user_permissions/1]). %%---------------------------------------------------------------------------- @@ -57,10 +58,12 @@ -spec(add_vhost/1 :: (vhost()) -> 'ok'). -spec(delete_vhost/1 :: (vhost()) -> 'ok'). -spec(list_vhosts/0 :: () -> [vhost()]). --spec(list_vhost_users/1 :: (vhost()) -> [username()]). --spec(list_user_vhosts/1 :: (username()) -> [vhost()]). -spec(set_permissions/4 :: (username(), vhost(), regexp(), regexp()) -> 'ok'). -spec(clear_permissions/2 :: (username(), vhost()) -> 'ok'). +-spec(list_vhost_permissions/1 :: + (vhost()) -> [{username(), regexp(), regexp()}]). +-spec(list_user_permissions/1 :: + (username()) -> [{vhost(), regexp(), regexp()}]). -endif. @@ -257,44 +260,16 @@ internal_delete_vhost(VHostPath) -> ok = rabbit_exchange:delete(Name, false) end, rabbit_exchange:list(VHostPath)), - lists:foreach(fun (Username) -> + lists:foreach(fun ({Username, _, _}) -> ok = clear_permissions(Username, VHostPath) end, - list_vhost_users(VHostPath)), + list_vhost_permissions(VHostPath)), ok = mnesia:delete({vhost, VHostPath}), ok. list_vhosts() -> mnesia:dirty_all_keys(vhost). -list_vhost_users(VHostPath) -> - [Username || - #user_permission{user_vhost = #user_vhost{username = Username}} <- - %% TODO: use dirty ops instead - rabbit_misc:execute_mnesia_transaction( - rabbit_misc:with_vhost( - VHostPath, - fun () -> mnesia:match_object( - #user_permission{user_vhost = #user_vhost{ - username = '_', - virtual_host = VHostPath}, - permission = '_'}) - end))]. - -list_user_vhosts(Username) -> - [VHostPath || - #user_permission{user_vhost = #user_vhost{virtual_host = VHostPath}} <- - %% TODO: use dirty ops instead - rabbit_misc:execute_mnesia_transaction( - rabbit_misc:with_user( - Username, - fun () -> mnesia:match_object( - #user_permission{user_vhost = #user_vhost{ - username = Username, - virtual_host = '_'}, - permission = '_'}) - end))]. - validate_regexp(RegexpBin) -> Regexp = binary_to_list(RegexpBin), case regexp:parse(Regexp) of @@ -317,7 +292,6 @@ set_permissions(Username, VHostPath, ConfigurationPerm, MessagingPerm) -> messaging = MessagingPerm}}) end)). - clear_permissions(Username, VHostPath) -> rabbit_misc:execute_mnesia_transaction( rabbit_misc:with_user_and_vhost( @@ -328,3 +302,38 @@ clear_permissions(Username, VHostPath) -> virtual_host = VHostPath}}) end)). +list_vhost_permissions(VHostPath) -> + [{Username, ConfigurationPerm, MessagingPerm} || + #user_permission{user_vhost = #user_vhost{username = Username}, + permission = #permission{ + configuration = ConfigurationPerm, + messaging = MessagingPerm}} <- + %% TODO: use dirty ops instead + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_vhost( + VHostPath, + fun () -> mnesia:match_object( + #user_permission{user_vhost = #user_vhost{ + username = '_', + virtual_host = VHostPath}, + permission = '_'}) + end))]. + +list_user_permissions(Username) -> + [{VHostPath, ConfigurationPerm, MessagingPerm} || + #user_permission{user_vhost = #user_vhost{virtual_host = VHostPath}, + permission = #permission{ + configuration = ConfigurationPerm, + messaging = MessagingPerm}} <- + %% TODO: use dirty ops instead + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_user( + Username, + fun () -> mnesia:match_object( + #user_permission{user_vhost = #user_vhost{ + username = Username, + virtual_host = '_'}, + permission = '_'}) + end))]. + + diff --git a/src/rabbit_control.erl b/src/rabbit_control.erl index 4d771871..293cd797 100644 --- a/src/rabbit_control.erl +++ b/src/rabbit_control.erl @@ -116,8 +116,8 @@ Available commands: set_permissions [-p ] clear_permissions [-p ] - list_user_vhosts - list_vhost_users + list_permissions [-p ] + list_user_permissions list_queues [-p ] [ ...] list_exchanges [-p ] [ ...] @@ -223,13 +223,10 @@ action(list_vhosts, Node, [], Inform) -> Inform("Listing vhosts", []), display_list(call(Node, {rabbit_access_control, list_vhosts, []})); -action(list_user_vhosts, Node, Args = [_Username], Inform) -> - Inform("Listing vhosts for user ~p", Args), - display_list(call(Node, {rabbit_access_control, list_user_vhosts, Args})); - -action(list_vhost_users, Node, Args = [_VHostPath], Inform) -> - Inform("Listing users for vhosts ~p", Args), - display_list(call(Node, {rabbit_access_control, list_vhost_users, Args})); +action(list_user_permissions, Node, Args = [_Username], Inform) -> + Inform("Listing permissions for user ~p", Args), + display_list(call(Node, {rabbit_access_control, list_user_permissions, + Args})); action(list_queues, Node, Args, Inform) -> Inform("Listing queues", []), @@ -277,7 +274,12 @@ action(set_permissions, Node, VHost, [Username, CPerm, MPerm], Inform) -> action(clear_permissions, Node, VHost, [Username], Inform) -> Inform("Clearing permissions for user ~p in vhost ~p", [Username, VHost]), - call(Node, {rabbit_access_control, clear_permissions, [Username, VHost]}). + call(Node, {rabbit_access_control, clear_permissions, [Username, VHost]}); + +action(list_permissions, Node, VHost, [], Inform) -> + Inform("Listing permissions in vhost ~p", [VHost]), + display_list(call(Node, {rabbit_access_control, list_vhost_permissions, + [VHost]})). parse_vhost_flag(Args) when is_list(Args) -> case Args of @@ -299,21 +301,17 @@ default_if_empty(List, Default) when is_list(List) -> end. display_info_list(Results, InfoItemKeys) when is_list(Results) -> - lists:foreach( - fun (Result) -> - io:fwrite( - lists:flatten( - rabbit_misc:intersperse( - "\t", - [format_info_item(Result, X) || X <- InfoItemKeys]))), - io:nl() - end, - Results), + lists:foreach(fun (Result) -> display_row([format_info_item(Result, X) || + X <- InfoItemKeys]) + end, Results), ok; - display_info_list(Other, _) -> Other. +display_row(Row) -> + io:fwrite(lists:flatten(rabbit_misc:intersperse("\t", Row))), + io:nl(). + format_info_item(Items, Key) -> {value, Info = {Key, Value}} = lists:keysearch(Key, 1, Items), case Info of @@ -330,8 +328,10 @@ format_info_item(Items, Key) -> end. display_list(L) when is_list(L) -> - lists:foreach(fun (I) -> - io:format("~s~n", [binary_to_list(I)]) + lists:foreach(fun (I) when is_binary(I) -> + io:format("~s~n", [url_encode(I)]); + (I) when is_tuple(I) -> + display_row([url_encode(V) || V <- tuple_to_list(I)]) end, lists:sort(L)), ok; diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 26398f9b..ef390e4d 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -448,9 +448,9 @@ test_user_management() -> {error, {no_such_user, _}} = control_action(clear_permissions, ["foo"]), {error, {no_such_user, _}} = - control_action(list_user_vhosts, ["foo"]), + control_action(list_user_permissions, ["foo"]), {error, {no_such_vhost, _}} = - control_action(list_vhost_users, ["/testhost"]), + control_action(list_permissions, ["-p", "/testhost"]), {error, {invalid_regexp, _, _}} = control_action(set_permissions, ["guest", "+foo", ".*"]), {error, {invalid_regexp, _, _}} = @@ -474,7 +474,8 @@ test_user_management() -> "foo", ".*", ".*"]), ok = control_action(set_permissions, ["-p", "/testhost", "foo", ".*", ".*"]), - ok = control_action(list_user_vhosts, ["foo"]), + ok = control_action(list_permissions, ["-p", "/testhost"]), + ok = control_action(list_user_permissions, ["foo"]), %% user/vhost unmapping ok = control_action(clear_permissions, ["-p", "/testhost", "foo"]), -- cgit v1.2.1 From 7b4ef01338bece10ade9a8b79a121a4c9babe18d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 19 Jan 2009 22:18:34 +0000 Subject: cache permissions in channel --- src/rabbit_access_control.erl | 1 - src/rabbit_channel.erl | 24 ++++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index ed88263c..22264ea2 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -145,7 +145,6 @@ check_resource_access(Username, check_resource_access(Username, R = #resource{virtual_host = VHostPath, name = Name}, Permission) -> - %% TODO: cache the results Res = case mnesia:dirty_read({user_permission, #user_vhost{username = Username, virtual_host = VHostPath}}) of diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 056ae649..ae502061 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -148,6 +148,7 @@ handle_message({deliver, ConsumerTag, AckRequired, Msg}, State1#ch{next_tag = DeliveryTag + 1}; handle_message({conserve_memory, Conserve}, State) -> + ok = clear_permission_cache(), ok = rabbit_writer:send_command( State#ch.writer_pid, #'channel.flow'{active = not(Conserve)}), State; @@ -199,13 +200,28 @@ return_queue_declare_ok(State, NoWait, Q) -> {reply, Reply, NewState} end. +check_resource_access(Username, Resource, Perm, PermIndex) -> + K = {resource_permission, Resource, Perm}, + %% TODO: we may want to make the cache bounded + case get(K) of + undefined -> R = rabbit_access_control:check_resource_access( + Username, Resource, PermIndex), + put(K, R), + R; + Other -> Other + end. + +clear_permission_cache() -> + [erase(R) || R = {resource_permission, _, _} <- get()], + ok. + check_configuration_permitted(Resource, #ch{ username = Username}) -> - rabbit_access_control:check_resource_access( - Username, Resource, #permission.configuration). + check_resource_access(Username, Resource, configuration, + #permission.configuration). check_messaging_permitted(Resource, #ch{ username = Username}) -> - rabbit_access_control:check_resource_access( - Username, Resource, #permission.messaging). + check_resource_access(Username, Resource, messaging, + #permission.messaging). expand_queue_name_shortcut(<<>>, #ch{ most_recently_declared_queue = <<>> }) -> rabbit_misc:protocol_error( -- cgit v1.2.1 From 55d51ed27ed18dc32d61168f0797c01f508ada78 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 19 Jan 2009 22:21:27 +0000 Subject: clear permission cache before hibernating --- src/rabbit_channel.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index fd9bb926..ce061467 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -153,6 +153,7 @@ handle_info({'EXIT', _Pid, Reason}, State) -> {stop, Reason, State}; handle_info(timeout, State) -> + ok = clear_permission_cache(), %% TODO: Once we drop support for R11B-5, we can change this to %% {noreply, State, hibernate}; proc_lib:hibernate(gen_server2, enter_loop, [?MODULE, [], State]). -- cgit v1.2.1 From 7d904a367bf4b9eb8b7ac780bc07381b6190877b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 20 Jan 2009 08:35:16 +0000 Subject: replace simple permission cache with lru cache, to make it bounded --- src/rabbit_channel.erl | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index ce061467..0ae3c186 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -48,6 +48,8 @@ -define(HIBERNATE_AFTER, 1000). +-define(MAX_PERMISSION_CACHE_SIZE, 12). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -202,28 +204,40 @@ return_queue_declare_ok(State, NoWait, Q) -> {reply, Reply, NewState} end. -check_resource_access(Username, Resource, Perm, PermIndex) -> - K = {resource_permission, Resource, Perm}, - %% TODO: we may want to make the cache bounded - case get(K) of - undefined -> R = rabbit_access_control:check_resource_access( - Username, Resource, PermIndex), - put(K, R), - R; - Other -> Other +lru_cache_lookup(K, LookupFun, MaxSize, Cache) -> + case lists:keytake(K, 1, Cache) of + {value, E = {_, V}, Cache1} -> + {V, [E | Cache1]}; + false -> + V = LookupFun(K), + {V, [{K, V} | lists:sublist(Cache, MaxSize - 1)]} end. +check_resource_access(Username, Resource, Perm) -> + Cache = case get(permission_cache) of + undefined -> []; + Other -> Other + end, + {Value, NewCache} = + lru_cache_lookup( + {Resource, Perm}, + fun ({R, P}) -> rabbit_access_control:check_resource_access( + Username, R, P) + end, + ?MAX_PERMISSION_CACHE_SIZE, + Cache), + put(permission_cache, NewCache), + Value. + clear_permission_cache() -> - [erase(R) || R = {resource_permission, _, _} <- get()], + erase(permission_cache), ok. check_configuration_permitted(Resource, #ch{ username = Username}) -> - check_resource_access(Username, Resource, configuration, - #permission.configuration). + check_resource_access(Username, Resource, #permission.configuration). check_messaging_permitted(Resource, #ch{ username = Username}) -> - check_resource_access(Username, Resource, messaging, - #permission.messaging). + check_resource_access(Username, Resource, #permission.messaging). expand_queue_name_shortcut(<<>>, #ch{ most_recently_declared_queue = <<>> }) -> rabbit_misc:protocol_error( -- cgit v1.2.1 From 78446f9f8e91f1371a19c4a1d3fa4d78dc0bba8e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 20 Jan 2009 20:35:20 +0000 Subject: simplify resource access cache check_resource_access throws an exception on failed auth, which closes the channel. Hence the cache can be simplified to a simple list of {Resource, Permission} pairs for which authorisation has previously succeeded. --- src/rabbit_channel.erl | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 0ae3c186..39867a4b 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -204,30 +204,21 @@ return_queue_declare_ok(State, NoWait, Q) -> {reply, Reply, NewState} end. -lru_cache_lookup(K, LookupFun, MaxSize, Cache) -> - case lists:keytake(K, 1, Cache) of - {value, E = {_, V}, Cache1} -> - {V, [E | Cache1]}; - false -> - V = LookupFun(K), - {V, [{K, V} | lists:sublist(Cache, MaxSize - 1)]} - end. - check_resource_access(Username, Resource, Perm) -> + V = {Resource, Perm}, Cache = case get(permission_cache) of undefined -> []; Other -> Other end, - {Value, NewCache} = - lru_cache_lookup( - {Resource, Perm}, - fun ({R, P}) -> rabbit_access_control:check_resource_access( - Username, R, P) - end, - ?MAX_PERMISSION_CACHE_SIZE, - Cache), - put(permission_cache, NewCache), - Value. + CacheTail = + case lists:member(V, Cache) of + true -> lists:delete(V, Cache); + false -> ok = rabbit_access_control:check_resource_access( + Username, Resource, Perm), + lists:sublist(Cache, ?MAX_PERMISSION_CACHE_SIZE - 1) + end, + put(permission_cache, [V | CacheTail]), + ok. clear_permission_cache() -> erase(permission_cache), -- cgit v1.2.1 -- cgit v1.2.1 From 60ddc29695d13c8ad356cdbf18da8718d6e4952f Mon Sep 17 00:00:00 2001 From: Ben Hood <0x6e6562@gmail.com> Date: Wed, 21 Jan 2009 13:18:34 +0000 Subject: Bad conflict resolution --- Makefile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Makefile b/Makefile index 83223667..e19c0d56 100644 --- a/Makefile +++ b/Makefile @@ -129,13 +129,8 @@ srcdist: distclean >> $(TARGET_SRC_DIR)/INSTALL cp README.in $(TARGET_SRC_DIR)/README elinks -dump -no-references -no-numbering $(WEB_URL)build-server.html \ -<<<<<<< local >> $(TARGET_SRC_DIR)/BUILD sed -i 's/%%VERSION%%/$(VERSION)/' $(TARGET_SRC_DIR)/ebin/rabbit_app.in -======= - >> $(TARGET_SRC_DIR)/README - sed -i 's/%%VERSION%%/$(VERSION)/' $(TARGET_SRC_DIR)/ebin/rabbit.app ->>>>>>> other cp -r $(AMQP_CODEGEN_DIR)/* $(TARGET_SRC_DIR)/codegen/ cp codegen.py Makefile $(TARGET_SRC_DIR) -- cgit v1.2.1 From 399666eeb629bdbc49ba67f9d37cfdaa9da254f3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 21 Jan 2009 14:17:54 +0000 Subject: remove merge conflict markers --- docs/rabbitmqctl.1.pod | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/rabbitmqctl.1.pod b/docs/rabbitmqctl.1.pod index fd8918cd..d2cb0199 100644 --- a/docs/rabbitmqctl.1.pod +++ b/docs/rabbitmqctl.1.pod @@ -157,12 +157,9 @@ messages_unacknowledged messages_uncommitted number of messages published in as yet uncommitted transactions -<<<<<<< local -======= messages sum of ready, unacknowledged and uncommitted messages ->>>>>>> other acks_uncommitted number of acknowledgements received in as yet uncommitted -- cgit v1.2.1 From dd684d5bb0ced0911946e3997e6bd88425a946e9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 21 Jan 2009 14:20:17 +0000 Subject: cosmetic: remove superfluous whitespace --- docs/rabbitmqctl.1.pod | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/rabbitmqctl.1.pod b/docs/rabbitmqctl.1.pod index d2cb0199..e3f62110 100644 --- a/docs/rabbitmqctl.1.pod +++ b/docs/rabbitmqctl.1.pod @@ -26,7 +26,7 @@ B<-n> I startup time). The output of hostname -s is usually the correct suffix to use after the "@" sign. See rabbitmq-server(1) for details of configuring the RabbitMQ broker. - + B<-q> quiet output mode is selected with the B<-q> flag. Informational messages are suppressed when quiet mode is in effect. @@ -43,32 +43,32 @@ stop_app This command is typically run prior to performing other management actions that require the RabbitMQ application to be stopped, e.g. I. - + start_app start the RabbitMQ application. This command is typically run prior to performing other management actions that require the RabbitMQ application to be stopped, e.g. I. - + status display various information about the RabbitMQ broker, such as whether the RabbitMQ application on the current node, its version number, what nodes are part of the broker, which of these are running. - + force return a RabbitMQ node to its virgin state. Removes the node from any cluster it belongs to, removes all data from the management database, such as configured users, vhosts and deletes all persistent messages. - + force_reset the same as I command, but resets the node unconditionally, regardless of the current management database state and cluster configuration. It should only be used as a last resort if the database or cluster configuration has been corrupted. - + rotate_logs [suffix] instruct the RabbitMQ node to rotate the log files. The RabbitMQ broker will attempt to append the current contents of the log file @@ -81,15 +81,15 @@ rotate_logs [suffix] specified. This command might be helpful when you are e.g. writing your own logrotate script and you do not want to restart the RabbitMQ node. - + cluster I ... instruct the node to become member of a cluster with the specified nodes determined by I option(s). See http://www.rabbitmq.com/clustering.html for more information about clustering. - + =head2 USER MANAGEMENT - + add_user I I create a user named I with (initial) password I. @@ -98,23 +98,23 @@ change_password I I list_users list all users. - + =head2 ACCESS CONTROL add_vhost I create a new virtual host called I. - + delete_vhost I delete a virtual host I. That command deletes also all its exchanges, queues and user mappings. - + list_vhosts list all virtual hosts. - + map_user_vhost I I grant the user named I access to the virtual host called I. - + unmap_user_vhost I I deny the user named I access to the virtual host called I. @@ -122,7 +122,7 @@ unmap_user_vhost I I list_user_vhost I list all the virtual hosts to which the user named I has been granted access. - + =head2 SERVER STATUS list_queues [-p I] [I ...] @@ -229,7 +229,7 @@ peer_address peer_port peer port - + state connection state (B, B, B, B, B, B, B) @@ -263,7 +263,7 @@ send_cnt send_pend send queue size - + =back The list_queues, list_exchanges and list_bindings commands accept an @@ -277,12 +277,12 @@ Create a user named foo with (initial) password bar at the Erlang node rabbit@test: rabbitmqctl -n rabbit@test add_user foo bar - + Grant user named foo access to the virtual host called test at the default Erlang node: rabbitmqctl map_user_vhost foo test - + Append the current logs' content to the files with ".1" suffix and reopen them: -- cgit v1.2.1 From afcb8fd9722a1a4a5a8eccac9811e4c9cd6d0c32 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 21 Jan 2009 14:20:52 +0000 Subject: add forgotten docs for delete_user command --- docs/rabbitmqctl.1.pod | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/rabbitmqctl.1.pod b/docs/rabbitmqctl.1.pod index e3f62110..68c26b14 100644 --- a/docs/rabbitmqctl.1.pod +++ b/docs/rabbitmqctl.1.pod @@ -92,7 +92,10 @@ cluster I ... add_user I I create a user named I with (initial) password I. - + +delete_user I + delete the user named I. + change_password I I change the password for the user named I to I. -- cgit v1.2.1 From bdd00f2cb05fa97acaea9d921c831f687d06ab56 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 21 Jan 2009 15:23:26 +0000 Subject: document new rabbitmqctl commands --- docs/rabbitmqctl.1.pod | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/rabbitmqctl.1.pod b/docs/rabbitmqctl.1.pod index 68c26b14..7909c148 100644 --- a/docs/rabbitmqctl.1.pod +++ b/docs/rabbitmqctl.1.pod @@ -114,17 +114,23 @@ delete_vhost I list_vhosts list all virtual hosts. -map_user_vhost I I - grant the user named I access to the virtual host called +set_permissions [-p I] I + set the permissions for the user named I in the virtual + host I, granting them configuration access to resources + with names matching the first I and messaging access to + resources with names matching the second . + +clear_permissions [-p I] I + remove the permissions for the user named I in the + virtual host I. + +list_permissions [-p I] + list all the users and their permissions in the virtual host I. -unmap_user_vhost I I - deny the user named I access to the virtual host called - I. - -list_user_vhost I - list all the virtual hosts to which the user named I has - been granted access. +list_user_permissions I + list the permissions of the user named I across all + virtual hosts. =head2 SERVER STATUS -- cgit v1.2.1 From 267ebdd5ee128e797a023b2e704c9c14e5adfe61 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 21 Jan 2009 17:21:32 +0000 Subject: anybody can do anything with server-named resources ...as long as they get hold of the name, which is strong --- src/rabbit_access_control.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 22264ea2..2ae4dfdd 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -142,6 +142,10 @@ check_resource_access(Username, check_resource_access(Username, R#resource{name = <<"amq.default">>}, Permission); +check_resource_access(_Username, + #resource{name = <<"amq.gen",_/binary>>}, + _Permission) -> + ok; check_resource_access(Username, R = #resource{virtual_host = VHostPath, name = Name}, Permission) -> -- cgit v1.2.1 From 0ca599b3a28ca43ae4b0adc74f91ee7d7a5f293b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 21 Jan 2009 20:13:09 +0000 Subject: turn rabbit_router into gen_server2 ...in order to protect it from the effects of long message queues. This isn't actually relevant at the moment because the bulk of the router code is disabled in order to deal with bug 19758. But we will re-enable that code eventually. --- src/rabbit_router.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_router.erl b/src/rabbit_router.erl index 26d857be..ff42ea04 100644 --- a/src/rabbit_router.erl +++ b/src/rabbit_router.erl @@ -32,7 +32,7 @@ -module(rabbit_router). -include("rabbit.hrl"). --behaviour(gen_server). +-behaviour(gen_server2). -export([start_link/0, deliver/5]). @@ -58,7 +58,7 @@ %%---------------------------------------------------------------------------- start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + gen_server2:start_link({local, ?SERVER}, ?MODULE, [], []). -ifdef(BUG19758). @@ -143,7 +143,7 @@ handle_call({deliver, QPids, Mandatory, Immediate, Txn, Message}, spawn( fun () -> R = run_bindings(QPids, Mandatory, Immediate, Txn, Message), - gen_server:reply(From, R) + gen_server2:reply(From, R) end), {noreply, State}. -- cgit v1.2.1 From 905b60490597e358415e229f993364ae31e37728 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 22 Jan 2009 12:54:35 +0000 Subject: typo --- docs/rabbitmqctl.1.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rabbitmqctl.1.pod b/docs/rabbitmqctl.1.pod index 7909c148..9123208d 100644 --- a/docs/rabbitmqctl.1.pod +++ b/docs/rabbitmqctl.1.pod @@ -114,7 +114,7 @@ delete_vhost I list_vhosts list all virtual hosts. -set_permissions [-p I] I +set_permissions [-p I] I I I set the permissions for the user named I in the virtual host I, granting them configuration access to resources with names matching the first I and messaging access to -- cgit v1.2.1 From da4f1505b9bccdbcd2f0c712bc0cca3379c2aadd Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 22 Jan 2009 12:56:33 +0000 Subject: fix indentation --- src/rabbit_access_control.erl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 2ae4dfdd..357a294a 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -309,7 +309,7 @@ list_vhost_permissions(VHostPath) -> [{Username, ConfigurationPerm, MessagingPerm} || #user_permission{user_vhost = #user_vhost{username = Username}, permission = #permission{ - configuration = ConfigurationPerm, + configuration = ConfigurationPerm, messaging = MessagingPerm}} <- %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction( @@ -326,7 +326,7 @@ list_user_permissions(Username) -> [{VHostPath, ConfigurationPerm, MessagingPerm} || #user_permission{user_vhost = #user_vhost{virtual_host = VHostPath}, permission = #permission{ - configuration = ConfigurationPerm, + configuration = ConfigurationPerm, messaging = MessagingPerm}} <- %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction( @@ -338,5 +338,3 @@ list_user_permissions(Username) -> virtual_host = '_'}, permission = '_'}) end))]. - - -- cgit v1.2.1 From 414fcba01a9ef35404e79075acfcaa753ba695b3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 22 Jan 2009 13:28:50 +0000 Subject: refactoring --- src/rabbit_access_control.erl | 44 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 357a294a..0d9632b5 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -307,34 +307,30 @@ clear_permissions(Username, VHostPath) -> list_vhost_permissions(VHostPath) -> [{Username, ConfigurationPerm, MessagingPerm} || - #user_permission{user_vhost = #user_vhost{username = Username}, - permission = #permission{ - configuration = ConfigurationPerm, - messaging = MessagingPerm}} <- - %% TODO: use dirty ops instead - rabbit_misc:execute_mnesia_transaction( - rabbit_misc:with_vhost( - VHostPath, - fun () -> mnesia:match_object( - #user_permission{user_vhost = #user_vhost{ - username = '_', - virtual_host = VHostPath}, - permission = '_'}) - end))]. + {Username, _, ConfigurationPerm, MessagingPerm} <- + list_permissions(rabbit_misc:with_vhost( + VHostPath, match_user_vhost('_', VHostPath)))]. list_user_permissions(Username) -> [{VHostPath, ConfigurationPerm, MessagingPerm} || - #user_permission{user_vhost = #user_vhost{virtual_host = VHostPath}, + {_, VHostPath, ConfigurationPerm, MessagingPerm} <- + list_permissions(rabbit_misc:with_user( + Username, match_user_vhost(Username, '_')))]. + +list_permissions(QueryThunk) -> + [{Username, VHostPath, ConfigurationPerm, MessagingPerm} || + #user_permission{user_vhost = #user_vhost{username = Username, + virtual_host = VHostPath}, permission = #permission{ configuration = ConfigurationPerm, messaging = MessagingPerm}} <- %% TODO: use dirty ops instead - rabbit_misc:execute_mnesia_transaction( - rabbit_misc:with_user( - Username, - fun () -> mnesia:match_object( - #user_permission{user_vhost = #user_vhost{ - username = Username, - virtual_host = '_'}, - permission = '_'}) - end))]. + rabbit_misc:execute_mnesia_transaction(QueryThunk)]. + +match_user_vhost(Username, VHostPath) -> + fun () -> mnesia:match_object( + #user_permission{user_vhost = #user_vhost{ + username = Username, + virtual_host = VHostPath}, + permission = '_'}) + end. -- cgit v1.2.1 From 8b9db9c0f50f03ddc794fa9b21ffaacdb70f7563 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 22 Jan 2009 15:54:44 +0000 Subject: don't infer table name from record type in mnesia ops This is in preparation for prefixing all table names with 'rabbit_' in order to avoid conflicts with other apps running in the same VM. This is a fairly straightforward change. The only tricky bit is the untangling of rabbit_exchange:indexed_delete. --- src/rabbit_access_control.erl | 30 ++++++++++++++++------- src/rabbit_amqqueue.erl | 5 ++-- src/rabbit_exchange.erl | 57 ++++++++++++++++++++++++------------------- src/rabbit_networking.erl | 2 ++ 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 0d9632b5..10d721f2 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -173,8 +173,10 @@ add_user(Username, Password) -> fun () -> case mnesia:read({user, Username}) of [] -> - ok = mnesia:write(#user{username = Username, - password = Password}); + ok = mnesia:write(user, + #user{username = Username, + password = Password}, + write); _ -> mnesia:abort({user_already_exists, Username}) end @@ -188,12 +190,14 @@ delete_user(Username) -> Username, fun () -> ok = mnesia:delete({user, Username}), - [ok = mnesia:delete_object(R) || + [ok = mnesia:delete_object(user_permissions, R, write) || R <- mnesia:match_object( + user_permission, #user_permission{user_vhost = #user_vhost{ username = Username, virtual_host = '_'}, - permission = '_'})], + permission = '_'}, + read)], ok end)), rabbit_log:info("Deleted user ~p~n", [Username]), @@ -204,8 +208,10 @@ change_password(Username, Password) -> rabbit_misc:with_user( Username, fun () -> - ok = mnesia:write(#user{username = Username, - password = Password}) + ok = mnesia:write(user, + #user{username = Username, + password = Password}, + write) end)), rabbit_log:info("Changed password for user ~p~n", [Username]), R. @@ -221,7 +227,9 @@ add_vhost(VHostPath) -> fun () -> case mnesia:read({vhost, VHostPath}) of [] -> - ok = mnesia:write(#vhost{virtual_host = VHostPath}), + ok = mnesia:write(vhost, + #vhost{virtual_host = VHostPath}, + write), [rabbit_exchange:declare( rabbit_misc:r(VHostPath, exchange, Name), Type, true, false, []) || @@ -287,12 +295,14 @@ set_permissions(Username, VHostPath, ConfigurationPerm, MessagingPerm) -> rabbit_misc:with_user_and_vhost( Username, VHostPath, fun () -> ok = mnesia:write( + user_permission, #user_permission{user_vhost = #user_vhost{ username = Username, virtual_host = VHostPath}, permission = #permission{ configuration = ConfigurationPerm, - messaging = MessagingPerm}}) + messaging = MessagingPerm}}, + write) end)). clear_permissions(Username, VHostPath) -> @@ -329,8 +339,10 @@ list_permissions(QueryThunk) -> match_user_vhost(Username, VHostPath) -> fun () -> mnesia:match_object( + user_permission, #user_permission{user_vhost = #user_vhost{ username = Username, virtual_host = VHostPath}, - permission = '_'}) + permission = '_'}, + read) end. diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index abbdce66..025571df 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -160,10 +160,10 @@ declare(QueueName, Durable, AutoDelete, Args) -> store_queue(Q = #amqqueue{durable = true}) -> ok = mnesia:write(durable_queues, Q, write), - ok = mnesia:write(Q), + ok = mnesia:write(amqqueue, Q, write), ok; store_queue(Q = #amqqueue{durable = false}) -> - ok = mnesia:write(Q), + ok = mnesia:write(amqqueue, Q, write), ok. start_queue_process(Q) -> @@ -194,6 +194,7 @@ with_or_die(Name, F) -> list(VHostPath) -> mnesia:dirty_match_object( + amqqueue, #amqqueue{name = rabbit_misc:r(VHostPath, queue), _ = '_'}). map(VHostPath, F) -> rabbit_misc:filter_exit_map(F, list(VHostPath)). diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 960e4945..c97b4adc 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -107,14 +107,14 @@ recover() -> fun () -> mnesia:foldl( fun (Exchange, Acc) -> - ok = mnesia:write(Exchange), + ok = mnesia:write(exchange, Exchange, write), Acc end, ok, durable_exchanges), mnesia:foldl( fun (Route, Acc) -> {_, ReverseRoute} = route_with_reverse(Route), - ok = mnesia:write(Route), - ok = mnesia:write(ReverseRoute), + ok = mnesia:write(route, Route, write), + ok = mnesia:write(reverse_route, ReverseRoute, write), Acc end, ok, durable_routes), ok @@ -129,7 +129,7 @@ declare(ExchangeName, Type, Durable, AutoDelete, Args) -> rabbit_misc:execute_mnesia_transaction( fun () -> case mnesia:wread({exchange, ExchangeName}) of - [] -> ok = mnesia:write(Exchange), + [] -> ok = mnesia:write(exchange, Exchange, write), if Durable -> ok = mnesia:write( durable_exchanges, Exchange, write); @@ -173,6 +173,7 @@ lookup_or_die(Name) -> list(VHostPath) -> mnesia:dirty_match_object( + exchange, #exchange{name = rabbit_misc:r(VHostPath, exchange), _ = '_'}). map(VHostPath, F) -> @@ -271,6 +272,7 @@ match_bindings(#exchange{name = Name}, Match) -> [QName || #route{binding = Binding = #binding{ queue_name = QName}} <- mnesia:dirty_match_object( + route, #route{binding = #binding{exchange_name = Name, _ = '_'}}), Match(Binding)] @@ -297,32 +299,35 @@ lookup_qpids(Queues) -> %% to be implemented for 0.91 ? delete_bindings_for_exchange(ExchangeName) -> - indexed_delete( - #route{binding = #binding{exchange_name = ExchangeName, - _ = '_'}}, - fun delete_forward_routes/1, fun mnesia:delete_object/1). + [begin + ok = mnesia:delete_object(reverse_route, reverse_route(Route), write), + ok = delete_forward_routes(Route) + end || Route <- mnesia:match_object( + route, + #route{binding = #binding{exchange_name = ExchangeName, + _ = '_'}}, + read)], + ok. delete_bindings_for_queue(QueueName) -> Exchanges = exchanges_for_queue(QueueName), - indexed_delete( - reverse_route(#route{binding = #binding{queue_name = QueueName, - _ = '_'}}), - fun mnesia:delete_object/1, fun delete_forward_routes/1), + [begin + ok = delete_forward_routes(reverse_route(Route)), + ok = mnesia:delete_object(reverse_route, Route, write) + end || Route <- mnesia:match_object( + reverse_route, + reverse_route( + #route{binding = #binding{queue_name = QueueName, + _ = '_'}}), + write)], [begin [X] = mnesia:read({exchange, ExchangeName}), ok = maybe_auto_delete(X) end || ExchangeName <- Exchanges], ok. -indexed_delete(Match, ForwardsDeleteFun, ReverseDeleteFun) -> - [begin - ok = ReverseDeleteFun(reverse_route(Route)), - ok = ForwardsDeleteFun(Route) - end || Route <- mnesia:match_object(Match)], - ok. - delete_forward_routes(Route) -> - ok = mnesia:delete_object(Route), + ok = mnesia:delete_object(route, Route, write), ok = mnesia:delete_object(durable_routes, Route, write). exchanges_for_queue(QueueName) -> @@ -342,7 +347,7 @@ has_bindings(ExchangeName) -> catch exit:{aborted, {badarg, _}} -> %% work around OTP-7025, which was fixed in R12B-1, by %% falling back on a less efficient method - case mnesia:match_object(MatchHead) of + case mnesia:match_object(route, MatchHead, read) of [] -> false; [_|_] -> true end @@ -400,8 +405,9 @@ sync_binding(ExchangeName, QueueName, RoutingKey, Arguments, Durable, Fun) -> true -> Fun(durable_routes, #route{binding = Binding}, write); false -> ok end, - [ok, ok] = [Fun(element(1, R), R, write) || - R <- tuple_to_list(route_with_reverse(Binding))], + {Route, ReverseRoute} = route_with_reverse(Binding), + ok = Fun(route, Route, write), + ok = Fun(reverse_route, ReverseRoute, write), ok. list_bindings(VHostPath) -> @@ -412,6 +418,7 @@ list_bindings(VHostPath) -> queue_name = QueueName, args = Arguments}} <- mnesia:dirty_match_object( + route, #route{binding = #binding{ exchange_name = rabbit_misc:r(VHostPath, exchange), _ = '_'}, @@ -571,7 +578,7 @@ list_exchange_bindings(ExchangeName) -> #route{binding = #binding{queue_name = QueueName, key = RoutingKey, args = Arguments}} - <- mnesia:dirty_match_object(Route)]. + <- mnesia:dirty_match_object(route, Route)]. % Refactoring is left as an exercise for the reader list_queue_bindings(QueueName) -> @@ -581,4 +588,4 @@ list_queue_bindings(QueueName) -> #route{binding = #binding{exchange_name = ExchangeName, key = RoutingKey, args = Arguments}} - <- mnesia:dirty_match_object(Route)]. + <- mnesia:dirty_match_object(route, Route)]. diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index 99ea37d8..81c40a31 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -123,6 +123,7 @@ stop_tcp_listener(Host, Port) -> tcp_listener_started(IPAddress, Port) -> ok = mnesia:dirty_write( + listener, #listener{node = node(), protocol = tcp, host = tcp_host(IPAddress), @@ -130,6 +131,7 @@ tcp_listener_started(IPAddress, Port) -> tcp_listener_stopped(IPAddress, Port) -> ok = mnesia:dirty_delete_object( + listener, #listener{node = node(), protocol = tcp, host = tcp_host(IPAddress), -- cgit v1.2.1 From 20b6f51d2e495d89fa4cd02772785a2f003bba50 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 22 Jan 2009 21:51:39 +0000 Subject: prefix all mnesia table names with 'rabbit_' and - get rid of plurals in table names - change rabbit_amqqueue to rabbit_queue - put table attributes in more sensible order --- src/rabbit_access_control.erl | 35 ++++++++++---------- src/rabbit_amqqueue.erl | 24 +++++++------- src/rabbit_exchange.erl | 75 +++++++++++++++++++++++-------------------- src/rabbit_misc.erl | 4 +-- src/rabbit_mnesia.erl | 70 +++++++++++++++++++++++++--------------- src/rabbit_networking.erl | 10 +++--- 6 files changed, 122 insertions(+), 96 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 10d721f2..883425d0 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -117,7 +117,7 @@ internal_lookup_vhost_access(Username, VHostPath) -> %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:read({user_permission, + case mnesia:read({rabbit_user_permission, #user_vhost{username = Username, virtual_host = VHostPath}}) of [] -> not_found; @@ -149,7 +149,7 @@ check_resource_access(_Username, check_resource_access(Username, R = #resource{virtual_host = VHostPath, name = Name}, Permission) -> - Res = case mnesia:dirty_read({user_permission, + Res = case mnesia:dirty_read({rabbit_user_permission, #user_vhost{username = Username, virtual_host = VHostPath}}) of [] -> @@ -171,9 +171,9 @@ check_resource_access(Username, add_user(Username, Password) -> R = rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:read({user, Username}) of + case mnesia:read({rabbit_user, Username}) of [] -> - ok = mnesia:write(user, + ok = mnesia:write(rabbit_user, #user{username = Username, password = Password}, write); @@ -189,10 +189,11 @@ delete_user(Username) -> rabbit_misc:with_user( Username, fun () -> - ok = mnesia:delete({user, Username}), - [ok = mnesia:delete_object(user_permissions, R, write) || + ok = mnesia:delete({rabbit_user, Username}), + [ok = mnesia:delete_object( + rabbit_user_permissions, R, write) || R <- mnesia:match_object( - user_permission, + rabbit_user_permission, #user_permission{user_vhost = #user_vhost{ username = Username, virtual_host = '_'}, @@ -208,7 +209,7 @@ change_password(Username, Password) -> rabbit_misc:with_user( Username, fun () -> - ok = mnesia:write(user, + ok = mnesia:write(rabbit_user, #user{username = Username, password = Password}, write) @@ -217,17 +218,17 @@ change_password(Username, Password) -> R. list_users() -> - mnesia:dirty_all_keys(user). + mnesia:dirty_all_keys(rabbit_user). lookup_user(Username) -> - rabbit_misc:dirty_read({user, Username}). + rabbit_misc:dirty_read({rabbit_user, Username}). add_vhost(VHostPath) -> R = rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:read({vhost, VHostPath}) of + case mnesia:read({rabbit_vhost, VHostPath}) of [] -> - ok = mnesia:write(vhost, + ok = mnesia:write(rabbit_vhost, #vhost{virtual_host = VHostPath}, write), [rabbit_exchange:declare( @@ -275,11 +276,11 @@ internal_delete_vhost(VHostPath) -> ok = clear_permissions(Username, VHostPath) end, list_vhost_permissions(VHostPath)), - ok = mnesia:delete({vhost, VHostPath}), + ok = mnesia:delete({rabbit_vhost, VHostPath}), ok. list_vhosts() -> - mnesia:dirty_all_keys(vhost). + mnesia:dirty_all_keys(rabbit_vhost). validate_regexp(RegexpBin) -> Regexp = binary_to_list(RegexpBin), @@ -295,7 +296,7 @@ set_permissions(Username, VHostPath, ConfigurationPerm, MessagingPerm) -> rabbit_misc:with_user_and_vhost( Username, VHostPath, fun () -> ok = mnesia:write( - user_permission, + rabbit_user_permission, #user_permission{user_vhost = #user_vhost{ username = Username, virtual_host = VHostPath}, @@ -310,7 +311,7 @@ clear_permissions(Username, VHostPath) -> rabbit_misc:with_user_and_vhost( Username, VHostPath, fun () -> - ok = mnesia:delete({user_permission, + ok = mnesia:delete({rabbit_user_permission, #user_vhost{username = Username, virtual_host = VHostPath}}) end)). @@ -339,7 +340,7 @@ list_permissions(QueryThunk) -> match_user_vhost(Username, VHostPath) -> fun () -> mnesia:match_object( - user_permission, + rabbit_user_permission, #user_permission{user_vhost = #user_vhost{ username = Username, virtual_host = VHostPath}, diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 025571df..3018582f 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -128,7 +128,7 @@ recover_durable_queues() -> R = rabbit_misc:execute_mnesia_transaction( fun () -> qlc:e(qlc:q([Q || Q = #amqqueue{pid = Pid} - <- mnesia:table(durable_queues), + <- mnesia:table(rabbit_durable_queue), node(Pid) == Node])) end), Queues = lists:map(fun start_queue_process/1, R), @@ -146,7 +146,7 @@ declare(QueueName, Durable, AutoDelete, Args) -> pid = none}), case rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:wread({amqqueue, QueueName}) of + case mnesia:wread({rabbit_queue, QueueName}) of [] -> ok = store_queue(Q), ok = add_default_binding(Q), Q; @@ -159,11 +159,11 @@ declare(QueueName, Durable, AutoDelete, Args) -> end. store_queue(Q = #amqqueue{durable = true}) -> - ok = mnesia:write(durable_queues, Q, write), - ok = mnesia:write(amqqueue, Q, write), + ok = mnesia:write(rabbit_durable_queue, Q, write), + ok = mnesia:write(rabbit_queue, Q, write), ok; store_queue(Q = #amqqueue{durable = false}) -> - ok = mnesia:write(amqqueue, Q, write), + ok = mnesia:write(rabbit_queue, Q, write), ok. start_queue_process(Q) -> @@ -177,7 +177,7 @@ add_default_binding(#amqqueue{name = QueueName}) -> ok. lookup(Name) -> - rabbit_misc:dirty_read({amqqueue, Name}). + rabbit_misc:dirty_read({rabbit_queue, Name}). with(Name, F, E) -> case lookup(Name) of @@ -194,7 +194,7 @@ with_or_die(Name, F) -> list(VHostPath) -> mnesia:dirty_match_object( - amqqueue, + rabbit_queue, #amqqueue{name = rabbit_misc:r(VHostPath, queue), _ = '_'}). map(VHostPath, F) -> rabbit_misc:filter_exit_map(F, list(VHostPath)). @@ -215,7 +215,7 @@ info_all(VHostPath, Items) -> map(VHostPath, fun (Q) -> info(Q, Items) end). stat(#amqqueue{pid = QPid}) -> gen_server2:call(QPid, stat). stat_all() -> - lists:map(fun stat/1, rabbit_misc:dirty_read_all(amqqueue)). + lists:map(fun stat/1, rabbit_misc:dirty_read_all(rabbit_queue)). delete(#amqqueue{ pid = QPid }, IfUnused, IfEmpty) -> gen_server2:call(QPid, {delete, IfUnused, IfEmpty}). @@ -291,18 +291,18 @@ unblock(QPid, ChPid) -> internal_delete(QueueName) -> rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:wread({amqqueue, QueueName}) of + case mnesia:wread({rabbit_queue, QueueName}) of [] -> {error, not_found}; [Q] -> ok = delete_queue(Q), - ok = mnesia:delete({durable_queues, QueueName}), + ok = mnesia:delete({rabbit_durable_queue, QueueName}), ok end end). delete_queue(#amqqueue{name = QueueName}) -> ok = rabbit_exchange:delete_bindings_for_queue(QueueName), - ok = mnesia:delete({amqqueue, QueueName}), + ok = mnesia:delete({rabbit_queue, QueueName}), ok. on_node_down(Node) -> @@ -312,7 +312,7 @@ on_node_down(Node) -> fun (Q, Acc) -> ok = delete_queue(Q), Acc end, ok, qlc:q([Q || Q = #amqqueue{pid = Pid} - <- mnesia:table(amqqueue), + <- mnesia:table(rabbit_queue), node(Pid) == Node])) end). diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index c97b4adc..b04a5b37 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -107,16 +107,18 @@ recover() -> fun () -> mnesia:foldl( fun (Exchange, Acc) -> - ok = mnesia:write(exchange, Exchange, write), + ok = mnesia:write(rabbit_exchange, Exchange, write), Acc - end, ok, durable_exchanges), + end, ok, rabbit_durable_exchange), mnesia:foldl( fun (Route, Acc) -> {_, ReverseRoute} = route_with_reverse(Route), - ok = mnesia:write(route, Route, write), - ok = mnesia:write(reverse_route, ReverseRoute, write), + ok = mnesia:write(rabbit_route, + Route, write), + ok = mnesia:write(rabbit_reverse_route, + ReverseRoute, write), Acc - end, ok, durable_routes), + end, ok, rabbit_durable_route), ok end). @@ -128,11 +130,11 @@ declare(ExchangeName, Type, Durable, AutoDelete, Args) -> arguments = Args}, rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:wread({exchange, ExchangeName}) of - [] -> ok = mnesia:write(exchange, Exchange, write), + case mnesia:wread({rabbit_exchange, ExchangeName}) of + [] -> ok = mnesia:write(rabbit_exchange, Exchange, write), if Durable -> - ok = mnesia:write( - durable_exchanges, Exchange, write); + ok = mnesia:write(rabbit_durable_exchange, + Exchange, write); true -> ok end, Exchange; @@ -161,7 +163,7 @@ assert_type(#exchange{ name = Name, type = ActualType }, RequiredType) -> [rabbit_misc:rs(Name), ActualType, RequiredType]). lookup(Name) -> - rabbit_misc:dirty_read({exchange, Name}). + rabbit_misc:dirty_read({rabbit_exchange, Name}). lookup_or_die(Name) -> case lookup(Name) of @@ -173,7 +175,7 @@ lookup_or_die(Name) -> list(VHostPath) -> mnesia:dirty_match_object( - exchange, + rabbit_exchange, #exchange{name = rabbit_misc:r(VHostPath, exchange), _ = '_'}). map(VHostPath, F) -> @@ -260,7 +262,7 @@ match_bindings(#exchange{name = Name}, Match) -> Query = qlc:q([QName || #route{binding = Binding = #binding{ exchange_name = ExchangeName, queue_name = QName}} <- - mnesia:table(route), + mnesia:table(rabbit_route), ExchangeName == Name, Match(Binding)]), lookup_qpids( @@ -272,7 +274,7 @@ match_bindings(#exchange{name = Name}, Match) -> [QName || #route{binding = Binding = #binding{ queue_name = QName}} <- mnesia:dirty_match_object( - route, + rabbit_route, #route{binding = #binding{exchange_name = Name, _ = '_'}}), Match(Binding)] @@ -283,12 +285,12 @@ match_routing_key(#exchange{name = Name}, RoutingKey) -> queue_name = '$1', key = RoutingKey, _ = '_'}}, - lookup_qpids(mnesia:dirty_select(route, [{MatchHead, [], ['$1']}])). + lookup_qpids(mnesia:dirty_select(rabbit_route, [{MatchHead, [], ['$1']}])). lookup_qpids(Queues) -> sets:fold( fun(Key, Acc) -> - case mnesia:dirty_read({amqqueue, Key}) of + case mnesia:dirty_read({rabbit_queue, Key}) of [#amqqueue{pid = QPid}] -> [QPid | Acc]; [] -> Acc end @@ -300,10 +302,11 @@ lookup_qpids(Queues) -> delete_bindings_for_exchange(ExchangeName) -> [begin - ok = mnesia:delete_object(reverse_route, reverse_route(Route), write), + ok = mnesia:delete_object(rabbit_reverse_route, + reverse_route(Route), write), ok = delete_forward_routes(Route) end || Route <- mnesia:match_object( - route, + rabbit_route, #route{binding = #binding{exchange_name = ExchangeName, _ = '_'}}, read)], @@ -313,22 +316,22 @@ delete_bindings_for_queue(QueueName) -> Exchanges = exchanges_for_queue(QueueName), [begin ok = delete_forward_routes(reverse_route(Route)), - ok = mnesia:delete_object(reverse_route, Route, write) + ok = mnesia:delete_object(rabbit_reverse_route, Route, write) end || Route <- mnesia:match_object( - reverse_route, + rabbit_reverse_route, reverse_route( #route{binding = #binding{queue_name = QueueName, _ = '_'}}), write)], [begin - [X] = mnesia:read({exchange, ExchangeName}), + [X] = mnesia:read({rabbit_exchange, ExchangeName}), ok = maybe_auto_delete(X) end || ExchangeName <- Exchanges], ok. delete_forward_routes(Route) -> - ok = mnesia:delete_object(route, Route, write), - ok = mnesia:delete_object(durable_routes, Route, write). + ok = mnesia:delete_object(rabbit_route, Route, write), + ok = mnesia:delete_object(rabbit_durable_route, Route, write). exchanges_for_queue(QueueName) -> MatchHead = reverse_route( @@ -337,17 +340,18 @@ exchanges_for_queue(QueueName) -> _ = '_'}}), sets:to_list( sets:from_list( - mnesia:select(reverse_route, [{MatchHead, [], ['$1']}]))). + mnesia:select(rabbit_reverse_route, [{MatchHead, [], ['$1']}]))). has_bindings(ExchangeName) -> MatchHead = #route{binding = #binding{exchange_name = ExchangeName, _ = '_'}}, try - continue(mnesia:select(route, [{MatchHead, [], ['$_']}], 1, read)) + continue(mnesia:select(rabbit_route, [{MatchHead, [], ['$_']}], + 1, read)) catch exit:{aborted, {badarg, _}} -> %% work around OTP-7025, which was fixed in R12B-1, by %% falling back on a less efficient method - case mnesia:match_object(route, MatchHead, read) of + case mnesia:match_object(rabbit_route, MatchHead, read) of [] -> false; [_|_] -> true end @@ -359,7 +363,7 @@ continue({[], Continuation}) -> continue(mnesia:select(Continuation)). call_with_exchange(Exchange, Fun) -> rabbit_misc:execute_mnesia_transaction( - fun() -> case mnesia:read({exchange, Exchange}) of + fun() -> case mnesia:read({rabbit_exchange, Exchange}) of [] -> {error, exchange_not_found}; [X] -> Fun(X) end @@ -368,7 +372,7 @@ call_with_exchange(Exchange, Fun) -> call_with_exchange_and_queue(Exchange, Queue, Fun) -> call_with_exchange( Exchange, - fun(X) -> case mnesia:read({amqqueue, Queue}) of + fun(X) -> case mnesia:read({rabbit_queue, Queue}) of [] -> {error, queue_not_found}; [Q] -> Fun(X, Q) end @@ -402,12 +406,13 @@ sync_binding(ExchangeName, QueueName, RoutingKey, Arguments, Durable, Fun) -> key = RoutingKey, args = sort_arguments(Arguments)}, ok = case Durable of - true -> Fun(durable_routes, #route{binding = Binding}, write); + true -> Fun(rabbit_durable_route, + #route{binding = Binding}, write); false -> ok end, {Route, ReverseRoute} = route_with_reverse(Binding), - ok = Fun(route, Route, write), - ok = Fun(reverse_route, ReverseRoute, write), + ok = Fun(rabbit_route, Route, write), + ok = Fun(rabbit_reverse_route, ReverseRoute, write), ok. list_bindings(VHostPath) -> @@ -418,7 +423,7 @@ list_bindings(VHostPath) -> queue_name = QueueName, args = Arguments}} <- mnesia:dirty_match_object( - route, + rabbit_route, #route{binding = #binding{ exchange_name = rabbit_misc:r(VHostPath, exchange), _ = '_'}, @@ -561,8 +566,8 @@ conditional_delete(Exchange = #exchange{name = ExchangeName}) -> unconditional_delete(#exchange{name = ExchangeName}) -> ok = delete_bindings_for_exchange(ExchangeName), - ok = mnesia:delete({durable_exchanges, ExchangeName}), - ok = mnesia:delete({exchange, ExchangeName}). + ok = mnesia:delete({rabbit_durable_exchange, ExchangeName}), + ok = mnesia:delete({rabbit_exchange, ExchangeName}). %%---------------------------------------------------------------------------- %% EXTENDED API @@ -578,7 +583,7 @@ list_exchange_bindings(ExchangeName) -> #route{binding = #binding{queue_name = QueueName, key = RoutingKey, args = Arguments}} - <- mnesia:dirty_match_object(route, Route)]. + <- mnesia:dirty_match_object(rabbit_route, Route)]. % Refactoring is left as an exercise for the reader list_queue_bindings(QueueName) -> @@ -588,4 +593,4 @@ list_queue_bindings(QueueName) -> #route{binding = #binding{exchange_name = ExchangeName, key = RoutingKey, args = Arguments}} - <- mnesia:dirty_match_object(route, Route)]. + <- mnesia:dirty_match_object(rabbit_route, Route)]. diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 85db50d7..214c9528 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -237,7 +237,7 @@ filter_exit_map(F, L) -> with_user(Username, Thunk) -> fun () -> - case mnesia:read({user, Username}) of + case mnesia:read({rabbit_user, Username}) of [] -> mnesia:abort({no_such_user, Username}); [_U] -> @@ -247,7 +247,7 @@ with_user(Username, Thunk) -> with_vhost(VHostPath, Thunk) -> fun () -> - case mnesia:read({vhost, VHostPath}) of + case mnesia:read({rabbit_vhost, VHostPath}) of [] -> mnesia:abort({no_such_vhost, VHostPath}); [_V] -> diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index b7f3dd0a..c0bf3d25 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -100,31 +100,51 @@ force_reset() -> reset(true). %%-------------------------------------------------------------------- table_definitions() -> - [{user, [{disc_copies, [node()]}, - {attributes, record_info(fields, user)}]}, - {user_permission, [{disc_copies, [node()]}, - {attributes, record_info(fields, user_permission)}]}, - {vhost, [{disc_copies, [node()]}, - {attributes, record_info(fields, vhost)}]}, - {rabbit_config, [{disc_copies, [node()]}]}, - {listener, [{type, bag}, - {attributes, record_info(fields, listener)}]}, - {durable_routes, [{disc_copies, [node()]}, - {record_name, route}, - {attributes, record_info(fields, route)}]}, - {route, [{type, ordered_set}, - {attributes, record_info(fields, route)}]}, - {reverse_route, [{type, ordered_set}, - {attributes, record_info(fields, reverse_route)}]}, - {durable_exchanges, [{disc_copies, [node()]}, - {record_name, exchange}, - {attributes, record_info(fields, exchange)}]}, - {exchange, [{attributes, record_info(fields, exchange)}]}, - {durable_queues, [{disc_copies, [node()]}, - {record_name, amqqueue}, - {attributes, record_info(fields, amqqueue)}]}, - {amqqueue, [{attributes, record_info(fields, amqqueue)}, - {index, [pid]}]}]. + [{rabbit_user, + [{record_name, user}, + {attributes, record_info(fields, user)}, + {disc_copies, [node()]}]}, + {rabbit_user_permission, + [{record_name, user_permission}, + {attributes, record_info(fields, user_permission)}, + {disc_copies, [node()]}]}, + {rabbit_vhost, + [{record_name, vhost}, + {attributes, record_info(fields, vhost)}, + {disc_copies, [node()]}]}, + {rabbit_config, + [{disc_copies, [node()]}]}, + {rabbit_listener, + [{record_name, listener}, + {attributes, record_info(fields, listener)}, + {type, bag}]}, + {rabbit_durable_route, + [{record_name, route}, + {attributes, record_info(fields, route)}, + {disc_copies, [node()]}]}, + {rabbit_route, + [{record_name, route}, + {attributes, record_info(fields, route)}, + {type, ordered_set}]}, + {rabbit_reverse_route, + [{record_name, reverse_route}, + {attributes, record_info(fields, reverse_route)}, + {type, ordered_set}]}, + {rabbit_durable_exchange, + [{record_name, exchange}, + {attributes, record_info(fields, exchange)}, + {disc_copies, [node()]}]}, + {rabbit_exchange, + [{record_name, exchange}, + {attributes, record_info(fields, exchange)}]}, + {rabbit_durable_queue, + [{record_name, amqqueue}, + {attributes, record_info(fields, amqqueue)}, + {disc_copies, [node()]}]}, + {rabbit_queue, + [{record_name, amqqueue}, + {attributes, record_info(fields, amqqueue)}, + {index, [pid]}]}]. table_names() -> [Tab || {Tab, _} <- table_definitions()]. diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index 81c40a31..2dbd5a5a 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -123,7 +123,7 @@ stop_tcp_listener(Host, Port) -> tcp_listener_started(IPAddress, Port) -> ok = mnesia:dirty_write( - listener, + rabbit_listener, #listener{node = node(), protocol = tcp, host = tcp_host(IPAddress), @@ -131,20 +131,20 @@ tcp_listener_started(IPAddress, Port) -> tcp_listener_stopped(IPAddress, Port) -> ok = mnesia:dirty_delete_object( - listener, + rabbit_listener, #listener{node = node(), protocol = tcp, host = tcp_host(IPAddress), port = Port}). active_listeners() -> - rabbit_misc:dirty_read_all(listener). + rabbit_misc:dirty_read_all(rabbit_listener). node_listeners(Node) -> - mnesia:dirty_read(listener, Node). + mnesia:dirty_read(rabbit_listener, Node). on_node_down(Node) -> - ok = mnesia:dirty_delete(listener, Node). + ok = mnesia:dirty_delete(rabbit_listener, Node). start_client(Sock) -> {ok, Child} = supervisor:start_child(rabbit_tcp_client_sup, []), -- cgit v1.2.1 From a0446d2ec2cf7b9fef2e5b5d2adc2b641cebae3a Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 26 Jan 2009 11:47:50 +0000 Subject: more efficient locking --- src/rabbit_access_control.erl | 6 +++--- src/rabbit_exchange.erl | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 883425d0..394eb2b1 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -171,7 +171,7 @@ check_resource_access(Username, add_user(Username, Password) -> R = rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:read({rabbit_user, Username}) of + case mnesia:wread({rabbit_user, Username}) of [] -> ok = mnesia:write(rabbit_user, #user{username = Username, @@ -198,7 +198,7 @@ delete_user(Username) -> username = Username, virtual_host = '_'}, permission = '_'}, - read)], + write)], ok end)), rabbit_log:info("Deleted user ~p~n", [Username]), @@ -226,7 +226,7 @@ lookup_user(Username) -> add_vhost(VHostPath) -> R = rabbit_misc:execute_mnesia_transaction( fun () -> - case mnesia:read({rabbit_vhost, VHostPath}) of + case mnesia:wread({rabbit_vhost, VHostPath}) of [] -> ok = mnesia:write(rabbit_vhost, #vhost{virtual_host = VHostPath}, diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index b04a5b37..19efd9fc 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -309,7 +309,7 @@ delete_bindings_for_exchange(ExchangeName) -> rabbit_route, #route{binding = #binding{exchange_name = ExchangeName, _ = '_'}}, - read)], + write)], ok. delete_bindings_for_queue(QueueName) -> -- cgit v1.2.1 From 9246651bd4a753d1858041042683b6a2c52fe01d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 27 Jan 2009 21:57:29 +0000 Subject: refine permission structure --- docs/rabbitmqctl.1.pod | 8 ++++---- ebin/rabbit_app.in | 2 +- include/rabbit.hrl | 7 ++++--- src/rabbit.erl | 7 ++++--- src/rabbit_access_control.erl | 36 +++++++++++++++++++----------------- src/rabbit_channel.erl | 37 ++++++++++++++++++++----------------- src/rabbit_control.erl | 6 +++--- src/rabbit_tests.erl | 12 +++++------- 8 files changed, 60 insertions(+), 55 deletions(-) diff --git a/docs/rabbitmqctl.1.pod b/docs/rabbitmqctl.1.pod index e9b9514e..d86aa271 100644 --- a/docs/rabbitmqctl.1.pod +++ b/docs/rabbitmqctl.1.pod @@ -114,11 +114,11 @@ delete_vhost I list_vhosts list all virtual hosts. -set_permissions [-p I] I I I +set_permissions [-p I] I I I I set the permissions for the user named I in the virtual - host I, granting them configuration access to resources - with names matching the first I and messaging access to - resources with names matching the second I. + host I, granting 'configure', 'write' and 'read' access + to resources with names matching the first, second and third + I, respectively. clear_permissions [-p I] I remove the permissions for the user named I in the diff --git a/ebin/rabbit_app.in b/ebin/rabbit_app.in index 77f9d299..5be07492 100644 --- a/ebin/rabbit_app.in +++ b/ebin/rabbit_app.in @@ -17,5 +17,5 @@ {default_user, <<"guest">>}, {default_pass, <<"guest">>}, {default_vhost, <<"/">>}, - {default_permissions, [<<".*">>, <<".*">>]}, + {default_permissions, [<<".*">>, <<".*">>, <<".*">>]}, {memory_alarms, auto}]}]}. diff --git a/include/rabbit.hrl b/include/rabbit.hrl index 8aba8a6f..c707112f 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -30,7 +30,7 @@ %% -record(user, {username, password}). --record(permission, {configuration, messaging}). +-record(permission, {configure, write, read}). -record(user_vhost, {username, virtual_host}). -record(user_permission, {user_vhost, permission}). @@ -92,8 +92,9 @@ #user{username :: username(), password :: password()}). -type(permission() :: - #permission{configuration :: regexp(), - messaging :: regexp()}). + #permission{configure :: regexp(), + write :: regexp(), + read :: regexp()}). -type(amqqueue() :: #amqqueue{name :: queue_name(), durable :: bool(), diff --git a/src/rabbit.erl b/src/rabbit.erl index 7ad13a7d..62ce821e 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -264,13 +264,14 @@ insert_default_data() -> {ok, DefaultUser} = application:get_env(default_user), {ok, DefaultPass} = application:get_env(default_pass), {ok, DefaultVHost} = application:get_env(default_vhost), - {ok, [DefaultConfigurationPerm, DefaultMessagingPerm]} = + {ok, [DefaultConfigurePerm, DefaultWritePerm, DefaultReadPerm]} = application:get_env(default_permissions), ok = rabbit_access_control:add_vhost(DefaultVHost), ok = rabbit_access_control:add_user(DefaultUser, DefaultPass), ok = rabbit_access_control:set_permissions(DefaultUser, DefaultVHost, - DefaultConfigurationPerm, - DefaultMessagingPerm), + DefaultConfigurePerm, + DefaultWritePerm, + DefaultReadPerm), ok. start_builtin_amq_applications() -> diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index 394eb2b1..da0ab9cf 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -38,7 +38,7 @@ -export([add_user/2, delete_user/1, change_password/2, list_users/0, lookup_user/1]). -export([add_vhost/1, delete_vhost/1, list_vhosts/0]). --export([set_permissions/4, clear_permissions/2, +-export([set_permissions/5, clear_permissions/2, list_vhost_permissions/1, list_user_permissions/1]). %%---------------------------------------------------------------------------- @@ -58,12 +58,13 @@ -spec(add_vhost/1 :: (vhost()) -> 'ok'). -spec(delete_vhost/1 :: (vhost()) -> 'ok'). -spec(list_vhosts/0 :: () -> [vhost()]). --spec(set_permissions/4 :: (username(), vhost(), regexp(), regexp()) -> 'ok'). +-spec(set_permissions/5 :: + (username(), vhost(), regexp(), regexp(), regexp()) -> 'ok'). -spec(clear_permissions/2 :: (username(), vhost()) -> 'ok'). -spec(list_vhost_permissions/1 :: - (vhost()) -> [{username(), regexp(), regexp()}]). + (vhost()) -> [{username(), regexp(), regexp(), regexp()}]). -spec(list_user_permissions/1 :: - (username()) -> [{vhost(), regexp(), regexp()}]). + (username()) -> [{vhost(), regexp(), regexp(), regexp()}]). -endif. @@ -272,7 +273,7 @@ internal_delete_vhost(VHostPath) -> ok = rabbit_exchange:delete(Name, false) end, rabbit_exchange:list(VHostPath)), - lists:foreach(fun ({Username, _, _}) -> + lists:foreach(fun ({Username, _, _, _}) -> ok = clear_permissions(Username, VHostPath) end, list_vhost_permissions(VHostPath)), @@ -289,9 +290,8 @@ validate_regexp(RegexpBin) -> {error, Reason} -> throw({error, {invalid_regexp, Regexp, Reason}}) end. -set_permissions(Username, VHostPath, ConfigurationPerm, MessagingPerm) -> - validate_regexp(ConfigurationPerm), - validate_regexp(MessagingPerm), +set_permissions(Username, VHostPath, ConfigurePerm, WritePerm, ReadPerm) -> + lists:map(fun validate_regexp/1, [ConfigurePerm, WritePerm, ReadPerm]), rabbit_misc:execute_mnesia_transaction( rabbit_misc:with_user_and_vhost( Username, VHostPath, @@ -301,8 +301,9 @@ set_permissions(Username, VHostPath, ConfigurationPerm, MessagingPerm) -> username = Username, virtual_host = VHostPath}, permission = #permission{ - configuration = ConfigurationPerm, - messaging = MessagingPerm}}, + configure = ConfigurePerm, + write = WritePerm, + read = ReadPerm}}, write) end)). @@ -317,24 +318,25 @@ clear_permissions(Username, VHostPath) -> end)). list_vhost_permissions(VHostPath) -> - [{Username, ConfigurationPerm, MessagingPerm} || - {Username, _, ConfigurationPerm, MessagingPerm} <- + [{Username, ConfigurePerm, WritePerm, ReadPerm} || + {Username, _, ConfigurePerm, WritePerm, ReadPerm} <- list_permissions(rabbit_misc:with_vhost( VHostPath, match_user_vhost('_', VHostPath)))]. list_user_permissions(Username) -> - [{VHostPath, ConfigurationPerm, MessagingPerm} || - {_, VHostPath, ConfigurationPerm, MessagingPerm} <- + [{VHostPath, ConfigurePerm, WritePerm, ReadPerm} || + {_, VHostPath, ConfigurePerm, WritePerm, ReadPerm} <- list_permissions(rabbit_misc:with_user( Username, match_user_vhost(Username, '_')))]. list_permissions(QueryThunk) -> - [{Username, VHostPath, ConfigurationPerm, MessagingPerm} || + [{Username, VHostPath, ConfigurePerm, WritePerm, ReadPerm} || #user_permission{user_vhost = #user_vhost{username = Username, virtual_host = VHostPath}, permission = #permission{ - configuration = ConfigurationPerm, - messaging = MessagingPerm}} <- + configure = ConfigurePerm, + write = WritePerm, + read = ReadPerm}} <- %% TODO: use dirty ops instead rabbit_misc:execute_mnesia_transaction(QueryThunk)]. diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 39867a4b..25fa20d3 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -224,11 +224,14 @@ clear_permission_cache() -> erase(permission_cache), ok. -check_configuration_permitted(Resource, #ch{ username = Username}) -> - check_resource_access(Username, Resource, #permission.configuration). +check_configure_permitted(Resource, #ch{ username = Username}) -> + check_resource_access(Username, Resource, #permission.configure). -check_messaging_permitted(Resource, #ch{ username = Username}) -> - check_resource_access(Username, Resource, #permission.messaging). +check_write_permitted(Resource, #ch{ username = Username}) -> + check_resource_access(Username, Resource, #permission.write). + +check_read_permitted(Resource, #ch{ username = Username}) -> + check_resource_access(Username, Resource, #permission.read). expand_queue_name_shortcut(<<>>, #ch{ most_recently_declared_queue = <<>> }) -> rabbit_misc:protocol_error( @@ -299,7 +302,7 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, immediate = Immediate}, Content, State = #ch{ virtual_host = VHostPath}) -> ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_messaging_permitted(ExchangeName, State), + check_write_permitted(ExchangeName, State), Exchange = rabbit_exchange:lookup_or_die(ExchangeName), %% We decode the content's properties here because we're almost %% certain to want to look at delivery-mode and priority. @@ -343,7 +346,7 @@ handle_method(#'basic.get'{queue = QueueNameBin, _, State = #ch{ writer_pid = WriterPid, next_tag = DeliveryTag }) -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), - check_messaging_permitted(QueueName, State), + check_read_permitted(QueueName, State), case rabbit_amqqueue:with_or_die( QueueName, fun (Q) -> rabbit_amqqueue:basic_get(Q, self(), NoAck) end) of @@ -378,7 +381,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, case dict:find(ConsumerTag, ConsumerMapping) of error -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), - check_messaging_permitted(QueueName, State), + check_read_permitted(QueueName, State), ActualConsumerTag = case ConsumerTag of <<>> -> rabbit_misc:binstring_guid("amq.ctag"); @@ -537,7 +540,7 @@ handle_method(#'exchange.declare'{exchange = ExchangeNameBin, _, State = #ch{ virtual_host = VHostPath }) -> CheckedType = rabbit_exchange:check_type(TypeNameBin), ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_configuration_permitted(ExchangeName, State), + check_configure_permitted(ExchangeName, State), X = case rabbit_exchange:lookup(ExchangeName) of {ok, FoundX} -> FoundX; {error, not_found} -> @@ -557,7 +560,7 @@ handle_method(#'exchange.declare'{exchange = ExchangeNameBin, nowait = NoWait}, _, State = #ch{ virtual_host = VHostPath }) -> ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_configuration_permitted(ExchangeName, State), + check_configure_permitted(ExchangeName, State), X = rabbit_exchange:lookup_or_die(ExchangeName), ok = rabbit_exchange:assert_type(X, rabbit_exchange:check_type(TypeNameBin)), return_ok(State, NoWait, #'exchange.declare_ok'{}); @@ -567,7 +570,7 @@ handle_method(#'exchange.delete'{exchange = ExchangeNameBin, nowait = NoWait}, _, State = #ch { virtual_host = VHostPath }) -> ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_configuration_permitted(ExchangeName, State), + check_configure_permitted(ExchangeName, State), case rabbit_exchange:delete(ExchangeName, IfUnused) of {error, not_found} -> rabbit_misc:protocol_error( @@ -618,11 +621,11 @@ handle_method(#'queue.declare'{queue = QueueNameBin, Other -> check_name('queue', Other) end, QueueName = rabbit_misc:r(VHostPath, queue, ActualNameBin), - check_configuration_permitted(QueueName, State), + check_configure_permitted(QueueName, State), Finish(rabbit_amqqueue:declare(QueueName, Durable, AutoDelete, Args)); Other = #amqqueue{name = QueueName} -> - check_configuration_permitted(QueueName, State), + check_configure_permitted(QueueName, State), Other end, return_queue_declare_ok(State, NoWait, Q); @@ -632,7 +635,7 @@ handle_method(#'queue.declare'{queue = QueueNameBin, nowait = NoWait}, _, State = #ch{ virtual_host = VHostPath }) -> QueueName = rabbit_misc:r(VHostPath, queue, QueueNameBin), - check_configuration_permitted(QueueName, State), + check_configure_permitted(QueueName, State), Q = rabbit_amqqueue:with_or_die(QueueName, fun (Q) -> Q end), return_queue_declare_ok(State, NoWait, Q); @@ -643,7 +646,7 @@ handle_method(#'queue.delete'{queue = QueueNameBin, }, _, State) -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), - check_configuration_permitted(QueueName, State), + check_configure_permitted(QueueName, State), case rabbit_amqqueue:with_or_die( QueueName, fun (Q) -> rabbit_amqqueue:delete(Q, IfUnused, IfEmpty) end) of @@ -680,7 +683,7 @@ handle_method(#'queue.purge'{queue = QueueNameBin, nowait = NoWait}, _, State) -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), - check_messaging_permitted(QueueName, State), + check_read_permitted(QueueName, State), {ok, PurgedMessageCount} = rabbit_amqqueue:with_or_die( QueueName, fun (Q) -> rabbit_amqqueue:purge(Q) end), @@ -730,11 +733,11 @@ binding_action(Fun, ExchangeNameBin, QueueNameBin, RoutingKey, Arguments, %% FIXME: don't allow binding to internal exchanges - %% including the one named "" ! QueueName = expand_queue_name_shortcut(QueueNameBin, State), - check_configuration_permitted(QueueName, State), + check_write_permitted(QueueName, State), ActualRoutingKey = expand_routing_key_shortcut(QueueNameBin, RoutingKey, State), ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), - check_configuration_permitted(ExchangeName, State), + check_read_permitted(ExchangeName, State), case Fun(ExchangeName, QueueName, ActualRoutingKey, Arguments) of {error, queue_not_found} -> rabbit_misc:protocol_error( diff --git a/src/rabbit_control.erl b/src/rabbit_control.erl index 293cd797..e6717d68 100644 --- a/src/rabbit_control.erl +++ b/src/rabbit_control.erl @@ -114,7 +114,7 @@ Available commands: delete_vhost list_vhosts - set_permissions [-p ] + set_permissions [-p ] clear_permissions [-p ] list_permissions [-p ] list_user_permissions @@ -267,10 +267,10 @@ action(Command, Node, Args, Inform) -> {VHost, RemainingArgs} = parse_vhost_flag(Args), action(Command, Node, VHost, RemainingArgs, Inform). -action(set_permissions, Node, VHost, [Username, CPerm, MPerm], Inform) -> +action(set_permissions, Node, VHost, [Username, CPerm, WPerm, RPerm], Inform) -> Inform("Setting permissions for user ~p in vhost ~p", [Username, VHost]), call(Node, {rabbit_access_control, set_permissions, - [Username, VHost, CPerm, MPerm]}); + [Username, VHost, CPerm, WPerm, RPerm]}); action(clear_permissions, Node, VHost, [Username], Inform) -> Inform("Clearing permissions for user ~p in vhost ~p", [Username, VHost]), diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index ef390e4d..6312e8e3 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -444,7 +444,7 @@ test_user_management() -> {error, {no_such_vhost, _}} = control_action(delete_vhost, ["/testhost"]), {error, {no_such_user, _}} = - control_action(set_permissions, ["foo", ".*", ".*"]), + control_action(set_permissions, ["foo", ".*", ".*", ".*"]), {error, {no_such_user, _}} = control_action(clear_permissions, ["foo"]), {error, {no_such_user, _}} = @@ -452,9 +452,7 @@ test_user_management() -> {error, {no_such_vhost, _}} = control_action(list_permissions, ["-p", "/testhost"]), {error, {invalid_regexp, _, _}} = - control_action(set_permissions, ["guest", "+foo", ".*"]), - {error, {invalid_regexp, _, _}} = - control_action(set_permissions, ["guest", ".*", "+foo"]), + control_action(set_permissions, ["guest", "+foo", ".*", ".*"]), %% user creation ok = control_action(add_user, ["foo", "bar"]), @@ -471,9 +469,9 @@ test_user_management() -> %% user/vhost mapping ok = control_action(set_permissions, ["-p", "/testhost", - "foo", ".*", ".*"]), + "foo", ".*", ".*", ".*"]), ok = control_action(set_permissions, ["-p", "/testhost", - "foo", ".*", ".*"]), + "foo", ".*", ".*", ".*"]), ok = control_action(list_permissions, ["-p", "/testhost"]), ok = control_action(list_user_permissions, ["foo"]), @@ -489,7 +487,7 @@ test_user_management() -> %% deleting a populated vhost ok = control_action(add_vhost, ["/testhost"]), ok = control_action(set_permissions, ["-p", "/testhost", - "foo", ".*", ".*"]), + "foo", ".*", ".*", ".*"]), ok = control_action(delete_vhost, ["/testhost"]), %% user deletion -- cgit v1.2.1 From 7e20e72d884b13b7e21231b6a0c3784de8a5ccb8 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 28 Jan 2009 23:18:08 +0000 Subject: cosmetic: break some overlong lines --- src/rabbit.erl | 3 ++- src/rabbit_channel.erl | 3 ++- src/rabbit_framing_channel.erl | 6 ++++-- src/rabbit_reader.erl | 6 ++++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 7ad13a7d..7f808bc9 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -87,7 +87,8 @@ stop() -> stop_and_halt() -> spawn(fun () -> SleepTime = 1000, - rabbit_log:info("Stop-and-halt request received; halting in ~p milliseconds~n", + rabbit_log:info("Stop-and-halt request received; " + "halting in ~p milliseconds~n", [SleepTime]), timer:sleep(SleepTime), init:stop() diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 39867a4b..5a1c0952 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -911,7 +911,8 @@ is_message_persistent(#content{properties = #'P_basic'{ 1 -> false; 2 -> true; undefined -> false; - Other -> rabbit_log:warning("Unknown delivery mode ~p - treating as 1, non-persistent~n", + Other -> rabbit_log:warning("Unknown delivery mode ~p - " + "treating as 1, non-persistent~n", [Other]), false end. diff --git a/src/rabbit_framing_channel.erl b/src/rabbit_framing_channel.erl index 060bed48..5c447792 100644 --- a/src/rabbit_framing_channel.erl +++ b/src/rabbit_framing_channel.erl @@ -95,13 +95,15 @@ collect_content(ChannelPid, MethodName) -> true -> rabbit_misc:protocol_error( command_invalid, - "expected content header for class ~w, got one for class ~w instead", + "expected content header for class ~w, " + "got one for class ~w instead", [ClassId, HeaderClassId]) end; _ -> rabbit_misc:protocol_error( command_invalid, - "expected content header for class ~w, got non content header frame instead", + "expected content header for class ~w, " + "got non content header frame instead", [ClassId]) end. diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 3f8d7cac..9dbc49df 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -173,7 +173,8 @@ setup_profiling() -> Value = rabbit_misc:get_config(profiling_enabled, false), case Value of once -> - rabbit_log:info("Enabling profiling for this connection, and disabling for subsequent.~n"), + rabbit_log:info("Enabling profiling for this connection, " + "and disabling for subsequent.~n"), rabbit_misc:set_config(profiling_enabled, false), fprof:trace(start); true -> @@ -404,7 +405,8 @@ wait_for_channel_termination(N, TimerRef) -> normal -> ok; _ -> rabbit_log:error( - "connection ~p, channel ~p - error while terminating:~n~p~n", + "connection ~p, channel ~p - " + "error while terminating:~n~p~n", [self(), Channel, Reason]) end, wait_for_channel_termination(N-1, TimerRef) -- cgit v1.2.1 From 36d808c76c6471a48da4e6ace3bbc9e3a2c87801 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 29 Jan 2009 21:06:23 +0000 Subject: don't terminate channel processes abnormally for amqp errors since that causes unnecessary error logging --- src/rabbit_channel.erl | 22 ++++++++++++++-------- src/rabbit_reader.erl | 14 ++++++++++++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 5a1c0952..88d563b7 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -35,12 +35,12 @@ -behaviour(gen_server2). --export([start_link/4, do/2, do/3, shutdown/1]). +-export([start_link/5, do/2, do/3, shutdown/1]). -export([send_command/2, deliver/4, conserve_memory/2]). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). --record(ch, {state, reader_pid, writer_pid, limiter_pid, +-record(ch, {state, channel, reader_pid, writer_pid, limiter_pid, transaction_id, tx_participants, next_tag, uncommitted_ack_q, unacked_message_q, username, virtual_host, @@ -54,7 +54,8 @@ -ifdef(use_specs). --spec(start_link/4 :: (pid(), pid(), username(), vhost()) -> pid()). +-spec(start_link/5 :: + (channel_number(), pid(), pid(), username(), vhost()) -> pid()). -spec(do/2 :: (pid(), amqp_method()) -> 'ok'). -spec(do/3 :: (pid(), amqp_method(), maybe(content())) -> 'ok'). -spec(shutdown/1 :: (pid()) -> 'ok'). @@ -66,9 +67,10 @@ %%---------------------------------------------------------------------------- -start_link(ReaderPid, WriterPid, Username, VHost) -> +start_link(Channel, ReaderPid, WriterPid, Username, VHost) -> {ok, Pid} = gen_server2:start_link( - ?MODULE, [ReaderPid, WriterPid, Username, VHost], []), + ?MODULE, [Channel, ReaderPid, WriterPid, + Username, VHost], []), Pid. do(Pid, Method) -> @@ -91,11 +93,12 @@ conserve_memory(Pid, Conserve) -> %%--------------------------------------------------------------------------- -init([ReaderPid, WriterPid, Username, VHost]) -> +init([Channel, ReaderPid, WriterPid, Username, VHost]) -> process_flag(trap_exit, true), link(WriterPid), rabbit_alarm:register(self(), {?MODULE, conserve_memory, []}), {ok, #ch{state = starting, + channel = Channel, reader_pid = ReaderPid, writer_pid = WriterPid, limiter_pid = undefined, @@ -123,8 +126,11 @@ handle_cast({method, Method, Content}, State) -> {stop, normal, State#ch{state = terminating}} catch exit:{amqp, Error, Explanation, none} -> - {stop, {amqp, Error, Explanation, - rabbit_misc:method_record_type(Method)}, State}; + ok = notify_queues(internal_rollback(State)), + Reason = {amqp, Error, Explanation, + rabbit_misc:method_record_type(Method)}, + State#ch.reader_pid ! {channel_exit, State#ch.channel, Reason}, + {stop, normal, State#ch{state = terminating}}; exit:normal -> {stop, normal, State}; _:Reason -> diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 9dbc49df..12ee299e 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -284,6 +284,8 @@ mainloop(Parent, Deb, State = #v1{sock= Sock, recv_ref = Ref}) -> exit(Reason); {'EXIT', _Pid, E = {writer, send_failed, _Error}} -> throw(E); + {channel_exit, Channel, Reason} -> + mainloop(Parent, Deb, handle_channel_exit(Channel, Reason, State)); {'EXIT', Pid, Reason} -> mainloop(Parent, Deb, handle_dependent_exit(Pid, Reason, State)); {terminate_channel, Channel, Ref1} -> @@ -351,6 +353,14 @@ terminate_channel(Channel, Ref, State) -> end, State. +handle_channel_exit(Channel, Reason, State) -> + %% We remove the channel from the inbound map only. That allows + %% the channel to be re-opened, but also means the remaining + %% cleanup, including possibly closing the connection, is deferred + %% until we get the (normal) exit signal. + erase({channel, Channel}), + handle_exception(State, Channel, Reason). + handle_dependent_exit(Pid, normal, State) -> channel_cleanup(Pid), maybe_close(State); @@ -711,8 +721,8 @@ send_to_new_channel(Channel, AnalyzedFrame, State) -> vhost = VHost}} = State, WriterPid = rabbit_writer:start(Sock, Channel, FrameMax), ChPid = rabbit_framing_channel:start_link( - fun rabbit_channel:start_link/4, - [self(), WriterPid, Username, VHost]), + fun rabbit_channel:start_link/5, + [Channel, self(), WriterPid, Username, VHost]), put({channel, Channel}, {chpid, ChPid}), put({chpid, ChPid}, {channel, Channel}), ok = rabbit_framing_channel:process(ChPid, AnalyzedFrame); -- cgit v1.2.1 From f1fa48a8b4c978d4cd7053618066f663cba8e4b3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 30 Jan 2009 11:48:55 +0000 Subject: get rid of index on 'pid' field in rabbit_queue it's not used by any of our queries --- src/rabbit_mnesia.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index c0bf3d25..15213861 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -143,8 +143,7 @@ table_definitions() -> {disc_copies, [node()]}]}, {rabbit_queue, [{record_name, amqqueue}, - {attributes, record_info(fields, amqqueue)}, - {index, [pid]}]}]. + {attributes, record_info(fields, amqqueue)}]}]. table_names() -> [Tab || {Tab, _} <- table_definitions()]. -- cgit v1.2.1 From 280d48d96e308bdd3b944e95a21d946e02a78483 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 6 Feb 2009 11:49:26 +0000 Subject: prevent unwanted path expansion --- scripts/rabbitmq-server | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/rabbitmq-server b/scripts/rabbitmq-server index 572262c9..9a35c477 100755 --- a/scripts/rabbitmq-server +++ b/scripts/rabbitmq-server @@ -73,6 +73,11 @@ fi RABBITMQ_START_RABBIT= [ "x" = "x$RABBITMQ_NODE_ONLY" ] && RABBITMQ_START_RABBIT='-noinput -s rabbit' +# we need to turn off path expansion because some of the vars, notably +# RABBITMQ_SERVER_ERL_ARGS, contain terms that look like globs and +# there is no other way of preventing their expansion. +set -f + exec erl \ -pa "`dirname $0`/../ebin" \ ${RABBITMQ_START_RABBIT} \ -- cgit v1.2.1 From 904a397ab7bad3f11f7efa996a48c28cee1e3755 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 6 Feb 2009 14:20:11 +0000 Subject: also prevent path expansion in rabbit-multi --- scripts/rabbitmq-multi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/rabbitmq-multi b/scripts/rabbitmq-multi index 84985e90..164c5e18 100755 --- a/scripts/rabbitmq-multi +++ b/scripts/rabbitmq-multi @@ -54,6 +54,11 @@ export \ RABBITMQ_SCRIPT_HOME \ RABBITMQ_PIDS_FILE +# we need to turn off path expansion because some of the vars, notably +# RABBITMQ_MULTI_ERL_ARGS, may contain terms that look like globs and +# there is no other way of preventing their expansion. +set -f + exec erl \ -pa "`dirname $0`/../ebin" \ -noinput \ -- cgit v1.2.1 From 7a9990fc28f348257a1979d53a97cb63042e2907 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 13 Feb 2009 15:05:19 +0000 Subject: Special-case as a warning the relatively common abrupt-disconnection message. --- src/rabbit_reader.erl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 12ee299e..09148bf0 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -231,8 +231,12 @@ start_connection(Parent, Deb, ClientSock) -> connection_state = pre_init}, handshake, 8)) catch - Ex -> rabbit_log:error("error on TCP connection ~p from ~s:~p~n~p~n", - [self(), PeerAddressS, PeerPort, Ex]) + connection_closed_abruptly -> + rabbit_log:warning("TCP connection ~p from ~s:~p closed abruptly~n", + [self(), PeerAddressS, PeerPort]); + Ex -> + rabbit_log:error("error on TCP connection ~p from ~s:~p~n~p~n", + [self(), PeerAddressS, PeerPort, Ex]) after rabbit_log:info("closing TCP connection ~p from ~s:~p~n", [self(), PeerAddressS, PeerPort]), -- cgit v1.2.1 From 8d8f8af34dd699c22434563b2d932df7e87cc557 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 17 Feb 2009 14:11:32 +0000 Subject: make it easier to add more exceptions to the 'warning' category in future --- src/rabbit_reader.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 09148bf0..9f642e35 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -231,12 +231,12 @@ start_connection(Parent, Deb, ClientSock) -> connection_state = pre_init}, handshake, 8)) catch - connection_closed_abruptly -> - rabbit_log:warning("TCP connection ~p from ~s:~p closed abruptly~n", - [self(), PeerAddressS, PeerPort]); - Ex -> - rabbit_log:error("error on TCP connection ~p from ~s:~p~n~p~n", - [self(), PeerAddressS, PeerPort, Ex]) + Ex -> (if Ex == connection_closed_abruptly -> + fun rabbit_log:warning/2; + true -> + fun rabbit_log:error/2 + end)("exception on TCP connection ~p from ~s:~p~n~p~n", + [self(), PeerAddressS, PeerPort, Ex]) after rabbit_log:info("closing TCP connection ~p from ~s:~p~n", [self(), PeerAddressS, PeerPort]), -- cgit v1.2.1 From bea9a0d173b08637a900f44fe5f495f00c88eda3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 27 Feb 2009 01:46:03 +0000 Subject: persister hibernation also changed force_snapshot to do a flush before snapshotting, which makes forced snapshots behave like ordinary snapshots. --- src/rabbit_persister.erl | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/rabbit_persister.erl b/src/rabbit_persister.erl index 94033a4f..99ae3f38 100644 --- a/src/rabbit_persister.erl +++ b/src/rabbit_persister.erl @@ -49,6 +49,8 @@ -define(LOG_BUNDLE_DELAY, 5). -define(COMPLETE_BUNDLE_DELAY, 2). +-define(HIBERNATE_AFTER, 10000). + -define(MAX_WRAP_ENTRIES, 500). -define(PERSISTER_LOG_FORMAT_VERSION, {2, 4}). @@ -164,10 +166,8 @@ handle_call({transaction, Key, MessageList}, From, State) -> do_noreply(internal_commit(From, Key, NewState)); handle_call({commit_transaction, TxnKey}, From, State) -> do_noreply(internal_commit(From, TxnKey, State)); -handle_call(force_snapshot, _From, State = #pstate{log_handle = LH, - snapshot = Snapshot}) -> - ok = take_snapshot(LH, Snapshot), - do_reply(ok, State); +handle_call(force_snapshot, _From, State) -> + do_reply(ok, flush(true, State)); handle_call(serial, _From, State = #pstate{snapshot = #psnapshot{serial = Serial}}) -> do_reply(Serial, State); @@ -183,8 +183,13 @@ handle_cast({extend_transaction, TxnKey, MessageList}, State) -> handle_cast(_Msg, State) -> {noreply, State}. +handle_info(timeout, State = #pstate{deadline = infinity}) -> + State1 = flush(true, State), + %% TODO: Once we drop support for R11B-5, we can change this to + %% {noreply, State1, hibernate}; + proc_lib:hibernate(gen_server2, enter_loop, [?MODULE, [], State1]); handle_info(timeout, State) -> - {noreply, flush(State)}; + do_noreply(flush(State)); handle_info(_Info, State) -> {noreply, State}. @@ -275,12 +280,13 @@ take_snapshot_and_save_old(LogHandle, Snapshot) -> rabbit_log:info("Saving persister log in ~p~n", [OldFileName]), ok = take_snapshot(LogHandle, OldFileName, Snapshot). -maybe_take_snapshot(State = #pstate{entry_count = EntryCount, log_handle = LH, - snapshot = Snapshot}) - when EntryCount >= ?MAX_WRAP_ENTRIES -> +maybe_take_snapshot(Force, State = #pstate{entry_count = EntryCount, + log_handle = LH, + snapshot = Snapshot}) + when Force orelse EntryCount >= ?MAX_WRAP_ENTRIES -> ok = take_snapshot(LH, Snapshot), State#pstate{entry_count = 0}; -maybe_take_snapshot(State) -> +maybe_take_snapshot(_Force, State) -> State. later_ms(DeltaMilliSec) -> @@ -298,7 +304,7 @@ compute_deadline(_TimerDelay, ExistingDeadline) -> ExistingDeadline. compute_timeout(infinity) -> - infinity; + ?HIBERNATE_AFTER; compute_timeout(Deadline) -> DeltaMilliSec = time_diff(Deadline, now()) * 1000.0, if @@ -314,18 +320,18 @@ do_noreply(State = #pstate{deadline = Deadline}) -> do_reply(Reply, State = #pstate{deadline = Deadline}) -> {reply, Reply, State, compute_timeout(Deadline)}. -flush(State = #pstate{pending_logs = PendingLogs, - pending_replies = Waiting, - log_handle = LogHandle}) -> - State1 = if - PendingLogs /= [] -> +flush(State) -> flush(false, State). + +flush(ForceSnapshot, State = #pstate{pending_logs = PendingLogs, + pending_replies = Waiting, + log_handle = LogHandle}) -> + State1 = if PendingLogs /= [] -> disk_log:alog(LogHandle, lists:reverse(PendingLogs)), - maybe_take_snapshot( - State#pstate{ - entry_count = State#pstate.entry_count + 1}); - true -> + State#pstate{entry_count = State#pstate.entry_count + 1}; + true -> State end, + State2 = maybe_take_snapshot(ForceSnapshot, State1), if Waiting /= [] -> ok = disk_log:sync(LogHandle), lists:foreach(fun (From) -> gen_server:reply(From, ok) end, @@ -333,7 +339,7 @@ flush(State = #pstate{pending_logs = PendingLogs, true -> ok end, - State1#pstate{deadline = infinity, + State2#pstate{deadline = infinity, pending_logs = [], pending_replies = []}. -- cgit v1.2.1 From f2b23b409d18179851feb0406840d0b5a716e4ff Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 27 Feb 2009 17:41:20 +0000 Subject: Check in macports packaging contributed by Chris Pettitt --- packaging/macports/net/rabbitmq-server/Portfile | 118 ++++++++++++--------- .../patch-org.macports.rabbitmq-server.plist.diff | 10 ++ .../net/rabbitmq-server/files/rabbitmq-defaults | 7 -- .../net/rabbitmq-server/files/rabbitmqctl_wrapper | 13 +-- 4 files changed, 81 insertions(+), 67 deletions(-) create mode 100644 packaging/macports/net/rabbitmq-server/files/patch-org.macports.rabbitmq-server.plist.diff delete mode 100644 packaging/macports/net/rabbitmq-server/files/rabbitmq-defaults diff --git a/packaging/macports/net/rabbitmq-server/Portfile b/packaging/macports/net/rabbitmq-server/Portfile index 82ae62aa..fdc142c8 100644 --- a/packaging/macports/net/rabbitmq-server/Portfile +++ b/packaging/macports/net/rabbitmq-server/Portfile @@ -1,79 +1,101 @@ -# $Id$ -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=tcl:et:sw=4:ts=4:sts=4 - -PortSystem 1.0 -name rabbitmq-server -version 1.3.0 -revision 0 -categories net -maintainers tonyg@rabbitmq.com -platforms darwin -description The RabbitMQ AMQP Server -long_description \ +# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=tcl:et:sw=4:ts=4:sts=4 +# $Id$ + +PortSystem 1.0 + +name rabbitmq-server +version 1.5.3 +revision 0 +categories net +maintainers tonyg@rabbitmq.com cpettitt@gmail.com +platforms darwin +description The RabbitMQ AMQP Server +long_description \ RabbitMQ is an implementation of AMQP, the emerging standard for \ high performance enterprise messaging. The RabbitMQ server is a \ robust and scalable implementation of an AMQP broker. -homepage http://www.rabbitmq.com/ -master_sites http://www.rabbitmq.com/releases/source/ -distname rabbitmq-${version} +homepage http://www.rabbitmq.com/ +master_sites http://www.rabbitmq.com/releases/rabbitmq-server/v${version}/ + +distname rabbitmq-server-${version} checksums \ - md5 46ee6dbbacdc67b25cc6ccd9c394b6f2 \ - sha1 67e1e640136a1993567ace97dc5f67b1ad8e6304 \ - rmd160 9e92502d36ab5cd1e3f0d39a46bb512b9440f35a + md5 3242a67885c2471b5ab62254bf024679 \ + sha1 f4d6a01eaa2c74fa32f567fe410d21d9be1b43aa \ + rmd160 1a1c4b97d765548028c161d1617905151ca9e040 -depends_build port:erlang -depends_run port:erlang +depends_build port:erlang port:py25-simplejson +depends_run port:erlang -use_configure no +set serveruser rabbitmq +set servergroup rabbitmq +set serverhome ${prefix}/var/lib/rabbitmq +set logdir ${prefix}/var/log/rabbitmq +set mnesiadbdir ${prefix}/var/lib/rabbitmq/mnesia +set plistloc ${prefix}/etc/LaunchDaemons/org.macports.rabbitmq-server -worksrcdir rabbitmq-${version}/erlang/rabbit +use_configure no use_parallel_build yes +build.args PYTHON=${prefix}/bin/python2.5 + destroot.destdir \ - DIST_DIR=${destroot}${prefix}/lib/erlang/lib/rabbitmq_server-${version} \ - SBIN_DIR=${destroot}${prefix}/sbin -destroot.target dist-unix + TARGET_DIR=${destroot}${prefix}/lib/erlang/lib/rabbitmq_server-${version} \ + SBIN_DIR=${destroot}${prefix}/sbin \ + MAN_DIR=${destroot}${prefix}/share/man destroot.keepdirs \ - ${destroot}${prefix}/var/lib/rabbitmq/pids \ - ${destroot}${prefix}/var/log/rabbitmq \ - ${destroot}${prefix}/var/lib/rabbitmq/mnesia + ${destroot}${logdir} \ + ${destroot}${mnesiadbdir} pre-destroot { - addgroup rabbitmq - adduser rabbitmq gid=[existsgroup rabbitmq] realname=RabbitMQ\ Server home=${prefix}/var/lib/rabbitmq + addgroup ${servergroup} + adduser ${serveruser} gid=[existsgroup ${servergroup}] realname=RabbitMQ\ Server home=${serverhome} } post-destroot { - xinstall -d ${destroot}${prefix}/etc/default - xinstall -d -g [existsgroup rabbitmq] -m 775 ${destroot}${prefix}/var/log/rabbitmq - xinstall -d -g [existsgroup rabbitmq] -m 775 ${destroot}${prefix}/var/lib/rabbitmq - xinstall -d -g [existsgroup rabbitmq] -m 775 ${destroot}${prefix}/var/lib/rabbitmq/pids - xinstall -d -g [existsgroup rabbitmq] -m 775 ${destroot}${prefix}/var/lib/rabbitmq/mnesia - file rename ${destroot}${prefix}/sbin/rabbitmqctl ${destroot}${prefix}/sbin/rabbitmqctl_real - xinstall -m 555 ${filespath}/rabbitmqctl_wrapper ${destroot}${prefix}/sbin - file rename ${destroot}${prefix}/sbin/rabbitmqctl_wrapper ${destroot}${prefix}/sbin/rabbitmqctl - file copy ${filespath}/rabbitmq-defaults ${destroot}${prefix}/etc/default/rabbitmq - reinplace "s:^CLUSTER_CONFIG_FILE=:CLUSTER_CONFIG_FILE=${prefix}:" \ + xinstall -d -g [existsgroup ${servergroup}] -m 775 ${destroot}${logdir} + xinstall -d -g [existsgroup ${servergroup}] -m 775 ${destroot}${serverhome} + xinstall -d -g [existsgroup ${servergroup}] -m 775 ${destroot}${mnesiadbdir} + + reinplace -E "s:(/etc/rabbitmq/rabbitmq.conf):${prefix}\\1:g" \ ${destroot}${prefix}/sbin/rabbitmq-multi \ ${destroot}${prefix}/sbin/rabbitmq-server \ - ${destroot}${prefix}/sbin/rabbitmqctl \ - ${destroot}${prefix}/sbin/rabbitmqctl_real - reinplace "s:^CONFIG_FILE=:CONFIG_FILE=${prefix}:" \ + ${destroot}${prefix}/sbin/rabbitmqctl + reinplace -E "s:(RABBITMQ_CLUSTER_CONFIG_FILE)=/:\\1=${prefix}/:" \ ${destroot}${prefix}/sbin/rabbitmq-multi \ ${destroot}${prefix}/sbin/rabbitmq-server \ - ${destroot}${prefix}/sbin/rabbitmqctl \ - ${destroot}${prefix}/sbin/rabbitmqctl_real - reinplace "s|@PREFIX@|${prefix}|" \ - ${destroot}${prefix}/sbin/rabbitmqctl \ - ${destroot}${prefix}/etc/default/rabbitmq + ${destroot}${prefix}/sbin/rabbitmqctl + reinplace -E "s:(RABBITMQ_LOG_BASE)=/:\\1=${prefix}/:" \ + ${destroot}${prefix}/sbin/rabbitmq-multi \ + ${destroot}${prefix}/sbin/rabbitmq-server \ + ${destroot}${prefix}/sbin/rabbitmqctl + reinplace -E "s:(RABBITMQ_MNESIA_BASE)=/:\\1=${prefix}/:" \ + ${destroot}${prefix}/sbin/rabbitmq-multi \ + ${destroot}${prefix}/sbin/rabbitmq-server \ + ${destroot}${prefix}/sbin/rabbitmqctl + reinplace -E "s:(RABBITMQ_PIDS_FILE)=/:\\1=${prefix}/:" \ + ${destroot}${prefix}/sbin/rabbitmq-multi \ + ${destroot}${prefix}/sbin/rabbitmq-server \ + ${destroot}${prefix}/sbin/rabbitmqctl + + file rename ${destroot}${prefix}/sbin/rabbitmqctl ${destroot}${prefix}/sbin/rabbitmqctl_real + xinstall -m 555 ${filespath}/rabbitmqctl_wrapper ${destroot}${prefix}/sbin + file rename ${destroot}${prefix}/sbin/rabbitmqctl_wrapper ${destroot}${prefix}/sbin/rabbitmqctl + + reinplace -E "s:@PREFIX@:${prefix}:" \ + ${destroot}${prefix}/sbin/rabbitmqctl +} + +pre-install { + system "cd ${destroot}${plistloc}; patch <${filespath}/patch-org.macports.rabbitmq-server.plist.diff" } startupitem.create yes startupitem.init "PATH=${prefix}/bin:${prefix}/sbin:\$PATH; export PATH" -startupitem.start "su rabbitmq -c rabbitmq-server 2>&1" +startupitem.start "rabbitmq-server 2>&1" startupitem.stop "rabbitmqctl stop 2>&1" startupitem.logfile ${prefix}/var/log/rabbitmq/startupitem.log diff --git a/packaging/macports/net/rabbitmq-server/files/patch-org.macports.rabbitmq-server.plist.diff b/packaging/macports/net/rabbitmq-server/files/patch-org.macports.rabbitmq-server.plist.diff new file mode 100644 index 00000000..45b49496 --- /dev/null +++ b/packaging/macports/net/rabbitmq-server/files/patch-org.macports.rabbitmq-server.plist.diff @@ -0,0 +1,10 @@ +--- org.macports.rabbitmq-server.plist.old 2009-02-26 08:00:31.000000000 -0800 ++++ org.macports.rabbitmq-server.plist 2009-02-26 08:01:27.000000000 -0800 +@@ -22,6 +22,7 @@ + ; + --pid=none + ++UserNamerabbitmq + Debug + Disabled + OnDemand diff --git a/packaging/macports/net/rabbitmq-server/files/rabbitmq-defaults b/packaging/macports/net/rabbitmq-server/files/rabbitmq-defaults deleted file mode 100644 index 1f9aad11..00000000 --- a/packaging/macports/net/rabbitmq-server/files/rabbitmq-defaults +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -# defaults file for rabbitmq-server -# - -PIDS_FILE=@PREFIX@/var/lib/rabbitmq/pids -LOG_BASE=@PREFIX@/var/log/rabbitmq -MNESIA_BASE=@PREFIX@/var/lib/rabbitmq/mnesia diff --git a/packaging/macports/net/rabbitmq-server/files/rabbitmqctl_wrapper b/packaging/macports/net/rabbitmq-server/files/rabbitmqctl_wrapper index 392c82ff..1996811e 100644 --- a/packaging/macports/net/rabbitmq-server/files/rabbitmqctl_wrapper +++ b/packaging/macports/net/rabbitmq-server/files/rabbitmqctl_wrapper @@ -1,13 +1,2 @@ #!/bin/bash -# Escape spaces and quotes, because shell is revolting. -for arg in "$@" ; do - # Escape quotes in parameters, so that they're passed through cleanly. - arg=$(sed -e 's/"/\\"/' <<-END - $arg - END - ) - CMDLINE="${CMDLINE} \"${arg}\"" -done - -cd / -exec su rabbitmq -c "@PREFIX@/sbin/rabbitmqctl_real ${CMDLINE}" +exec sudo -H -u rabbitmq "@PREFIX@/sbin/rabbitmqctl_real" "$@" -- cgit v1.2.1 From 2bf6a51c27a1331bca2a39b362a9740f6edb3ecb Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Tue, 3 Mar 2009 08:27:05 +0000 Subject: Follow advice of MacPorts committer --- packaging/macports/net/rabbitmq-server/Portfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packaging/macports/net/rabbitmq-server/Portfile b/packaging/macports/net/rabbitmq-server/Portfile index fdc142c8..d9d16dbb 100644 --- a/packaging/macports/net/rabbitmq-server/Portfile +++ b/packaging/macports/net/rabbitmq-server/Portfile @@ -5,9 +5,8 @@ PortSystem 1.0 name rabbitmq-server version 1.5.3 -revision 0 categories net -maintainers tonyg@rabbitmq.com cpettitt@gmail.com +maintainers tonyg@rabbitmq.com platforms darwin description The RabbitMQ AMQP Server long_description \ @@ -19,8 +18,6 @@ long_description \ homepage http://www.rabbitmq.com/ master_sites http://www.rabbitmq.com/releases/rabbitmq-server/v${version}/ -distname rabbitmq-server-${version} - checksums \ md5 3242a67885c2471b5ab62254bf024679 \ sha1 f4d6a01eaa2c74fa32f567fe410d21d9be1b43aa \ -- cgit v1.2.1 From 17412ea3f94e7047fe6268dff0cc1cd29effd066 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 12 Mar 2009 17:05:31 +0000 Subject: first cut at extending gen_server2 with priorities This just changes the representation of the message queue; the API changes are still to come. --- src/gen_server2.erl | 12 ++--- src/priority_queue.erl | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 src/priority_queue.erl diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 11bb66d7..fbaff765 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -276,7 +276,7 @@ enter_loop(Mod, Options, State, ServerName, Timeout) -> Name = get_proc_name(ServerName), Parent = get_parent(), Debug = debug_options(Name, Options), - Queue = queue:new(), + Queue = priority_queue:new(), loop(Parent, Name, State, Mod, Timeout, Queue, Debug). %%%======================================================================== @@ -294,7 +294,7 @@ init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name, Mod, Args, Options) -> Debug = debug_options(Name, Options), - Queue = queue:new(), + Queue = priority_queue:new(), case catch Mod:init(Args) of {ok, State} -> proc_lib:init_ack(Starter, {ok, self()}), @@ -326,9 +326,9 @@ init_it(Starter, Parent, Name, Mod, Args, Options) -> loop(Parent, Name, State, Mod, Time, Queue, Debug) -> receive Input -> loop(Parent, Name, State, Mod, - Time, queue:in(Input, Queue), Debug) + Time, priority_queue:in(Input, Queue), Debug) after 0 -> - case queue:out(Queue) of + case priority_queue:out(Queue) of {{value, Msg}, Queue1} -> process_msg(Parent, Name, State, Mod, Time, Queue1, Debug, Msg); @@ -336,7 +336,7 @@ loop(Parent, Name, State, Mod, Time, Queue, Debug) -> receive Input -> loop(Parent, Name, State, Mod, - Time, queue:in(Input, Queue1), Debug) + Time, priority_queue:in(Input, Queue1), Debug) after Time -> process_msg(Parent, Name, State, Mod, Time, Queue1, Debug, timeout) @@ -850,5 +850,5 @@ format_status(Opt, StatusData) -> {data, [{"Status", SysState}, {"Parent", Parent}, {"Logged events", Log}, - {"Queued messages", queue:to_list(Queue)}]} | + {"Queued messages", priority_queue:to_list(Queue)}]} | Specfic]. diff --git a/src/priority_queue.erl b/src/priority_queue.erl new file mode 100644 index 00000000..f660c039 --- /dev/null +++ b/src/priority_queue.erl @@ -0,0 +1,125 @@ +%% 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 Developers of the Original Code are LShift Ltd, +%% Cohesive Financial Technologies LLC, and Rabbit Technologies Ltd. +%% +%% Portions created before 22-Nov-2008 00:00:00 GMT by LShift Ltd, +%% Cohesive Financial Technologies LLC, or Rabbit Technologies Ltd +%% are Copyright (C) 2007-2008 LShift Ltd, Cohesive Financial +%% Technologies LLC, and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd are Copyright (C) 2007-2009 LShift +%% Ltd. Portions created by Cohesive Financial Technologies LLC are +%% Copyright (C) 2007-2009 Cohesive Financial Technologies +%% LLC. Portions created by Rabbit Technologies Ltd are Copyright +%% (C) 2007-2009 Rabbit Technologies Ltd. +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +%% 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 the highest priority. +%% +%% 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 in/3 is invoked for the first time we change the +%% representation from an ordinary queue to a gb_tree with {Priority, +%% Counter} as the key. Counter is incremented with for every 'in', + +-module(priority_queue). + +-export([new/0, is_queue/1, is_empty/1, len/1, to_list/1, in/2, in/3, out/1]). + +new() -> + {queue, [], []}. + +is_queue({queue, R, F}) when is_list(R), is_list(F) -> + true; +is_queue({pqueue, Counter, _Tree}) when is_integer(Counter), Counter >= 0 -> + true; +is_queue(_) -> + false. + +is_empty({queue, [], []}) -> + true; +is_empty({queue, In,Out}) when is_list(In), is_list(Out) -> + false; +is_empty({pqueue, _, Tree}) -> + gb_trees:is_empty(Tree). + +len({queue, R, F}) when is_list(R), is_list(F) -> + length(R) + length(F); +len({pqueue, _, Tree}) -> + gb_trees:size(Tree). + +to_list({queue, In, Out}) when is_list(In), is_list(Out) -> + Out ++ lists:reverse(In, []); +to_list({pqueue, _, Tree}) -> + gb_trees:to_list(Tree). + +in(X, {queue, [_] = In, []}) -> + {queue, [X], In}; +in(X, {queue, In, Out}) when is_list(In), is_list(Out) -> + {queue, [X|In], Out}; +in(Item, Other) -> + in(Item, infinity, Other). + +in(Item, Priority, {queue, In, Out}) -> + in(Item, Priority, {pqueue, 0, to_tree(In, Out)}); +in(Item, Priority, {pqueue, Counter, Tree}) -> + {pqueue, Counter + 1, gb_trees:insert({Priority, Counter}, Item, Tree)}. + +out({queue, [], []} = Q) -> + {empty, Q}; +out({queue, [V], []}) -> + {{value, V}, {queue, [], []}}; +out({queue, [Y|In], []}) -> + [V|Out] = lists:reverse(In, []), + {{value, V}, {queue, [Y], Out}}; +out({queue, In, [V]}) when is_list(In) -> + {{value,V}, r2f(In)}; +out({queue, In,[V|Out]}) when is_list(In) -> + {{value, V}, {queue, In, Out}}; +out({pqueue, Counter, Tree}) -> + {_, Item, Tree1} = gb_trees:take_smallest(Tree), + {{value, Item}, case gb_trees:is_empty(Tree1) of + true -> {queue, queue:new()}; + false -> {pqueue, Counter, Tree1} + end}. + +to_tree(In, Out) -> + lists:foldl(fun (V, {C, T}) -> + {C + 1, gb_trees:insert({infinity, C}, V, T)} + end, {0, gb_trees:empty()}, Out ++ lists:reverse(In, [])). + +r2f([]) -> + {queue, [], []}; +r2f([_] = R) -> + {queue, [], R}; +r2f([X,Y]) -> + {queue, [X], [Y]}; +r2f([X,Y|R]) -> + {queue, [X,Y], lists:reverse(R, [])}. -- cgit v1.2.1 From 2235300152b05828813ef654737f56112616d18c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 12 Mar 2009 18:13:52 +0000 Subject: fix bug --- src/gen_server2.erl | 49 ++++++++++++++++++++++++++++++++++++++++++++----- src/priority_queue.erl | 3 ++- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index fbaff765..11ac7987 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -107,8 +107,8 @@ %% API -export([start/3, start/4, start_link/3, start_link/4, - call/2, call/3, - cast/2, reply/2, + call/2, call/3, pcall/3, pcall/4, + cast/2, pcast/3, reply/2, abcast/2, abcast/3, multi_call/2, multi_call/3, multi_call/4, enter_loop/3, enter_loop/4, enter_loop/5]). @@ -188,6 +188,22 @@ call(Name, Request, Timeout) -> exit({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end. +pcall(Name, Priority, Request) -> + case catch gen:call(Name, '$gen_pcall', {Priority, Request}) of + {ok,Res} -> + Res; + {'EXIT',Reason} -> + exit({Reason, {?MODULE, pcall, [Name, Priority, Request]}}) + end. + +pcall(Name, Priority, Request, Timeout) -> + case catch gen:call(Name, '$gen_pcall', {Priority, Request}, Timeout) of + {ok,Res} -> + Res; + {'EXIT',Reason} -> + exit({Reason, {?MODULE, pcall, [Name, Priority, Request, Timeout]}}) + end. + %% ----------------------------------------------------------------- %% Make a cast to a generic server. %% ----------------------------------------------------------------- @@ -207,6 +223,22 @@ do_cast(Dest, Request) -> cast_msg(Request) -> {'$gen_cast',Request}. +pcast({global,Name}, Priority, Request) -> + catch global:send(Name, cast_msg(Priority, Request)), + ok; +pcast({Name,Node}=Dest, Priority, Request) when is_atom(Name), is_atom(Node) -> + do_cast(Dest, Priority, Request); +pcast(Dest, Priority, Request) when is_atom(Dest) -> + do_cast(Dest, Priority, Request); +pcast(Dest, Priority, Request) when is_pid(Dest) -> + do_cast(Dest, Priority, Request). + +do_cast(Dest, Priority, Request) -> + do_send(Dest, cast_msg(Priority, Request)), + ok. + +cast_msg(Priority, Request) -> {'$gen_pcast', {Priority, Request}}. + %% ----------------------------------------------------------------- %% Send a reply to the client. %% ----------------------------------------------------------------- @@ -326,7 +358,7 @@ init_it(Starter, Parent, Name, Mod, Args, Options) -> loop(Parent, Name, State, Mod, Time, Queue, Debug) -> receive Input -> loop(Parent, Name, State, Mod, - Time, priority_queue:in(Input, Queue), Debug) + Time, in(Input, Queue), Debug) after 0 -> case priority_queue:out(Queue) of {{value, Msg}, Queue1} -> @@ -336,14 +368,21 @@ loop(Parent, Name, State, Mod, Time, Queue, Debug) -> receive Input -> loop(Parent, Name, State, Mod, - Time, priority_queue:in(Input, Queue1), Debug) + Time, in(Input, Queue1), Debug) after Time -> process_msg(Parent, Name, State, Mod, Time, Queue1, Debug, timeout) end end end. - + +in({'$gen_pcast', {Priority, Msg}}, Queue) -> + priority_queue:in({'$gen_cast', Msg}, Priority, Queue); +in({'$gen_pcall', From, {Priority, Msg}}, Queue) -> + priority_queue:in({'$gen_call', From, Msg}, Priority, Queue); +in(Input, Queue) -> + priority_queue:in(Input, Queue). + process_msg(Parent, Name, State, Mod, Time, Queue, Debug, Msg) -> case Msg of {system, From, Req} -> diff --git a/src/priority_queue.erl b/src/priority_queue.erl index f660c039..484b5262 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -88,7 +88,8 @@ in(Item, Other) -> in(Item, infinity, Other). in(Item, Priority, {queue, In, Out}) -> - in(Item, Priority, {pqueue, 0, to_tree(In, Out)}); + {Counter, Tree} = to_tree(In, Out), + in(Item, Priority, {pqueue, Counter, Tree}); in(Item, Priority, {pqueue, Counter, Tree}) -> {pqueue, Counter + 1, gb_trees:insert({Priority, Counter}, Item, Tree)}. -- cgit v1.2.1 From 300e0e6b2ef6f7d99ed46395c184d864f99f15b6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 12 Mar 2009 18:25:57 +0000 Subject: another bug fix --- src/priority_queue.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 484b5262..55f76976 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -107,7 +107,7 @@ out({queue, In,[V|Out]}) when is_list(In) -> out({pqueue, Counter, Tree}) -> {_, Item, Tree1} = gb_trees:take_smallest(Tree), {{value, Item}, case gb_trees:is_empty(Tree1) of - true -> {queue, queue:new()}; + true -> {queue, [], []}; false -> {pqueue, Counter, Tree1} end}. -- cgit v1.2.1 From 1cd250ab0d3b78f167a3242be108bfe799f2e934 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 12 Mar 2009 18:34:31 +0000 Subject: make in/2 insert items that are low priority and make to_list do something sensible. --- src/priority_queue.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 55f76976..370d2e66 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -36,7 +36,7 @@ %% Priorities should be integers - the higher the value the higher the %% priority - but we don't actually check that. %% -%% in/2 inserts items with the highest priority. +%% 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 @@ -76,22 +76,22 @@ len({pqueue, _, Tree}) -> gb_trees:size(Tree). to_list({queue, In, Out}) when is_list(In), is_list(Out) -> - Out ++ lists:reverse(In, []); + [{0, V} || V <- Out ++ lists:reverse(In, [])]; to_list({pqueue, _, Tree}) -> - gb_trees:to_list(Tree). + [{-P, V} || {{P, _C}, V} <- gb_trees:to_list(Tree)]. in(X, {queue, [_] = In, []}) -> {queue, [X], In}; in(X, {queue, In, Out}) when is_list(In), is_list(Out) -> {queue, [X|In], Out}; in(Item, Other) -> - in(Item, infinity, Other). + in(Item, 0, Other). in(Item, Priority, {queue, In, Out}) -> {Counter, Tree} = to_tree(In, Out), in(Item, Priority, {pqueue, Counter, Tree}); in(Item, Priority, {pqueue, Counter, Tree}) -> - {pqueue, Counter + 1, gb_trees:insert({Priority, Counter}, Item, Tree)}. + {pqueue, Counter + 1, gb_trees:insert({-Priority, Counter}, Item, Tree)}. out({queue, [], []} = Q) -> {empty, Q}; @@ -113,7 +113,7 @@ out({pqueue, Counter, Tree}) -> to_tree(In, Out) -> lists:foldl(fun (V, {C, T}) -> - {C + 1, gb_trees:insert({infinity, C}, V, T)} + {C + 1, gb_trees:insert({0, C}, V, T)} end, {0, gb_trees:empty()}, Out ++ lists:reverse(In, [])). r2f([]) -> -- cgit v1.2.1 From 3bb32268a41fd277c68f48bebda4638c40a747d2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 12 Mar 2009 19:03:32 +0000 Subject: document priority functionality --- src/gen_server2.erl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 11ac7987..ba8becfc 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -16,6 +16,11 @@ %% The original code could reorder messages when communicating with a %% process on a remote node that was not currently connected. %% +%% 4) The new functions gen_server2:pcall/3, pcall/4, and pcast/3 +%% allow callers to attach priorities to requests. Requests with +%% higher priorities are processed before requests with lower +%% priorities. The default priority is 0. +%% %% All modifications are (C) 2009 LShift Ltd. %% ``The contents of this file are subject to the Erlang Public License, -- cgit v1.2.1 From f14b9c85912ba31a3bf7426df8817ba8ab7653bf Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 13 Mar 2009 08:41:52 +0000 Subject: change representation from gb_tree to kv list which is much more efficient for small numbers of priorities; the common case. --- src/priority_queue.erl | 85 +++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 370d2e66..e29d4a7b 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -45,9 +45,10 @@ %% functions directly in here, thus saving on inter-module calls and %% eliminating a level of boxing. %% -%% When in/3 is invoked for the first time we change the -%% representation from an ordinary queue to a gb_tree with {Priority, -%% Counter} as the key. Counter is incremented with for every 'in', +%% 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. + -module(priority_queue). @@ -58,40 +59,44 @@ new() -> is_queue({queue, R, F}) when is_list(R), is_list(F) -> true; -is_queue({pqueue, Counter, _Tree}) when is_integer(Counter), Counter >= 0 -> - true; +is_queue({pqueue, Queues}) when is_list(Queues) -> + lists:all(fun ({P, Q}) -> is_integer(P) andalso queue:is_queue(Q) end, + Queues); is_queue(_) -> false. is_empty({queue, [], []}) -> true; -is_empty({queue, In,Out}) when is_list(In), is_list(Out) -> - false; -is_empty({pqueue, _, Tree}) -> - gb_trees:is_empty(Tree). +is_empty(_) -> + false. len({queue, R, F}) when is_list(R), is_list(F) -> length(R) + length(F); -len({pqueue, _, Tree}) -> - gb_trees:size(Tree). +len({pqueue, Queues}) -> + lists:sum([queue:len(Q) || {_, Q} <- Queues]). to_list({queue, In, Out}) when is_list(In), is_list(Out) -> [{0, V} || V <- Out ++ lists:reverse(In, [])]; -to_list({pqueue, _, Tree}) -> - [{-P, V} || {{P, _C}, V} <- gb_trees:to_list(Tree)]. +to_list({pqueue, Queues}) -> + [{P, V} || {P, Q} <- Queues, V <- queue:to_list(Q)]. -in(X, {queue, [_] = In, []}) -> +in(Item, Q) -> + in(Item, 0, Q). + +in(X, 0, {queue, [_] = In, []}) -> {queue, [X], In}; -in(X, {queue, In, Out}) when is_list(In), is_list(Out) -> +in(X, 0, {queue, In, Out}) when is_list(In), is_list(Out) -> {queue, [X|In], Out}; -in(Item, Other) -> - in(Item, 0, Other). - -in(Item, Priority, {queue, In, Out}) -> - {Counter, Tree} = to_tree(In, Out), - in(Item, Priority, {pqueue, Counter, Tree}); -in(Item, Priority, {pqueue, Counter, Tree}) -> - {pqueue, Counter + 1, gb_trees:insert({-Priority, Counter}, Item, Tree)}. +in(X, Priority, {queue, In, Out}) -> + in(X, Priority, {pqueue, [{0, {In, Out}}]}); +in(X, Priority, {pqueue, Queues}) -> + P = -Priority, + {pqueue, case lists:keysearch(P, 1, Queues) of + {value, {_, Q}} -> + lists:keyreplace(P, 1, Queues, {P, queue:in(X, Q)}); + false -> + lists:keysort(1, [{P, {[X], []}} | Queues]) + end}. out({queue, [], []} = Q) -> {empty, Q}; @@ -104,23 +109,19 @@ out({queue, In, [V]}) when is_list(In) -> {{value,V}, r2f(In)}; out({queue, In,[V|Out]}) when is_list(In) -> {{value, V}, {queue, In, Out}}; -out({pqueue, Counter, Tree}) -> - {_, Item, Tree1} = gb_trees:take_smallest(Tree), - {{value, Item}, case gb_trees:is_empty(Tree1) of - true -> {queue, [], []}; - false -> {pqueue, Counter, Tree1} - end}. - -to_tree(In, Out) -> - lists:foldl(fun (V, {C, T}) -> - {C + 1, gb_trees:insert({0, C}, V, T)} - end, {0, gb_trees:empty()}, Out ++ lists:reverse(In, [])). +out({pqueue, [{P, Q} | Queues]}) -> + {R, Q1} = queue:out(Q), + NewQ = case queue:is_empty(Q1) of + true -> case Queues of + [] -> {queue, [], []}; + [{0, {In, Out}}] -> {queue, In, Out}; + [_|_] -> {pqueue, Queues} + end; + false -> {pqueue, [{P, Q1} | Queues]} + end, + {R, NewQ}. -r2f([]) -> - {queue, [], []}; -r2f([_] = R) -> - {queue, [], R}; -r2f([X,Y]) -> - {queue, [X], [Y]}; -r2f([X,Y|R]) -> - {queue, [X,Y], lists:reverse(R, [])}. +r2f([]) -> {queue, [], []}; +r2f([_] = R) -> {queue, [], R}; +r2f([X,Y]) -> {queue, [X], [Y]}; +r2f([X,Y|R]) -> {queue, [X,Y], lists:reverse(R, [])}. -- cgit v1.2.1 From 3592e4dafe2b32f6afe2143afc6fd327a3eb1cd5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 13 Mar 2009 08:43:57 +0000 Subject: fix bug --- src/priority_queue.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index e29d4a7b..5c10259b 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -78,7 +78,7 @@ len({pqueue, Queues}) -> to_list({queue, In, Out}) when is_list(In), is_list(Out) -> [{0, V} || V <- Out ++ lists:reverse(In, [])]; to_list({pqueue, Queues}) -> - [{P, V} || {P, Q} <- Queues, V <- queue:to_list(Q)]. + [{-P, V} || {P, Q} <- Queues, V <- queue:to_list(Q)]. in(Item, Q) -> in(Item, 0, Q). -- cgit v1.2.1 From ec6c9a607d1f4c3d59d19fb56cd87e9f79ed6ae5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 13 Mar 2009 08:58:45 +0000 Subject: do not use priorities for notify_sent and unblock since the benefit is unproven --- src/rabbit_amqqueue.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 3018582f..59b4d039 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -200,10 +200,10 @@ list(VHostPath) -> map(VHostPath, F) -> rabbit_misc:filter_exit_map(F, list(VHostPath)). info(#amqqueue{ pid = QPid }) -> - gen_server2:call(QPid, info). + gen_server2:pcall(QPid, 9, info). info(#amqqueue{ pid = QPid }, Items) -> - case gen_server2:call(QPid, {info, Items}) of + case gen_server2:pcall(QPid, 9, {info, Items}) of {ok, Res} -> Res; {error, Error} -> throw(Error) end. -- cgit v1.2.1 From 10694b8539768e732b505e1db356e42306e92c27 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Wed, 25 Mar 2009 17:50:29 +0000 Subject: Include generate_app in source tarball --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 81ac5687..47aa586c 100644 --- a/Makefile +++ b/Makefile @@ -133,7 +133,7 @@ srcdist: distclean sed -i 's/%%VERSION%%/$(VERSION)/' $(TARGET_SRC_DIR)/ebin/rabbit_app.in cp -r $(AMQP_CODEGEN_DIR)/* $(TARGET_SRC_DIR)/codegen/ - cp codegen.py Makefile $(TARGET_SRC_DIR) + cp codegen.py Makefile generate_app $(TARGET_SRC_DIR) cp -r scripts $(TARGET_SRC_DIR) cp -r docs $(TARGET_SRC_DIR) -- cgit v1.2.1 From a44c0fa0741968d392d59f04ba183937800c0db3 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 26 Mar 2009 17:38:03 +0000 Subject: Correct name of table. --- src/rabbit_access_control.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl index da0ab9cf..54348d9a 100644 --- a/src/rabbit_access_control.erl +++ b/src/rabbit_access_control.erl @@ -192,7 +192,7 @@ delete_user(Username) -> fun () -> ok = mnesia:delete({rabbit_user, Username}), [ok = mnesia:delete_object( - rabbit_user_permissions, R, write) || + rabbit_user_permission, R, write) || R <- mnesia:match_object( rabbit_user_permission, #user_permission{user_vhost = #user_vhost{ -- cgit v1.2.1 From ccd07959dcc4f57db3ca84a6def335426964380b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 31 Mar 2009 11:47:44 +0100 Subject: don't call into queue module and don't break its encapsulation --- src/priority_queue.erl | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 5c10259b..57175102 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -47,7 +47,10 @@ %% %% 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. +%% 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). @@ -60,7 +63,7 @@ new() -> is_queue({queue, R, F}) when is_list(R), is_list(F) -> true; is_queue({pqueue, Queues}) when is_list(Queues) -> - lists:all(fun ({P, Q}) -> is_integer(P) andalso queue:is_queue(Q) end, + lists:all(fun ({P, Q}) -> is_integer(P) andalso is_queue(Q) end, Queues); is_queue(_) -> false. @@ -73,12 +76,12 @@ is_empty(_) -> len({queue, R, F}) when is_list(R), is_list(F) -> length(R) + length(F); len({pqueue, Queues}) -> - lists:sum([queue:len(Q) || {_, Q} <- Queues]). + lists:sum([len(Q) || {_, Q} <- Queues]). to_list({queue, In, Out}) when is_list(In), is_list(Out) -> [{0, V} || V <- Out ++ lists:reverse(In, [])]; to_list({pqueue, Queues}) -> - [{-P, V} || {P, Q} <- Queues, V <- queue:to_list(Q)]. + [{-P, V} || {P, Q} <- Queues, V <- to_list(Q)]. in(Item, Q) -> in(Item, 0, Q). @@ -88,14 +91,14 @@ in(X, 0, {queue, [_] = In, []}) -> in(X, 0, {queue, In, Out}) when is_list(In), is_list(Out) -> {queue, [X|In], Out}; in(X, Priority, {queue, In, Out}) -> - in(X, Priority, {pqueue, [{0, {In, Out}}]}); + in(X, Priority, {pqueue, [{0, {queue, In, Out}}]}); in(X, Priority, {pqueue, Queues}) -> P = -Priority, {pqueue, case lists:keysearch(P, 1, Queues) of {value, {_, Q}} -> - lists:keyreplace(P, 1, Queues, {P, queue:in(X, Q)}); + lists:keyreplace(P, 1, Queues, {P, in(X, Q)}); false -> - lists:keysort(1, [{P, {[X], []}} | Queues]) + lists:keysort(1, [{P, {queue, [X], []}} | Queues]) end}. out({queue, [], []} = Q) -> @@ -110,12 +113,12 @@ out({queue, In, [V]}) when is_list(In) -> out({queue, In,[V|Out]}) when is_list(In) -> {{value, V}, {queue, In, Out}}; out({pqueue, [{P, Q} | Queues]}) -> - {R, Q1} = queue:out(Q), - NewQ = case queue:is_empty(Q1) of + {R, Q1} = out(Q), + NewQ = case is_empty(Q1) of true -> case Queues of - [] -> {queue, [], []}; - [{0, {In, Out}}] -> {queue, In, Out}; - [_|_] -> {pqueue, Queues} + [] -> {queue, [], []}; + [{0, OnlyQ}] -> OnlyQ; + [_|_] -> {pqueue, Queues} end; false -> {pqueue, [{P, Q1} | Queues]} end, -- cgit v1.2.1 From 6cb73add3a0ec8e5ce619d65e3939299fdbfb3f5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 31 Mar 2009 13:11:32 +0100 Subject: small simplification --- src/priority_queue.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 57175102..1ad4b34a 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -90,8 +90,8 @@ in(X, 0, {queue, [_] = In, []}) -> {queue, [X], In}; in(X, 0, {queue, In, Out}) when is_list(In), is_list(Out) -> {queue, [X|In], Out}; -in(X, Priority, {queue, In, Out}) -> - in(X, Priority, {pqueue, [{0, {queue, In, Out}}]}); +in(X, Priority, Q = {queue, _, _}) -> + in(X, Priority, {pqueue, [{0, Q}]}); in(X, Priority, {pqueue, Queues}) -> P = -Priority, {pqueue, case lists:keysearch(P, 1, Queues) of -- cgit v1.2.1 From 97e12e6817e01e4fff9411eecfc777e218af9633 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 31 Mar 2009 13:18:07 +0100 Subject: fix bug --- src/priority_queue.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 1ad4b34a..226c6bd8 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -81,7 +81,7 @@ len({pqueue, Queues}) -> to_list({queue, In, Out}) when is_list(In), is_list(Out) -> [{0, V} || V <- Out ++ lists:reverse(In, [])]; to_list({pqueue, Queues}) -> - [{-P, V} || {P, Q} <- Queues, V <- to_list(Q)]. + [{-P, V} || {P, Q} <- Queues, {0, V} <- to_list(Q)]. in(Item, Q) -> in(Item, 0, Q). -- cgit v1.2.1 From 88d1946897efa813a92d20002846c1f5d58352ff Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 31 Mar 2009 13:43:49 +0100 Subject: add some priority_queue tests --- src/rabbit_tests.erl | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 6312e8e3..946b8c31 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -45,6 +45,7 @@ test_content_prop_roundtrip(Datum, Binary) -> Binary = rabbit_binary_generator:encode_properties(Types, Values). %% assertion all_tests() -> + passed = test_priority_queue(), passed = test_parsing(), passed = test_topic_matching(), passed = test_log_management(), @@ -55,6 +56,58 @@ all_tests() -> passed = test_server_status(), passed. +test_priority_queue() -> + + false = priority_queue:is_queue(not_a_queue), + + %% empty Q + Q = priority_queue:new(), + {true, true, 0, [], []} = test_priority_queue(Q), + + %% 1-4 element no-priority Q + true = lists:all(fun (X) -> X =:= passed end, + lists:map(fun test_simple_n_element_queue/1, + lists:seq(1, 4))), + + %% 1-element priority Q + Q1 = priority_queue:in(foo, 1, priority_queue:new()), + {true, false, 1, [{1, foo}], [foo]} = test_priority_queue(Q1), + + %% 2-element same-priority Q + Q2 = priority_queue:in(bar, 1, Q1), + {true, false, 2, [{1, foo}, {1, bar}], [foo, bar]} = + test_priority_queue(Q2), + + %% 2-element different-priority Q + Q3 = priority_queue:in(bar, 2, Q1), + {true, false, 2, [{2, bar}, {1, foo}], [bar, foo]} = + test_priority_queue(Q3), + + passed. + +priority_queue_in_all(Q, L) -> + lists:foldl(fun (X, Acc) -> priority_queue:in(X, Acc) end, Q, L). + +priority_queue_out_all(Q) -> + case priority_queue:out(Q) of + {empty, _} -> []; + {{value, V}, Q1} -> [V | priority_queue_out_all(Q1)] + end. + +test_priority_queue(Q) -> + {priority_queue:is_queue(Q), + priority_queue:is_empty(Q), + priority_queue:len(Q), + priority_queue:to_list(Q), + priority_queue_out_all(Q)}. + +test_simple_n_element_queue(N) -> + Items = lists:seq(1, N), + Q = priority_queue_in_all(priority_queue:new(), Items), + ToListRes = [{0, X} || X <- Items], + {true, false, N, ToListRes, Items} = test_priority_queue(Q), + passed. + test_parsing() -> passed = test_content_properties(), passed. -- cgit v1.2.1 From 2444efd7f19c80066c2197edf76d4ce7b7ac298e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 31 Mar 2009 14:40:53 +0100 Subject: add some signatures --- src/priority_queue.erl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 226c6bd8..b872c8d5 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -57,6 +57,27 @@ -export([new/0, is_queue/1, is_empty/1, len/1, to_list/1, in/2, in/3, out/1]). +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(priority() :: integer()). +-type(squeue() :: {queue, [any()], [any()]}). +-type(pqueue() :: squeue() | {pqueue, [{priority(), squeue()}]}). + +-spec(new/0 :: () -> pqueue()). +-spec(is_queue/1 :: (any()) -> bool()). +-spec(is_empty/1 :: (pqueue()) -> bool()). +-spec(len/1 :: (pqueue()) -> non_neg_integer()). +-spec(to_list/1 :: (pqueue()) -> [{priority(), any()}]). +-spec(in/2 :: (any(), pqueue()) -> pqueue()). +-spec(in/3 :: (any(), priority(), pqueue()) -> pqueue()). +-spec(out/1 :: (pqueue()) -> {empty | {value, any()}, pqueue()}). + +-endif. + +%%---------------------------------------------------------------------------- + new() -> {queue, [], []}. -- cgit v1.2.1 From 074f16acbd597a51c941f716cdba9c20fca13153 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 1 Apr 2009 15:08:42 +0100 Subject: wait forever --- src/rabbit_alarm.erl | 3 ++- src/rabbit_amqqueue.erl | 30 +++++++++++++++--------------- src/rabbit_guid.erl | 3 ++- src/rabbit_limiter.erl | 2 +- src/rabbit_persister.erl | 8 ++++---- src/rabbit_reader.erl | 4 ++-- src/rabbit_router.erl | 3 ++- 7 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/rabbit_alarm.erl b/src/rabbit_alarm.erl index 875624ba..21999f16 100644 --- a/src/rabbit_alarm.erl +++ b/src/rabbit_alarm.erl @@ -78,7 +78,8 @@ stop() -> register(Pid, HighMemMFA) -> ok = gen_event:call(alarm_handler, ?MODULE, - {register, Pid, HighMemMFA}). + {register, Pid, HighMemMFA}, + infinity). %%---------------------------------------------------------------------------- diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index c941f307..fdc99c38 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -213,10 +213,10 @@ list(VHostPath) -> map(VHostPath, F) -> rabbit_misc:filter_exit_map(F, list(VHostPath)). info(#amqqueue{ pid = QPid }) -> - gen_server2:call(QPid, info). + gen_server2:call(QPid, info, infinity). info(#amqqueue{ pid = QPid }, Items) -> - case gen_server2:call(QPid, {info, Items}) of + case gen_server2:call(QPid, {info, Items}, infinity) of {ok, Res} -> Res; {error, Error} -> throw(Error) end. @@ -225,20 +225,20 @@ info_all(VHostPath) -> map(VHostPath, fun (Q) -> info(Q) end). info_all(VHostPath, Items) -> map(VHostPath, fun (Q) -> info(Q, Items) end). -stat(#amqqueue{pid = QPid}) -> gen_server2:call(QPid, stat). +stat(#amqqueue{pid = QPid}) -> gen_server2:call(QPid, stat, infinity). stat_all() -> lists:map(fun stat/1, rabbit_misc:dirty_read_all(rabbit_queue)). delete(#amqqueue{ pid = QPid }, IfUnused, IfEmpty) -> - gen_server2:call(QPid, {delete, IfUnused, IfEmpty}). + gen_server2:call(QPid, {delete, IfUnused, IfEmpty}, infinity). -purge(#amqqueue{ pid = QPid }) -> gen_server2:call(QPid, purge). +purge(#amqqueue{ pid = QPid }) -> gen_server2:call(QPid, purge, infinity). deliver(_IsMandatory, true, Txn, Message, QPid) -> - gen_server2:call(QPid, {deliver_immediately, Txn, Message}); + gen_server2:call(QPid, {deliver_immediately, Txn, Message}, infinity); deliver(true, _IsImmediate, Txn, Message, QPid) -> - gen_server2:call(QPid, {deliver, Txn, Message}), + gen_server2:call(QPid, {deliver, Txn, Message}, infinity), true; deliver(false, _IsImmediate, Txn, Message, QPid) -> gen_server2:cast(QPid, {deliver, Txn, Message}), @@ -254,10 +254,9 @@ ack(QPid, Txn, MsgIds, ChPid) -> gen_server2:cast(QPid, {ack, Txn, MsgIds, ChPid}). commit_all(QPids, Txn) -> - Timeout = length(QPids) * ?CALL_TIMEOUT, safe_pmap_ok( fun (QPid) -> exit({queue_disappeared, QPid}) end, - fun (QPid) -> gen_server2:call(QPid, {commit, Txn}, Timeout) end, + fun (QPid) -> gen_server2:call(QPid, {commit, Txn}, infinity) end, QPids). rollback_all(QPids, Txn) -> @@ -267,12 +266,11 @@ rollback_all(QPids, Txn) -> QPids). notify_down_all(QPids, ChPid) -> - Timeout = length(QPids) * ?CALL_TIMEOUT, safe_pmap_ok( %% we don't care if the queue process has terminated in the %% meantime fun (_) -> ok end, - fun (QPid) -> gen_server2:call(QPid, {notify_down, ChPid}, Timeout) end, + fun (QPid) -> gen_server2:call(QPid, {notify_down, ChPid}, infinity) end, QPids). limit_all(QPids, ChPid, LimiterPid) -> @@ -282,18 +280,20 @@ limit_all(QPids, ChPid, LimiterPid) -> QPids). claim_queue(#amqqueue{pid = QPid}, ReaderPid) -> - gen_server2:call(QPid, {claim_queue, ReaderPid}). + gen_server2:call(QPid, {claim_queue, ReaderPid}, infinity). basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> - gen_server2:call(QPid, {basic_get, ChPid, NoAck}). + gen_server2:call(QPid, {basic_get, ChPid, NoAck}, infinity). basic_consume(#amqqueue{pid = QPid}, NoAck, ReaderPid, ChPid, LimiterPid, ConsumerTag, ExclusiveConsume, OkMsg) -> gen_server2:call(QPid, {basic_consume, NoAck, ReaderPid, ChPid, - LimiterPid, ConsumerTag, ExclusiveConsume, OkMsg}). + LimiterPid, ConsumerTag, ExclusiveConsume, OkMsg}, + infinity). basic_cancel(#amqqueue{pid = QPid}, ChPid, ConsumerTag, OkMsg) -> - ok = gen_server2:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}). + ok = gen_server2:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}, + infinity). notify_sent(QPid, ChPid) -> gen_server2:cast(QPid, {notify_sent, ChPid}). diff --git a/src/rabbit_guid.erl b/src/rabbit_guid.erl index 51c1665b..2be00503 100644 --- a/src/rabbit_guid.erl +++ b/src/rabbit_guid.erl @@ -82,7 +82,8 @@ guid() -> %% and time. We combine that with a process-local counter to give %% us a GUID that is monotonically increasing per process. G = case get(guid) of - undefined -> {{gen_server:call(?SERVER, serial), self()}, 0}; + undefined -> {{gen_server:call(?SERVER, serial, infinity), self()}, + 0}; {S, I} -> {S, I+1} end, put(guid, G), diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 532be26d..3f9b6ebb 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -90,7 +90,7 @@ can_send(undefined, _QPid) -> can_send(LimiterPid, QPid) -> rabbit_misc:with_exit_handler( fun () -> true end, - fun () -> gen_server2:call(LimiterPid, {can_send, QPid}) end). + fun () -> gen_server2:call(LimiterPid, {can_send, QPid}, infinity) end). %% Let the limiter know that the channel has received some acks from a %% consumer diff --git a/src/rabbit_persister.erl b/src/rabbit_persister.erl index 99ae3f38..f4fa4599 100644 --- a/src/rabbit_persister.erl +++ b/src/rabbit_persister.erl @@ -95,7 +95,7 @@ start_link() -> transaction(MessageList) -> ?LOGDEBUG("transaction ~p~n", [MessageList]), TxnKey = rabbit_guid:guid(), - gen_server:call(?SERVER, {transaction, TxnKey, MessageList}). + gen_server:call(?SERVER, {transaction, TxnKey, MessageList}, infinity). extend_transaction(TxnKey, MessageList) -> ?LOGDEBUG("extend_transaction ~p ~p~n", [TxnKey, MessageList]), @@ -107,17 +107,17 @@ dirty_work(MessageList) -> commit_transaction(TxnKey) -> ?LOGDEBUG("commit_transaction ~p~n", [TxnKey]), - gen_server:call(?SERVER, {commit_transaction, TxnKey}). + gen_server:call(?SERVER, {commit_transaction, TxnKey}, infinity). rollback_transaction(TxnKey) -> ?LOGDEBUG("rollback_transaction ~p~n", [TxnKey]), gen_server:cast(?SERVER, {rollback_transaction, TxnKey}). force_snapshot() -> - gen_server:call(?SERVER, force_snapshot). + gen_server:call(?SERVER, force_snapshot, infinity). serial() -> - gen_server:call(?SERVER, serial). + gen_server:call(?SERVER, serial, infinity). %%-------------------------------------------------------------------- diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 9f642e35..ef8038e7 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -161,10 +161,10 @@ system_code_change(Misc, _Module, _OldVsn, _Extra) -> {ok, Misc}. info(Pid) -> - gen_server:call(Pid, info). + gen_server:call(Pid, info, infinity). info(Pid, Items) -> - case gen_server:call(Pid, {info, Items}) of + case gen_server:call(Pid, {info, Items}, infinity) of {ok, Res} -> Res; {error, Error} -> throw(Error) end. diff --git a/src/rabbit_router.erl b/src/rabbit_router.erl index ff42ea04..0b06a063 100644 --- a/src/rabbit_router.erl +++ b/src/rabbit_router.erl @@ -112,7 +112,8 @@ deliver_per_node(NodeQPids, Mandatory, Immediate, fun ({Node, QPids}) -> try gen_server2:call( {?SERVER, Node}, - {deliver, QPids, Mandatory, Immediate, Txn, Message}) + {deliver, QPids, Mandatory, Immediate, Txn, Message}, + infinity) catch _Class:_Reason -> %% TODO: figure out what to log (and do!) here -- cgit v1.2.1 From 0dfed790b7ab4cbf18b8837c2490f5b0fa46c805 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Mon, 13 Apr 2009 17:34:05 -0400 Subject: Fix double-exchange-deletion bug --- src/rabbit_channel.erl | 2 +- src/rabbit_exchange.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 7574cd67..c43efbed 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -578,7 +578,7 @@ handle_method(#'exchange.delete'{exchange = ExchangeNameBin, ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), check_configure_permitted(ExchangeName, State), case rabbit_exchange:delete(ExchangeName, IfUnused) of - {error, not_found} -> + {error, exchange_not_found} -> rabbit_misc:protocol_error( not_found, "no ~s", [rabbit_misc:rs(ExchangeName)]); {error, in_use} -> diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index a57e8076..6b33def4 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -90,7 +90,7 @@ -spec(topic_matches/2 :: (binary(), binary()) -> bool()). -spec(headers_match/2 :: (amqp_table(), amqp_table()) -> bool()). -spec(delete/2 :: (exchange_name(), bool()) -> - 'ok' | not_found() | {'error', 'in_use'}). + 'ok' | {'error', 'exchange_not_found'} | {'error', 'in_use'}). -spec(list_queue_bindings/1 :: (queue_name()) -> [{exchange_name(), routing_key(), amqp_table()}]). -spec(list_exchange_bindings/1 :: (exchange_name()) -> -- cgit v1.2.1 From cba506c55e742d5404d30af6bf154c62af0e74e2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 14 Apr 2009 14:54:51 +0100 Subject: Backed out changeset 4ee61cc2d6f0 This was committed directly on 'default' in error. --- src/rabbit_channel.erl | 2 +- src/rabbit_exchange.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index c43efbed..7574cd67 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -578,7 +578,7 @@ handle_method(#'exchange.delete'{exchange = ExchangeNameBin, ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), check_configure_permitted(ExchangeName, State), case rabbit_exchange:delete(ExchangeName, IfUnused) of - {error, exchange_not_found} -> + {error, not_found} -> rabbit_misc:protocol_error( not_found, "no ~s", [rabbit_misc:rs(ExchangeName)]); {error, in_use} -> diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 6b33def4..a57e8076 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -90,7 +90,7 @@ -spec(topic_matches/2 :: (binary(), binary()) -> bool()). -spec(headers_match/2 :: (amqp_table(), amqp_table()) -> bool()). -spec(delete/2 :: (exchange_name(), bool()) -> - 'ok' | {'error', 'exchange_not_found'} | {'error', 'in_use'}). + 'ok' | not_found() | {'error', 'in_use'}). -spec(list_queue_bindings/1 :: (queue_name()) -> [{exchange_name(), routing_key(), amqp_table()}]). -spec(list_exchange_bindings/1 :: (exchange_name()) -> -- cgit v1.2.1 From 2fc070e4fec26687cfd9af4fd10cf60ce36a0ad4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 15 Apr 2009 17:48:37 +0100 Subject: handle negative priorities --- src/priority_queue.erl | 2 ++ src/rabbit_tests.erl | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index b872c8d5..88ad0c18 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -111,6 +111,8 @@ in(X, 0, {queue, [_] = In, []}) -> {queue, [X], In}; in(X, 0, {queue, In, Out}) when is_list(In), is_list(Out) -> {queue, [X|In], Out}; +in(X, Priority, Q = {queue, [], []}) -> + in(X, Priority, {pqueue, []}); in(X, Priority, Q = {queue, _, _}) -> in(X, Priority, {pqueue, [{0, Q}]}); in(X, Priority, {pqueue, Queues}) -> diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 946b8c31..7d5fe2c1 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -83,6 +83,10 @@ test_priority_queue() -> {true, false, 2, [{2, bar}, {1, foo}], [bar, foo]} = test_priority_queue(Q3), + %% 1-element negative priority Q + Q4 = priority_queue:in(foo, -1, priority_queue:new()), + {true, false, 1, [{-1, foo}], [foo]} = test_priority_queue(Q4), + passed. priority_queue_in_all(Q, L) -> -- cgit v1.2.1 From 83497bbd0c37ddef8b07a1c8f82311ea9264ce61 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 24 Apr 2009 17:08:19 +0100 Subject: Fixed unused-variable warning --- src/priority_queue.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 88ad0c18..732757c4 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -111,7 +111,7 @@ in(X, 0, {queue, [_] = In, []}) -> {queue, [X], In}; in(X, 0, {queue, In, Out}) when is_list(In), is_list(Out) -> {queue, [X|In], Out}; -in(X, Priority, Q = {queue, [], []}) -> +in(X, Priority, _Q = {queue, [], []}) -> in(X, Priority, {pqueue, []}); in(X, Priority, Q = {queue, _, _}) -> in(X, Priority, {pqueue, [{0, Q}]}); -- cgit v1.2.1