From 5d381a47d1ff648fff4d9f83615cf0cb92cbf510 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 1 Feb 2011 16:28:36 +0000 Subject: Beginning of channel.credit support. Tests pass, except for drain=true. --- src/rabbit_amqqueue_process.erl | 3 ++- src/rabbit_channel.erl | 54 ++++++++++++++++++++++++++--------------- src/rabbit_limiter.erl | 46 +++++++++++++++++------------------ 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 3418c663..b74b9034 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1075,7 +1075,8 @@ handle_cast({limit, ChPid, LimiterPid}, State) -> true -> ok end, - NewLimited = Limited andalso LimiterPid =/= undefined, + NewLimited = Limited andalso LimiterPid =/= undefined + andalso rabbit_limiter:is_blocked(LimiterPid), C#cr{limiter_pid = LimiterPid, is_limit_active = NewLimited} end)); diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a82e5eff..eb634cca 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -462,6 +462,7 @@ check_name(Kind, NameBin = <<"amq.", _/binary>>) -> check_name(_Kind, NameBin) -> NameBin. +%% TODO port this queue_blocked(QPid, State = #ch{blocking = Blocking}) -> case dict:find(QPid, Blocking) of error -> State; @@ -1003,31 +1004,46 @@ handle_method(#'confirm.select'{nowait = NoWait}, _, State) -> return_ok(State#ch{confirm_enabled = true}, NoWait, #'confirm.select_ok'{}); -handle_method(#'channel.flow'{active = true}, _, - State = #ch{limiter_pid = LimiterPid}) -> - LimiterPid1 = case rabbit_limiter:unblock(LimiterPid) of - ok -> LimiterPid; - stopped -> unlimit_queues(State) - end, - {reply, #'channel.flow_ok'{active = true}, - State#ch{limiter_pid = LimiterPid1}}; +handle_method(#'channel.flow'{active = true}, Content, State) -> + {noreply, State1 = #ch{writer_pid = WriterPid}} = + handle_method(#'channel.credit'{credit = -1, drain = true}, + Content, State), + ok = rabbit_writer:send_command(WriterPid, + #'channel.flow_ok'{active = true}), + {noreply, State1}; -handle_method(#'channel.flow'{active = false}, _, - State = #ch{limiter_pid = LimiterPid, +handle_method(#'channel.flow'{active = false}, Content, State) -> + {noreply, State1 = #ch{writer_pid = WriterPid}} = + handle_method(#'channel.credit'{credit = 0, drain = true}, + Content, State), + ok = rabbit_writer:send_command(WriterPid, + #'channel.flow_ok'{active = false}), + {noreply, State1}; + +handle_method(#'channel.credit'{credit = Credit, drain = Drain}, _, + State = #ch{limiter_pid = LimiterPid, consumer_mapping = Consumers}) -> LimiterPid1 = case LimiterPid of undefined -> start_limiter(State); Other -> Other end, - State1 = State#ch{limiter_pid = LimiterPid1}, - ok = rabbit_limiter:block(LimiterPid1), - case consumer_queues(Consumers) of - [] -> {reply, #'channel.flow_ok'{active = false}, State1}; - QPids -> Queues = [{QPid, erlang:monitor(process, QPid)} || - QPid <- QPids], - ok = rabbit_amqqueue:flush_all(QPids, self()), - {noreply, State1#ch{blocking = dict:from_list(Queues)}} - end; + LimiterPid2 = + case rabbit_limiter:set_credit(LimiterPid1, Credit, Drain) of + ok -> limit_queues(LimiterPid1, State), + LimiterPid1; + stopped -> unlimit_queues(State) + end, + State1 = State#ch{limiter_pid = LimiterPid2}, + {noreply, State1}; + + %% TODO port this bit + %% case consumer_queues(Consumers) of + %% [] -> {reply, #'channel.flow_ok'{active = false}, State1}; + %% QPids -> Queues = [{QPid, erlang:monitor(process, QPid)} || + %% QPid <- QPids], + %% ok = rabbit_amqqueue:flush_all(QPids, self()), + %% {noreply, State1#ch{blocking = dict:from_list(Queues)}} + %% end; handle_method(_MethodRecord, _Content, _State) -> rabbit_misc:protocol_error( diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 86ea7282..bf9cf583 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -22,7 +22,7 @@ handle_info/2, prioritise_call/3]). -export([start_link/2]). -export([limit/2, can_send/3, ack/2, register/2, unregister/2]). --export([get_limit/1, block/1, unblock/1, is_blocked/1]). +-export([get_limit/1, set_credit/3, is_blocked/1]). %%---------------------------------------------------------------------------- @@ -38,8 +38,8 @@ -spec(register/2 :: (maybe_pid(), pid()) -> 'ok'). -spec(unregister/2 :: (maybe_pid(), pid()) -> 'ok'). -spec(get_limit/1 :: (maybe_pid()) -> non_neg_integer()). --spec(block/1 :: (maybe_pid()) -> 'ok'). --spec(unblock/1 :: (maybe_pid()) -> 'ok' | 'stopped'). +%% -spec(block/1 :: (maybe_pid()) -> 'ok'). +%% -spec(unblock/1 :: (maybe_pid()) -> 'ok' | 'stopped'). -spec(is_blocked/1 :: (maybe_pid()) -> boolean()). -endif. @@ -48,7 +48,8 @@ -record(lim, {prefetch_count = 0, ch_pid, - blocked = false, + credit = unlimited, + drain = false, queues = dict:new(), % QPid -> {MonitorRef, Notify} volume = 0}). %% 'Notify' is a boolean that indicates whether a queue should be @@ -95,15 +96,12 @@ get_limit(Pid) -> fun () -> 0 end, fun () -> gen_server2:call(Pid, get_limit, infinity) end). -block(undefined) -> +set_credit(undefined, _, _) -> ok; -block(LimiterPid) -> - gen_server2:call(LimiterPid, block, infinity). - -unblock(undefined) -> - ok; -unblock(LimiterPid) -> - gen_server2:call(LimiterPid, unblock, infinity). +set_credit(LimiterPid, -1, Drain) -> + gen_server2:call(LimiterPid, {set_credit, unlimited, Drain}, infinity); +set_credit(LimiterPid, Credit, Drain) -> + gen_server2:call(LimiterPid, {set_credit, Credit, Drain}, infinity). is_blocked(undefined) -> false; @@ -121,14 +119,18 @@ prioritise_call(get_limit, _From, _State) -> 9; prioritise_call(_Msg, _From, _State) -> 0. handle_call({can_send, _QPid, _AckRequired}, _From, - State = #lim{blocked = true}) -> + State = #lim{credit = 0}) -> {reply, false, State}; handle_call({can_send, QPid, AckRequired}, _From, - State = #lim{volume = Volume}) -> + State = #lim{volume = Volume, credit = Credit}) -> case limit_reached(State) of true -> {reply, false, limit_queue(QPid, State)}; false -> {reply, true, State#lim{volume = if AckRequired -> Volume + 1; true -> Volume + end, + credit = case Credit of + unlimited -> unlimited; + _ -> Credit - 1 end}} end; @@ -141,11 +143,8 @@ handle_call({limit, PrefetchCount}, _From, State) -> {stop, State1} -> {stop, normal, stopped, State1} end; -handle_call(block, _From, State) -> - {reply, ok, State#lim{blocked = true}}; - -handle_call(unblock, _From, State) -> - case maybe_notify(State, State#lim{blocked = false}) of +handle_call({set_credit, Credit, Drain}, _From, State) -> + case maybe_notify(State, State#lim{credit = Credit, drain = Drain}) of {cont, State1} -> {reply, ok, State1}; {stop, State1} -> {stop, normal, stopped, State1} end; @@ -183,9 +182,9 @@ maybe_notify(OldState, NewState) -> case (limit_reached(OldState) orelse blocked(OldState)) andalso not (limit_reached(NewState) orelse blocked(NewState)) of true -> NewState1 = notify_queues(NewState), - {case NewState1#lim.prefetch_count of - 0 -> stop; - _ -> cont + {case {NewState1#lim.prefetch_count, NewState1#lim.credit} of + {0, unlimited} -> stop; + _ -> cont end, NewState1}; false -> {cont, NewState} end. @@ -193,7 +192,8 @@ maybe_notify(OldState, NewState) -> limit_reached(#lim{prefetch_count = Limit, volume = Volume}) -> Limit =/= 0 andalso Volume >= Limit. -blocked(#lim{blocked = Blocked}) -> Blocked. +blocked(#lim{credit = 0}) -> true; +blocked(_) -> false. remember_queue(QPid, State = #lim{queues = Queues}) -> case dict:is_key(QPid, Queues) of -- cgit v1.2.1 From 80bf3ebfe759c7164aa44a1e15a346b4139b1eb6 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 1 Feb 2011 16:53:28 +0000 Subject: Support drain (probably in a rather inefficient way). --- src/rabbit_amqqueue_process.erl | 7 +++++-- src/rabbit_limiter.erl | 32 +++++++++++++++++--------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b74b9034..9f497f3d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -335,7 +335,9 @@ ch_record_state_transition(OldCR, NewCR) -> deliver_msgs_to_consumers(Funs = {PredFun, DeliverFun}, FunAcc, State = #q{q = #amqqueue{name = QName}, active_consumers = ActiveConsumers, - blocked_consumers = BlockedConsumers}) -> + blocked_consumers = BlockedConsumers, + backing_queue = BQ, + backing_queue_state = BQS}) -> case queue:out(ActiveConsumers) of {{value, QEntry = {ChPid, #consumer{tag = ConsumerTag, ack_required = AckRequired}}}, @@ -345,7 +347,8 @@ deliver_msgs_to_consumers(Funs = {PredFun, DeliverFun}, FunAcc, acktags = ChAckTags} = ch_record(ChPid), IsMsgReady = PredFun(FunAcc, State), case (IsMsgReady andalso - rabbit_limiter:can_send( LimiterPid, self(), AckRequired )) of + rabbit_limiter:can_send( LimiterPid, self(), AckRequired, + BQ:len(BQS) )) of true -> {{Message, IsDelivered, AckTag}, FunAcc1, State1} = DeliverFun(AckRequired, FunAcc, State), diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index bf9cf583..cd3ac9c5 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -21,7 +21,7 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, prioritise_call/3]). -export([start_link/2]). --export([limit/2, can_send/3, ack/2, register/2, unregister/2]). +-export([limit/2, can_send/4, ack/2, register/2, unregister/2]). -export([get_limit/1, set_credit/3, is_blocked/1]). %%---------------------------------------------------------------------------- @@ -33,7 +33,7 @@ -spec(start_link/2 :: (pid(), non_neg_integer()) -> rabbit_types:ok_pid_or_error()). -spec(limit/2 :: (maybe_pid(), non_neg_integer()) -> 'ok' | 'stopped'). --spec(can_send/3 :: (maybe_pid(), pid(), boolean()) -> boolean()). +-spec(can_send/4 :: (maybe_pid(), pid(), boolean(), non_neg_integer()) -> boolean()). -spec(ack/2 :: (maybe_pid(), non_neg_integer()) -> 'ok'). -spec(register/2 :: (maybe_pid(), pid()) -> 'ok'). -spec(unregister/2 :: (maybe_pid(), pid()) -> 'ok'). @@ -70,12 +70,12 @@ limit(LimiterPid, PrefetchCount) -> %% Ask the limiter whether the queue can deliver a message without %% breaching a limit -can_send(undefined, _QPid, _AckRequired) -> +can_send(undefined, _QPid, _AckRequired, _Len) -> true; -can_send(LimiterPid, QPid, AckRequired) -> +can_send(LimiterPid, QPid, AckRequired, Len) -> rabbit_misc:with_exit_handler( fun () -> true end, - fun () -> gen_server2:call(LimiterPid, {can_send, QPid, AckRequired}, + fun () -> gen_server2:call(LimiterPid, {can_send, QPid, AckRequired, Len}, infinity) end). %% Let the limiter know that the channel has received some acks from a @@ -118,20 +118,22 @@ init([ChPid, UnackedMsgCount]) -> prioritise_call(get_limit, _From, _State) -> 9; prioritise_call(_Msg, _From, _State) -> 0. -handle_call({can_send, _QPid, _AckRequired}, _From, +handle_call({can_send, _QPid, _AckRequired, _Len}, _From, State = #lim{credit = 0}) -> {reply, false, State}; -handle_call({can_send, QPid, AckRequired}, _From, - State = #lim{volume = Volume, credit = Credit}) -> +handle_call({can_send, QPid, AckRequired, Len}, _From, + State = #lim{volume = Volume, credit = Credit, drain = Drain}) -> case limit_reached(State) of true -> {reply, false, limit_queue(QPid, State)}; - false -> {reply, true, State#lim{volume = if AckRequired -> Volume + 1; - true -> Volume - end, - credit = case Credit of - unlimited -> unlimited; - _ -> Credit - 1 - end}} + false -> {reply, true, + State#lim{volume = if AckRequired -> Volume + 1; + true -> Volume + end, + credit = case {Credit, Len, Drain} of + {unlimited, _, _} -> unlimited; + {_, 1, true} -> 0; + {_, _, _} -> Credit - 1 + end}} end; handle_call(get_limit, _From, State = #lim{prefetch_count = PrefetchCount}) -> -- cgit v1.2.1 From c44c0fb9bb2adcbb9082efdcd18f4444e8a4d203 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 2 Feb 2011 12:04:07 +0000 Subject: Credit needs to be per ctag. --- src/rabbit_amqqueue_process.erl | 2 +- src/rabbit_channel.erl | 57 +++++++++------- src/rabbit_limiter.erl | 142 ++++++++++++++++++++++++++-------------- 3 files changed, 127 insertions(+), 74 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 9f497f3d..9fda12cd 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -348,7 +348,7 @@ deliver_msgs_to_consumers(Funs = {PredFun, DeliverFun}, FunAcc, IsMsgReady = PredFun(FunAcc, State), case (IsMsgReady andalso rabbit_limiter:can_send( LimiterPid, self(), AckRequired, - BQ:len(BQS) )) of + ConsumerTag, BQ:len(BQS) )) of true -> {{Message, IsDelivered, AckTag}, FunAcc1, State1} = DeliverFun(AckRequired, FunAcc, State), diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index eb634cca..bac106f9 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1004,23 +1004,34 @@ handle_method(#'confirm.select'{nowait = NoWait}, _, State) -> return_ok(State#ch{confirm_enabled = true}, NoWait, #'confirm.select_ok'{}); -handle_method(#'channel.flow'{active = true}, Content, State) -> - {noreply, State1 = #ch{writer_pid = WriterPid}} = - handle_method(#'channel.credit'{credit = -1, drain = true}, - Content, State), - ok = rabbit_writer:send_command(WriterPid, - #'channel.flow_ok'{active = true}), - {noreply, State1}; +handle_method(#'channel.flow'{active = true}, _, + State = #ch{limiter_pid = LimiterPid}) -> + LimiterPid1 = case rabbit_limiter:unblock(LimiterPid) of + ok -> LimiterPid; + stopped -> unlimit_queues(State) + end, + {reply, #'channel.flow_ok'{active = true}, + State#ch{limiter_pid = LimiterPid1}}; -handle_method(#'channel.flow'{active = false}, Content, State) -> - {noreply, State1 = #ch{writer_pid = WriterPid}} = - handle_method(#'channel.credit'{credit = 0, drain = true}, - Content, State), - ok = rabbit_writer:send_command(WriterPid, - #'channel.flow_ok'{active = false}), - {noreply, State1}; +handle_method(#'channel.flow'{active = false}, _, + State = #ch{limiter_pid = LimiterPid, + consumer_mapping = Consumers}) -> + LimiterPid1 = case LimiterPid of + undefined -> start_limiter(State); + Other -> Other + end, + State1 = State#ch{limiter_pid = LimiterPid1}, + ok = rabbit_limiter:block(LimiterPid1), + case consumer_queues(Consumers) of + [] -> {reply, #'channel.flow_ok'{active = false}, State1}; + QPids -> Queues = [{QPid, erlang:monitor(process, QPid)} || + QPid <- QPids], + ok = rabbit_amqqueue:flush_all(QPids, self()), + {noreply, State1#ch{blocking = dict:from_list(Queues)}} + end; -handle_method(#'channel.credit'{credit = Credit, drain = Drain}, _, +handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, + drain = Drain}, _, State = #ch{limiter_pid = LimiterPid, consumer_mapping = Consumers}) -> LimiterPid1 = case LimiterPid of @@ -1028,7 +1039,7 @@ handle_method(#'channel.credit'{credit = Credit, drain = Drain}, _, Other -> Other end, LimiterPid2 = - case rabbit_limiter:set_credit(LimiterPid1, Credit, Drain) of + case rabbit_limiter:set_credit(LimiterPid1, CTag, Credit, Drain) of ok -> limit_queues(LimiterPid1, State), LimiterPid1; stopped -> unlimit_queues(State) @@ -1036,7 +1047,7 @@ handle_method(#'channel.credit'{credit = Credit, drain = Drain}, _, State1 = State#ch{limiter_pid = LimiterPid2}, {noreply, State1}; - %% TODO port this bit + %% TODO port this bit ? %% case consumer_queues(Consumers) of %% [] -> {reply, #'channel.flow_ok'{active = false}, State1}; %% QPids -> Queues = [{QPid, erlang:monitor(process, QPid)} || @@ -1237,12 +1248,12 @@ consumer_queues(Consumers) -> notify_limiter(undefined, _Acked) -> ok; notify_limiter(LimiterPid, Acked) -> - case rabbit_misc:queue_fold(fun ({_, none, _}, Acc) -> Acc; - ({_, _, _}, Acc) -> Acc + 1 - end, 0, Acked) of - 0 -> ok; - Count -> rabbit_limiter:ack(LimiterPid, Count) - end. + %% TODO this could be faster, group the acks + rabbit_misc:queue_fold( + fun ({_, none, _}, Acc) -> Acc; + ({_, CTag, _}, Acc) -> rabbit_limiter:ack(LimiterPid, CTag), + Acc + end, ok, Acked). is_message_persistent(Content) -> case rabbit_basic:is_message_persistent(Content) of diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index cd3ac9c5..efe6023b 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -21,8 +21,8 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, prioritise_call/3]). -export([start_link/2]). --export([limit/2, can_send/4, ack/2, register/2, unregister/2]). --export([get_limit/1, set_credit/3, is_blocked/1]). +-export([limit/2, can_send/5, ack/2, register/2, unregister/2]). +-export([get_limit/1, block/1, unblock/1, set_credit/4, is_blocked/1]). %%---------------------------------------------------------------------------- @@ -33,13 +33,13 @@ -spec(start_link/2 :: (pid(), non_neg_integer()) -> rabbit_types:ok_pid_or_error()). -spec(limit/2 :: (maybe_pid(), non_neg_integer()) -> 'ok' | 'stopped'). --spec(can_send/4 :: (maybe_pid(), pid(), boolean(), non_neg_integer()) -> boolean()). --spec(ack/2 :: (maybe_pid(), non_neg_integer()) -> 'ok'). +-spec(can_send/5 :: (maybe_pid(), pid(), boolean(), binary(), non_neg_integer()) -> boolean()). +-spec(ack/2 :: (maybe_pid(), binary()) -> 'ok'). -spec(register/2 :: (maybe_pid(), pid()) -> 'ok'). -spec(unregister/2 :: (maybe_pid(), pid()) -> 'ok'). -spec(get_limit/1 :: (maybe_pid()) -> non_neg_integer()). -%% -spec(block/1 :: (maybe_pid()) -> 'ok'). -%% -spec(unblock/1 :: (maybe_pid()) -> 'ok' | 'stopped'). +-spec(block/1 :: (maybe_pid()) -> 'ok'). +-spec(unblock/1 :: (maybe_pid()) -> 'ok' | 'stopped'). -spec(is_blocked/1 :: (maybe_pid()) -> boolean()). -endif. @@ -48,10 +48,13 @@ -record(lim, {prefetch_count = 0, ch_pid, - credit = unlimited, - drain = false, + blocked = false, + credits = dict:new(), queues = dict:new(), % QPid -> {MonitorRef, Notify} volume = 0}). + +-record(credit, {credit = 0, drain = false}). + %% '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. @@ -70,18 +73,19 @@ limit(LimiterPid, PrefetchCount) -> %% Ask the limiter whether the queue can deliver a message without %% breaching a limit -can_send(undefined, _QPid, _AckRequired, _Len) -> +can_send(undefined, _QPid, _AckRequired, _CTag, _Len) -> true; -can_send(LimiterPid, QPid, AckRequired, Len) -> +can_send(LimiterPid, QPid, AckRequired, CTag, Len) -> rabbit_misc:with_exit_handler( fun () -> true end, - fun () -> gen_server2:call(LimiterPid, {can_send, QPid, AckRequired, Len}, + fun () -> gen_server2:call(LimiterPid, + {can_send, QPid, AckRequired, CTag, Len}, infinity) end). %% Let the limiter know that the channel has received some acks from a %% consumer -ack(undefined, _Count) -> ok; -ack(LimiterPid, Count) -> gen_server2:cast(LimiterPid, {ack, Count}). +ack(undefined, _CTag) -> ok; +ack(LimiterPid, CTag) -> gen_server2:cast(LimiterPid, {ack, CTag}). register(undefined, _QPid) -> ok; register(LimiterPid, QPid) -> gen_server2:cast(LimiterPid, {register, QPid}). @@ -96,12 +100,20 @@ get_limit(Pid) -> fun () -> 0 end, fun () -> gen_server2:call(Pid, get_limit, infinity) end). -set_credit(undefined, _, _) -> +block(undefined) -> + ok; +block(LimiterPid) -> + gen_server2:call(LimiterPid, block, infinity). + +unblock(undefined) -> ok; -set_credit(LimiterPid, -1, Drain) -> - gen_server2:call(LimiterPid, {set_credit, unlimited, Drain}, infinity); -set_credit(LimiterPid, Credit, Drain) -> - gen_server2:call(LimiterPid, {set_credit, Credit, Drain}, infinity). +unblock(LimiterPid) -> + gen_server2:call(LimiterPid, unblock, infinity). + +set_credit(undefined, _, _, _) -> + ok; +set_credit(LimiterPid, CTag, Credit, Drain) -> + gen_server2:call(LimiterPid, {set_credit, CTag, Credit, Drain}, infinity). is_blocked(undefined) -> false; @@ -118,47 +130,47 @@ init([ChPid, UnackedMsgCount]) -> prioritise_call(get_limit, _From, _State) -> 9; prioritise_call(_Msg, _From, _State) -> 0. -handle_call({can_send, _QPid, _AckRequired, _Len}, _From, - State = #lim{credit = 0}) -> +handle_call({can_send, _QPid, _AckRequired, _CTag, _Len}, _From, + State = #lim{blocked = true}) -> {reply, false, State}; -handle_call({can_send, QPid, AckRequired, Len}, _From, - State = #lim{volume = Volume, credit = Credit, drain = Drain}) -> - case limit_reached(State) of +handle_call({can_send, QPid, AckRequired, CTag, Len}, _From, + State = #lim{volume = Volume}) -> + case limit_reached(CTag, State) of true -> {reply, false, limit_queue(QPid, State)}; false -> {reply, true, - State#lim{volume = if AckRequired -> Volume + 1; - true -> Volume - end, - credit = case {Credit, Len, Drain} of - {unlimited, _, _} -> unlimited; - {_, 1, true} -> 0; - {_, _, _} -> Credit - 1 - end}} + decr_credit(CTag, Len, + State#lim{volume = if AckRequired -> Volume + 1; + true -> Volume + end})} end; handle_call(get_limit, _From, State = #lim{prefetch_count = PrefetchCount}) -> {reply, PrefetchCount, State}; handle_call({limit, PrefetchCount}, _From, State) -> - case maybe_notify(State, State#lim{prefetch_count = PrefetchCount}) of + case maybe_notify(irrelevant, + State, State#lim{prefetch_count = PrefetchCount}) of {cont, State1} -> {reply, ok, State1}; {stop, State1} -> {stop, normal, stopped, State1} end; -handle_call({set_credit, Credit, Drain}, _From, State) -> - case maybe_notify(State, State#lim{credit = Credit, drain = Drain}) of - {cont, State1} -> {reply, ok, State1}; - {stop, State1} -> {stop, normal, stopped, State1} - end; +handle_call(block, _From, State) -> + {reply, ok, State#lim{blocked = true}}; + +handle_call(unblock, _From, State) -> + maybe_notify_reply(irrelevant, State, State#lim{blocked = false}); + +handle_call({set_credit, CTag, Credit, Drain}, _From, State) -> + maybe_notify_reply(CTag, State, update_credit(CTag, Credit, Drain, State)); handle_call(is_blocked, _From, State) -> {reply, blocked(State), State}. -handle_cast({ack, Count}, State = #lim{volume = Volume}) -> +handle_cast({ack, CTag}, State = #lim{volume = Volume}) -> NewVolume = if Volume == 0 -> 0; - true -> Volume - Count + true -> Volume - 1 end, - {cont, State1} = maybe_notify(State, State#lim{volume = NewVolume}), + {cont, State1} = maybe_notify(CTag, State, State#lim{volume = NewVolume}), {noreply, State1}; handle_cast({register, QPid}, State) -> @@ -180,22 +192,52 @@ code_change(_, State, _) -> %% Internal plumbing %%---------------------------------------------------------------------------- -maybe_notify(OldState, NewState) -> - case (limit_reached(OldState) orelse blocked(OldState)) andalso - not (limit_reached(NewState) orelse blocked(NewState)) of +maybe_notify_reply(CTag, OldState, NewState) -> + case maybe_notify(CTag, OldState, NewState) of + {cont, State} -> {reply, ok, State}; + {stop, State} -> {stop, normal, stopped, State} + end. + +maybe_notify(CTag, OldState, NewState) -> + case (limit_reached(CTag, OldState) orelse blocked(OldState)) andalso + not (limit_reached(CTag, NewState) orelse blocked(NewState)) of true -> NewState1 = notify_queues(NewState), - {case {NewState1#lim.prefetch_count, NewState1#lim.credit} of - {0, unlimited} -> stop; - _ -> cont + {case {NewState1#lim.prefetch_count, + dict:size(NewState1#lim.credits)} of + {0, 0} -> stop; + _ -> cont end, NewState1}; false -> {cont, NewState} end. -limit_reached(#lim{prefetch_count = Limit, volume = Volume}) -> - Limit =/= 0 andalso Volume >= Limit. +limit_reached(CTag, #lim{prefetch_count = Limit, volume = Volume, + credits = Credits}) -> + case dict:find(CTag, Credits) of + {ok, #credit{ credit = 0 }} -> true; + _ -> false + end orelse (Limit =/= 0 andalso Volume >= Limit). + +decr_credit(CTag, Len, State = #lim{ credits = Credits } ) -> + case dict:find(CTag, Credits) of + {ok, #credit{ credit = Credit, drain = Drain }} -> + NewCredit = case {Len, Drain} of + {1, true} -> 0; + {_, _} -> Credit - 1 + end, + update_credit(CTag, NewCredit, Drain, State); + error -> + State + end. + +update_credit(CTag, -1, _Drain, State = #lim{credits = Credits}) -> + State#lim{credits = dict:erase(CTag, Credits)}; + +update_credit(CTag, Credit, Drain, State = #lim{credits = Credits}) -> + State#lim{credits = dict:store(CTag, + #credit{credit = Credit, drain = Drain}, + Credits)}. -blocked(#lim{credit = 0}) -> true; -blocked(_) -> false. +blocked(#lim{blocked = Blocked}) -> Blocked. remember_queue(QPid, State = #lim{queues = Queues}) -> case dict:is_key(QPid, Queues) of -- cgit v1.2.1 From b5861c7c239af97813adac731b58049ad66fd9d6 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 2 Feb 2011 12:40:13 +0000 Subject: Return a basic.credit-state after receiving a basic.credit. --- src/rabbit_channel.erl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index bac106f9..a0624101 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1034,6 +1034,19 @@ handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, drain = Drain}, _, State = #ch{limiter_pid = LimiterPid, consumer_mapping = Consumers}) -> + %% We get Available first because it's likely that as soon as we set + %% the credit msgs will get consumed and it'll be out of date. Why do we + %% want that? Because at least then it's consistent with the credit value + %% we return. And Available is always going to be racy. + Available = case dict:find(CTag, Consumers) of + {ok, QName} -> + case rabbit_amqqueue:with( + QName, fun (Q) -> rabbit_amqqueue:stat(Q) end) of + {ok, Len, _} -> Len; + _ -> -1 + end; + error -> -1 + end, LimiterPid1 = case LimiterPid of undefined -> start_limiter(State); Other -> Other @@ -1045,7 +1058,10 @@ handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, stopped -> unlimit_queues(State) end, State1 = State#ch{limiter_pid = LimiterPid2}, - {noreply, State1}; + return_ok(State1, false, #'basic.credit_state'{consumer_tag = CTag, + credit = Credit, + available = Available, + drain = Drain}); %% TODO port this bit ? %% case consumer_queues(Consumers) of -- cgit v1.2.1 From 988d8dfe9c3bd91b30861062aaaa274400ce8453 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 2 Feb 2011 17:36:48 +0000 Subject: Send a credit-state when magical draining happens. --- src/rabbit_channel.erl | 2 +- src/rabbit_limiter.erl | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a0624101..c4b9daf7 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -462,7 +462,7 @@ check_name(Kind, NameBin = <<"amq.", _/binary>>) -> check_name(_Kind, NameBin) -> NameBin. -%% TODO port this +%% TODO port this(?) queue_blocked(QPid, State = #ch{blocking = Blocking}) -> case dict:find(QPid, Blocking) of error -> State; diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index efe6023b..4a05050f 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -15,6 +15,7 @@ %% -module(rabbit_limiter). +-include("rabbit_framing.hrl"). -behaviour(gen_server2). @@ -217,18 +218,27 @@ limit_reached(CTag, #lim{prefetch_count = Limit, volume = Volume, _ -> false end orelse (Limit =/= 0 andalso Volume >= Limit). -decr_credit(CTag, Len, State = #lim{ credits = Credits } ) -> +decr_credit(CTag, Len, State = #lim{ credits = Credits, ch_pid = ChPid } ) -> case dict:find(CTag, Credits) of {ok, #credit{ credit = Credit, drain = Drain }} -> - NewCredit = case {Len, Drain} of - {1, true} -> 0; - {_, _} -> Credit - 1 + NewCredit = case {Credit, Len, Drain} of + {1, _, _} -> 0; %% Usual reduction to 0 + {_, 1, true} -> send_drained(ChPid, CTag), + 0; %% Magic reduction to 0 + {_, _, _} -> Credit - 1 end, update_credit(CTag, NewCredit, Drain, State); error -> State end. +send_drained(ChPid, CTag) -> + rabbit_channel:send_command(ChPid, + #'basic.credit_state'{consumer_tag = CTag, + credit = 0, + available = 0, + drain = true}). + update_credit(CTag, -1, _Drain, State = #lim{credits = Credits}) -> State#lim{credits = dict:erase(CTag, Credits)}; -- cgit v1.2.1 From 34fe529ddb4d61ac74b6976e3b79e5b3e4928230 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Feb 2011 13:38:11 +0000 Subject: Change mind again on how this should work - have a synchronous reply method, and an async method for spontaneous notification. --- src/rabbit_channel.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index c4b9daf7..139dfd47 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1058,10 +1058,10 @@ handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, stopped -> unlimit_queues(State) end, State1 = State#ch{limiter_pid = LimiterPid2}, - return_ok(State1, false, #'basic.credit_state'{consumer_tag = CTag, - credit = Credit, - available = Available, - drain = Drain}); + return_ok(State1, false, #'basic.credit_ok'{consumer_tag = CTag, + credit = Credit, + available = Available, + drain = Drain}); %% TODO port this bit ? %% case consumer_queues(Consumers) of -- cgit v1.2.1 From 8e1b3d290e6b4d0b20f5eaf51a56195c80da0889 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Feb 2011 15:49:12 +0000 Subject: Remove redundant fields from credit-ok. --- src/rabbit_channel.erl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 139dfd47..f76026d2 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1058,10 +1058,7 @@ handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, stopped -> unlimit_queues(State) end, State1 = State#ch{limiter_pid = LimiterPid2}, - return_ok(State1, false, #'basic.credit_ok'{consumer_tag = CTag, - credit = Credit, - available = Available, - drain = Drain}); + return_ok(State1, false, #'basic.credit_ok'{available = Available}); %% TODO port this bit ? %% case consumer_queues(Consumers) of -- cgit v1.2.1 From 4c8c7fd13676cdf3cb02b15acd4a811bb5b04a14 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 9 Feb 2011 16:27:42 +0000 Subject: Use a base for basic.credit, to account for messages that have been delivered but not received by the client --- src/rabbit_channel.erl | 6 ++++-- src/rabbit_limiter.erl | 58 +++++++++++++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index f76026d2..5b264cc6 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1030,7 +1030,9 @@ handle_method(#'channel.flow'{active = false}, _, {noreply, State1#ch{blocking = dict:from_list(Queues)}} end; -handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, +handle_method(#'basic.credit'{consumer_tag = CTag, + credit = Credit, + count = Count, drain = Drain}, _, State = #ch{limiter_pid = LimiterPid, consumer_mapping = Consumers}) -> @@ -1052,7 +1054,7 @@ handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, Other -> Other end, LimiterPid2 = - case rabbit_limiter:set_credit(LimiterPid1, CTag, Credit, Drain) of + case rabbit_limiter:set_credit(LimiterPid1, CTag, Credit, Count, Drain) of ok -> limit_queues(LimiterPid1, State), LimiterPid1; stopped -> unlimit_queues(State) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 4a05050f..a9c0406f 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -23,7 +23,7 @@ handle_info/2, prioritise_call/3]). -export([start_link/2]). -export([limit/2, can_send/5, ack/2, register/2, unregister/2]). --export([get_limit/1, block/1, unblock/1, set_credit/4, is_blocked/1]). +-export([get_limit/1, block/1, unblock/1, set_credit/5, is_blocked/1]). %%---------------------------------------------------------------------------- @@ -54,7 +54,7 @@ queues = dict:new(), % QPid -> {MonitorRef, Notify} volume = 0}). --record(credit, {credit = 0, drain = false}). +-record(credit, {count = 0, credit = 0, drain = false}). %% '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 @@ -111,10 +111,11 @@ unblock(undefined) -> unblock(LimiterPid) -> gen_server2:call(LimiterPid, unblock, infinity). -set_credit(undefined, _, _, _) -> +set_credit(undefined, _, _, _, _) -> ok; -set_credit(LimiterPid, CTag, Credit, Drain) -> - gen_server2:call(LimiterPid, {set_credit, CTag, Credit, Drain}, infinity). +set_credit(LimiterPid, CTag, Credit, Count, Drain) -> + io:format("Set credit for ~p: credit ~p, count ~p, drain ~p~n", [CTag, Credit, Count, Drain]), + gen_server2:call(LimiterPid, {set_credit, CTag, Credit, Count, Drain}, infinity). is_blocked(undefined) -> false; @@ -161,8 +162,8 @@ handle_call(block, _From, State) -> handle_call(unblock, _From, State) -> maybe_notify_reply(irrelevant, State, State#lim{blocked = false}); -handle_call({set_credit, CTag, Credit, Drain}, _From, State) -> - maybe_notify_reply(CTag, State, update_credit(CTag, Credit, Drain, State)); +handle_call({set_credit, CTag, Credit, Count, Drain}, _From, State) -> + maybe_notify_reply(CTag, State, update_credit(CTag, Credit, Count, Drain, State)); handle_call(is_blocked, _From, State) -> {reply, blocked(State), State}. @@ -218,34 +219,47 @@ limit_reached(CTag, #lim{prefetch_count = Limit, volume = Volume, _ -> false end orelse (Limit =/= 0 andalso Volume >= Limit). -decr_credit(CTag, Len, State = #lim{ credits = Credits, ch_pid = ChPid } ) -> +decr_credit(CTag, Len, State = #lim{ credits = Credits, + ch_pid = ChPid } ) -> case dict:find(CTag, Credits) of - {ok, #credit{ credit = Credit, drain = Drain }} -> - NewCredit = case {Credit, Len, Drain} of - {1, _, _} -> 0; %% Usual reduction to 0 - {_, 1, true} -> send_drained(ChPid, CTag), - 0; %% Magic reduction to 0 - {_, _, _} -> Credit - 1 - end, - update_credit(CTag, NewCredit, Drain, State); + {ok, #credit{ credit = Credit, count = Count, drain = Drain }} -> + {NewCredit, NewCount} = + case {Credit, Len, Drain} of + {1, _, _} -> {0, Count + 1}; %% Usual reduction to 0 + {_, 1, true} -> + NewCount0 = Count + (Credit - 1), + send_drained(ChPid, CTag, NewCount0), + {0, NewCount0}; %% Magic reduction to 0 + {_, _, _} -> {Credit - 1, Count + 1} + end, + update_credit(CTag, NewCredit, NewCount, Drain, State); error -> State end. -send_drained(ChPid, CTag) -> +send_drained(ChPid, CTag, Count) -> rabbit_channel:send_command(ChPid, #'basic.credit_state'{consumer_tag = CTag, credit = 0, + count = Count, available = 0, drain = true}). -update_credit(CTag, -1, _Drain, State = #lim{credits = Credits}) -> +update_credit(CTag, -1, _Count, _Drain, State = #lim{credits = Credits}) -> State#lim{credits = dict:erase(CTag, Credits)}; -update_credit(CTag, Credit, Drain, State = #lim{credits = Credits}) -> - State#lim{credits = dict:store(CTag, - #credit{credit = Credit, drain = Drain}, - Credits)}. +%% Edge case: if the queue has nothing in it, and drain is set, we want to +%% send a credit. +update_credit(CTag, Credit, Count, Drain, State = #lim{credits = Credits}) -> + New = case dict:find(CTag, Credits) of + #credit{ count = OldCount, + credit = OldCredit } = Old -> + Old#credit{ credit = erlang:max( + 0, OldCount + OldCredit - Count), + count = Count, drain = Drain }; + _ -> #credit{ count = Count, credit = Credit, drain = Drain } + end, + State#lim{credits = dict:store(CTag, New, Credits)}. blocked(#lim{blocked = Blocked}) -> Blocked. -- cgit v1.2.1 From 0e9717aaf780e7cc37ff07da68d6c880a32950e4 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 9 Feb 2011 17:03:54 +0000 Subject: Remove io:format --- src/rabbit_limiter.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index a9c0406f..6798f7e5 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -114,7 +114,6 @@ unblock(LimiterPid) -> set_credit(undefined, _, _, _, _) -> ok; set_credit(LimiterPid, CTag, Credit, Count, Drain) -> - io:format("Set credit for ~p: credit ~p, count ~p, drain ~p~n", [CTag, Credit, Count, Drain]), gen_server2:call(LimiterPid, {set_credit, CTag, Credit, Count, Drain}, infinity). is_blocked(undefined) -> -- cgit v1.2.1 From c05eadffe1a8a9141e669bd169b611adb0097f2f Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 9 Feb 2011 17:52:18 +0000 Subject: Separate calculating the credit from updating the state, and do it properly. --- src/rabbit_limiter.erl | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 6798f7e5..a9ecf2e0 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -162,7 +162,7 @@ handle_call(unblock, _From, State) -> maybe_notify_reply(irrelevant, State, State#lim{blocked = false}); handle_call({set_credit, CTag, Credit, Count, Drain}, _From, State) -> - maybe_notify_reply(CTag, State, update_credit(CTag, Credit, Count, Drain, State)); + maybe_notify_reply(CTag, State, reset_credit(CTag, Credit, Count, Drain, State)); handle_call(is_blocked, _From, State) -> {reply, blocked(State), State}. @@ -244,21 +244,31 @@ send_drained(ChPid, CTag, Count) -> available = 0, drain = true}). +%% Assert the credit state. The count may not match ours, in which +%% case we must rebase the credit. +%% TODO Edge case: if the queue has nothing in it, and drain is set, +%% we want to send a basic.credit back. +reset_credit(CTag, Credit0, Count0, Drain, State = #lim{credits = Credits}) -> + Count = + case dict:find(CTag, Credits) of + {ok, #credit{ count = LocalCount }} -> + LocalCount; + _ -> Count0 + end, + %% Our credit may have been reduced while messages are + %% in flight, so we bottom out at 0. + Credit = erlang:max(0, Count0 + Credit0 - Count), + update_credit(CTag, Credit, Count, Drain, State). + +%% Store the credit update_credit(CTag, -1, _Count, _Drain, State = #lim{credits = Credits}) -> State#lim{credits = dict:erase(CTag, Credits)}; -%% Edge case: if the queue has nothing in it, and drain is set, we want to -%% send a credit. update_credit(CTag, Credit, Count, Drain, State = #lim{credits = Credits}) -> - New = case dict:find(CTag, Credits) of - #credit{ count = OldCount, - credit = OldCredit } = Old -> - Old#credit{ credit = erlang:max( - 0, OldCount + OldCredit - Count), - count = Count, drain = Drain }; - _ -> #credit{ count = Count, credit = Credit, drain = Drain } - end, - State#lim{credits = dict:store(CTag, New, Credits)}. + State#lim{credits = dict:store(CTag, + #credit{credit = Credit, + count = Count, + drain = Drain}, Credits)}. blocked(#lim{blocked = Blocked}) -> Blocked. -- cgit v1.2.1 From 2970f6af9cc43ce2b9b44a3b116cb1dfe6b51493 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Fri, 11 Feb 2011 13:54:29 +0000 Subject: Use serial number arithmetic for credit --- src/rabbit_limiter.erl | 15 +++++++++------ src/rabbit_misc.erl | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/rabbit_tests.erl | 26 ++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index a9ecf2e0..50cd2aaa 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -25,6 +25,8 @@ -export([limit/2, can_send/5, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, set_credit/5, is_blocked/1]). +-import(rabbit_misc, [serial_add/2, serial_diff/2]). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -224,12 +226,13 @@ decr_credit(CTag, Len, State = #lim{ credits = Credits, {ok, #credit{ credit = Credit, count = Count, drain = Drain }} -> {NewCredit, NewCount} = case {Credit, Len, Drain} of - {1, _, _} -> {0, Count + 1}; %% Usual reduction to 0 + {1, _, _} -> {0, serial_add(Count, 1)}; {_, 1, true} -> - NewCount0 = Count + (Credit - 1), + %% Drain, so advance til credit = 0 + NewCount0 = serial_add(Count, (Credit - 1)), send_drained(ChPid, CTag, NewCount0), {0, NewCount0}; %% Magic reduction to 0 - {_, _, _} -> {Credit - 1, Count + 1} + {_, _, _} -> {Credit - 1, serial_add(Count, 1)} end, update_credit(CTag, NewCredit, NewCount, Drain, State); error -> @@ -255,9 +258,9 @@ reset_credit(CTag, Credit0, Count0, Drain, State = #lim{credits = Credits}) -> LocalCount; _ -> Count0 end, - %% Our credit may have been reduced while messages are - %% in flight, so we bottom out at 0. - Credit = erlang:max(0, Count0 + Credit0 - Count), + %% Our credit may have been reduced while messages are in flight, + %% so we bottom out at 0. + Credit = erlang:max(0, serial_diff(serial_add(Count0, Credit0), Count)), update_credit(CTag, Credit, Count, Drain, State). %% Store the credit diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 3a4fb024..aef8c22a 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -56,12 +56,14 @@ -export([lock_file/1]). -export([const_ok/1, const/1]). -export([ntoa/1, ntoab/1]). +-export([serial_add/2, serial_compare/2, serial_diff/2]). %%---------------------------------------------------------------------------- -ifdef(use_specs). -export_type([resource_name/0, thunk/1, const/1]). +-export_type([serial_number/0]). -type(ok_or_error() :: rabbit_types:ok_or_error(any())). -type(thunk(T) :: fun(() -> T)). @@ -75,6 +77,8 @@ fun ((atom(), [term()]) -> [{digraph:vertex(), digraph_label()}])). -type(graph_edge_fun() :: fun ((atom(), [term()]) -> [{digraph:vertex(), digraph:vertex()}])). +-type(serial_number() :: non_neg_integer()). +-type(serial_compare_result() :: 'equal' | 'less' | 'greater'). -spec(method_record_type/1 :: (rabbit_framing:amqp_method_record()) -> rabbit_framing:amqp_method_name()). @@ -194,6 +198,12 @@ -spec(const/1 :: (A) -> const(A)). -spec(ntoa/1 :: (inet:ip_address()) -> string()). -spec(ntoab/1 :: (inet:ip_address()) -> string()). +-spec(serial_add/2 :: (serial_number(), non_neg_integer()) -> + serial_number()). +-spec(serial_compare/2 :: (serial_number(), serial_number()) -> + serial_compare_result()). +-spec(serial_diff/2 :: (serial_number(), serial_number()) -> + integer()). -endif. @@ -849,3 +859,44 @@ ntoab(IP) -> 0 -> Str; _ -> "[" ++ Str ++ "]" end. + +%% Serial arithmetic for unsigned ints. +%% http://www.faqs.org/rfcs/rfc1982.html +%% SERIAL_BITS = 32 + +%% 2 ^ SERIAL_BITS +-define(SERIAL_MAX, 16#100000000). +%% 2 ^ (SERIAL_BITS - 1) - 1 +-define(SERIAL_MAX_ADDEND, 16#7fffffff). + +serial_add(S, N) when N =< ?SERIAL_MAX_ADDEND -> + (S + N) rem ?SERIAL_MAX; +serial_add(S, N) -> + exit({out_of_bound_serial_addition, S, N}). + +serial_compare(A, B) -> + if A =:= B -> + equal; + (A < B andalso B - A < ?SERIAL_MAX_ADDEND) orelse + (A > B andalso A - B > ?SERIAL_MAX_ADDEND) -> + less; + (A < B andalso B - A > ?SERIAL_MAX_ADDEND) orelse + (A > B andalso B - A < ?SERIAL_MAX_ADDEND) -> + greater; + true -> exit({indeterminate_serial_comparison, A, B}) + end. + +-define(SERIAL_DIFF_BOUND, 16#80000000). + +serial_diff(A, B) -> + Diff = A - B, + if Diff > (?SERIAL_DIFF_BOUND) -> + %% B is actually greater than A + - (?SERIAL_MAX - Diff); + Diff < - (?SERIAL_DIFF_BOUND) -> + ?SERIAL_MAX + Diff; + Diff < ?SERIAL_DIFF_BOUND andalso Diff > -?SERIAL_DIFF_BOUND -> + Diff; + true -> + exit({indeterminate_serial_diff, A, B}) + end. diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 49b09508..925649b8 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -18,7 +18,7 @@ -compile([export_all]). --export([all_tests/0, test_parsing/0]). +-export([all_tests/0, test_parsing/0, test_serial_arithmetic/0]). -include("rabbit.hrl"). -include("rabbit_framing.hrl"). @@ -46,6 +46,7 @@ all_tests() -> passed = test_parsing(), passed = test_content_framing(), passed = test_content_transcoding(), + passed = test_serial_arithmetic(), passed = test_topic_matching(), passed = test_log_management(), passed = test_app_management(), @@ -580,6 +581,29 @@ sequence_with_content(Sequence) -> rabbit_framing_amqp_0_9_1), Sequence). +test_serial_arithmetic() -> + 1 = rabbit_misc:serial_add(0, 1), + 16#7fffffff = rabbit_misc:serial_add(0, 16#7fffffff), + 0 = rabbit_misc:serial_add(16#ffffffff, 1), + %% Cannot add more than 2 ^ 31 - 1 + case catch rabbit_misc:serial_add(200, 16#80000000) of + {'EXIT', {out_of_bound_serial_addition, _, _}} -> ok; + _ -> exit(fail_out_of_bound_serial_addition) + end, + + 1 = rabbit_misc:serial_diff(1, 0), + 2 = rabbit_misc:serial_diff(1, 16#ffffffff), + -2 = rabbit_misc:serial_diff(16#ffffffff, 1), + case catch rabbit_misc:serial_diff(0, 16#80000000) of + {'EXIT', {indeterminate_serial_diff, _, _}} -> ok; + _ -> exit(fail_indeterminate_serial_difference) + end, + case catch rabbit_misc:serial_diff(16#ffffffff, 16#7fffffff) of + {'EXIT', {indeterminate_serial_diff, _, _}} -> ok; + _ -> exit(fail_indeterminate_serial_difference) + end, + passed. + test_topic_match(P, R) -> test_topic_match(P, R, true). -- cgit v1.2.1 From 30ae7c070be46821bac4bc1df6fe1da4ad569da5 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 25 Jul 2011 11:20:23 +0100 Subject: ...and unbreak. --- src/rabbit_channel.erl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 2f6cccd4..f441adc8 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1111,13 +1111,11 @@ handle_method(#'basic.credit'{consumer_tag = CTag, %% want that? Because at least then it's consistent with the credit value %% we return. And Available is always going to be racy. Available = case dict:find(CTag, Consumers) of - {ok, QName} -> - case rabbit_amqqueue:with( - QName, fun (Q) -> rabbit_amqqueue:stat(Q) end) of - {ok, Len, _} -> Len; - _ -> -1 - end; - error -> -1 + {ok, {Q, _}} -> case rabbit_amqqueue:stat(Q) of + {ok, Len, _} -> Len; + _ -> -1 + end; + error -> -1 %% TODO these -1s smell very iffy! end, LimiterPid1 = case LimiterPid of undefined -> start_limiter(State); -- cgit v1.2.1 From 767fc91fecc0ac98e9d6dd87257f4af18f138dfb Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 25 Jul 2011 11:27:42 +0100 Subject: Face down, nine-edge first. --- src/rabbit_channel.erl | 3 ++- src/rabbit_limiter.erl | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index f441adc8..0610af65 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1122,7 +1122,8 @@ handle_method(#'basic.credit'{consumer_tag = CTag, Other -> Other end, LimiterPid2 = - case rabbit_limiter:set_credit(LimiterPid1, CTag, Credit, Count, Drain) of + case rabbit_limiter:set_credit( + LimiterPid1, CTag, Credit, Count, Drain) of ok -> limit_queues(LimiterPid1, State), LimiterPid1; stopped -> unlimit_queues(State) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 7729a10a..81f973e4 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -36,7 +36,9 @@ -spec(start_link/2 :: (pid(), non_neg_integer()) -> rabbit_types:ok_pid_or_error()). -spec(limit/2 :: (maybe_pid(), non_neg_integer()) -> 'ok' | 'stopped'). --spec(can_send/5 :: (maybe_pid(), pid(), boolean(), binary(), non_neg_integer()) -> boolean()). +-spec(can_send/5 :: + (maybe_pid(), pid(), boolean(), binary(), non_neg_integer()) + -> boolean()). -spec(ack/2 :: (maybe_pid(), binary()) -> 'ok'). -spec(register/2 :: (maybe_pid(), pid()) -> 'ok'). -spec(unregister/2 :: (maybe_pid(), pid()) -> 'ok'). @@ -116,7 +118,8 @@ unblock(LimiterPid) -> set_credit(undefined, _, _, _, _) -> ok; set_credit(LimiterPid, CTag, Credit, Count, Drain) -> - gen_server2:call(LimiterPid, {set_credit, CTag, Credit, Count, Drain}, infinity). + gen_server2:call( + LimiterPid, {set_credit, CTag, Credit, Count, Drain}, infinity). is_blocked(undefined) -> false; @@ -164,7 +167,8 @@ handle_call(unblock, _From, State) -> maybe_notify_reply(irrelevant, State, State#lim{blocked = false}); handle_call({set_credit, CTag, Credit, Count, Drain}, _From, State) -> - maybe_notify_reply(CTag, State, reset_credit(CTag, Credit, Count, Drain, State)); + maybe_notify_reply(CTag, State, + reset_credit(CTag, Credit, Count, Drain, State)); handle_call(is_blocked, _From, State) -> {reply, blocked(State), State}. -- cgit v1.2.1 From 5a4b32e1ccf37570973097086d4a07b6958207d1 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 25 Jul 2011 11:38:18 +0100 Subject: Remove some dead TODOs from when this was very young. --- src/rabbit_channel.erl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 0610af65..7f0f0002 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -501,7 +501,6 @@ check_name(Kind, NameBin = <<"amq.", _/binary>>) -> check_name(_Kind, NameBin) -> NameBin. -%% TODO port this(?) queue_blocked(QPid, State = #ch{blocking = Blocking}) -> case dict:find(QPid, Blocking) of error -> State; @@ -1131,15 +1130,6 @@ handle_method(#'basic.credit'{consumer_tag = CTag, State1 = State#ch{limiter_pid = LimiterPid2}, return_ok(State1, false, #'basic.credit_ok'{available = Available}); - %% TODO port this bit ? - %% case consumer_queues(Consumers) of - %% [] -> {reply, #'channel.flow_ok'{active = false}, State1}; - %% QPids -> Queues = [{QPid, erlang:monitor(process, QPid)} || - %% QPid <- QPids], - %% ok = rabbit_amqqueue:flush_all(QPids, self()), - %% {noreply, State1#ch{blocking = dict:from_list(Queues)}} - %% end; - handle_method(_MethodRecord, _Content, _State) -> rabbit_misc:protocol_error( command_invalid, "unimplemented method", []). -- cgit v1.2.1 From e29f550382dea13acce9d7c5e503ef0a8d67f722 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 24 Aug 2011 15:59:49 +0100 Subject: Rectify some poor conflict resolution choices. --- src/rabbit_amqqueue_process.erl | 9 +++++++-- src/rabbit_limiter.erl | 13 +++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 2ca3c572..8333b753 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -369,7 +369,9 @@ ch_record_state_transition(OldCR, NewCR) -> deliver_msgs_to_consumers(Funs = {PredFun, DeliverFun}, FunAcc, State = #q{q = #amqqueue{name = QName}, active_consumers = ActiveConsumers, - blocked_consumers = BlockedConsumers}) -> + blocked_consumers = BlockedConsumers, + backing_queue = BQ, + backing_queue_state = BQS}) -> case queue:out(ActiveConsumers) of {{value, QEntry = {ChPid, #consumer{tag = ConsumerTag, ack_required = AckRequired}}}, @@ -379,7 +381,9 @@ deliver_msgs_to_consumers(Funs = {PredFun, DeliverFun}, FunAcc, acktags = ChAckTags} = ch_record(ChPid), IsMsgReady = PredFun(FunAcc, State), case (IsMsgReady andalso - rabbit_limiter:can_send(Limiter, self(), AckRequired)) of + rabbit_limiter:can_send(Limiter, self(), + AckRequired, ConsumerTag, + BQ:len(BQS))) of true -> {{Message, IsDelivered, AckTag}, FunAcc1, State1} = DeliverFun(AckRequired, FunAcc, State), @@ -1117,6 +1121,7 @@ handle_cast({limit, ChPid, Limiter}, State) -> andalso rabbit_limiter:is_blocked(Limiter), C#cr{limiter = Limiter, is_limit_active = Limited} end)); + handle_cast({flush, ChPid}, State) -> ok = rabbit_channel:flushed(ChPid, self()), noreply(State); diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index c219eaec..5bc20636 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -24,7 +24,7 @@ -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). --export([limit/2, can_send/3, ack/2, register/2, unregister/2]). +-export([limit/2, can_send/5, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). -export([set_credit/5]). @@ -47,7 +47,7 @@ -spec(enable/2 :: (token(), non_neg_integer()) -> token()). -spec(disable/1 :: (token()) -> token()). -spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). --spec(can_send/3 :: (token(), pid(), boolean()) -> boolean()). +-spec(can_send/3 :: (token(), pid(), boolean(), ) -> boolean()). -spec(ack/2 :: (token(), non_neg_integer()) -> 'ok'). -spec(register/2 :: (token(), pid()) -> 'ok'). -spec(unregister/2 :: (token(), pid()) -> 'ok'). @@ -94,18 +94,19 @@ limit(Limiter, PrefetchCount) -> %% breaching a limit. Note that we don't use maybe_call here in order %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. -can_send(#token{pid = Pid, enabled = true}, QPid, AckRequired) -> +can_send(#token{pid = Pid, enabled = true}, QPid, AckRequired, CTag, Len) -> rabbit_misc:with_exit_handler( fun () -> true end, fun () -> - gen_server2:call(Pid, {can_send, QPid, AckRequired}, infinity) + gen_server2:call(Pid, {can_send, QPid, AckRequired, CTag, Len}, + infinity) end); -can_send(_, _, _) -> +can_send(_, _, _, _, _) -> true. %% Let the limiter know that the channel has received some acks from a %% consumer -ack(Limiter, Count) -> maybe_cast(Limiter, {ack, Count}). +ack(Limiter, CTag) -> maybe_cast(Limiter, {ack, CTag}). register(Limiter, QPid) -> maybe_cast(Limiter, {register, QPid}). -- cgit v1.2.1 From 955e20aea7d75e6be6e10db0ad05c58a64fe70ea Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 24 Aug 2011 16:32:57 +0100 Subject: And correct specs --- 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 5bc20636..f102c3b9 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -47,7 +47,8 @@ -spec(enable/2 :: (token(), non_neg_integer()) -> token()). -spec(disable/1 :: (token()) -> token()). -spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). --spec(can_send/3 :: (token(), pid(), boolean(), ) -> boolean()). +-spec(can_send/5 :: (token(), pid(), boolean(), + rabbit_types:ctag(), non_neg_integer()) -> boolean()). -spec(ack/2 :: (token(), non_neg_integer()) -> 'ok'). -spec(register/2 :: (token(), pid()) -> 'ok'). -spec(unregister/2 :: (token(), pid()) -> 'ok'). @@ -55,7 +56,9 @@ -spec(block/1 :: (token()) -> 'ok'). -spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). -spec(is_blocked/1 :: (token()) -> boolean()). -%% Missing: set_credit +-spec(set_credit/5 :: (token(), rabbit_types:ctag(), + non_neg_integer(), + non_neg_integer(), boolean()) -> 'ok'). -endif. -- cgit v1.2.1 From 4265c5ab338940d3a0e19583c2f90660a96da3bb Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 6 Jun 2012 18:03:59 +0100 Subject: Move banner things from stdout to logs. Leave a vestigal banner so that people who have started Rabbit by hand have something to look at. --- src/rabbit.erl | 96 ++++++++++++++++++++++++---------------------------------- 1 file changed, 40 insertions(+), 56 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index fc5d4e93..30776387 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -252,6 +252,9 @@ %%---------------------------------------------------------------------------- +%% HiPE compilation happens before we have log handlers - so we have +%% to io:format/2, it's all we can do. + maybe_hipe_compile() -> {ok, Want} = application:get_env(rabbit, hipe_compile), Can = code:which(hipe) =/= non_existing, @@ -302,7 +305,7 @@ start() -> ok = ensure_application_loaded(), ok = ensure_working_log_handlers(), ok = app_utils:start_applications(app_startup_order()), - ok = print_plugin_info(rabbit_plugins:active()) + ok = log_broker_started(rabbit_plugins:active()) end). boot() -> @@ -317,7 +320,7 @@ boot() -> StartupApps = app_utils:app_dependency_order(ToBeLoaded, false), ok = app_utils:start_applications(StartupApps), - ok = print_plugin_info(Plugins) + ok = log_broker_started(Plugins) end). start_it(StartFun) -> @@ -402,8 +405,8 @@ start(normal, []) -> {ok, SupPid} = rabbit_sup:start_link(), true = register(rabbit, self()), print_banner(), + log_banner(), [ok = run_boot_step(Step) || Step <- boot_steps()], - io:format("~nbroker running~n"), {ok, SupPid}; Error -> Error @@ -433,22 +436,16 @@ app_shutdown_order() -> %%--------------------------------------------------------------------------- %% boot step logic -run_boot_step({StepName, Attributes}) -> - Description = case lists:keysearch(description, 1, Attributes) of - {value, {_, D}} -> D; - false -> StepName - end, +run_boot_step({_StepName, Attributes}) -> case [MFA || {mfa, MFA} <- Attributes] of [] -> - io:format("-- ~s~n", [Description]); + ok; MFAs -> - io:format("starting ~-60s ...", [Description]), [try apply(M,F,A) catch _:Reason -> boot_step_error(Reason, erlang:get_stacktrace()) end || {M,F,A} <- MFAs], - io:format("done~n"), ok end. @@ -646,23 +643,13 @@ force_event_refresh() -> %%--------------------------------------------------------------------------- %% misc -print_plugin_info([]) -> - ok; -print_plugin_info(Plugins) -> - %% This gets invoked by rabbitmqctl start_app, outside the context - %% of the rabbit application - rabbit_misc:with_local_io( - fun() -> - io:format("~n-- plugins running~n"), - [print_plugin_info( - AppName, element(2, application:get_key(AppName, vsn))) - || AppName <- Plugins], - ok - end). - -print_plugin_info(Plugin, Vsn) -> - Len = 76 - length(Vsn), - io:format("~-" ++ integer_to_list(Len) ++ "s ~s~n", [Plugin, Vsn]). +log_broker_started([]) -> + error_logger:info_msg("Server startup complete~n", []), + io:format("~nBroker running~n"); +log_broker_started(Plugins) -> + error_logger:info_msg("Server startup complete; plugins are:~n~n~p~n", + [Plugins]), + io:format("~nBroker running with ~p plugins.~n", [length(Plugins)]). erts_version_check() -> FoundVer = erlang:system_info(version), @@ -675,23 +662,14 @@ erts_version_check() -> print_banner() -> {ok, Product} = application:get_key(id), {ok, Version} = application:get_key(vsn), - ProductLen = string:len(Product), - io:format("~n" - "+---+ +---+~n" - "| | | |~n" - "| | | |~n" - "| | | |~n" - "| +---+ +-------+~n" - "| |~n" - "| ~s +---+ |~n" - "| | | |~n" - "| ~s +---+ |~n" - "| |~n" - "+-------------------+~n" - "~s~n~s~n~s~n~n", - [Product, string:right([$v|Version], ProductLen), - ?PROTOCOL_VERSION, - ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), + io:format("~n~s ~s. ~s~n~s~n~n", + [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), + io:format("Logs: ~s~n ~s~n", [log_location(kernel), + log_location(sasl)]). + +log_banner() -> + {ok, Product} = application:get_key(id), + {ok, Version} = application:get_key(vsn), Settings = [{"node", node()}, {"app descriptor", app_location()}, {"home dir", home_dir()}, @@ -700,20 +678,26 @@ print_banner() -> {"log", log_location(kernel)}, {"sasl log", log_location(sasl)}, {"database dir", rabbit_mnesia:dir()}, - {"erlang version", erlang:system_info(version)}], + {"erlang version", erlang:system_info(otp_release)}], DescrLen = 1 + lists:max([length(K) || {K, _V} <- Settings]), Format = fun (K, V) -> - io:format("~-" ++ integer_to_list(DescrLen) ++ "s: ~s~n", - [K, V]) + rabbit_misc:format( + "~-" ++ integer_to_list(DescrLen) ++ "s: ~s~n", [K, V]) end, - lists:foreach(fun ({"config file(s)" = K, []}) -> - Format(K, "(none)"); - ({"config file(s)" = K, [V0 | Vs]}) -> - Format(K, V0), [Format("", V) || V <- Vs]; - ({K, V}) -> - Format(K, V) - end, Settings), - io:nl(). + Banner = iolist_to_binary( + rabbit_misc:format( + "~s ~s~n~s~n~s~n~s~n", + [Product, Version, ?PROTOCOL_VERSION, ?COPYRIGHT_MESSAGE, + ?INFORMATION_MESSAGE]) ++ + [case S of + {"config file(s)" = K, []} -> + Format(K, "(none)"); + {"config file(s)" = K, [V0 | Vs]} -> + Format(K, V0), [Format("", V) || V <- Vs]; + {K, V} -> + Format(K, V) + end || S <- Settings]), + error_logger:info_msg("~s~n", [Banner]). app_location() -> {ok, Application} = application:get_application(), -- cgit v1.2.1 From d3cd3d7aaadbb33ef19d673d52d20d69da341242 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 6 Jun 2012 18:12:15 +0100 Subject: Oops --- src/rabbit.erl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 30776387..024bfdf6 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -644,12 +644,19 @@ force_event_refresh() -> %% misc log_broker_started([]) -> - error_logger:info_msg("Server startup complete~n", []), - io:format("~nBroker running~n"); + rabbit_misc:with_local_io( + fun() -> + error_logger:info_msg("Server startup complete~n", []), + io:format("~nBroker running~n") + end); log_broker_started(Plugins) -> - error_logger:info_msg("Server startup complete; plugins are:~n~n~p~n", - [Plugins]), - io:format("~nBroker running with ~p plugins.~n", [length(Plugins)]). + rabbit_misc:with_local_io( + fun() -> + error_logger:info_msg( + "Server startup complete; plugins are:~n~n~p~n", [Plugins]), + io:format("~nBroker running with ~p plugins.~n", + [length(Plugins)]) + end). erts_version_check() -> FoundVer = erlang:system_info(version), -- cgit v1.2.1 From 415cf35c55afc0c8d89eeb8f7e595aa30f8f6a44 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 6 Jun 2012 18:14:06 +0100 Subject: I never saw the point of that. --- src/rabbit.erl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 024bfdf6..dca0b3d7 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -678,7 +678,6 @@ log_banner() -> {ok, Product} = application:get_key(id), {ok, Version} = application:get_key(vsn), Settings = [{"node", node()}, - {"app descriptor", app_location()}, {"home dir", home_dir()}, {"config file(s)", config_files()}, {"cookie hash", rabbit_nodes:cookie_hash()}, @@ -706,10 +705,6 @@ log_banner() -> end || S <- Settings]), error_logger:info_msg("~s~n", [Banner]). -app_location() -> - {ok, Application} = application:get_application(), - filename:absname(code:where_is_file(atom_to_list(Application) ++ ".app")). - home_dir() -> case init:get_argument(home) of {ok, [[Home]]} -> Home; -- cgit v1.2.1 From 589e6181c4e0d4ffe4ec91685e6e6ee318ff4e4a Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 24 Oct 2012 17:32:09 +0100 Subject: re-order record definitions so they match the OTP supervisor --- src/supervisor2.erl | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 5af38573..a4e21e47 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -85,6 +85,20 @@ -export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]). -export([handle_cast/2]). +%%-------------------------------------------------------------------------- +%% Records - here we differ from supervisor.erl in that we do not +%% embed type specifications directly in our records, so that -D use_specs +%% can be used to turn this off for older versions of Erlang +%%-------------------------------------------------------------------------- + +-record(child, {pid = undefined, % pid is undefined when child is not running + name, + mfa, + restart_type, + shutdown, + child_type, + modules = []}). + -define(DICT, dict). -record(state, {name, @@ -97,14 +111,6 @@ module, args}). --record(child, {pid = undefined, % pid is undefined when child is not running - name, - mfa, - restart_type, - shutdown, - child_type, - modules = []}). - -define(is_simple(State), State#state.strategy =:= simple_one_for_one orelse State#state.strategy =:= simple_one_for_one_terminate). -define(is_terminate_simple(State), -- cgit v1.2.1 From 481f29b1d5e98568de58e007dfd8b6c075cc74e0 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 26 Oct 2012 10:30:59 +0100 Subject: re-order things a bit and pretend use_specs doesn't exist for now - closer diff with otp supervisor --- src/supervisor2.erl | 209 +++++++++++++++++++++++----------------------------- 1 file changed, 94 insertions(+), 115 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index a4e21e47..99c41aa6 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -86,44 +86,11 @@ -export([handle_cast/2]). %%-------------------------------------------------------------------------- -%% Records - here we differ from supervisor.erl in that we do not -%% embed type specifications directly in our records, so that -D use_specs -%% can be used to turn this off for older versions of Erlang -%%-------------------------------------------------------------------------- - --record(child, {pid = undefined, % pid is undefined when child is not running - name, - mfa, - restart_type, - shutdown, - child_type, - modules = []}). --define(DICT, dict). - --record(state, {name, - strategy, - children = [], - dynamics = ?DICT:new(), - intensity, - period, - restarts = [], - module, - args}). +-export_type([child_spec/0, startchild_ret/0, strategy/0]). --define(is_simple(State), State#state.strategy =:= simple_one_for_one orelse - State#state.strategy =:= simple_one_for_one_terminate). --define(is_terminate_simple(State), - State#state.strategy =:= simple_one_for_one_terminate). - --ifdef(use_specs). - -%%-------------------------------------------------------------------------- -%% Types %%-------------------------------------------------------------------------- --export_type([child_spec/0, startchild_ret/0, strategy/0, sup_name/0]). - -type child() :: 'undefined' | pid(). -type child_id() :: term(). -type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. @@ -146,11 +113,19 @@ Type :: worker(), Modules :: modules()}. - -type strategy() :: 'one_for_all' | 'one_for_one' | 'rest_for_one' | 'simple_one_for_one' | 'simple_one_for_one_terminate'. +%%-------------------------------------------------------------------------- + +-record(child, {pid = undefined, % pid is undefined when child is not running + name, + mfa, + restart_type, + shutdown, + child_type, + modules = []}). -type child_rec() :: #child{pid :: child() | {restarting,pid()} | [pid()], name :: child_id(), mfa :: mfargs(), @@ -159,15 +134,28 @@ child_type :: worker(), modules :: modules()}. +-define(DICT, dict). + +-record(state, {name, + strategy, + children = [], + dynamics = ?DICT:new(), + intensity, + period, + restarts = [], + module, + args}). -type state() :: #state{strategy :: strategy(), children :: [child_rec()], dynamics :: ?DICT(), intensity :: non_neg_integer(), period :: pos_integer()}. -%%-------------------------------------------------------------------------- -%% Callback behaviour -%%-------------------------------------------------------------------------- +-define(is_simple(State), State#state.strategy =:= simple_one_for_one orelse + State#state.strategy =:= simple_one_for_one_terminate). + +-define(is_terminate_simple(State), + State#state.strategy =:= simple_one_for_one_terminate). -callback init(Args :: term()) -> {ok, {{RestartStrategy :: strategy(), @@ -176,9 +164,21 @@ [ChildSpec :: child_spec()]}} | ignore. -%%-------------------------------------------------------------------------- -%% Specs -%%-------------------------------------------------------------------------- +%%% --------------------------------------------------- +%%% This is a general process supervisor built upon gen_server.erl. +%%% Servers/processes should/could also be built using gen_server.erl. +%%% SupName = {local, atom()} | {global, atom()}. +%%% --------------------------------------------------- + +start_link(Mod, Args) -> + gen_server:start_link(?MODULE, {self, Mod, Args}, []). + +start_link(SupName, Mod, Args) -> + gen_server:start_link(SupName, ?MODULE, {SupName, Mod, Args}, []). + +%%% --------------------------------------------------- +%%% Interface functions. +%%% --------------------------------------------------- -type startchild_err() :: 'already_present' | {'already_started', Child :: child()} | term(). @@ -189,6 +189,8 @@ -spec start_child(SupRef, ChildSpec) -> startchild_ret() when SupRef :: sup_ref(), ChildSpec :: child_spec() | (List :: [term()]). +start_child(Supervisor, ChildSpec) -> + call(Supervisor, {start_child, ChildSpec}). -spec restart_child(SupRef, Id) -> Result when SupRef :: sup_ref(), @@ -197,18 +199,32 @@ | {'ok', Child :: child(), Info :: term()} | {'error', Error}, Error :: 'running' | 'not_found' | 'simple_one_for_one' | term(). +restart_child(Supervisor, Name) -> + call(Supervisor, {restart_child, Name}). -spec delete_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: child_id(), Result :: 'ok' | {'error', Error}, Error :: 'running' | 'not_found' | 'simple_one_for_one'. +delete_child(Supervisor, Name) -> + call(Supervisor, {delete_child, Name}). + +%%----------------------------------------------------------------- +%% Func: terminate_child/2 +%% Returns: ok | {error, Reason} +%% Note that the child is *always* terminated in some +%% way (maybe killed). +%%----------------------------------------------------------------- -spec terminate_child(SupRef, Id) -> Result when SupRef :: sup_ref(), + Id :: pid() | child_id(), Result :: 'ok' | {'error', Error}, Error :: 'not_found' | 'simple_one_for_one'. +terminate_child(Supervisor, Name) -> + call(Supervisor, {terminate_child, Name}). -spec which_children(SupRef) -> [{Id,Child,Type,Modules}] when SupRef :: sup_ref(), @@ -216,86 +232,16 @@ Child :: child(), Type :: worker(), Modules :: modules(). - --spec check_childspecs(ChildSpecs) -> Result when - ChildSpecs :: [child_spec()], - Result :: 'ok' | {'error', Error :: term()}. - --type init_sup_name() :: sup_name() | 'self'. - --type stop_rsn() :: 'shutdown' | {'bad_return', {module(),'init', term()}} - | {'bad_start_spec', term()} | {'start_spec', term()} - | {'supervisor_data', term()}. - --spec init({init_sup_name(), module(), [term()]}) -> - {'ok', state()} | 'ignore' | {'stop', stop_rsn()}. - --type call() :: 'which_children'. --spec handle_call(call(), term(), state()) -> {'reply', term(), state()}. - --spec handle_cast('null', state()) -> {'noreply', state()}. - --spec handle_info(term(), state()) -> - {'noreply', state()} | {'stop', 'shutdown', state()}. - --spec terminate(term(), state()) -> 'ok'. - --spec code_change(term(), state(), term()) -> - {'ok', state()} | {'error', term()}. - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{init,1}]; -behaviour_info(_Other) -> - undefined. - --endif. - -%%% --------------------------------------------------- -%%% This is a general process supervisor built upon gen_server.erl. -%%% Servers/processes should/could also be built using gen_server.erl. -%%% SupName = {local, atom()} | {global, atom()}. -%%% --------------------------------------------------- -start_link(Mod, Args) -> - gen_server:start_link(?MODULE, {self, Mod, Args}, []). - -start_link(SupName, Mod, Args) -> - gen_server:start_link(SupName, ?MODULE, {SupName, Mod, Args}, []). - -%%% --------------------------------------------------- -%%% Interface functions. -%%% --------------------------------------------------- -start_child(Supervisor, ChildSpec) -> - call(Supervisor, {start_child, ChildSpec}). - -restart_child(Supervisor, Name) -> - call(Supervisor, {restart_child, Name}). - -delete_child(Supervisor, Name) -> - call(Supervisor, {delete_child, Name}). - -%%----------------------------------------------------------------- -%% Func: terminate_child/2 -%% Returns: ok | {error, Reason} -%% Note that the child is *always* terminated in some -%% way (maybe killed). -%%----------------------------------------------------------------- -terminate_child(Supervisor, Name) -> - call(Supervisor, {terminate_child, Name}). - which_children(Supervisor) -> call(Supervisor, which_children). -find_child(Supervisor, Name) -> - [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), - Name1 =:= Name]. - call(Supervisor, Req) -> gen_server:call(Supervisor, Req, infinity). +-spec check_childspecs(ChildSpecs) -> Result when + ChildSpecs :: [child_spec()], + Result :: 'ok' | {'error', Error :: term()}. + check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> case check_startspec(ChildSpecs) of {ok, _} -> ok; @@ -303,11 +249,32 @@ check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> end; check_childspecs(X) -> {error, {badarg, X}}. +find_child(Supervisor, Name) -> + [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), + Name1 =:= Name]. + +%-export([behaviour_info/1]). + +%behaviour_info(callbacks) -> +% [{init,1}]; +%behaviour_info(_Other) -> +% undefined. + %%% --------------------------------------------------- %%% %%% Initialize the supervisor. %%% %%% --------------------------------------------------- + +-type init_sup_name() :: sup_name() | 'self'. + +-type stop_rsn() :: 'shutdown' | {'bad_return', {module(),'init', term()}} + | {'bad_start_spec', term()} | {'start_spec', term()} + | {'supervisor_data', term()}. + +-spec init({init_sup_name(), module(), [term()]}) -> + {'ok', state()} | 'ignore' | {'stop', stop_rsn()}. + init({SupName, Mod, Args}) -> process_flag(trap_exit, true), case Mod:init(Args) of @@ -413,6 +380,9 @@ do_start_child_i(M, F, A) -> %%% Callback functions. %%% %%% --------------------------------------------------- +-type call() :: 'which_children'. +-spec handle_call(call(), term(), state()) -> {'reply', term(), state()}. + handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> #child{mfa = {M, F, A}} = hd(State#state.children), Args = A ++ EArgs, @@ -500,6 +470,7 @@ handle_call(which_children, _From, State) -> State#state.children), {reply, Resp, State}. +-spec handle_cast('null', state()) -> {'noreply', state()}. %%% Hopefully cause a function-clause as there is no API function %%% that utilizes cast. handle_cast(null, State) -> @@ -508,6 +479,9 @@ handle_cast(null, State) -> {noreply, State}. +-spec handle_info(term(), state()) -> + {'noreply', state()} | {'stop', 'shutdown', state()}. + handle_info({delayed_restart, {RestartType, Reason, Child}}, State) when ?is_simple(State) -> {ok, NState} = do_restart(RestartType, Reason, Child, State), @@ -539,6 +513,8 @@ handle_info(Msg, State) -> %% %% Terminate this server. %% +-spec terminate(term(), state()) -> 'ok'. + terminate(_Reason, State) when ?is_terminate_simple(State) -> terminate_simple_children( hd(State#state.children), State#state.dynamics, State#state.name), @@ -556,6 +532,9 @@ terminate(_Reason, State) -> %% NOTE: This requires that the init function of the call-back module %% does not have any side effects. %% +-spec code_change(term(), state(), term()) -> + {'ok', state()} | {'error', term()}. + code_change(_, State, _) -> case (State#state.module):init(State#state.args) of {ok, {SupFlags, StartSpec}} -> -- cgit v1.2.1 From 6a092c9622facf5076d191f35ee856f1c48d3ce4 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 26 Oct 2012 11:10:29 +0100 Subject: (un)cosmetic - reduce the diff --- src/supervisor2.erl | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 99c41aa6..2252c4b5 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -102,7 +102,7 @@ -type shutdown() :: 'brutal_kill' | timeout(). -type worker() :: 'worker' | 'supervisor'. -type sup_name() :: {'local', Name :: atom()} | {'global', Name :: atom()}. --type sup_ref() :: (Name :: atom()) +-type sup_ref() :: (Name :: atom()) | {Name :: atom(), Node :: node()} | {'global', Name :: atom()} | pid(). @@ -119,8 +119,9 @@ %%-------------------------------------------------------------------------- --record(child, {pid = undefined, % pid is undefined when child is not running - name, +-record(child, {% pid is undefined when child is not running + pid = undefined, + name, mfa, restart_type, shutdown, @@ -592,7 +593,7 @@ update_childspec1([], Children, KeepOld) -> lists:reverse(Children ++ KeepOld). update_chsp(OldCh, Children) -> - case lists:map(fun (Ch) when OldCh#child.name =:= Ch#child.name -> + case lists:map(fun(Ch) when OldCh#child.name =:= Ch#child.name -> Ch#child{pid = OldCh#child.pid}; (Ch) -> Ch @@ -603,7 +604,7 @@ update_chsp(OldCh, Children) -> NewC -> {ok, NewC} end. - + %%% --------------------------------------------------- %%% Start a new child. %%% --------------------------------------------------- @@ -893,7 +894,7 @@ shutdown(Pid, brutal_kill) -> {'DOWN', _MRef, process, Pid, OtherReason} -> {error, OtherReason} end; - {error, Reason} -> + {error, Reason} -> {error, Reason} end; @@ -902,7 +903,7 @@ shutdown(Pid, Time) -> case monitor_child(Pid) of ok -> exit(Pid, shutdown), %% Try to shutdown gracefully - receive + receive {'DOWN', _MRef, process, Pid, shutdown} -> ok; {'DOWN', _MRef, process, Pid, OtherReason} -> @@ -914,14 +915,14 @@ shutdown(Pid, Time) -> {error, OtherReason} end end; - {error, Reason} -> + {error, Reason} -> {error, Reason} end. %% Help function to shutdown/2 switches from link to monitor approach monitor_child(Pid) -> - - %% Do the monitor operation first so that if the child dies + + %% Do the monitor operation first so that if the child dies %% before the monitoring is done causing a 'DOWN'-message with %% reason noproc, we will get the real reason in the 'EXIT'-message %% unless a naughty child has already done unlink... @@ -931,19 +932,19 @@ monitor_child(Pid) -> receive %% If the child dies before the unlik we must empty %% the mail-box of the 'EXIT'-message and the 'DOWN'-message. - {'EXIT', Pid, Reason} -> - receive + {'EXIT', Pid, Reason} -> + receive {'DOWN', _, process, Pid, _} -> {error, Reason} end - after 0 -> + after 0 -> %% If a naughty child did unlink and the child dies before %% monitor the result will be that shutdown/2 receives a %% 'DOWN'-message with reason noproc. %% If the child should die after the unlink there %% will be a 'DOWN'-message with a correct reason - %% that will be handled in shutdown/2. - ok + %% that will be handled in shutdown/2. + ok end. @@ -1021,11 +1022,11 @@ init_state1(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) -> validIntensity(MaxIntensity), validPeriod(Period), {ok, #state{name = supname(SupName,Mod), - strategy = Strategy, - intensity = MaxIntensity, - period = Period, - module = Mod, - args = Args}}; + strategy = Strategy, + intensity = MaxIntensity, + period = Period, + module = Mod, + args = Args}}; init_state1(_SupName, Type, _, _) -> {invalid_type, Type}. @@ -1038,14 +1039,14 @@ validStrategy(What) -> throw({invalid_strategy, What}). validIntensity(Max) when is_integer(Max), Max >= 0 -> true; -validIntensity(What) -> throw({invalid_intensity, What}). +validIntensity(What) -> throw({invalid_intensity, What}). validPeriod(Period) when is_integer(Period), Period > 0 -> true; validPeriod(What) -> throw({invalid_period, What}). -supname(self,Mod) -> {self(),Mod}; -supname(N,_) -> N. +supname(self, Mod) -> {self(), Mod}; +supname(N, _) -> N. %%% ------------------------------------------------------ %%% Check that the children start specification is valid. @@ -1124,7 +1125,7 @@ validShutdown(Shutdown, _) -> throw({invalid_shutdown, Shutdown}). validMods(dynamic) -> true; validMods(Mods) when is_list(Mods) -> - lists:foreach(fun (Mod) -> + lists:foreach(fun(Mod) -> if is_atom(Mod) -> ok; true -> throw({invalid_module, Mod}) @@ -1142,7 +1143,7 @@ validMods(Mods) -> throw({invalid_modules, Mods}). %%% Returns: {ok, State'} | {terminate, State'} %%% ------------------------------------------------------ -add_restart(State) -> +add_restart(State) -> I = State#state.intensity, P = State#state.period, R = State#state.restarts, -- cgit v1.2.1 From 8282fc8c337d9c08bd044af9055dd42f185d2651 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 26 Oct 2012 14:23:33 +0100 Subject: Work in progress. Lots of (reverse) costmetic changes, pull over count_children/1 and move things around a bit. The OTP style of dynamic child handling hasn't been merged yet, so we're not even compiling yet. --- src/supervisor2.erl | 294 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 181 insertions(+), 113 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 2252c4b5..eae2f298 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -55,7 +55,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% Copyright Ericsson AB 1996-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -75,15 +75,15 @@ -behaviour(gen_server). %% External exports --export([start_link/2,start_link/3, +-export([start_link/2, start_link/3, start_child/2, restart_child/2, delete_child/2, terminate_child/2, - which_children/1, find_child/2, - check_childspecs/1]). + which_children/1, count_children/1, + find_child/2, check_childspecs/1]). %% Internal exports --export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]). --export([handle_cast/2]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). %%-------------------------------------------------------------------------- @@ -96,9 +96,7 @@ -type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. -type modules() :: [module()] | 'dynamic'. -type delay() :: non_neg_integer(). --type restart() :: 'permanent' | 'transient' | 'temporary' | 'intrinsic' - | {'permanent', delay()} | {'transient', delay()} - | {'intrinsic', delay()}. +-type restart() :: 'permanent' | 'transient' | 'temporary' | 'intrinsic' | {'permanent', delay()} | {'transient', delay()} | {'intrinsic', delay()}. -type shutdown() :: 'brutal_kill' | timeout(). -type worker() :: 'worker' | 'supervisor'. -type sup_name() :: {'local', Name :: atom()} | {'global', Name :: atom()}. @@ -114,69 +112,69 @@ Modules :: modules()}. -type strategy() :: 'one_for_all' | 'one_for_one' - | 'rest_for_one' | 'simple_one_for_one' - | 'simple_one_for_one_terminate'. + | 'rest_for_one' | 'simple_one_for_one' | 'simple_one_for_one_terminate'. %%-------------------------------------------------------------------------- -record(child, {% pid is undefined when child is not running - pid = undefined, - name, - mfa, - restart_type, - shutdown, - child_type, - modules = []}). --type child_rec() :: #child{pid :: child() | {restarting,pid()} | [pid()], - name :: child_id(), - mfa :: mfargs(), - restart_type :: restart(), - shutdown :: shutdown(), - child_type :: worker(), - modules :: modules()}. + pid = undefined :: child() | {restarting,pid()} | [pid()], + name :: child_id(), + mfa :: mfargs(), + restart_type :: restart(), + shutdown :: shutdown(), + child_type :: worker(), + modules = [] :: modules()}). +-type child_rec() :: #child{}. -define(DICT, dict). +-define(SETS, sets). +-define(SET, set). -record(state, {name, - strategy, - children = [], - dynamics = ?DICT:new(), - intensity, - period, + strategy :: strategy(), + children = [] :: [child_rec()], + dynamics = ?DICT:new() :: ?DICT(), + intensity :: non_neg_integer(), + period :: pos_integer(), restarts = [], module, args}). --type state() :: #state{strategy :: strategy(), - children :: [child_rec()], - dynamics :: ?DICT(), - intensity :: non_neg_integer(), - period :: pos_integer()}. +-type state() :: #state{}. --define(is_simple(State), State#state.strategy =:= simple_one_for_one orelse - State#state.strategy =:= simple_one_for_one_terminate). - --define(is_terminate_simple(State), - State#state.strategy =:= simple_one_for_one_terminate). +-define(is_simple(State), State#state.strategy =:= simple_one_for_one orelse State#state.strategy =:= simple_one_for_one_terminate). +-define(is_terminate_simple(State), State#state.strategy =:= simple_one_for_one_terminate). -callback init(Args :: term()) -> {ok, {{RestartStrategy :: strategy(), - MaxR :: non_neg_integer(), - MaxT :: non_neg_integer()}, + MaxR :: non_neg_integer(), + MaxT :: non_neg_integer()}, [ChildSpec :: child_spec()]}} | ignore. +-define(restarting(_Pid_), {restarting,_Pid_}). + %%% --------------------------------------------------- %%% This is a general process supervisor built upon gen_server.erl. %%% Servers/processes should/could also be built using gen_server.erl. %%% SupName = {local, atom()} | {global, atom()}. %%% --------------------------------------------------- -start_link(Mod, Args) -> - gen_server:start_link(?MODULE, {self, Mod, Args}, []). +-type startlink_err() :: {'already_started', pid()} | 'shutdown' | term(). +-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}. +-spec start_link(Module, Args) -> startlink_ret() when + Module :: module(), + Args :: term(). +start_link(Mod, Args) -> + gen_server:start_link(supervisor, {self, Mod, Args}, []). + +-spec start_link(SupName, Module, Args) -> startlink_ret() when + SupName :: sup_name(), + Module :: module(), + Args :: term(). start_link(SupName, Mod, Args) -> gen_server:start_link(SupName, ?MODULE, {SupName, Mod, Args}, []). - + %%% --------------------------------------------------- %%% Interface functions. %%% --------------------------------------------------- @@ -220,7 +218,6 @@ delete_child(Supervisor, Name) -> -spec terminate_child(SupRef, Id) -> Result when SupRef :: sup_ref(), - Id :: pid() | child_id(), Result :: 'ok' | {'error', Error}, Error :: 'not_found' | 'simple_one_for_one'. @@ -229,20 +226,29 @@ terminate_child(Supervisor, Name) -> -spec which_children(SupRef) -> [{Id,Child,Type,Modules}] when SupRef :: sup_ref(), - Id :: child_id() | 'undefined', + Id :: child_id() | undefined, Child :: child(), Type :: worker(), Modules :: modules(). which_children(Supervisor) -> call(Supervisor, which_children). +-spec count_children(SupRef) -> PropListOfCounts when + SupRef :: sup_ref(), + PropListOfCounts :: [Count], + Count :: {specs, ChildSpecCount :: non_neg_integer()} + | {active, ActiveProcessCount :: non_neg_integer()} + | {supervisors, ChildSupervisorCount :: non_neg_integer()} + |{workers, ChildWorkerCount :: non_neg_integer()}. +count_children(Supervisor) -> + call(Supervisor, count_children). + call(Supervisor, Req) -> gen_server:call(Supervisor, Req, infinity). -spec check_childspecs(ChildSpecs) -> Result when ChildSpecs :: [child_spec()], Result :: 'ok' | {'error', Error :: term()}. - check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> case check_startspec(ChildSpecs) of {ok, _} -> ok; @@ -250,21 +256,22 @@ check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> end; check_childspecs(X) -> {error, {badarg, X}}. +%%%----------------------------------------------------------------- +%%% Called by timer:apply_after from restart/2 +%-spec try_again_restart(SupRef, Child) -> ok when +% SupRef :: sup_ref(), +% Child :: child_id() | pid(). +%try_again_restart(Supervisor, Child) -> +% cast(Supervisor, {try_again_restart, Child}). + find_child(Supervisor, Name) -> [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), Name1 =:= Name]. -%-export([behaviour_info/1]). - -%behaviour_info(callbacks) -> -% [{init,1}]; -%behaviour_info(_Other) -> -% undefined. - %%% --------------------------------------------------- -%%% +%%% %%% Initialize the supervisor. -%%% +%%% %%% --------------------------------------------------- -type init_sup_name() :: sup_name() | 'self'. @@ -321,12 +328,12 @@ init_dynamic(_State, StartSpec) -> %%----------------------------------------------------------------- %% Func: start_children/2 -%% Args: Children = [#child] in start order -%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} -%% Purpose: Start all children. The new list contains #child's +%% Args: Children = [child_rec()] in start order +%% SupName = {local, atom()} | {global, atom()} | {pid(), Mod} +%% Purpose: Start all children. The new list contains #child's %% with pids. %% Returns: {ok, NChildren} | {error, NChildren} -%% NChildren = [#child] in termination order (reversed +%% NChildren = [child_rec()] in termination order (reversed %% start order) %%----------------------------------------------------------------- start_children(Children, SupName) -> start_children(Children, [], SupName). @@ -375,36 +382,47 @@ do_start_child_i(M, F, A) -> {error, What} end. - %%% --------------------------------------------------- -%%% +%%% %%% Callback functions. -%%% +%%% %%% --------------------------------------------------- --type call() :: 'which_children'. +-type call() :: 'which_children' | 'count_children' | {_, _}. % XXX: refine -spec handle_call(call(), term(), state()) -> {'reply', term(), state()}. handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> - #child{mfa = {M, F, A}} = hd(State#state.children), + Child = hd(State#state.children), + #child{mfa = {M, F, A}} = Child, Args = A ++ EArgs, case do_start_child_i(M, F, Args) of {ok, undefined} -> {reply, {ok, undefined}, State}; {ok, Pid} -> - NState = State#state{dynamics = - ?DICT:store(Pid, Args, State#state.dynamics)}, + NState = State#state{dynamics = ?DICT:store(Pid, Args, State#state.dynamics)}, {reply, {ok, Pid}, NState}; {ok, Pid, Extra} -> - NState = State#state{dynamics = - ?DICT:store(Pid, Args, State#state.dynamics)}, + NState = State#state{dynamics = ?DICT:store(Pid, Args, State#state.dynamics)}, {reply, {ok, Pid, Extra}, NState}; What -> {reply, What, State} end; -%%% The requests terminate_child, delete_child and restart_child are -%%% invalid for simple_one_for_one and simple_one_for_one_terminate -%%% supervisors. +%% terminate_child for simple_one_for_one can only be done with pid +handle_call({terminate_child, Name}, _From, State) when not is_pid(Name), + ?is_simple(State) -> + {reply, {error, simple_one_for_one}, State}; + +handle_call({terminate_child, Name}, _From, State) -> + case get_child(Name, State) of + {value, Child} -> + NChild = do_terminate(Child, State#state.name), + {reply, ok, replace_child(NChild, State)}; + _ -> + {reply, {error, not_found}, State} + end; + +%%% The requests delete_child and restart_child are invalid for +%%% simple_one_for_one and simple_one_for_one_terminate supervisors. handle_call({_Req, _Data}, _From, State) when ?is_simple(State) -> {reply, {error, State#state.strategy}, State}; @@ -447,15 +465,6 @@ handle_call({delete_child, Name}, _From, State) -> {reply, {error, not_found}, State} end; -handle_call({terminate_child, Name}, _From, State) -> - case get_child(Name, State) of - {value, Child} -> - NChild = do_terminate(Child, State#state.name), - {reply, ok, replace_child(NChild, State)}; - _ -> - {reply, {error, not_found}, State} - end; - handle_call(which_children, _From, State) when ?is_simple(State) -> [#child{child_type = CT, modules = Mods}] = State#state.children, Reply = lists:map(fun ({Pid, _}) -> {undefined, Pid, CT, Mods} end, @@ -469,7 +478,58 @@ handle_call(which_children, _From, State) -> {Name, Pid, ChildType, Mods} end, State#state.children), - {reply, Resp, State}. + {reply, Resp, State}; + +handle_call(count_children, _From, #state{children = [#child{restart_type = temporary, + child_type = CT}]} = State) + when ?is_simple(State) -> + {Active, Count} = + ?SETS:fold(fun(Pid, {Alive, Tot}) -> + case is_pid(Pid) andalso is_process_alive(Pid) of + true ->{Alive+1, Tot +1}; + false -> + {Alive, Tot + 1} + end + end, {0, 0}, dynamics_db(temporary, State#state.dynamics)), + Reply = case CT of + supervisor -> [{specs, 1}, {active, Active}, + {supervisors, Count}, {workers, 0}]; + worker -> [{specs, 1}, {active, Active}, + {supervisors, 0}, {workers, Count}] + end, + {reply, Reply, State}; + +handle_call(count_children, _From, #state{children = [#child{restart_type = RType, + child_type = CT}]} = State) + when ?is_simple(State) -> + {Active, Count} = + ?DICT:fold(fun(Pid, _Val, {Alive, Tot}) -> + case is_pid(Pid) andalso is_process_alive(Pid) of + true -> + {Alive+1, Tot +1}; + false -> + {Alive, Tot + 1} + end + end, {0, 0}, dynamics_db(RType, State#state.dynamics)), + Reply = case CT of + supervisor -> [{specs, 1}, {active, Active}, + {supervisors, Count}, {workers, 0}]; + worker -> [{specs, 1}, {active, Active}, + {supervisors, 0}, {workers, Count}] + end, + {reply, Reply, State}; + +handle_call(count_children, _From, State) -> + %% Specs and children are together on the children list... + {Specs, Active, Supers, Workers} = + lists:foldl(fun(Child, Counts) -> + count_child(Child, Counts) + end, {0,0,0,0}, State#state.children), + + %% Reformat counts to a property list. + Reply = [{specs, Specs}, {active, Active}, + {supervisors, Supers}, {workers, Workers}], + {reply, Reply, State}. -spec handle_cast('null', state()) -> {'noreply', state()}. %%% Hopefully cause a function-clause as there is no API function @@ -477,12 +537,35 @@ handle_call(which_children, _From, State) -> handle_cast(null, State) -> error_logger:error_msg("ERROR: Supervisor received cast-message 'null'~n", []), - {noreply, State}. +count_child(#child{pid = Pid, child_type = worker}, + {Specs, Active, Supers, Workers}) -> + case is_pid(Pid) andalso is_process_alive(Pid) of + true -> {Specs+1, Active+1, Supers, Workers+1}; + false -> {Specs+1, Active, Supers, Workers+1} + end; +count_child(#child{pid = Pid, child_type = supervisor}, + {Specs, Active, Supers, Workers}) -> + case is_pid(Pid) andalso is_process_alive(Pid) of + true -> {Specs+1, Active+1, Supers+1, Workers}; + false -> {Specs+1, Active, Supers+1, Workers} + end. + +%% +%% Take care of terminated children. +%% -spec handle_info(term(), state()) -> {'noreply', state()} | {'stop', 'shutdown', state()}. +handle_info({'EXIT', Pid, Reason}, State) -> + case restart_child(Pid, Reason, State) of + {ok, State1} -> + {noreply, State1}; + {shutdown, State1} -> + {stop, shutdown, State1} + end; + handle_info({delayed_restart, {RestartType, Reason, Child}}, State) when ?is_simple(State) -> {ok, NState} = do_restart(RestartType, Reason, Child, State), @@ -496,21 +579,11 @@ handle_info({delayed_restart, {RestartType, Reason, Child}}, State) -> {noreply, State} end; -%% -%% Take care of terminated children. -%% -handle_info({'EXIT', Pid, Reason}, State) -> - case restart_child(Pid, Reason, State) of - {ok, State1} -> - {noreply, State1}; - {shutdown, State1} -> - {stop, shutdown, State1} - end; - handle_info(Msg, State) -> error_logger:error_msg("Supervisor received unexpected message: ~p~n", [Msg]), {noreply, State}. + %% %% Terminate this server. %% @@ -563,14 +636,13 @@ check_flags({Strategy, MaxIntensity, Period}) -> check_flags(What) -> {bad_flags, What}. -update_childspec(State, StartSpec) when ?is_simple(State) -> +update_childspec(State, StartSpec) when ?is_simple(State) -> case check_startspec(StartSpec) of {ok, [Child]} -> {ok, State#state{children = [Child]}}; Error -> {error, Error} end; - update_childspec(State, StartSpec) -> case check_startspec(StartSpec) of {ok, Children} -> @@ -589,7 +661,7 @@ update_childspec1([Child|OldC], Children, KeepOld) -> update_childspec1(OldC, Children, [Child|KeepOld]) end; update_childspec1([], Children, KeepOld) -> - % Return them in (keeped) reverse start order. + %% Return them in (kept) reverse start order. lists:reverse(Children ++ KeepOld). update_chsp(OldCh, Children) -> @@ -615,14 +687,10 @@ handle_start_child(Child, State) -> case do_start_child(State#state.name, Child) of {ok, Pid} -> Children = State#state.children, - {{ok, Pid}, - State#state{children = - [Child#child{pid = Pid}|Children]}}; + {{ok, Pid}, State#state{children = [Child#child{pid = Pid}|Children]}}; {ok, Pid, Extra} -> Children = State#state.children, - {{ok, Pid, Extra}, - State#state{children = - [Child#child{pid = Pid}|Children]}}; + {{ok, Pid, Extra}, State#state{children = [Child#child{pid = Pid}|Children]}}; {error, What} -> {{error, {What, Child}}, State} end; @@ -634,7 +702,7 @@ handle_start_child(Child, State) -> %%% --------------------------------------------------- %%% Restart. A process has terminated. -%%% Returns: {ok, #state} | {shutdown, #state} +%%% Returns: {ok, state()} | {shutdown, state()} %%% --------------------------------------------------- restart_child(Pid, Reason, State) when ?is_simple(State) -> @@ -1002,12 +1070,12 @@ remove_child(Child, State) -> %% Type = {Strategy, MaxIntensity, Period} %% Strategy = one_for_one | one_for_all | simple_one_for_one | %% rest_for_one -%% MaxIntensity = integer() -%% Period = integer() +%% MaxIntensity = integer() >= 0 +%% Period = integer() > 0 %% Mod :== atom() -%% Arsg :== term() +%% Args :== term() %% Purpose: Check that Type is of correct type (!) -%% Returns: {ok, #state} | Error +%% Returns: {ok, state()} | Error %%----------------------------------------------------------------- init_state(SupName, Type, Mod, Args) -> case catch init_state1(SupName, Type, Mod, Args) of @@ -1053,15 +1121,15 @@ supname(N, _) -> N. %%% Shall be a six (6) tuple %%% {Name, Func, RestartType, Shutdown, ChildType, Modules} %%% where Name is an atom -%%% Func is {Mod, Fun, Args} == {atom, atom, list} +%%% Func is {Mod, Fun, Args} == {atom(), atom(), list()} %%% RestartType is permanent | temporary | transient | %%% intrinsic | {permanent, Delay} | %%% {transient, Delay} | {intrinsic, Delay} %% where Delay >= 0 -%%% Shutdown = integer() | infinity | brutal_kill +%%% Shutdown = integer() > 0 | infinity | brutal_kill %%% ChildType = supervisor | worker %%% Modules = [atom()] | dynamic -%%% Returns: {ok, [#child]} | Error +%%% Returns: {ok, [child_rec()]} | Error %%% ------------------------------------------------------ check_startspec(Children) -> check_startspec(Children, []). @@ -1117,7 +1185,7 @@ validDelay(Delay) when is_number(Delay), Delay >= 0 -> true; validDelay(What) -> throw({invalid_delay, What}). -validShutdown(Shutdown, _) +validShutdown(Shutdown, _) when is_integer(Shutdown), Shutdown > 0 -> true; validShutdown(infinity, supervisor) -> true; validShutdown(brutal_kill, _) -> true; -- cgit v1.2.1 From d8a163f5185bcb3849143f2af8e479d848bc0b93 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Mon, 29 Oct 2012 15:14:10 +0000 Subject: no more simple_one_for_one_terminate; include support for dynamic_db --- src/mirrored_supervisor.erl | 5 +- src/rabbit_amqqueue_sup.erl | 2 +- src/rabbit_channel_sup_sup.erl | 2 +- src/rabbit_client_sup.erl | 2 +- src/rabbit_mirror_queue_slave_sup.erl | 2 +- src/supervisor2.erl | 246 +++++++++++++++++++++++++++------- src/supervisor2_tests.erl | 2 +- src/test_sup.erl | 2 +- 8 files changed, 204 insertions(+), 59 deletions(-) diff --git a/src/mirrored_supervisor.erl b/src/mirrored_supervisor.erl index 24c3ebd0..e070d520 100644 --- a/src/mirrored_supervisor.erl +++ b/src/mirrored_supervisor.erl @@ -212,9 +212,8 @@ start_link0(Prefix, Group, Init) -> init(Mod, Args) -> case Mod:init(Args) of {ok, {{Bad, _, _}, _ChildSpecs}} when - Bad =:= simple_one_for_one orelse - Bad =:= simple_one_for_one_terminate -> erlang:error(badarg); - Init -> Init + Bad =:= simple_one_for_one -> erlang:error(badarg); + Init -> Init end. start_child(Sup, ChildSpec) -> call(Sup, {start_child, ChildSpec}). diff --git a/src/rabbit_amqqueue_sup.erl b/src/rabbit_amqqueue_sup.erl index a4305e5f..7586fe46 100644 --- a/src/rabbit_amqqueue_sup.erl +++ b/src/rabbit_amqqueue_sup.erl @@ -47,6 +47,6 @@ start_child(Node, Args) -> supervisor2:start_child({?SERVER, Node}, Args). init([]) -> - {ok, {{simple_one_for_one_terminate, 10, 10}, + {ok, {{simple_one_for_one, 10, 10}, [{rabbit_amqqueue, {rabbit_amqqueue_process, start_link, []}, temporary, ?MAX_WAIT, worker, [rabbit_amqqueue_process]}]}}. diff --git a/src/rabbit_channel_sup_sup.erl b/src/rabbit_channel_sup_sup.erl index 995c41fb..29cd8787 100644 --- a/src/rabbit_channel_sup_sup.erl +++ b/src/rabbit_channel_sup_sup.erl @@ -43,6 +43,6 @@ start_channel(Pid, Args) -> %%---------------------------------------------------------------------------- init([]) -> - {ok, {{simple_one_for_one_terminate, 0, 1}, + {ok, {{simple_one_for_one, 0, 1}, [{channel_sup, {rabbit_channel_sup, start_link, []}, temporary, infinity, supervisor, [rabbit_channel_sup]}]}}. diff --git a/src/rabbit_client_sup.erl b/src/rabbit_client_sup.erl index c508f1b9..b4db97b1 100644 --- a/src/rabbit_client_sup.erl +++ b/src/rabbit_client_sup.erl @@ -44,5 +44,5 @@ start_link(SupName, Callback) -> supervisor2:start_link(SupName, ?MODULE, Callback). init({M,F,A}) -> - {ok, {{simple_one_for_one_terminate, 0, 1}, + {ok, {{simple_one_for_one, 0, 1}, [{client, {M,F,A}, temporary, infinity, supervisor, [M]}]}}. diff --git a/src/rabbit_mirror_queue_slave_sup.erl b/src/rabbit_mirror_queue_slave_sup.erl index a2034876..a20c50ef 100644 --- a/src/rabbit_mirror_queue_slave_sup.erl +++ b/src/rabbit_mirror_queue_slave_sup.erl @@ -31,7 +31,7 @@ start_link() -> supervisor2:start_link({local, ?SERVER}, ?MODULE, []). start_child(Node, Args) -> supervisor2:start_child({?SERVER, Node}, Args). init([]) -> - {ok, {{simple_one_for_one_terminate, 10, 10}, + {ok, {{simple_one_for_one, 10, 10}, [{rabbit_mirror_queue_slave, {rabbit_mirror_queue_slave, start_link, []}, temporary, ?MAX_WAIT, worker, [rabbit_mirror_queue_slave]}]}}. diff --git a/src/supervisor2.erl b/src/supervisor2.erl index eae2f298..2256b98e 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -3,12 +3,7 @@ %% %% 1) the module name is supervisor2 %% -%% 2) there is a new strategy called -%% simple_one_for_one_terminate. This is exactly the same as for -%% simple_one_for_one, except that children *are* explicitly -%% terminated as per the shutdown component of the child_spec. -%% -%% 3) child specifications can contain, as the restart type, a tuple +%% 2) child specifications can contain, as the restart type, a tuple %% {permanent, Delay} | {transient, Delay} | {intrinsic, Delay} %% where Delay >= 0 (see point (4) below for intrinsic). The delay, %% in seconds, indicates what should happen if a child, upon being @@ -41,14 +36,14 @@ %% perspective it's a normal exit, whilst from supervisor's %% perspective, it's an abnormal exit. %% -%% 4) Added an 'intrinsic' restart type. Like the transient type, this +%% 3) Added an 'intrinsic' restart type. Like the transient type, this %% type means the child should only be restarted if the child exits %% abnormally. Unlike the transient type, if the child exits %% normally, the supervisor itself also exits normally. If the %% child is a supervisor and it exits normally (i.e. with reason of %% 'shutdown') then the child's parent also exits normally. %% -%% 5) normal, and {shutdown, _} exit reasons are all treated the same +%% 4) normal, and {shutdown, _} exit reasons are all treated the same %% (i.e. are regarded as normal exits) %% %% All modifications are (C) 2010-2012 VMware, Inc. @@ -91,7 +86,7 @@ %%-------------------------------------------------------------------------- --type child() :: 'undefined' | pid(). +-type child() :: 'undefined' | pid(). -type child_id() :: term(). -type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. -type modules() :: [module()] | 'dynamic'. @@ -112,7 +107,7 @@ Modules :: modules()}. -type strategy() :: 'one_for_all' | 'one_for_one' - | 'rest_for_one' | 'simple_one_for_one' | 'simple_one_for_one_terminate'. + | 'rest_for_one' | 'simple_one_for_one'. %%-------------------------------------------------------------------------- @@ -132,17 +127,16 @@ -record(state, {name, strategy :: strategy(), - children = [] :: [child_rec()], - dynamics = ?DICT:new() :: ?DICT(), - intensity :: non_neg_integer(), - period :: pos_integer(), + children = [] :: [child_rec()], + dynamics :: ?DICT() | ?SET(), + intensity :: non_neg_integer(), + period :: pos_integer(), restarts = [], module, args}). -type state() :: #state{}. --define(is_simple(State), State#state.strategy =:= simple_one_for_one orelse State#state.strategy =:= simple_one_for_one_terminate). --define(is_terminate_simple(State), State#state.strategy =:= simple_one_for_one_terminate). +-define(is_simple(State), State#state.strategy =:= simple_one_for_one). -callback init(Args :: term()) -> {ok, {{RestartStrategy :: strategy(), @@ -166,7 +160,7 @@ Module :: module(), Args :: term(). start_link(Mod, Args) -> - gen_server:start_link(supervisor, {self, Mod, Args}, []). + gen_server:start_link(supervisor2, {self, Mod, Args}, []). -spec start_link(SupName, Module, Args) -> startlink_ret() when SupName :: sup_name(), @@ -398,10 +392,10 @@ handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> {ok, undefined} -> {reply, {ok, undefined}, State}; {ok, Pid} -> - NState = State#state{dynamics = ?DICT:store(Pid, Args, State#state.dynamics)}, + NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State), {reply, {ok, Pid}, NState}; {ok, Pid, Extra} -> - NState = State#state{dynamics = ?DICT:store(Pid, Args, State#state.dynamics)}, + NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State), {reply, {ok, Pid, Extra}, NState}; What -> {reply, What, State} @@ -422,7 +416,7 @@ handle_call({terminate_child, Name}, _From, State) -> end; %%% The requests delete_child and restart_child are invalid for -%%% simple_one_for_one and simple_one_for_one_terminate supervisors. +%%% simple_one_for_one supervisors. handle_call({_Req, _Data}, _From, State) when ?is_simple(State) -> {reply, {error, State#state.strategy}, State}; @@ -465,10 +459,20 @@ handle_call({delete_child, Name}, _From, State) -> {reply, {error, not_found}, State} end; -handle_call(which_children, _From, State) when ?is_simple(State) -> - [#child{child_type = CT, modules = Mods}] = State#state.children, +handle_call(which_children, _From, #state{children = [#child{restart_type = temporary, + child_type = CT, + modules = Mods}]} = + State) when ?is_simple(State) -> + Reply = lists:map(fun(Pid) -> {undefined, Pid, CT, Mods} end, + ?SETS:to_list(dynamics_db(temporary, State#state.dynamics))), + {reply, Reply, State}; + +handle_call(which_children, _From, #state{children = [#child{restart_type = RType, + child_type = CT, + modules = Mods}]} = + State) when ?is_simple(State) -> Reply = lists:map(fun ({Pid, _}) -> {undefined, Pid, CT, Mods} end, - ?DICT:to_list(State#state.dynamics)), + ?DICT:to_list(dynamics_db(RType, State#state.dynamics))), {reply, Reply, State}; handle_call(which_children, _From, State) -> @@ -589,13 +593,12 @@ handle_info(Msg, State) -> %% -spec terminate(term(), state()) -> 'ok'. -terminate(_Reason, State) when ?is_terminate_simple(State) -> - terminate_simple_children( - hd(State#state.children), State#state.dynamics, State#state.name), - ok; +terminate(_Reason, #state{children=[Child]} = State) when ?is_simple(State) -> + terminate_dynamic_children(Child, dynamics_db(Child#child.restart_type, + State#state.dynamics), + State#state.name); terminate(_Reason, State) -> - terminate_children(State#state.children, State#state.name), - ok. + terminate_children(State#state.children, State#state.name). %% %% Change code for the supervisor. @@ -686,11 +689,9 @@ handle_start_child(Child, State) -> false -> case do_start_child(State#state.name, Child) of {ok, Pid} -> - Children = State#state.children, - {{ok, Pid}, State#state{children = [Child#child{pid = Pid}|Children]}}; + {{ok, Pid}, save_child(Child#child{pid = Pid}, State)}; {ok, Pid, Extra} -> - Children = State#state.children, - {{ok, Pid, Extra}, State#state{children = [Child#child{pid = Pid}|Children]}}; + {{ok, Pid, Extra}, save_child(Child#child{pid = Pid}, State)}; {error, What} -> {{error, {What, Child}}, State} end; @@ -705,16 +706,15 @@ handle_start_child(Child, State) -> %%% Returns: {ok, state()} | {shutdown, state()} %%% --------------------------------------------------- -restart_child(Pid, Reason, State) when ?is_simple(State) -> - case ?DICT:find(Pid, State#state.dynamics) of +restart_child(Pid, Reason, #state{children = [Child]} = State) when ?is_simple(State) -> + RestartType = Child#child.restart_type, + case dynamic_child_args(Pid, dynamics_db(RestartType, State#state.dynamics)) of {ok, Args} -> - [Child] = State#state.children, - RestartType = Child#child.restart_type, {M, F, _} = Child#child.mfa, NChild = Child#child{pid = Pid, mfa = {M, F, Args}}, do_restart(RestartType, Reason, NChild, State); error -> - {ok, State} + {ok, State} end; restart_child(Pid, Reason, State) -> Children = State#state.children, @@ -793,9 +793,7 @@ restart1(Child, State) -> {terminate, State} end. -restart(Strategy, Child, State, Restart) - when Strategy =:= simple_one_for_one orelse - Strategy =:= simple_one_for_one_terminate -> +restart(simple_one_for_one, Child, State, Restart) -> #child{mfa = {M, F, A}} = Child, Dynamics = ?DICT:erase(Child#child.pid, State#state.dynamics), case do_start_child_i(M, F, A) of @@ -859,12 +857,7 @@ terminate_children([], _SupName, Res) -> Res. terminate_simple_children(Child, Dynamics, SupName) -> - Pids = dict:fold(fun (Pid, _Args, Pids) -> - erlang:monitor(process, Pid), - unlink(Pid), - exit(Pid, child_exit_reason(Child)), - [Pid | Pids] - end, [], Dynamics), + Pids = monitor_children(Child, Dynamics), TimeoutMsg = {timeout, make_ref()}, TRef = timeout_start(Child, TimeoutMsg), {Replies, Timedout} = @@ -898,6 +891,21 @@ terminate_simple_children(Child, Dynamics, SupName) -> end || {Pid, Reply} <- Replies], ok. +monitor_children(Child=#child{restart_type=temporary}, Dynamics) -> + ?SETS:fold(fun (Pid, _Args, Pids) -> + erlang:monitor(process, Pid), + unlink(Pid), + exit(Pid, child_exit_reason(Child)), + [Pid | Pids] + end, [], Dynamics); +monitor_children(Child, Dynamics) -> + dict:fold(fun (Pid, _Args, Pids) -> + erlang:monitor(process, Pid), + unlink(Pid), + exit(Pid, child_exit_reason(Child)), + [Pid | Pids] + end, [], Dynamics). + child_exit_reason(#child{shutdown = brutal_kill}) -> kill; child_exit_reason(#child{}) -> shutdown. @@ -1016,11 +1024,149 @@ monitor_child(Pid) -> end. +%%----------------------------------------------------------------- +%% Func: terminate_dynamic_children/3 +%% Args: Child = child_rec() +%% Dynamics = ?DICT() | ?SET() +%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} +%% Returns: ok +%% +%% +%% Shutdown all dynamic children. This happens when the supervisor is +%% stopped. Because the supervisor can have millions of dynamic children, we +%% can have an significative overhead here. +%%----------------------------------------------------------------- +terminate_dynamic_children(Child, Dynamics, SupName) -> + {Pids, EStack0} = monitor_dynamic_children(Child, Dynamics), + Sz = ?SETS:size(Pids), + EStack = case Child#child.shutdown of + brutal_kill -> + ?SETS:fold(fun(P, _) -> exit(P, kill) end, ok, Pids), + wait_dynamic_children(Child, Pids, Sz, undefined, EStack0); + infinity -> + ?SETS:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids), + wait_dynamic_children(Child, Pids, Sz, undefined, EStack0); + Time -> + ?SETS:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids), + TRef = erlang:start_timer(Time, self(), kill), + wait_dynamic_children(Child, Pids, Sz, TRef, EStack0) + end, + %% Unroll stacked errors and report them + ?DICT:fold(fun(Reason, Ls, _) -> + report_error(shutdown_error, Reason, + Child#child{pid=Ls}, SupName) + end, ok, EStack). + + +monitor_dynamic_children(#child{restart_type=temporary}, Dynamics) -> + ?SETS:fold(fun(P, {Pids, EStack}) -> + case monitor_child(P) of + ok -> + {?SETS:add_element(P, Pids), EStack}; + {error, normal} -> + {Pids, EStack}; + {error, Reason} -> + {Pids, ?DICT:append(Reason, P, EStack)} + end + end, {?SETS:new(), ?DICT:new()}, Dynamics); +monitor_dynamic_children(#child{restart_type=RType}, Dynamics) -> + ?DICT:fold(fun(P, _, {Pids, EStack}) when is_pid(P) -> + case monitor_child(P) of + ok -> + {?SETS:add_element(P, Pids), EStack}; + {error, normal} when RType =/= permanent -> + {Pids, EStack}; + {error, Reason} -> + {Pids, ?DICT:append(Reason, P, EStack)} + end; + (?restarting(_), _, {Pids, EStack}) -> + {Pids, EStack} + end, {?SETS:new(), ?DICT:new()}, Dynamics). + + +wait_dynamic_children(_Child, _Pids, 0, undefined, EStack) -> + EStack; +wait_dynamic_children(_Child, _Pids, 0, TRef, EStack) -> + %% If the timer has expired before its cancellation, we must empty the + %% mail-box of the 'timeout'-message. + erlang:cancel_timer(TRef), + receive + {timeout, TRef, kill} -> + EStack + after 0 -> + EStack + end; +wait_dynamic_children(#child{shutdown=brutal_kill} = Child, Pids, Sz, + TRef, EStack) -> + receive + {'DOWN', _MRef, process, Pid, killed} -> + wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, + TRef, EStack); + + {'DOWN', _MRef, process, Pid, Reason} -> + wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, + TRef, ?DICT:append(Reason, Pid, EStack)) + end; +wait_dynamic_children(#child{restart_type=RType} = Child, Pids, Sz, + TRef, EStack) -> + receive + {'DOWN', _MRef, process, Pid, shutdown} -> + wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, + TRef, EStack); + + {'DOWN', _MRef, process, Pid, normal} when RType =/= permanent -> + wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, + TRef, EStack); + + {'DOWN', _MRef, process, Pid, Reason} -> + wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, + TRef, ?DICT:append(Reason, Pid, EStack)); + + {timeout, TRef, kill} -> + ?SETS:fold(fun(P, _) -> exit(P, kill) end, ok, Pids), + wait_dynamic_children(Child, Pids, Sz-1, undefined, EStack) + end. + %%----------------------------------------------------------------- %% Child/State manipulating functions. %%----------------------------------------------------------------- -state_del_child(#child{pid = Pid}, State) when ?is_simple(State) -> - NDynamics = ?DICT:erase(Pid, State#state.dynamics), + +%% Note we do not want to save the parameter list for temporary processes as +%% they will not be restarted, and hence we do not need this information. +%% Especially for dynamic children to simple_one_for_one supervisors +%% it could become very costly as it is not uncommon to spawn +%% very many such processes. +save_child(#child{restart_type = temporary, + mfa = {M, F, _}} = Child, #state{children = Children} = State) -> + State#state{children = [Child#child{mfa = {M, F, undefined}} |Children]}; +save_child(Child, #state{children = Children} = State) -> + State#state{children = [Child |Children]}. + +save_dynamic_child(temporary, Pid, _, #state{dynamics = Dynamics} = State) -> + State#state{dynamics = ?SETS:add_element(Pid, dynamics_db(temporary, Dynamics))}; +save_dynamic_child(RestartType, Pid, Args, #state{dynamics = Dynamics} = State) -> + State#state{dynamics = ?DICT:store(Pid, Args, dynamics_db(RestartType, Dynamics))}. + +dynamics_db(temporary, undefined) -> + ?SETS:new(); +dynamics_db(_, undefined) -> + ?DICT:new(); +dynamics_db(_,Dynamics) -> + Dynamics. + +dynamic_child_args(Pid, Dynamics) -> + case ?SETS:is_set(Dynamics) of + true -> + {ok, undefined}; + false -> + ?DICT:find(Pid, Dynamics) + end. + +state_del_child(#child{pid = Pid, restart_type = temporary}, State) when ?is_simple(State) -> + NDynamics = ?SETS:del_element(Pid, dynamics_db(temporary, State#state.dynamics)), + State#state{dynamics = NDynamics}; +state_del_child(#child{pid = Pid, restart_type = RType}, State) when ?is_simple(State) -> + NDynamics = ?DICT:erase(Pid, dynamics_db(RType, State#state.dynamics)), State#state{dynamics = NDynamics}; state_del_child(Child, State) -> NChildren = del_child(Child#child.name, State#state.children), @@ -1051,6 +1197,7 @@ split_child(_, [], After) -> get_child(Name, State) -> lists:keysearch(Name, #child.name, State#state.children). + replace_child(Child, State) -> Chs = do_replace_child(Child, State#state.children), State#state{children = Chs}. @@ -1098,7 +1245,6 @@ init_state1(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) -> init_state1(_SupName, Type, _, _) -> {invalid_type, Type}. -validStrategy(simple_one_for_one_terminate) -> true; validStrategy(simple_one_for_one) -> true; validStrategy(one_for_one) -> true; validStrategy(one_for_all) -> true; diff --git a/src/supervisor2_tests.erl b/src/supervisor2_tests.erl index e42ded7b..ff1a7f3c 100644 --- a/src/supervisor2_tests.erl +++ b/src/supervisor2_tests.erl @@ -65,6 +65,6 @@ init([Timeout]) -> [{local, ?MODULE}, ?MODULE, []]}, transient, Timeout, supervisor, [?MODULE]}]}}; init([]) -> - {ok, {{simple_one_for_one_terminate, 0, 1}, + {ok, {{simple_one_for_one, 0, 1}, [{test_worker, {?MODULE, start_link, []}, temporary, 1000, worker, [?MODULE]}]}}. diff --git a/src/test_sup.erl b/src/test_sup.erl index 7f4b5049..6a56e64a 100644 --- a/src/test_sup.erl +++ b/src/test_sup.erl @@ -34,7 +34,7 @@ %%---------------------------------------------------------------------------- test_supervisor_delayed_restart() -> - passed = with_sup(simple_one_for_one_terminate, + passed = with_sup(simple_one_for_one, fun (SupPid) -> {ok, _ChildPid} = supervisor2:start_child(SupPid, []), -- cgit v1.2.1 From 403786326c589a18968f2d3d1cb318e5a09a52b8 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 7 Nov 2012 16:17:50 +0000 Subject: adds the erlang base dir before calling erl --- scripts/rabbitmq-defaults | 8 ++++++++ scripts/rabbitmq-plugins | 2 +- scripts/rabbitmq-server | 4 ++-- scripts/rabbitmqctl | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/rabbitmq-defaults b/scripts/rabbitmq-defaults index 4763f086..f618f584 100644 --- a/scripts/rabbitmq-defaults +++ b/scripts/rabbitmq-defaults @@ -18,6 +18,14 @@ ### next line potentially updated in package install steps SYS_PREFIX= +### next line will be updated when generating a standalone release +ERL_DIR= + +### TODO fix these values +CLEAN_BOOT_FILE="${SYS_PREFIX}/releases/${RELEASE_VERSION}/start_clean" +SASL_BOOT_FILE="${SYS_PREFIX}/releases/${RELEASE_VERSION}/start_sasl" + + ## Set default values CONFIG_FILE=${SYS_PREFIX}/etc/rabbitmq/rabbitmq diff --git a/scripts/rabbitmq-plugins b/scripts/rabbitmq-plugins index 97c74791..d9347258 100755 --- a/scripts/rabbitmq-plugins +++ b/scripts/rabbitmq-plugins @@ -26,7 +26,7 @@ ##--- End of overridden variables -exec erl \ +exec ${ERL_DIR}erl \ -pa "${RABBITMQ_HOME}/ebin" \ -noinput \ -hidden \ diff --git a/scripts/rabbitmq-server b/scripts/rabbitmq-server index e1686627..c3d61868 100755 --- a/scripts/rabbitmq-server +++ b/scripts/rabbitmq-server @@ -82,7 +82,7 @@ case "$(uname -s)" in esac RABBITMQ_EBIN_ROOT="${RABBITMQ_HOME}/ebin" -if ! erl -pa "$RABBITMQ_EBIN_ROOT" \ +if ! ${ERL_DIR}erl -pa "$RABBITMQ_EBIN_ROOT" \ -noinput \ -hidden \ -s rabbit_prelaunch \ @@ -103,7 +103,7 @@ RABBITMQ_LISTEN_ARG= # there is no other way of preventing their expansion. set -f -exec erl \ +exec ${ERL_DIR}erl \ -pa ${RABBITMQ_EBIN_ROOT} \ ${RABBITMQ_START_RABBIT} \ -sname ${RABBITMQ_NODENAME} \ diff --git a/scripts/rabbitmqctl b/scripts/rabbitmqctl index a5fade72..b7351624 100755 --- a/scripts/rabbitmqctl +++ b/scripts/rabbitmqctl @@ -26,7 +26,7 @@ ##--- End of overridden variables -exec erl \ +exec ${ERL_DIR}erl \ -pa "${RABBITMQ_HOME}/ebin" \ -noinput \ -hidden \ -- cgit v1.2.1 From 266d92fc1fba50e54aa4842a4423e7fb3be35fa0 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 7 Nov 2012 16:18:32 +0000 Subject: adds Makefile for standalone mac release --- packaging/mac/Makefile | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packaging/mac/Makefile diff --git a/packaging/mac/Makefile b/packaging/mac/Makefile new file mode 100644 index 00000000..a21ec88a --- /dev/null +++ b/packaging/mac/Makefile @@ -0,0 +1,61 @@ +VERSION=0.0.0 +SOURCE_DIR=rabbitmq-server-$(VERSION) +TARGET_DIR=rabbitmq_server-$(VERSION) +TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) +RLS_DIR=$(TARGET_DIR)/release + +ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') +ERTS_ROOT_DIR=$(shell erl -noshell -eval 'io:format("~s", [code:root_dir()]), halt().') + +# used to generate the erlang release +RABBITMQ_HOME=$(TARGET_DIR) +RABBITMQ_EBIN_ROOT=$(RABBITMQ_HOME)/ebin +RABBITMQ_PLUGINS_DIR=$(RABBITMQ_HOME)/plugins +RABBITMQ_PLUGINS_EXPAND_DIR=$(RABBITMQ_PLUGINS_DIR)/expand + +dist: + tar -zxf ../../dist/$(SOURCE_DIR).tar.gz + + $(MAKE) -C $(SOURCE_DIR) \ + TARGET_DIR=`pwd`/$(TARGET_DIR) \ + SBIN_DIR=`pwd`/$(TARGET_DIR)/sbin \ + MAN_DIR=`pwd`/$(TARGET_DIR)/share/man \ + install + + sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults + + chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults + + mkdir -p $(TARGET_DIR)/etc/rabbitmq + + $(MAKE) generate_release + +## todo see where the .tar is being created + mkdirp -p $(RLS_DIR) + tar -C $(RLS_DIR) -xzf $(RABBITMQ_HOME)/rabbit.tar.gz + +# add minimal boot file + cp $(ERTS_ROOT_DIR)/bin/start_clean.boot $(RLS_DIR)/releases/$(VERSION) + cp $(ERTS_ROOT_DIR)/bin/start_sasl.boot $(RLS_DIR)/releases/$(VERSION) + + tar -zcf $(TARGET_TARBALL).tar.gz $(RLS_DIR) + rm -rf $(SOURCE_DIR) $(TARGET_DIR) + +clean: clean_partial + rm -f rabbitmq-server-generic-unix-*.tar.gz + +clean_partial: + rm -rf $(SOURCE_DIR) + rm -rf $(TARGET_DIR) + +.PHONY : generate_release +generate_release: + erl \ + -pa "$(RABBITMQ_EBIN_ROOT)" \ + -noinput \ + -hidden \ + -s rabbit_release \ + -extra "$(RABBITMQ_PLUGINS_DIR)" "$(RABBITMQ_PLUGINS_EXPAND_DIR)" "$(RABBITMQ_HOME)" -- cgit v1.2.1 From 888215642285b60f4960bafbeb52f355a7fd04d7 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 7 Nov 2012 16:24:22 +0000 Subject: adds file to generate the erlang release --- src/rabbit_release.erl | 148 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 src/rabbit_release.erl diff --git a/src/rabbit_release.erl b/src/rabbit_release.erl new file mode 100644 index 00000000..bcba2699 --- /dev/null +++ b/src/rabbit_release.erl @@ -0,0 +1,148 @@ +%% based on rabbit_prelaunch.erl from rabbitmq-server source code +-module(rabbit_release). + +-export([start/0, stop/0, make_tar/2]). + +-include("rabbit.hrl"). + +-define(BaseApps, [rabbit]). +-define(ERROR_CODE, 1). + +start() -> + %% Determine our various directories + [PluginsDistDir, UnpackedPluginDir, RabbitHome] = + init:get_plain_arguments(), + RootName = UnpackedPluginDir ++ "/rabbit", + + prepare_plugins(PluginsDistDir, UnpackedPluginDir), + + PluginAppNames = [ P#plugin.name || P <- rabbit_plugins:list(PluginsDistDir) ], + + %% we need to call find_plugins because it has the secondary effect of adding the + %% plugin ebin folder to the code path. We need that in order to load the plugin app + RequiredApps = find_plugins(UnpackedPluginDir), + + %% Build the entire set of dependencies - this will load the + %% applications along the way + AllApps = case catch sets:to_list(expand_dependencies(RequiredApps)) of + {failed_to_load_app, App, Err} -> + terminate("failed to load application ~s:~n~p", + [App, Err]); + AppList -> + AppList + end, + + %% we need a list of ERTS apps we need to ship with rabbit + BaseApps = AllApps -- PluginAppNames, + + AppVersions = [determine_version(App) || App <- BaseApps], + RabbitVersion = proplists:get_value(rabbit, AppVersions), + + %% Build the overall release descriptor + RDesc = {release, + {"rabbit", RabbitVersion}, + {erts, erlang:system_info(version)}, + AppVersions}, + + %% Write it out to $RABBITMQ_PLUGINS_EXPAND_DIR/rabbit.rel + rabbit_file:write_file(RootName ++ ".rel", io_lib:format("~p.~n", [RDesc])), + + %% Compile the script + systools:make_script(RootName), + systools:script2boot(RootName), + %% Make release tarfile + make_tar(RootName, RabbitHome), + terminate(0), + ok. + +stop() -> + ok. + +make_tar(Release, RabbitHome) -> + systools:make_tar(Release, + [ + {dirs, [docs, etc, include, plugins, sbin, share]}, + {erts, code:root_dir()}, + {outdir, RabbitHome} + ]). + +determine_version(App) -> + application:load(App), + {ok, Vsn} = application:get_key(App, vsn), + {App, Vsn}. + +delete_recursively(Fn) -> + case rabbit_file:recursive_delete([Fn]) of + ok -> ok; + {error, {Path, E}} -> {error, {cannot_delete, Path, E}}; + Error -> Error + end. + +prepare_plugins(PluginsDistDir, DestDir) -> + %% Eliminate the contents of the destination directory + case delete_recursively(DestDir) of + ok -> ok; + {error, E} -> terminate("Could not delete dir ~s (~p)", [DestDir, E]) + end, + case filelib:ensure_dir(DestDir ++ "/") of + ok -> ok; + {error, E2} -> terminate("Could not create dir ~s (~p)", [DestDir, E2]) + end, + + [prepare_plugin(Plugin, DestDir) || Plugin <- rabbit_plugins:list(PluginsDistDir)]. + +prepare_plugin(#plugin{type = ez, location = Location}, PluginDestDir) -> + zip:unzip(Location, [{cwd, PluginDestDir}]); +prepare_plugin(#plugin{type = dir, name = Name, location = Location}, + PluginsDestDir) -> + rabbit_file:recursive_copy(Location, + filename:join([PluginsDestDir, Name])). + +expand_dependencies(Pending) -> + expand_dependencies(sets:new(), Pending). +expand_dependencies(Current, []) -> + Current; +expand_dependencies(Current, [Next|Rest]) -> + case sets:is_element(Next, Current) of + true -> + expand_dependencies(Current, Rest); + false -> + case application:load(Next) of + ok -> + ok; + {error, {already_loaded, _}} -> + ok; + {error, Reason} -> + throw({failed_to_load_app, Next, Reason}) + end, + {ok, Required} = application:get_key(Next, applications), + Unique = [A || A <- Required, not(sets:is_element(A, Current))], + expand_dependencies(sets:add_element(Next, Current), Rest ++ Unique) + end. + +find_plugins(PluginDir) -> + [prepare_dir_plugin(PluginName) || + PluginName <- filelib:wildcard(PluginDir ++ "/*/ebin/*.app")]. + +prepare_dir_plugin(PluginAppDescFn) -> + %% Add the plugin ebin directory to the load path + PluginEBinDirN = filename:dirname(PluginAppDescFn), + code:add_path(PluginEBinDirN), + + %% We want the second-last token + NameTokens = string:tokens(PluginAppDescFn,"/."), + PluginNameString = lists:nth(length(NameTokens) - 1, NameTokens), + list_to_atom(PluginNameString). + +terminate(Fmt, Args) -> + io:format("ERROR: " ++ Fmt ++ "~n", Args), + terminate(?ERROR_CODE). + +terminate(Status) -> + case os:type() of + {unix, _} -> halt(Status); + {win32, _} -> init:stop(Status), + receive + after infinity -> ok + end + end. -- cgit v1.2.1 From 0aca77c2ad5ab599d74723704bda3355b06f5af4 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 8 Nov 2012 12:53:27 +0000 Subject: Pass length to prioritise_* --- src/file_handle_cache.erl | 4 ++-- src/gen_server2.erl | 12 ++++++++---- src/gm.erl | 8 ++++---- src/rabbit_amqqueue_process.erl | 10 +++++----- src/rabbit_channel.erl | 10 +++++----- src/rabbit_limiter.erl | 6 +++--- src/rabbit_mirror_queue_slave.erl | 10 +++++----- src/rabbit_msg_store.erl | 10 +++++----- src/rabbit_msg_store_gc.erl | 6 +++--- src/worker_pool_worker.erl | 6 +++--- 10 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/file_handle_cache.erl b/src/file_handle_cache.erl index 3260d369..f39ba01d 100644 --- a/src/file_handle_cache.erl +++ b/src/file_handle_cache.erl @@ -152,7 +152,7 @@ -export([ulimit/0]). -export([start_link/0, start_link/2, init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3, prioritise_cast/2]). + handle_info/2, terminate/2, code_change/3, prioritise_cast/3]). -define(SERVER, ?MODULE). -define(RESERVED_FOR_OTHERS, 100). @@ -848,7 +848,7 @@ init([AlarmSet, AlarmClear]) -> alarm_set = AlarmSet, alarm_clear = AlarmClear }}. -prioritise_cast(Msg, _State) -> +prioritise_cast(Msg, _Len, _State) -> case Msg of {release, _, _} -> 5; _ -> 0 diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 78bbbe06..7bdfa91a 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -1179,16 +1179,20 @@ find_prioritisers(GS2State = #gs2_state { mod = Mod }) -> function_exported_or_default(Mod, Fun, Arity, Default) -> case erlang:function_exported(Mod, Fun, Arity) of true -> case Arity of - 2 -> fun (Msg, GS2State = #gs2_state { state = State }) -> - case catch Mod:Fun(Msg, State) of + 2 -> fun (Msg, GS2State = #gs2_state { queue = Queue, + state = State }) -> + Length = priority_queue:len(Queue), + case catch Mod:Fun(Msg, Length, State) of Res when is_integer(Res) -> Res; Err -> handle_common_termination(Err, Msg, GS2State) end end; - 3 -> fun (Msg, From, GS2State = #gs2_state { state = State }) -> - case catch Mod:Fun(Msg, From, State) of + 3 -> fun (Msg, From, GS2State = #gs2_state { queue = Queue, + state = State }) -> + Length = priority_queue:len(Queue), + case catch Mod:Fun(Msg, From, Length, State) of Res when is_integer(Res) -> Res; Err -> diff --git a/src/gm.erl b/src/gm.erl index 4a95de0d..98685ebb 100644 --- a/src/gm.erl +++ b/src/gm.erl @@ -380,7 +380,7 @@ confirmed_broadcast/2, info/1, forget_group/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3, prioritise_info/2]). + code_change/3, prioritise_info/3]). -ifndef(use_specs). -export([behaviour_info/1]). @@ -718,12 +718,12 @@ terminate(Reason, State = #state { module = Module, code_change(_OldVsn, State, _Extra) -> {ok, State}. -prioritise_info(flush, _State) -> +prioritise_info(flush, _Len, _State) -> 1; -prioritise_info({'DOWN', _MRef, process, _Pid, _Reason}, +prioritise_info({'DOWN', _MRef, process, _Pid, _Reason}, _Len, #state { members_state = MS }) when MS /= undefined -> 1; -prioritise_info(_, _State) -> +prioritise_info(_, _Len, _State) -> 0. diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 43fe3578..d706de3c 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -29,8 +29,8 @@ -export([init_with_backing_queue_state/7]). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, - handle_info/2, handle_pre_hibernate/1, prioritise_call/3, - prioritise_cast/2, prioritise_info/2, format_message_queue/2]). + handle_info/2, handle_pre_hibernate/1, prioritise_call/4, + prioritise_cast/3, prioritise_info/3, format_message_queue/2]). %% Queue's state -record(q, {q, @@ -956,7 +956,7 @@ emit_consumer_deleted(ChPid, ConsumerTag) -> %%---------------------------------------------------------------------------- -prioritise_call(Msg, _From, _State) -> +prioritise_call(Msg, _From, _Len, _State) -> case Msg of info -> 9; {info, _Items} -> 9; @@ -965,7 +965,7 @@ prioritise_call(Msg, _From, _State) -> _ -> 0 end. -prioritise_cast(Msg, _State) -> +prioritise_cast(Msg, _Len, _State) -> case Msg of delete_immediately -> 8; {set_ram_duration_target, _Duration} -> 8; @@ -974,7 +974,7 @@ prioritise_cast(Msg, _State) -> _ -> 0 end. -prioritise_info(Msg, #q{q = #amqqueue{exclusive_owner = DownPid}}) -> +prioritise_info(Msg, _Len, #q{q = #amqqueue{exclusive_owner = DownPid}}) -> case Msg of {'DOWN', _, process, DownPid, _} -> 8; update_ram_duration -> 8; diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a94d2ab5..c14a8b34 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -27,8 +27,8 @@ -export([force_event_refresh/0]). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, - handle_info/2, handle_pre_hibernate/1, prioritise_call/3, - prioritise_cast/2, prioritise_info/2, format_message_queue/2]). + handle_info/2, handle_pre_hibernate/1, prioritise_call/4, + prioritise_cast/3, prioritise_info/3, format_message_queue/2]). %% Internal -export([list_local/0]). @@ -213,20 +213,20 @@ init([Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, VHost, {ok, State1, hibernate, {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. -prioritise_call(Msg, _From, _State) -> +prioritise_call(Msg, _From, _Len, _State) -> case Msg of info -> 9; {info, _Items} -> 9; _ -> 0 end. -prioritise_cast(Msg, _State) -> +prioritise_cast(Msg, _Len, _State) -> case Msg of {confirm, _MsgSeqNos, _QPid} -> 5; _ -> 0 end. -prioritise_info(Msg, _State) -> +prioritise_info(Msg, _Len, _State) -> case Msg of emit_stats -> 7; _ -> 0 diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 2b15498e..05c27250 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -19,7 +19,7 @@ -behaviour(gen_server2). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, - handle_info/2, prioritise_call/3]). + handle_info/2, prioritise_call/4]). -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). -export([limit/2, can_send/3, ack/2, register/2, unregister/2]). @@ -126,8 +126,8 @@ is_blocked(Limiter) -> init([]) -> {ok, #lim{}}. -prioritise_call(get_limit, _From, _State) -> 9; -prioritise_call(_Msg, _From, _State) -> 0. +prioritise_call(get_limit, _From, _Len, _State) -> 9; +prioritise_call(_Msg, _From, _Len, _State) -> 0. handle_call({can_send, QPid, _AckRequired}, _From, State = #lim{blocked = true}) -> diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 1ba1420f..1d733cf7 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -27,8 +27,8 @@ -export([start_link/1, set_maximum_since_use/2, info/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3, handle_pre_hibernate/1, prioritise_call/3, - prioritise_cast/2, prioritise_info/2]). + code_change/3, handle_pre_hibernate/1, prioritise_call/4, + prioritise_cast/3, prioritise_info/3]). -export([joined/2, members_changed/3, handle_msg/3]). @@ -305,14 +305,14 @@ handle_pre_hibernate(State = #state { backing_queue = BQ, BQS3 = BQ:handle_pre_hibernate(BQS2), {hibernate, stop_rate_timer(State #state { backing_queue_state = BQS3 })}. -prioritise_call(Msg, _From, _State) -> +prioritise_call(Msg, _From, _Len, _State) -> case Msg of info -> 9; {gm_deaths, _Deaths} -> 5; _ -> 0 end. -prioritise_cast(Msg, _State) -> +prioritise_cast(Msg, _Len, _State) -> case Msg of {set_ram_duration_target, _Duration} -> 8; {set_maximum_since_use, _Age} -> 8; @@ -322,7 +322,7 @@ prioritise_cast(Msg, _State) -> _ -> 0 end. -prioritise_info(Msg, _State) -> +prioritise_info(Msg, _Len, _State) -> case Msg of update_ram_duration -> 8; sync_timeout -> 6; diff --git a/src/rabbit_msg_store.erl b/src/rabbit_msg_store.erl index c2e55022..d656098a 100644 --- a/src/rabbit_msg_store.erl +++ b/src/rabbit_msg_store.erl @@ -29,8 +29,8 @@ -export([transform_dir/3, force_recovery/2]). %% upgrade -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3, prioritise_call/3, prioritise_cast/2, - prioritise_info/2, format_message_queue/2]). + code_change/3, prioritise_call/4, prioritise_cast/3, + prioritise_info/3, format_message_queue/2]). %%---------------------------------------------------------------------------- @@ -738,7 +738,7 @@ init([Server, BaseDir, ClientRefs, StartupFunState]) -> hibernate, {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. -prioritise_call(Msg, _From, _State) -> +prioritise_call(Msg, _From, _Len, _State) -> case Msg of successfully_recovered_state -> 7; {new_client_state, _Ref, _Pid, _MODC, _CloseFDsFun} -> 7; @@ -746,7 +746,7 @@ prioritise_call(Msg, _From, _State) -> _ -> 0 end. -prioritise_cast(Msg, _State) -> +prioritise_cast(Msg, _Len, _State) -> case Msg of {combine_files, _Source, _Destination, _Reclaimed} -> 8; {delete_file, _File, _Reclaimed} -> 8; @@ -755,7 +755,7 @@ prioritise_cast(Msg, _State) -> _ -> 0 end. -prioritise_info(Msg, _State) -> +prioritise_info(Msg, _Len, _State) -> case Msg of sync -> 8; _ -> 0 diff --git a/src/rabbit_msg_store_gc.erl b/src/rabbit_msg_store_gc.erl index 3b61ed0b..bdabf406 100644 --- a/src/rabbit_msg_store_gc.erl +++ b/src/rabbit_msg_store_gc.erl @@ -23,7 +23,7 @@ -export([set_maximum_since_use/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, prioritise_cast/2]). + terminate/2, code_change/3, prioritise_cast/3]). -record(state, { pending_no_readers, @@ -79,8 +79,8 @@ init([MsgStoreState]) -> msg_store_state = MsgStoreState }, hibernate, {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. -prioritise_cast({set_maximum_since_use, _Age}, _State) -> 8; -prioritise_cast(_Msg, _State) -> 0. +prioritise_cast({set_maximum_since_use, _Age}, _Len, _State) -> 8; +prioritise_cast(_Msg, _Len, _State) -> 0. handle_call(stop, _From, State) -> {stop, normal, ok, State}. diff --git a/src/worker_pool_worker.erl b/src/worker_pool_worker.erl index 1ddcebb2..6db6d156 100644 --- a/src/worker_pool_worker.erl +++ b/src/worker_pool_worker.erl @@ -23,7 +23,7 @@ -export([set_maximum_since_use/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3, prioritise_cast/2]). + terminate/2, code_change/3, prioritise_cast/3]). %%---------------------------------------------------------------------------- @@ -73,8 +73,8 @@ init([WId]) -> {ok, WId, hibernate, {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. -prioritise_cast({set_maximum_since_use, _Age}, _State) -> 8; -prioritise_cast(_Msg, _State) -> 0. +prioritise_cast({set_maximum_since_use, _Age}, _Len, _State) -> 8; +prioritise_cast(_Msg, _Len, _State) -> 0. handle_call({submit, Fun}, From, WId) -> gen_server2:reply(From, run(Fun)), -- cgit v1.2.1 From 044bfbcecbc7e2a86f331d9a493d8d2daed5e260 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 8 Nov 2012 13:34:10 +0000 Subject: Fix arities, support 'drop'. --- src/gen_server2.erl | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 7bdfa91a..3ef45062 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -16,12 +16,14 @@ %% The original code could reorder messages when communicating with a %% process on a remote node that was not currently connected. %% -%% 4) The callback module can optionally implement prioritise_call/3, -%% prioritise_cast/2 and prioritise_info/2. These functions take -%% Message, From and State or just Message and State and return a -%% single integer representing the priority attached to the message. -%% Messages with higher priorities are processed before requests with -%% lower priorities. The default priority is 0. +%% 4) The callback module can optionally implement prioritise_call/4, +%% prioritise_cast/3 and prioritise_info/3. These functions take +%% Message, From, Length and State or just Message, Length and State +%% (where Length is the current number of messages waiting to be +%% processed) and return a single integer representing the priority +%% attached to the message, or 'drop' to ignore it. Messages with +%% higher priorities are processed before requests with lower +%% priorities. The default priority is 0. %% %% 5) The callback module can optionally implement %% handle_pre_hibernate/1 and handle_post_hibernate/1. These will be @@ -650,6 +652,9 @@ in({system, _From, _Req} = Input, GS2State) -> in(Input, GS2State = #gs2_state { prioritise_info = PI }) -> in(Input, PI(Input, GS2State), GS2State). +in(_Input, drop, GS2State) -> + GS2State; + in(Input, Priority, GS2State = #gs2_state { queue = Queue }) -> GS2State # gs2_state { queue = priority_queue:in(Input, Priority, Queue) }. @@ -1166,11 +1171,11 @@ whereis_name(Name) -> find_prioritisers(GS2State = #gs2_state { mod = Mod }) -> PrioriCall = function_exported_or_default( - Mod, 'prioritise_call', 3, + Mod, 'prioritise_call', 4, fun (_Msg, _From, _State) -> 0 end), - PrioriCast = function_exported_or_default(Mod, 'prioritise_cast', 2, + PrioriCast = function_exported_or_default(Mod, 'prioritise_cast', 3, fun (_Msg, _State) -> 0 end), - PrioriInfo = function_exported_or_default(Mod, 'prioritise_info', 2, + PrioriInfo = function_exported_or_default(Mod, 'prioritise_info', 3, fun (_Msg, _State) -> 0 end), GS2State #gs2_state { prioritise_call = PrioriCall, prioritise_cast = PrioriCast, @@ -1179,20 +1184,24 @@ find_prioritisers(GS2State = #gs2_state { mod = Mod }) -> function_exported_or_default(Mod, Fun, Arity, Default) -> case erlang:function_exported(Mod, Fun, Arity) of true -> case Arity of - 2 -> fun (Msg, GS2State = #gs2_state { queue = Queue, + 3 -> fun (Msg, GS2State = #gs2_state { queue = Queue, state = State }) -> Length = priority_queue:len(Queue), case catch Mod:Fun(Msg, Length, State) of + drop -> + drop; Res when is_integer(Res) -> Res; Err -> handle_common_termination(Err, Msg, GS2State) end end; - 3 -> fun (Msg, From, GS2State = #gs2_state { queue = Queue, + 4 -> fun (Msg, From, GS2State = #gs2_state { queue = Queue, state = State }) -> Length = priority_queue:len(Queue), case catch Mod:Fun(Msg, From, Length, State) of + drop -> + drop; Res when is_integer(Res) -> Res; Err -> -- cgit v1.2.1 From 7c6415eda10ea30feb706d9b38de354560abc504 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 8 Nov 2012 13:42:44 +0000 Subject: Doesn't make sense to drop calls. --- src/gen_server2.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 3ef45062..12390051 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -21,7 +21,8 @@ %% Message, From, Length and State or just Message, Length and State %% (where Length is the current number of messages waiting to be %% processed) and return a single integer representing the priority -%% attached to the message, or 'drop' to ignore it. Messages with +%% attached to the message, or 'drop' to ignore it (for +%% prioritise_cast/3 and prioritise_info/3 only). Messages with %% higher priorities are processed before requests with lower %% priorities. The default priority is 0. %% @@ -1200,8 +1201,6 @@ function_exported_or_default(Mod, Fun, Arity, Default) -> state = State }) -> Length = priority_queue:len(Queue), case catch Mod:Fun(Msg, From, Length, State) of - drop -> - drop; Res when is_integer(Res) -> Res; Err -> -- cgit v1.2.1 From 0f8065523d649cfb5420818e1d2ab60aa705bbcd Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 8 Nov 2012 13:47:01 +0000 Subject: Make priority_queue:len/1 fast. --- src/priority_queue.erl | 70 +++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/priority_queue.erl b/src/priority_queue.erl index 780fa2e9..14da39ad 100644 --- a/src/priority_queue.erl +++ b/src/priority_queue.erl @@ -69,9 +69,9 @@ %%---------------------------------------------------------------------------- new() -> - {queue, [], []}. + {queue, [], [], 0}. -is_queue({queue, R, F}) when is_list(R), is_list(F) -> +is_queue({queue, R, F, L}) when is_list(R), is_list(F), is_integer(L) -> true; is_queue({pqueue, Queues}) when is_list(Queues) -> lists:all(fun ({infinity, Q}) -> is_queue(Q); @@ -80,17 +80,17 @@ is_queue({pqueue, Queues}) when is_list(Queues) -> is_queue(_) -> false. -is_empty({queue, [], []}) -> +is_empty({queue, [], [], 0}) -> true; is_empty(_) -> false. -len({queue, R, F}) when is_list(R), is_list(F) -> - length(R) + length(F); +len({queue, _R, _F, L}) -> + L; len({pqueue, Queues}) -> lists:sum([len(Q) || {_, Q} <- Queues]). -to_list({queue, In, Out}) when is_list(In), is_list(Out) -> +to_list({queue, In, Out, _Len}) when is_list(In), is_list(Out) -> [{0, V} || V <- Out ++ lists:reverse(In, [])]; to_list({pqueue, Queues}) -> [{maybe_negate_priority(P), V} || {P, Q} <- Queues, @@ -99,13 +99,13 @@ to_list({pqueue, Queues}) -> in(Item, Q) -> in(Item, 0, Q). -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, 0, {queue, [_] = In, [], 1}) -> + {queue, [X], In, 2}; +in(X, 0, {queue, In, Out, Len}) when is_list(In), is_list(Out) -> + {queue, [X|In], Out, Len + 1}; +in(X, Priority, _Q = {queue, [], [], 0}) -> in(X, Priority, {pqueue, []}); -in(X, Priority, Q = {queue, _, _}) -> +in(X, Priority, Q = {queue, _, _, _}) -> in(X, Priority, {pqueue, [{0, Q}]}); in(X, Priority, {pqueue, Queues}) -> P = maybe_negate_priority(Priority), @@ -113,33 +113,33 @@ in(X, Priority, {pqueue, Queues}) -> {value, {_, Q}} -> lists:keyreplace(P, 1, Queues, {P, in(X, Q)}); false when P == infinity -> - [{P, {queue, [X], []}} | Queues]; + [{P, {queue, [X], [], 1}} | Queues]; false -> case Queues of [{infinity, InfQueue} | Queues1] -> [{infinity, InfQueue} | - lists:keysort(1, [{P, {queue, [X], []}} | Queues1])]; + lists:keysort(1, [{P, {queue, [X], [], 1}} | Queues1])]; _ -> - lists:keysort(1, [{P, {queue, [X], []}} | Queues]) + lists:keysort(1, [{P, {queue, [X], [], 1}} | Queues]) end end}. -out({queue, [], []} = Q) -> +out({queue, [], [], 0} = Q) -> {empty, Q}; -out({queue, [V], []}) -> - {{value, V}, {queue, [], []}}; -out({queue, [Y|In], []}) -> +out({queue, [V], [], 1}) -> + {{value, V}, {queue, [], [], 0}}; +out({queue, [Y|In], [], Len}) -> [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}}; + {{value, V}, {queue, [Y], Out}, Len - 1}; +out({queue, In, [V], Len}) when is_list(In) -> + {{value,V}, r2f(In, Len - 1)}; +out({queue, In,[V|Out], Len}) when is_list(In) -> + {{value, V}, {queue, In, Out, Len - 1}}; out({pqueue, [{P, Q} | Queues]}) -> {R, Q1} = out(Q), NewQ = case is_empty(Q1) of true -> case Queues of - [] -> {queue, [], []}; + [] -> {queue, [], [], 0}; [{0, OnlyQ}] -> OnlyQ; [_|_] -> {pqueue, Queues} end; @@ -147,13 +147,13 @@ out({pqueue, [{P, Q} | Queues]}) -> end, {R, NewQ}. -join(A, {queue, [], []}) -> +join(A, {queue, [], [], 0}) -> A; -join({queue, [], []}, B) -> +join({queue, [], [], 0}, B) -> B; -join({queue, AIn, AOut}, {queue, BIn, BOut}) -> - {queue, BIn, AOut ++ lists:reverse(AIn, BOut)}; -join(A = {queue, _, _}, {pqueue, BPQ}) -> +join({queue, AIn, AOut, ALen}, {queue, BIn, BOut, BLen}) -> + {queue, BIn, AOut ++ lists:reverse(AIn, BOut), ALen + BLen}; +join(A = {queue, _, _, _}, {pqueue, BPQ}) -> {Pre, Post} = lists:splitwith(fun ({P, _}) -> P < 0 orelse P == infinity end, BPQ), Post1 = case Post of @@ -162,7 +162,7 @@ join(A = {queue, _, _}, {pqueue, BPQ}) -> _ -> [ {0, A} | Post ] end, {pqueue, Pre ++ Post1}; -join({pqueue, APQ}, B = {queue, _, _}) -> +join({pqueue, APQ}, B = {queue, _, _, _}) -> {Pre, Post} = lists:splitwith(fun ({P, _}) -> P < 0 orelse P == infinity end, APQ), Post1 = case Post of @@ -185,10 +185,10 @@ merge([{PA, A}|As], Bs = [{PB, _}|_], Acc) when PA < PB orelse PA == infinity -> merge(As = [{_, _}|_], [{PB, B}|Bs], Acc) -> merge(As, Bs, [ {PB, B} | Acc ]). -r2f([]) -> {queue, [], []}; -r2f([_] = R) -> {queue, [], R}; -r2f([X,Y]) -> {queue, [X], [Y]}; -r2f([X,Y|R]) -> {queue, [X,Y], lists:reverse(R, [])}. +r2f([], 0) -> {queue, [], [], 0}; +r2f([_] = R, 1) -> {queue, [], R, 1}; +r2f([X,Y], 2) -> {queue, [X], [Y], 2}; +r2f([X,Y|R], L) -> {queue, [X,Y], lists:reverse(R, []), L}. maybe_negate_priority(infinity) -> infinity; maybe_negate_priority(P) -> -P. -- cgit v1.2.1 From 1640bb6af3b1a568de2da9bdf70b983269e2bd63 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 9 Nov 2012 10:56:47 +0000 Subject: specifies boot file for erl calls --- scripts/rabbitmq-defaults | 6 ++---- scripts/rabbitmq-plugins | 1 + scripts/rabbitmq-server | 2 +- scripts/rabbitmqctl | 1 + 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/rabbitmq-defaults b/scripts/rabbitmq-defaults index f618f584..c6923586 100644 --- a/scripts/rabbitmq-defaults +++ b/scripts/rabbitmq-defaults @@ -21,10 +21,8 @@ SYS_PREFIX= ### next line will be updated when generating a standalone release ERL_DIR= -### TODO fix these values -CLEAN_BOOT_FILE="${SYS_PREFIX}/releases/${RELEASE_VERSION}/start_clean" -SASL_BOOT_FILE="${SYS_PREFIX}/releases/${RELEASE_VERSION}/start_sasl" - +CLEAN_BOOT_FILE=start_clean +SASL_BOOT_FILE=start_sasl ## Set default values diff --git a/scripts/rabbitmq-plugins b/scripts/rabbitmq-plugins index d9347258..985c7122 100755 --- a/scripts/rabbitmq-plugins +++ b/scripts/rabbitmq-plugins @@ -31,6 +31,7 @@ exec ${ERL_DIR}erl \ -noinput \ -hidden \ -sname rabbitmq-plugins$$ \ + -boot "${CLEAN_BOOT_FILE}" \ -s rabbit_plugins_main \ -enabled_plugins_file "$RABBITMQ_ENABLED_PLUGINS_FILE" \ -plugins_dist_dir "$RABBITMQ_PLUGINS_DIR" \ diff --git a/scripts/rabbitmq-server b/scripts/rabbitmq-server index c3d61868..3253bd7b 100755 --- a/scripts/rabbitmq-server +++ b/scripts/rabbitmq-server @@ -107,7 +107,7 @@ exec ${ERL_DIR}erl \ -pa ${RABBITMQ_EBIN_ROOT} \ ${RABBITMQ_START_RABBIT} \ -sname ${RABBITMQ_NODENAME} \ - -boot start_sasl \ + -boot "${SASL_BOOT_FILE}" \ ${RABBITMQ_CONFIG_ARG} \ +W w \ ${RABBITMQ_SERVER_ERL_ARGS} \ diff --git a/scripts/rabbitmqctl b/scripts/rabbitmqctl index b7351624..5471abf7 100755 --- a/scripts/rabbitmqctl +++ b/scripts/rabbitmqctl @@ -32,6 +32,7 @@ exec ${ERL_DIR}erl \ -hidden \ ${RABBITMQ_CTL_ERL_ARGS} \ -sname rabbitmqctl$$ \ + -boot "${CLEAN_BOOT_FILE}" \ -s rabbit_control_main \ -nodename $RABBITMQ_NODENAME \ -extra "$@" -- cgit v1.2.1 From 79a2ec921ee4e2d2873a247a9414655a73aea154 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 9 Nov 2012 10:58:28 +0000 Subject: replaces boot files accordingly in rabbitmq-defaults --- packaging/mac/Makefile | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packaging/mac/Makefile b/packaging/mac/Makefile index a21ec88a..ea16e72d 100644 --- a/packaging/mac/Makefile +++ b/packaging/mac/Makefile @@ -2,7 +2,7 @@ VERSION=0.0.0 SOURCE_DIR=rabbitmq-server-$(VERSION) TARGET_DIR=rabbitmq_server-$(VERSION) TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) -RLS_DIR=$(TARGET_DIR)/release +RLS_DIR=$(TARGET_DIR)/release/$(TARGET_DIR) ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') ERTS_ROOT_DIR=$(shell erl -noshell -eval 'io:format("~s", [code:root_dir()]), halt().') @@ -25,6 +25,10 @@ dist: sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 \ + && sed -e 's:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":' \ $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults @@ -34,18 +38,21 @@ dist: $(MAKE) generate_release ## todo see where the .tar is being created - mkdirp -p $(RLS_DIR) + mkdir -p $(RLS_DIR) tar -C $(RLS_DIR) -xzf $(RABBITMQ_HOME)/rabbit.tar.gz # add minimal boot file cp $(ERTS_ROOT_DIR)/bin/start_clean.boot $(RLS_DIR)/releases/$(VERSION) cp $(ERTS_ROOT_DIR)/bin/start_sasl.boot $(RLS_DIR)/releases/$(VERSION) - tar -zcf $(TARGET_TARBALL).tar.gz $(RLS_DIR) +# move rabbitmq files to top level folder + mv $(RLS_DIR)/lib/rabbit-$(VERSION)/* $(RLS_DIR) + + tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) rm -rf $(SOURCE_DIR) $(TARGET_DIR) clean: clean_partial - rm -f rabbitmq-server-generic-unix-*.tar.gz + rm -f rabbitmq-server-mac-standalone-*.tar.gz clean_partial: rm -rf $(SOURCE_DIR) -- cgit v1.2.1 -- cgit v1.2.1 From 3684e4d03b6af3a7face8319b4bcaf7949f557d8 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 15 Nov 2012 11:56:11 +0000 Subject: emit names instead of pids in queue process' queue events --- src/rabbit_amqqueue.erl | 26 ++++++++++++-------------- src/rabbit_amqqueue_process.erl | 10 ++++------ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 9fb453c1..4684ad7c 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -34,7 +34,7 @@ -export([start_mirroring/1, stop_mirroring/1]). %% internal --export([internal_declare/2, internal_delete/2, run_backing_queue/3, +-export([internal_declare/2, internal_delete/1, run_backing_queue/3, set_ram_duration_target/2, set_maximum_since_use/2]). -include("rabbit.hrl"). @@ -156,11 +156,11 @@ -spec(notify_sent_queue_down/1 :: (pid()) -> 'ok'). -spec(unblock/2 :: (pid(), pid()) -> 'ok'). -spec(flush_all/2 :: (qpids(), pid()) -> 'ok'). --spec(internal_delete/2 :: - (name(), pid()) -> rabbit_types:ok_or_error('not_found') | - rabbit_types:connection_exit() | - fun (() -> rabbit_types:ok_or_error('not_found') | - rabbit_types:connection_exit())). +-spec(internal_delete/1 :: + (name()) -> rabbit_types:ok_or_error('not_found') | + rabbit_types:connection_exit() | + fun (() -> rabbit_types:ok_or_error('not_found') | + rabbit_types:connection_exit())). -spec(run_backing_queue/3 :: (pid(), atom(), (fun ((atom(), A) -> {[rabbit_types:msg_id()], A}))) -> 'ok'). @@ -257,7 +257,7 @@ internal_declare(Q = #amqqueue{name = QueueName}, false) -> [ExistingQ = #amqqueue{pid = QPid}] -> case rabbit_misc:is_process_alive(QPid) of true -> rabbit_misc:const(ExistingQ); - false -> TailFun = internal_delete(QueueName, QPid), + false -> TailFun = internal_delete(QueueName), fun () -> TailFun(), ExistingQ end end end @@ -574,7 +574,7 @@ internal_delete1(QueueName) -> %% after the transaction. rabbit_binding:remove_for_destination(QueueName). -internal_delete(QueueName, QPid) -> +internal_delete(QueueName) -> rabbit_misc:execute_mnesia_tx_with_tail( fun () -> case mnesia:wread({rabbit_queue, QueueName}) of @@ -584,8 +584,7 @@ internal_delete(QueueName, QPid) -> fun() -> ok = T(), ok = rabbit_event:notify(queue_deleted, - [{pid, QPid}, - {name, QueueName}]) + [{name, QueueName}]) end end end). @@ -605,7 +604,7 @@ stop_mirroring(QPid) -> ok = delegate_call(QPid, stop_mirroring). on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( fun () -> QsDels = - qlc:e(qlc:q([{{QName, Pid}, delete_queue(QName)} || + qlc:e(qlc:q([{QName, delete_queue(QName)} || #amqqueue{name = QName, pid = Pid, slave_pids = []} <- mnesia:table(rabbit_queue), @@ -618,10 +617,9 @@ on_node_down(Node) -> fun () -> T(), lists:foreach( - fun({QName, QPid}) -> + fun(QName) -> ok = rabbit_event:notify(queue_deleted, - [{pid, QPid}, - {name, QName}]) + [{name, QName}]) end, Qs) end end). diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 92b00db0..67f925a4 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -85,7 +85,7 @@ %%---------------------------------------------------------------------------- -define(STATISTICS_KEYS, - [pid, + [name, policy, exclusive_consumer_pid, exclusive_consumer_tag, @@ -101,16 +101,14 @@ ]). -define(CREATION_EVENT_KEYS, - [pid, - name, + [name, durable, auto_delete, arguments, owner_pid ]). --define(INFO_KEYS, - ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [pid]). +-define(INFO_KEYS, ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS). %%---------------------------------------------------------------------------- @@ -183,7 +181,7 @@ terminate(Reason, State = #q{q = #amqqueue{name = QName}, fun (BQS) -> BQS1 = BQ:delete_and_terminate(Reason, BQS), %% don't care if the internal delete doesn't return 'ok'. - rabbit_amqqueue:internal_delete(QName, self()), + rabbit_amqqueue:internal_delete(QName), BQS1 end, State). -- cgit v1.2.1 From 919323e6f72f587fc9493c2884e07203f8aa6ff1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 15 Nov 2012 12:18:32 +0000 Subject: reference queue by name in consumer events --- src/rabbit_amqqueue_process.erl | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 67f925a4..9bd465dd 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -275,7 +275,8 @@ terminate_shutdown(Fun, State) -> case BQS of undefined -> State1; _ -> ok = rabbit_memory_monitor:deregister(self()), - [emit_consumer_deleted(Ch, CTag) + QName = qname(State), + [emit_consumer_deleted(Ch, CTag, QName) || {Ch, CTag, _} <- consumers(State1)], State1#q{backing_queue_state = Fun(BQS)} end. @@ -589,9 +590,9 @@ remove_consumer(ChPid, ConsumerTag, Queue) -> (CP /= ChPid) or (CTag /= ConsumerTag) end, Queue). -remove_consumers(ChPid, Queue) -> +remove_consumers(ChPid, Queue, QName) -> queue:filter(fun ({CP, #consumer{tag = CTag}}) when CP =:= ChPid -> - emit_consumer_deleted(ChPid, CTag), + emit_consumer_deleted(ChPid, CTag, QName), false; (_) -> true @@ -632,7 +633,8 @@ handle_ch_down(DownPid, State = #q{exclusive_consumer = Holder, C = #cr{ch_pid = ChPid, acktags = ChAckTags, blocked_consumers = Blocked} -> - _ = remove_consumers(ChPid, Blocked), %% for stats emission + QName = qname(State), + _ = remove_consumers(ChPid, Blocked, QName), %% for stats emission ok = erase_ch_record(C), State1 = State#q{ exclusive_consumer = case Holder of @@ -640,7 +642,8 @@ handle_ch_down(DownPid, State = #q{exclusive_consumer = Holder, Other -> Other end, active_consumers = remove_consumers( - ChPid, State#q.active_consumers), + ChPid, State#q.active_consumers, + QName), senders = Senders1}, case should_auto_delete(State1) of true -> {stop, State1}; @@ -948,19 +951,19 @@ emit_stats(State) -> emit_stats(State, Extra) -> rabbit_event:notify(queue_stats, Extra ++ infos(?STATISTICS_KEYS, State)). -emit_consumer_created(ChPid, ConsumerTag, Exclusive, AckRequired) -> +emit_consumer_created(ChPid, ConsumerTag, Exclusive, AckRequired, QName) -> rabbit_event:notify(consumer_created, [{consumer_tag, ConsumerTag}, {exclusive, Exclusive}, {ack_required, AckRequired}, {channel, ChPid}, - {queue, self()}]). + {queue, QName}]). -emit_consumer_deleted(ChPid, ConsumerTag) -> +emit_consumer_deleted(ChPid, ConsumerTag, QName) -> rabbit_event:notify(consumer_deleted, [{consumer_tag, ConsumerTag}, {channel, ChPid}, - {queue, self()}]). + {queue, QName}]). %%---------------------------------------------------------------------------- @@ -1068,9 +1071,8 @@ handle_call({basic_get, ChPid, NoAck}, _From, handle_call({basic_consume, NoAck, ChPid, Limiter, ConsumerTag, ExclusiveConsume, OkMsg}, - _From, State = #q{exclusive_consumer = ExistingHolder}) -> - case check_exclusive_access(ExistingHolder, ExclusiveConsume, - State) of + _From, State = #q{exclusive_consumer = Holder}) -> + case check_exclusive_access(Holder, ExclusiveConsume, State) of in_use -> reply({error, exclusive_consume_unavailable}, State); ok -> @@ -1079,7 +1081,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, Consumer = #consumer{tag = ConsumerTag, ack_required = not NoAck}, ExclusiveConsumer = if ExclusiveConsume -> {ChPid, ConsumerTag}; - true -> ExistingHolder + true -> Holder end, State1 = State#q{has_had_consumers = true, exclusive_consumer = ExclusiveConsumer}, @@ -1094,7 +1096,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, run_message_queue(State1#q{active_consumers = AC1}) end, emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, - not NoAck), + not NoAck, qname(State2)), reply(ok, State2) end; @@ -1105,7 +1107,7 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, not_found -> reply(ok, State); C = #cr{blocked_consumers = Blocked} -> - emit_consumer_deleted(ChPid, ConsumerTag), + emit_consumer_deleted(ChPid, ConsumerTag, qname(State)), Blocked1 = remove_consumer(ChPid, ConsumerTag, Blocked), update_consumer_count(C#cr{blocked_consumers = Blocked1}, -1), State1 = State#q{ @@ -1115,7 +1117,7 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, end, active_consumers = remove_consumer( ChPid, ConsumerTag, - State#q.active_consumers)}, + State#q.active_consumers)}, case should_auto_delete(State1) of false -> reply(ok, ensure_expiry_timer(State1)); true -> stop_later(normal, From, ok, State1) @@ -1169,11 +1171,13 @@ handle_call(stop_mirroring, _From, State = #q{backing_queue = BQ, handle_call(force_event_refresh, _From, State = #q{exclusive_consumer = Exclusive}) -> rabbit_event:notify(queue_created, infos(?CREATION_EVENT_KEYS, State)), + QName = qname(State), case Exclusive of - none -> [emit_consumer_created(Ch, CTag, false, AckRequired) || + none -> [emit_consumer_created( + Ch, CTag, false, AckRequired, QName) || {Ch, CTag, AckRequired} <- consumers(State)]; {Ch, CTag} -> [{Ch, CTag, AckRequired}] = consumers(State), - emit_consumer_created(Ch, CTag, true, AckRequired) + emit_consumer_created(Ch, CTag, true, AckRequired, QName) end, reply(ok, State). -- cgit v1.2.1 From ed581bc28021574bf0356ec3f96104777203aa75 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 12:43:06 +0100 Subject: moves files from mac folder to release folder --- packaging/mac/Makefile | 68 ---------------------------------------------- packaging/release/Makefile | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 68 deletions(-) delete mode 100644 packaging/mac/Makefile create mode 100644 packaging/release/Makefile diff --git a/packaging/mac/Makefile b/packaging/mac/Makefile deleted file mode 100644 index ea16e72d..00000000 --- a/packaging/mac/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -VERSION=0.0.0 -SOURCE_DIR=rabbitmq-server-$(VERSION) -TARGET_DIR=rabbitmq_server-$(VERSION) -TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) -RLS_DIR=$(TARGET_DIR)/release/$(TARGET_DIR) - -ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') -ERTS_ROOT_DIR=$(shell erl -noshell -eval 'io:format("~s", [code:root_dir()]), halt().') - -# used to generate the erlang release -RABBITMQ_HOME=$(TARGET_DIR) -RABBITMQ_EBIN_ROOT=$(RABBITMQ_HOME)/ebin -RABBITMQ_PLUGINS_DIR=$(RABBITMQ_HOME)/plugins -RABBITMQ_PLUGINS_EXPAND_DIR=$(RABBITMQ_PLUGINS_DIR)/expand - -dist: - tar -zxf ../../dist/$(SOURCE_DIR).tar.gz - - $(MAKE) -C $(SOURCE_DIR) \ - TARGET_DIR=`pwd`/$(TARGET_DIR) \ - SBIN_DIR=`pwd`/$(TARGET_DIR)/sbin \ - MAN_DIR=`pwd`/$(TARGET_DIR)/share/man \ - install - - sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 \ - && sed -e 's:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults - - chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults - - mkdir -p $(TARGET_DIR)/etc/rabbitmq - - $(MAKE) generate_release - -## todo see where the .tar is being created - mkdir -p $(RLS_DIR) - tar -C $(RLS_DIR) -xzf $(RABBITMQ_HOME)/rabbit.tar.gz - -# add minimal boot file - cp $(ERTS_ROOT_DIR)/bin/start_clean.boot $(RLS_DIR)/releases/$(VERSION) - cp $(ERTS_ROOT_DIR)/bin/start_sasl.boot $(RLS_DIR)/releases/$(VERSION) - -# move rabbitmq files to top level folder - mv $(RLS_DIR)/lib/rabbit-$(VERSION)/* $(RLS_DIR) - - tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) - rm -rf $(SOURCE_DIR) $(TARGET_DIR) - -clean: clean_partial - rm -f rabbitmq-server-mac-standalone-*.tar.gz - -clean_partial: - rm -rf $(SOURCE_DIR) - rm -rf $(TARGET_DIR) - -.PHONY : generate_release -generate_release: - erl \ - -pa "$(RABBITMQ_EBIN_ROOT)" \ - -noinput \ - -hidden \ - -s rabbit_release \ - -extra "$(RABBITMQ_PLUGINS_DIR)" "$(RABBITMQ_PLUGINS_EXPAND_DIR)" "$(RABBITMQ_HOME)" diff --git a/packaging/release/Makefile b/packaging/release/Makefile new file mode 100644 index 00000000..ea16e72d --- /dev/null +++ b/packaging/release/Makefile @@ -0,0 +1,68 @@ +VERSION=0.0.0 +SOURCE_DIR=rabbitmq-server-$(VERSION) +TARGET_DIR=rabbitmq_server-$(VERSION) +TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) +RLS_DIR=$(TARGET_DIR)/release/$(TARGET_DIR) + +ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') +ERTS_ROOT_DIR=$(shell erl -noshell -eval 'io:format("~s", [code:root_dir()]), halt().') + +# used to generate the erlang release +RABBITMQ_HOME=$(TARGET_DIR) +RABBITMQ_EBIN_ROOT=$(RABBITMQ_HOME)/ebin +RABBITMQ_PLUGINS_DIR=$(RABBITMQ_HOME)/plugins +RABBITMQ_PLUGINS_EXPAND_DIR=$(RABBITMQ_PLUGINS_DIR)/expand + +dist: + tar -zxf ../../dist/$(SOURCE_DIR).tar.gz + + $(MAKE) -C $(SOURCE_DIR) \ + TARGET_DIR=`pwd`/$(TARGET_DIR) \ + SBIN_DIR=`pwd`/$(TARGET_DIR)/sbin \ + MAN_DIR=`pwd`/$(TARGET_DIR)/share/man \ + install + + sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 \ + && sed -e 's:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults + + chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults + + mkdir -p $(TARGET_DIR)/etc/rabbitmq + + $(MAKE) generate_release + +## todo see where the .tar is being created + mkdir -p $(RLS_DIR) + tar -C $(RLS_DIR) -xzf $(RABBITMQ_HOME)/rabbit.tar.gz + +# add minimal boot file + cp $(ERTS_ROOT_DIR)/bin/start_clean.boot $(RLS_DIR)/releases/$(VERSION) + cp $(ERTS_ROOT_DIR)/bin/start_sasl.boot $(RLS_DIR)/releases/$(VERSION) + +# move rabbitmq files to top level folder + mv $(RLS_DIR)/lib/rabbit-$(VERSION)/* $(RLS_DIR) + + tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) + rm -rf $(SOURCE_DIR) $(TARGET_DIR) + +clean: clean_partial + rm -f rabbitmq-server-mac-standalone-*.tar.gz + +clean_partial: + rm -rf $(SOURCE_DIR) + rm -rf $(TARGET_DIR) + +.PHONY : generate_release +generate_release: + erl \ + -pa "$(RABBITMQ_EBIN_ROOT)" \ + -noinput \ + -hidden \ + -s rabbit_release \ + -extra "$(RABBITMQ_PLUGINS_DIR)" "$(RABBITMQ_PLUGINS_EXPAND_DIR)" "$(RABBITMQ_HOME)" -- cgit v1.2.1 From 71e0da5f582eb7b7900cf09ca65a4a189453d4b7 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 13:00:48 +0100 Subject: renames release folder to standalone --- packaging/release/Makefile | 68 ----------------------------------------- packaging/standalone/Makefile | 70 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 68 deletions(-) delete mode 100644 packaging/release/Makefile create mode 100644 packaging/standalone/Makefile diff --git a/packaging/release/Makefile b/packaging/release/Makefile deleted file mode 100644 index ea16e72d..00000000 --- a/packaging/release/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -VERSION=0.0.0 -SOURCE_DIR=rabbitmq-server-$(VERSION) -TARGET_DIR=rabbitmq_server-$(VERSION) -TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) -RLS_DIR=$(TARGET_DIR)/release/$(TARGET_DIR) - -ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') -ERTS_ROOT_DIR=$(shell erl -noshell -eval 'io:format("~s", [code:root_dir()]), halt().') - -# used to generate the erlang release -RABBITMQ_HOME=$(TARGET_DIR) -RABBITMQ_EBIN_ROOT=$(RABBITMQ_HOME)/ebin -RABBITMQ_PLUGINS_DIR=$(RABBITMQ_HOME)/plugins -RABBITMQ_PLUGINS_EXPAND_DIR=$(RABBITMQ_PLUGINS_DIR)/expand - -dist: - tar -zxf ../../dist/$(SOURCE_DIR).tar.gz - - $(MAKE) -C $(SOURCE_DIR) \ - TARGET_DIR=`pwd`/$(TARGET_DIR) \ - SBIN_DIR=`pwd`/$(TARGET_DIR)/sbin \ - MAN_DIR=`pwd`/$(TARGET_DIR)/share/man \ - install - - sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 \ - && sed -e 's:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults - - chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults - - mkdir -p $(TARGET_DIR)/etc/rabbitmq - - $(MAKE) generate_release - -## todo see where the .tar is being created - mkdir -p $(RLS_DIR) - tar -C $(RLS_DIR) -xzf $(RABBITMQ_HOME)/rabbit.tar.gz - -# add minimal boot file - cp $(ERTS_ROOT_DIR)/bin/start_clean.boot $(RLS_DIR)/releases/$(VERSION) - cp $(ERTS_ROOT_DIR)/bin/start_sasl.boot $(RLS_DIR)/releases/$(VERSION) - -# move rabbitmq files to top level folder - mv $(RLS_DIR)/lib/rabbit-$(VERSION)/* $(RLS_DIR) - - tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) - rm -rf $(SOURCE_DIR) $(TARGET_DIR) - -clean: clean_partial - rm -f rabbitmq-server-mac-standalone-*.tar.gz - -clean_partial: - rm -rf $(SOURCE_DIR) - rm -rf $(TARGET_DIR) - -.PHONY : generate_release -generate_release: - erl \ - -pa "$(RABBITMQ_EBIN_ROOT)" \ - -noinput \ - -hidden \ - -s rabbit_release \ - -extra "$(RABBITMQ_PLUGINS_DIR)" "$(RABBITMQ_PLUGINS_EXPAND_DIR)" "$(RABBITMQ_HOME)" diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile new file mode 100644 index 00000000..4658afd5 --- /dev/null +++ b/packaging/standalone/Makefile @@ -0,0 +1,70 @@ +VERSION=0.0.0 +SOURCE_DIR=rabbitmq-server-$(VERSION) +TARGET_DIR=rabbitmq_server-$(VERSION) +TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) +RLS_DIR=$(TARGET_DIR)/release/$(TARGET_DIR) + +ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') +ERTS_ROOT_DIR=$(shell erl -noshell -eval 'io:format("~s", [code:root_dir()]), halt().') + +# used to generate the erlang release +RABBITMQ_HOME=$(TARGET_DIR) +RABBITMQ_EBIN_ROOT=$(RABBITMQ_HOME)/ebin +RABBITMQ_PLUGINS_DIR=$(RABBITMQ_HOME)/plugins +RABBITMQ_PLUGINS_EXPAND_DIR=$(RABBITMQ_PLUGINS_DIR)/expand + +dist: + tar -zxf ../../dist/$(SOURCE_DIR).tar.gz + + $(MAKE) -C $(SOURCE_DIR) \ + TARGET_DIR=`pwd`/$(TARGET_DIR) \ + SBIN_DIR=`pwd`/$(TARGET_DIR)/sbin \ + MAN_DIR=`pwd`/$(TARGET_DIR)/share/man \ + install + + sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 \ + && sed -e 's:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults + + chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults + + mkdir -p $(TARGET_DIR)/etc/rabbitmq + + $(MAKE) generate_release + +## todo see where the .tar is being created + mkdir -p $(RLS_DIR) + tar -C $(RLS_DIR) -xzf $(RABBITMQ_HOME)/rabbit.tar.gz + +# add minimal boot file + cp $(ERTS_ROOT_DIR)/bin/start_clean.boot $(RLS_DIR)/releases/$(VERSION) + cp $(ERTS_ROOT_DIR)/bin/start_sasl.boot $(RLS_DIR)/releases/$(VERSION) + +# move rabbitmq files to top level folder + mv $(RLS_DIR)/lib/rabbit-$(VERSION)/* $(RLS_DIR) + + tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) + rm -rf $(SOURCE_DIR) $(TARGET_DIR) + +clean: clean_partial + rm -f rabbitmq-server-mac-standalone-*.tar.gz + +clean_partial: + rm -rf $(SOURCE_DIR) + rm -rf $(TARGET_DIR) + +.PHONY : generate_release +generate_release: + erlc -I $(TARGET_DIR)/include/ -o erlang -Wall -v +debug_info -Duse_specs -Duse_proper_qc -pa $(TARGET_DIR)/ebin/ src/rabbit_release.erl + erl \ + -pa "$(RABBITMQ_EBIN_ROOT)" \ + -pa src \ + -noinput \ + -hidden \ + -s rabbit_release \ + -extra "$(RABBITMQ_PLUGINS_DIR)" "$(RABBITMQ_PLUGINS_EXPAND_DIR)" "$(RABBITMQ_HOME)" -- cgit v1.2.1 From a7442cc212a9b3d6065f80fcdeb5fe18142d7e8b Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 13:09:12 +0100 Subject: renames generated tarball --- packaging/standalone/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 4658afd5..077f67b7 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -1,7 +1,7 @@ VERSION=0.0.0 SOURCE_DIR=rabbitmq-server-$(VERSION) TARGET_DIR=rabbitmq_server-$(VERSION) -TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) +TARGET_TARBALL=rabbitmq-server-standalone-$(VERSION) RLS_DIR=$(TARGET_DIR)/release/$(TARGET_DIR) ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') @@ -52,7 +52,7 @@ dist: rm -rf $(SOURCE_DIR) $(TARGET_DIR) clean: clean_partial - rm -f rabbitmq-server-mac-standalone-*.tar.gz + rm -f rabbitmq-server-standalone-*.tar.gz clean_partial: rm -rf $(SOURCE_DIR) -- cgit v1.2.1 From ffdfc1f15f811a1ba4642359f930a266b371c990 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 13:09:49 +0100 Subject: moves rabbit_release to the standalone packaging folder --- packaging/standalone/src/rabbit_release.erl | 148 ++++++++++++++++++++++++++++ src/rabbit_release.erl | 148 ---------------------------- 2 files changed, 148 insertions(+), 148 deletions(-) create mode 100644 packaging/standalone/src/rabbit_release.erl delete mode 100644 src/rabbit_release.erl diff --git a/packaging/standalone/src/rabbit_release.erl b/packaging/standalone/src/rabbit_release.erl new file mode 100644 index 00000000..bcba2699 --- /dev/null +++ b/packaging/standalone/src/rabbit_release.erl @@ -0,0 +1,148 @@ +%% based on rabbit_prelaunch.erl from rabbitmq-server source code +-module(rabbit_release). + +-export([start/0, stop/0, make_tar/2]). + +-include("rabbit.hrl"). + +-define(BaseApps, [rabbit]). +-define(ERROR_CODE, 1). + +start() -> + %% Determine our various directories + [PluginsDistDir, UnpackedPluginDir, RabbitHome] = + init:get_plain_arguments(), + RootName = UnpackedPluginDir ++ "/rabbit", + + prepare_plugins(PluginsDistDir, UnpackedPluginDir), + + PluginAppNames = [ P#plugin.name || P <- rabbit_plugins:list(PluginsDistDir) ], + + %% we need to call find_plugins because it has the secondary effect of adding the + %% plugin ebin folder to the code path. We need that in order to load the plugin app + RequiredApps = find_plugins(UnpackedPluginDir), + + %% Build the entire set of dependencies - this will load the + %% applications along the way + AllApps = case catch sets:to_list(expand_dependencies(RequiredApps)) of + {failed_to_load_app, App, Err} -> + terminate("failed to load application ~s:~n~p", + [App, Err]); + AppList -> + AppList + end, + + %% we need a list of ERTS apps we need to ship with rabbit + BaseApps = AllApps -- PluginAppNames, + + AppVersions = [determine_version(App) || App <- BaseApps], + RabbitVersion = proplists:get_value(rabbit, AppVersions), + + %% Build the overall release descriptor + RDesc = {release, + {"rabbit", RabbitVersion}, + {erts, erlang:system_info(version)}, + AppVersions}, + + %% Write it out to $RABBITMQ_PLUGINS_EXPAND_DIR/rabbit.rel + rabbit_file:write_file(RootName ++ ".rel", io_lib:format("~p.~n", [RDesc])), + + %% Compile the script + systools:make_script(RootName), + systools:script2boot(RootName), + %% Make release tarfile + make_tar(RootName, RabbitHome), + terminate(0), + ok. + +stop() -> + ok. + +make_tar(Release, RabbitHome) -> + systools:make_tar(Release, + [ + {dirs, [docs, etc, include, plugins, sbin, share]}, + {erts, code:root_dir()}, + {outdir, RabbitHome} + ]). + +determine_version(App) -> + application:load(App), + {ok, Vsn} = application:get_key(App, vsn), + {App, Vsn}. + +delete_recursively(Fn) -> + case rabbit_file:recursive_delete([Fn]) of + ok -> ok; + {error, {Path, E}} -> {error, {cannot_delete, Path, E}}; + Error -> Error + end. + +prepare_plugins(PluginsDistDir, DestDir) -> + %% Eliminate the contents of the destination directory + case delete_recursively(DestDir) of + ok -> ok; + {error, E} -> terminate("Could not delete dir ~s (~p)", [DestDir, E]) + end, + case filelib:ensure_dir(DestDir ++ "/") of + ok -> ok; + {error, E2} -> terminate("Could not create dir ~s (~p)", [DestDir, E2]) + end, + + [prepare_plugin(Plugin, DestDir) || Plugin <- rabbit_plugins:list(PluginsDistDir)]. + +prepare_plugin(#plugin{type = ez, location = Location}, PluginDestDir) -> + zip:unzip(Location, [{cwd, PluginDestDir}]); +prepare_plugin(#plugin{type = dir, name = Name, location = Location}, + PluginsDestDir) -> + rabbit_file:recursive_copy(Location, + filename:join([PluginsDestDir, Name])). + +expand_dependencies(Pending) -> + expand_dependencies(sets:new(), Pending). +expand_dependencies(Current, []) -> + Current; +expand_dependencies(Current, [Next|Rest]) -> + case sets:is_element(Next, Current) of + true -> + expand_dependencies(Current, Rest); + false -> + case application:load(Next) of + ok -> + ok; + {error, {already_loaded, _}} -> + ok; + {error, Reason} -> + throw({failed_to_load_app, Next, Reason}) + end, + {ok, Required} = application:get_key(Next, applications), + Unique = [A || A <- Required, not(sets:is_element(A, Current))], + expand_dependencies(sets:add_element(Next, Current), Rest ++ Unique) + end. + +find_plugins(PluginDir) -> + [prepare_dir_plugin(PluginName) || + PluginName <- filelib:wildcard(PluginDir ++ "/*/ebin/*.app")]. + +prepare_dir_plugin(PluginAppDescFn) -> + %% Add the plugin ebin directory to the load path + PluginEBinDirN = filename:dirname(PluginAppDescFn), + code:add_path(PluginEBinDirN), + + %% We want the second-last token + NameTokens = string:tokens(PluginAppDescFn,"/."), + PluginNameString = lists:nth(length(NameTokens) - 1, NameTokens), + list_to_atom(PluginNameString). + +terminate(Fmt, Args) -> + io:format("ERROR: " ++ Fmt ++ "~n", Args), + terminate(?ERROR_CODE). + +terminate(Status) -> + case os:type() of + {unix, _} -> halt(Status); + {win32, _} -> init:stop(Status), + receive + after infinity -> ok + end + end. diff --git a/src/rabbit_release.erl b/src/rabbit_release.erl deleted file mode 100644 index bcba2699..00000000 --- a/src/rabbit_release.erl +++ /dev/null @@ -1,148 +0,0 @@ -%% based on rabbit_prelaunch.erl from rabbitmq-server source code --module(rabbit_release). - --export([start/0, stop/0, make_tar/2]). - --include("rabbit.hrl"). - --define(BaseApps, [rabbit]). --define(ERROR_CODE, 1). - -start() -> - %% Determine our various directories - [PluginsDistDir, UnpackedPluginDir, RabbitHome] = - init:get_plain_arguments(), - RootName = UnpackedPluginDir ++ "/rabbit", - - prepare_plugins(PluginsDistDir, UnpackedPluginDir), - - PluginAppNames = [ P#plugin.name || P <- rabbit_plugins:list(PluginsDistDir) ], - - %% we need to call find_plugins because it has the secondary effect of adding the - %% plugin ebin folder to the code path. We need that in order to load the plugin app - RequiredApps = find_plugins(UnpackedPluginDir), - - %% Build the entire set of dependencies - this will load the - %% applications along the way - AllApps = case catch sets:to_list(expand_dependencies(RequiredApps)) of - {failed_to_load_app, App, Err} -> - terminate("failed to load application ~s:~n~p", - [App, Err]); - AppList -> - AppList - end, - - %% we need a list of ERTS apps we need to ship with rabbit - BaseApps = AllApps -- PluginAppNames, - - AppVersions = [determine_version(App) || App <- BaseApps], - RabbitVersion = proplists:get_value(rabbit, AppVersions), - - %% Build the overall release descriptor - RDesc = {release, - {"rabbit", RabbitVersion}, - {erts, erlang:system_info(version)}, - AppVersions}, - - %% Write it out to $RABBITMQ_PLUGINS_EXPAND_DIR/rabbit.rel - rabbit_file:write_file(RootName ++ ".rel", io_lib:format("~p.~n", [RDesc])), - - %% Compile the script - systools:make_script(RootName), - systools:script2boot(RootName), - %% Make release tarfile - make_tar(RootName, RabbitHome), - terminate(0), - ok. - -stop() -> - ok. - -make_tar(Release, RabbitHome) -> - systools:make_tar(Release, - [ - {dirs, [docs, etc, include, plugins, sbin, share]}, - {erts, code:root_dir()}, - {outdir, RabbitHome} - ]). - -determine_version(App) -> - application:load(App), - {ok, Vsn} = application:get_key(App, vsn), - {App, Vsn}. - -delete_recursively(Fn) -> - case rabbit_file:recursive_delete([Fn]) of - ok -> ok; - {error, {Path, E}} -> {error, {cannot_delete, Path, E}}; - Error -> Error - end. - -prepare_plugins(PluginsDistDir, DestDir) -> - %% Eliminate the contents of the destination directory - case delete_recursively(DestDir) of - ok -> ok; - {error, E} -> terminate("Could not delete dir ~s (~p)", [DestDir, E]) - end, - case filelib:ensure_dir(DestDir ++ "/") of - ok -> ok; - {error, E2} -> terminate("Could not create dir ~s (~p)", [DestDir, E2]) - end, - - [prepare_plugin(Plugin, DestDir) || Plugin <- rabbit_plugins:list(PluginsDistDir)]. - -prepare_plugin(#plugin{type = ez, location = Location}, PluginDestDir) -> - zip:unzip(Location, [{cwd, PluginDestDir}]); -prepare_plugin(#plugin{type = dir, name = Name, location = Location}, - PluginsDestDir) -> - rabbit_file:recursive_copy(Location, - filename:join([PluginsDestDir, Name])). - -expand_dependencies(Pending) -> - expand_dependencies(sets:new(), Pending). -expand_dependencies(Current, []) -> - Current; -expand_dependencies(Current, [Next|Rest]) -> - case sets:is_element(Next, Current) of - true -> - expand_dependencies(Current, Rest); - false -> - case application:load(Next) of - ok -> - ok; - {error, {already_loaded, _}} -> - ok; - {error, Reason} -> - throw({failed_to_load_app, Next, Reason}) - end, - {ok, Required} = application:get_key(Next, applications), - Unique = [A || A <- Required, not(sets:is_element(A, Current))], - expand_dependencies(sets:add_element(Next, Current), Rest ++ Unique) - end. - -find_plugins(PluginDir) -> - [prepare_dir_plugin(PluginName) || - PluginName <- filelib:wildcard(PluginDir ++ "/*/ebin/*.app")]. - -prepare_dir_plugin(PluginAppDescFn) -> - %% Add the plugin ebin directory to the load path - PluginEBinDirN = filename:dirname(PluginAppDescFn), - code:add_path(PluginEBinDirN), - - %% We want the second-last token - NameTokens = string:tokens(PluginAppDescFn,"/."), - PluginNameString = lists:nth(length(NameTokens) - 1, NameTokens), - list_to_atom(PluginNameString). - -terminate(Fmt, Args) -> - io:format("ERROR: " ++ Fmt ++ "~n", Args), - terminate(?ERROR_CODE). - -terminate(Status) -> - case os:type() of - {unix, _} -> halt(Status); - {win32, _} -> init:stop(Status), - receive - after infinity -> ok - end - end. -- cgit v1.2.1 From d7358f7873a1520251f5ee37597445ec824ac114 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 13:24:29 +0100 Subject: fixes erlc invocation for rabbit_release.erl --- packaging/standalone/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 077f67b7..c57387d8 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -60,7 +60,7 @@ clean_partial: .PHONY : generate_release generate_release: - erlc -I $(TARGET_DIR)/include/ -o erlang -Wall -v +debug_info -Duse_specs -Duse_proper_qc -pa $(TARGET_DIR)/ebin/ src/rabbit_release.erl + erlc -I $(TARGET_DIR)/include/ -o src -Wall -v +debug_info -Duse_specs -Duse_proper_qc -pa $(TARGET_DIR)/ebin/ src/rabbit_release.erl erl \ -pa "$(RABBITMQ_EBIN_ROOT)" \ -pa src \ @@ -68,3 +68,4 @@ generate_release: -hidden \ -s rabbit_release \ -extra "$(RABBITMQ_PLUGINS_DIR)" "$(RABBITMQ_PLUGINS_EXPAND_DIR)" "$(RABBITMQ_HOME)" + rm src/rabbit_release.beam -- cgit v1.2.1 From 287dc46703160f83e34e186c807058f1fd381699 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 13:27:59 +0100 Subject: moves the sed invocation to its own target --- packaging/standalone/Makefile | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index c57387d8..f979bb0c 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -22,16 +22,7 @@ dist: MAN_DIR=`pwd`/$(TARGET_DIR)/share/man \ install - sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 \ - && sed -e 's:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults - - chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults + $(MAKE) fix_rabbitmq_defaults mkdir -p $(TARGET_DIR)/etc/rabbitmq @@ -69,3 +60,19 @@ generate_release: -s rabbit_release \ -extra "$(RABBITMQ_PLUGINS_DIR)" "$(RABBITMQ_PLUGINS_EXPAND_DIR)" "$(RABBITMQ_HOME)" rm src/rabbit_release.beam + +## Here we set the RABBITMQ_HOME variable, +## then we make ERL_DIR point to our released erl +## and we add the paths to our released start_clean and start_sasl boot scripts +.PHONY : fix_rabbitmq_defaults +fix_rabbitmq_defaults: + sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 \ + && sed -e 's:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":' \ + $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults + + chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults -- cgit v1.2.1 From f3654f1ece558874e1e5500c95b92884947caff8 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 13:30:44 +0100 Subject: removes todo comment --- packaging/standalone/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index f979bb0c..cd85ffca 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -28,7 +28,6 @@ dist: $(MAKE) generate_release -## todo see where the .tar is being created mkdir -p $(RLS_DIR) tar -C $(RLS_DIR) -xzf $(RABBITMQ_HOME)/rabbit.tar.gz -- cgit v1.2.1 From 73110798abe6b3ca4a1091f8597d0a09d9a07026 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 13:58:30 +0100 Subject: cosmetics + comments --- packaging/standalone/src/rabbit_release.erl | 34 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/packaging/standalone/src/rabbit_release.erl b/packaging/standalone/src/rabbit_release.erl index bcba2699..a7569595 100644 --- a/packaging/standalone/src/rabbit_release.erl +++ b/packaging/standalone/src/rabbit_release.erl @@ -1,4 +1,18 @@ -%% based on rabbit_prelaunch.erl from rabbitmq-server source code +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is VMware, Inc. +%% Copyright (c) 2007-2012 VMware, Inc. All rights reserved. +%% -module(rabbit_release). -export([start/0, stop/0, make_tar/2]). @@ -8,6 +22,13 @@ -define(BaseApps, [rabbit]). -define(ERROR_CODE, 1). +%% This module is based on rabbit_prelaunch.erl from rabbitmq-server source code +%% We need to calculate all the ERTS apps we need to ship with a +%% standalone rabbit. To acomplish that we need to unpack and load the plugins +%% apps that are shiped with rabbit. +%% Once we get that we generate an erlang release inside a tarball. +%% Our make file will work with that release to generate our final rabbitmq +%% package. start() -> %% Determine our various directories [PluginsDistDir, UnpackedPluginDir, RabbitHome] = @@ -16,10 +37,12 @@ start() -> prepare_plugins(PluginsDistDir, UnpackedPluginDir), - PluginAppNames = [ P#plugin.name || P <- rabbit_plugins:list(PluginsDistDir) ], + PluginAppNames = [P#plugin.name || + P <- rabbit_plugins:list(PluginsDistDir)], - %% we need to call find_plugins because it has the secondary effect of adding the - %% plugin ebin folder to the code path. We need that in order to load the plugin app + %% we need to call find_plugins because it has the secondary effect of + %% adding the plugin ebin folder to the code path. + %% We need that in order to load the plugin app RequiredApps = find_plugins(UnpackedPluginDir), %% Build the entire set of dependencies - this will load the @@ -89,7 +112,8 @@ prepare_plugins(PluginsDistDir, DestDir) -> {error, E2} -> terminate("Could not create dir ~s (~p)", [DestDir, E2]) end, - [prepare_plugin(Plugin, DestDir) || Plugin <- rabbit_plugins:list(PluginsDistDir)]. + [prepare_plugin(Plugin, DestDir) || + Plugin <- rabbit_plugins:list(PluginsDistDir)]. prepare_plugin(#plugin{type = ez, location = Location}, PluginDestDir) -> zip:unzip(Location, [{cwd, PluginDestDir}]); -- cgit v1.2.1 From 7d94f4241fcb354c60aed2b50d6a08fb192d9191 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Fri, 16 Nov 2012 15:19:23 +0100 Subject: cleans up unused/duplicated functions --- packaging/standalone/src/rabbit_release.erl | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/packaging/standalone/src/rabbit_release.erl b/packaging/standalone/src/rabbit_release.erl index a7569595..877fd73e 100644 --- a/packaging/standalone/src/rabbit_release.erl +++ b/packaging/standalone/src/rabbit_release.erl @@ -15,7 +15,7 @@ %% -module(rabbit_release). --export([start/0, stop/0, make_tar/2]). +-export([start/0]). -include("rabbit.hrl"). @@ -75,10 +75,7 @@ start() -> systools:script2boot(RootName), %% Make release tarfile make_tar(RootName, RabbitHome), - terminate(0), - ok. - -stop() -> + rabbit_misc:quit(0), ok. make_tar(Release, RabbitHome) -> @@ -160,13 +157,4 @@ prepare_dir_plugin(PluginAppDescFn) -> terminate(Fmt, Args) -> io:format("ERROR: " ++ Fmt ++ "~n", Args), - terminate(?ERROR_CODE). - -terminate(Status) -> - case os:type() of - {unix, _} -> halt(Status); - {win32, _} -> init:stop(Status), - receive - after infinity -> ok - end - end. + rabbit_misc:quit(?ERROR_CODE). -- cgit v1.2.1 From e2f5beaf5d2f9db139fc8cfa4e61d4d5f06bbbff Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 16 Nov 2012 14:54:28 +0000 Subject: propagate API change --- src/rabbit_tests.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 096f9490..e25843c3 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2539,7 +2539,7 @@ test_queue_recover() -> {{_Msg1, true, _AckTag1, CountMinusOne}, VQ2} = rabbit_variable_queue:fetch(true, VQ1), _VQ3 = rabbit_variable_queue:delete_and_terminate(shutdown, VQ2), - rabbit_amqqueue:internal_delete(QName, QPid1) + rabbit_amqqueue:internal_delete(QName) end), passed. -- cgit v1.2.1 From 97491563e8d8fb1cac3b6f01b5471b78307bc11e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 16 Nov 2012 16:53:56 +0000 Subject: identify queues by name rather than pid in channel stats - in deliver_to_queues, take the result of rabbit_amqqueue:lookup and use it to a) add entries to a QPid -> QName mapping in the state for all master pids, and b) setup monitors for all master pids - for the stats creation in deliver_to_queues, map the DeliveredQPids to the associated QNames, via the mapping in the State. Note that this will ignore slave pids, hence we only get one stat per QName (which is good). Also, in the event that the master died between lookup and delivery (and the delivery was 'mandatory'), we will not record any stats at all. - in monitor_delivering_queue, which is called by basic.{consume,get} we add to the mapping - in ack/2 we use the mapping to obtain QNames from QPids, and use that in stats. Since a queue may have vanished prior to the ack/reject arriving, we need to handle the case of no entry being present in the mapping for the given QPid. - in record_sent we have the QName anyway, so can just record stats against that instead of the QPid. - in the 'DOWN' handler we use the mapping to determine the QName from the pid, and pass that to erase_queue_stats, which can now remove entries based on the QName. We then remove the entry from the mapping. --- src/rabbit_channel.erl | 124 ++++++++++++++++++++++++++++--------------------- src/rabbit_tests.erl | 29 ++++++------ 2 files changed, 86 insertions(+), 67 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index b97af6d8..f8f099f6 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -35,8 +35,9 @@ -record(ch, {state, protocol, channel, reader_pid, writer_pid, conn_pid, conn_name, limiter, tx_status, next_tag, unacked_message_q, uncommitted_message_q, uncommitted_acks, uncommitted_nacks, user, - virtual_host, most_recently_declared_queue, queue_monitors, - consumer_mapping, blocking, queue_consumers, delivering_queues, + virtual_host, most_recently_declared_queue, + queue_names, queue_monitors, consumer_mapping, + blocking, queue_consumers, delivering_queues, queue_collector_pid, stats_timer, confirm_enabled, publish_seqno, unconfirmed, confirmed, capabilities, trace_state}). @@ -194,6 +195,7 @@ init([Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, VHost, user = User, virtual_host = VHost, most_recently_declared_queue = <<>>, + queue_names = dict:new(), queue_monitors = pmon:new(), consumer_mapping = dict:new(), blocking = sets:new(), @@ -334,9 +336,13 @@ handle_info({'DOWN', _MRef, process, QPid, Reason}, State) -> State3 = handle_consuming_queue_down(QPid, State2), State4 = handle_delivering_queue_down(QPid, State3), credit_flow:peer_down(QPid), - erase_queue_stats(QPid), - noreply(State4#ch{queue_monitors = pmon:erase( - QPid, State4#ch.queue_monitors)}); + #ch{queue_names = QNames, queue_monitors = QMons} = State4, + case dict:find(QPid, QNames) of + {ok, QName} -> erase_queue_stats(QName); + error -> ok + end, + noreply(State4#ch{queue_names = dict:erase(QPid, QNames), + queue_monitors = pmon:erase(QPid, QMons)}); handle_info({'EXIT', _Pid, Reason}, State) -> {stop, Reason, State}. @@ -677,7 +683,7 @@ handle_method(#'basic.get'{queue = QueueNameBin, QueueName, ConnPid, fun (Q) -> rabbit_amqqueue:basic_get(Q, self(), NoAck) end) of {ok, MessageCount, - Msg = {_QName, QPid, _MsgId, Redelivered, + Msg = {QName, QPid, _MsgId, Redelivered, #basic_message{exchange_name = ExchangeName, routing_keys = [RoutingKey | _CcRoutes], content = Content}}} -> @@ -689,7 +695,7 @@ handle_method(#'basic.get'{queue = QueueNameBin, routing_key = RoutingKey, message_count = MessageCount}, Content), - State1 = monitor_delivering_queue(NoAck, QPid, State), + State1 = monitor_delivering_queue(NoAck, QPid, QName, State), {noreply, record_sent(none, not(NoAck), Msg, State1)}; empty -> {reply, #'basic.get_empty'{}, State} @@ -728,10 +734,11 @@ handle_method(#'basic.consume'{queue = QueueNameBin, consumer_tag = ActualConsumerTag})), Q} end) of - {ok, Q = #amqqueue{pid = QPid}} -> + {ok, Q = #amqqueue{pid = QPid, name = QName}} -> CM1 = dict:store(ActualConsumerTag, Q, ConsumerMapping), State1 = monitor_delivering_queue( - NoAck, QPid, State#ch{consumer_mapping = CM1}), + NoAck, QPid, QName, + State#ch{consumer_mapping = CM1}), {noreply, case NoWait of true -> consumer_monitor(ActualConsumerTag, State1); @@ -1126,9 +1133,12 @@ consumer_monitor(ConsumerTag, State end. -monitor_delivering_queue(NoAck, QPid, State = #ch{queue_monitors = QMons, - delivering_queues = DQ}) -> - State#ch{queue_monitors = pmon:monitor(QPid, QMons), +monitor_delivering_queue(NoAck, QPid, QName, + State = #ch{queue_names = QNames, + queue_monitors = QMons, + delivering_queues = DQ}) -> + State#ch{queue_names = dict:store(QPid, QName, QNames), + queue_monitors = pmon:monitor(QPid, QMons), delivering_queues = case NoAck of true -> DQ; false -> sets:add_element(QPid, DQ) @@ -1233,18 +1243,18 @@ reject(Requeue, Acked, Limiter) -> ok = notify_limiter(Limiter, Acked). record_sent(ConsumerTag, AckRequired, - Msg = {_QName, QPid, MsgId, Redelivered, _Message}, + Msg = {QName, QPid, MsgId, Redelivered, _Message}, State = #ch{unacked_message_q = UAMQ, next_tag = DeliveryTag, trace_state = TraceState}) -> - incr_stats([{queue_stats, QPid, 1}], case {ConsumerTag, AckRequired} of - {none, true} -> get; - {none, false} -> get_no_ack; - {_ , true} -> deliver; - {_ , false} -> deliver_no_ack - end, State), + incr_stats([{queue_stats, QName, 1}], case {ConsumerTag, AckRequired} of + {none, true} -> get; + {none, false} -> get_no_ack; + {_ , true} -> deliver; + {_ , false} -> deliver_no_ack + end, State), case Redelivered of - true -> incr_stats([{queue_stats, QPid, 1}], redeliver, State); + true -> incr_stats([{queue_stats, QName, 1}], redeliver, State); false -> ok end, rabbit_trace:tap_trace_out(Msg, TraceState), @@ -1277,11 +1287,15 @@ collect_acks(ToAcc, PrefixAcc, Q, DeliveryTag, Multiple) -> precondition_failed("unknown delivery tag ~w", [DeliveryTag]) end. -ack(Acked, State) -> +ack(Acked, State = #ch{queue_names = QNames}) -> Incs = fold_per_queue( fun (QPid, MsgIds, L) -> ok = rabbit_amqqueue:ack(QPid, MsgIds, self()), - [{queue_stats, QPid, length(MsgIds)} | L] + case dict:find(QPid, QNames) of + {ok, QName} -> Count = length(MsgIds), + [{queue_stats, QName, Count} | L]; + error -> L + end end, [], Acked), ok = notify_limiter(State#ch.limiter, Acked), incr_stats(Incs, ack, State). @@ -1339,23 +1353,30 @@ notify_limiter(Limiter, Acked) -> deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{ exchange_name = XName}, msg_seq_no = MsgSeqNo}, - QNames}, State) -> - {RoutingRes, DeliveredQPids} = - rabbit_amqqueue:deliver_flow(rabbit_amqqueue:lookup(QNames), Delivery), - State1 = State#ch{queue_monitors = - pmon:monitor_all(DeliveredQPids, - State#ch.queue_monitors)}, - State2 = process_routing_result(RoutingRes, DeliveredQPids, - XName, MsgSeqNo, Message, State1), + DelQNames}, State = #ch{queue_names = QNames, + queue_monitors = QMons}) -> + Qs = rabbit_amqqueue:lookup(DelQNames), + {RoutingRes, DeliveredQPids} = rabbit_amqqueue:deliver_flow(Qs, Delivery), + {QNames1, QMons1} = + lists:foldl(fun (#amqqueue{pid = QPid, name = QName}, + {QNames0, QMons0}) -> + {dict:store(QPid, QName, QNames0), + pmon:monitor(QPid, QMons0)} + end, {QNames, pmon:monitor_all(DeliveredQPids, QMons)}, Qs), + State1 = process_routing_result(RoutingRes, DeliveredQPids, + XName, MsgSeqNo, Message, + State#ch{queue_names = QNames1, + queue_monitors = QMons1}), incr_stats([{exchange_stats, XName, 1} | - [{queue_exchange_stats, {QPid, XName}, 1} || - QPid <- DeliveredQPids]], publish, State2), - State2. + [{queue_exchange_stats, {QName, XName}, 1} || + QPid <- DeliveredQPids, + {ok, QName} <- [dict:find(QPid, QNames1)]]], + publish, State1), + State1. process_routing_result(unroutable, _, XName, MsgSeqNo, Msg, State) -> ok = basic_return(Msg, State, no_route), - incr_stats([{exchange_stats, Msg#basic_message.exchange_name, 1}], - return_unroutable, State), + incr_stats([{exchange_stats, XName, 1}], return_unroutable, State), record_confirm(MsgSeqNo, XName, State); process_routing_result(routed, [], XName, MsgSeqNo, _, State) -> record_confirm(MsgSeqNo, XName, State); @@ -1489,24 +1510,23 @@ emit_stats(State) -> emit_stats(State, []). emit_stats(State, Extra) -> - CoarseStats = infos(?STATISTICS_KEYS, State), + Coarse = infos(?STATISTICS_KEYS, State), case rabbit_event:stats_level(State, #ch.stats_timer) of - coarse -> - rabbit_event:notify(channel_stats, Extra ++ CoarseStats); - fine -> - FineStats = - [{channel_queue_stats, - [{QPid, Stats} || {{queue_stats, QPid}, Stats} <- get()]}, - {channel_exchange_stats, - [{X, Stats} || {{exchange_stats, X}, Stats} <- get()]}, - {channel_queue_exchange_stats, - [{QX, Stats} || - {{queue_exchange_stats, QX}, Stats} <- get()]}], - rabbit_event:notify(channel_stats, - Extra ++ CoarseStats ++ FineStats) + coarse -> rabbit_event:notify(channel_stats, Extra ++ Coarse); + fine -> Fine = [{channel_queue_stats, + [{QName, Stats} || + {{queue_stats, QName}, Stats} <- get()]}, + {channel_exchange_stats, + [{XName, Stats} || + {{exchange_stats, XName}, Stats} <- get()]}, + {channel_queue_exchange_stats, + [{QX, Stats} || + {{queue_exchange_stats, QX}, Stats} <- get()]}], + rabbit_event:notify(channel_stats, Extra ++ Coarse ++ Fine) end. -erase_queue_stats(QPid) -> - erase({queue_stats, QPid}), +erase_queue_stats(QName) -> + erase({queue_stats, QName}), [erase({queue_exchange_stats, QX}) || - {{queue_exchange_stats, QX = {QPid0, _}}, _} <- get(), QPid =:= QPid0]. + {{queue_exchange_stats, QX = {QName0, _}}, _} <- get(), + QName0 =:= QName]. diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index e25843c3..8b2f3b3a 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1286,8 +1286,7 @@ test_statistics() -> QName = receive #'queue.declare_ok'{queue = Q0} -> Q0 after ?TIMEOUT -> throw(failed_to_receive_queue_declare_ok) end, - {ok, Q} = rabbit_amqqueue:lookup(rabbit_misc:r(<<"/">>, queue, QName)), - QPid = Q#amqqueue.pid, + QRes = rabbit_misc:r(<<"/">>, queue, QName), X = rabbit_misc:r(<<"/">>, exchange, <<"">>), rabbit_tests_event_receiver:start(self(), [node()], [channel_stats]), @@ -1311,9 +1310,9 @@ test_statistics() -> length(proplists:get_value( channel_queue_exchange_stats, E)) > 0 end), - [{QPid,[{get,1}]}] = proplists:get_value(channel_queue_stats, Event2), + [{QRes, [{get,1}]}] = proplists:get_value(channel_queue_stats, Event2), [{X,[{publish,1}]}] = proplists:get_value(channel_exchange_stats, Event2), - [{{QPid,X},[{publish,1}]}] = + [{{QRes,X},[{publish,1}]}] = proplists:get_value(channel_queue_exchange_stats, Event2), %% Check the stats remove stuff on queue deletion @@ -1338,31 +1337,31 @@ test_refresh_events(SecondaryNode) -> [channel_created, queue_created]), {_Writer, Ch} = test_spawn(), - expect_events(Ch, channel_created), + expect_events(pid, Ch, channel_created), rabbit_channel:shutdown(Ch), {_Writer2, Ch2} = test_spawn(SecondaryNode), - expect_events(Ch2, channel_created), + expect_events(pid, Ch2, channel_created), rabbit_channel:shutdown(Ch2), - {new, #amqqueue { pid = QPid } = Q} = + {new, #amqqueue{name = QName} = Q} = rabbit_amqqueue:declare(test_queue(), false, false, [], none), - expect_events(QPid, queue_created), + expect_events(name, QName, queue_created), rabbit_amqqueue:delete(Q, false, false), rabbit_tests_event_receiver:stop(), passed. -expect_events(Pid, Type) -> - expect_event(Pid, Type), +expect_events(Tag, Key, Type) -> + expect_event(Tag, Key, Type), rabbit:force_event_refresh(), - expect_event(Pid, Type). + expect_event(Tag, Key, Type). -expect_event(Pid, Type) -> +expect_event(Tag, Key, Type) -> receive #event{type = Type, props = Props} -> - case pget(pid, Props) of - Pid -> ok; - _ -> expect_event(Pid, Type) + case pget(Tag, Props) of + Key -> ok; + _ -> expect_event(Tag, Key, Type) end after ?TIMEOUT -> throw({failed_to_receive_event, Type}) end. -- cgit v1.2.1 From 3ea53152cc47f3f126aaa7c8683d4a39d7d39062 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 16 Nov 2012 17:43:42 +0000 Subject: optimise this brings perf roughly on par with default --- src/rabbit_channel.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index f8f099f6..6ccc2e65 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1360,8 +1360,10 @@ deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{ {QNames1, QMons1} = lists:foldl(fun (#amqqueue{pid = QPid, name = QName}, {QNames0, QMons0}) -> - {dict:store(QPid, QName, QNames0), - pmon:monitor(QPid, QMons0)} + {case dict:is_key(QPid, QNames0) of + true -> QNames0; + false -> dict:store(QPid, QName, QNames0) + end, pmon:monitor(QPid, QMons0)} end, {QNames, pmon:monitor_all(DeliveredQPids, QMons)}, Qs), State1 = process_routing_result(RoutingRes, DeliveredQPids, XName, MsgSeqNo, Message, -- cgit v1.2.1 -- cgit v1.2.1 From 27ff7e9bc045e187942c6484e4391e422b1d0bfe Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 19 Nov 2012 12:18:13 +0000 Subject: optimise "no messages dead-lettered during expiry" case --- src/rabbit_amqqueue_process.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index f87f5777..1c324bbf 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -728,8 +728,10 @@ drop_expired_messages(State = #q{dlx = DLX, {Next, BQS2}; _ -> {Next, Msgs, BQS2} = BQ:dropwhile(ExpirePred, true, BQS), - DLXFun = dead_letter_fun(expired), - DLXFun(Msgs), + case Msgs of + [] -> ok; + _ -> (dead_letter_fun(expired))(Msgs) + end, {Next, BQS2} end, ensure_ttl_timer(case Props of -- cgit v1.2.1 From 4655ba493b26a70075944ade32d764f2f32cffa7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 19 Nov 2012 16:15:26 +0000 Subject: cosmetic --- src/rabbit_amqqueue_process.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 1c324bbf..abdbd24b 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1135,11 +1135,11 @@ handle_call(stat, _From, State) -> handle_call({delete, IfUnused, IfEmpty}, From, State = #q{backing_queue_state = BQS, backing_queue = BQ}) -> - IsEmpty = BQ:is_empty(BQS), + IsEmpty = BQ:is_empty(BQS), IsUnused = is_unused(State), if - IfEmpty and not(IsEmpty) -> reply({error, not_empty}, State); - IfUnused and not(IsUnused) -> reply({error, in_use}, State); + IfEmpty and not(IsEmpty) -> reply({error, not_empty}, State); + IfUnused and not(IsUnused) -> reply({error, in_use}, State); true -> stop(From, {ok, BQ:len(BQS)}, State) end; -- cgit v1.2.1 From 4885f41e4f37537f8fcf5c33ccdb964fcdc5bf1a Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 19 Nov 2012 18:16:49 +0000 Subject: format mq slave message queue to facilitate debugging --- src/rabbit_mirror_queue_slave.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 1ba1420f..bea4758c 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -28,7 +28,7 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, handle_pre_hibernate/1, prioritise_call/3, - prioritise_cast/2, prioritise_info/2]). + prioritise_cast/2, prioritise_info/2, format_message_queue/2]). -export([joined/2, members_changed/3, handle_msg/3]). @@ -329,6 +329,8 @@ prioritise_info(Msg, _State) -> _ -> 0 end. +format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ). + %% --------------------------------------------------------------------------- %% GM %% --------------------------------------------------------------------------- -- cgit v1.2.1 From 17d021928a1bdfcff280fe1e6c44d5556cbf3bf1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 20 Nov 2012 13:34:59 +0000 Subject: introduce bq:drop/2 and use it in slaves to prevent msg fetching - 'drop' is the same as 'fetch' except it doesn't read messages from the msg store - slaves never fetch messages, they only drop them - technically, mq_master:drop doesn't need to exist, since 'drop' is only invoked by the slaves, but we provide an implementation for completeness. --- src/rabbit_backing_queue.erl | 8 ++++++++ src/rabbit_mirror_queue_master.erl | 38 ++++++++++++++++++++++++++++---------- src/rabbit_mirror_queue_slave.erl | 19 ++----------------- src/rabbit_variable_queue.erl | 12 +++++++++++- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index af660c60..00de3e17 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -29,6 +29,10 @@ ('empty' | %% Message, IsDelivered, AckTag, Remaining_Len {rabbit_types:basic_message(), boolean(), Ack, non_neg_integer()})). +-type(drop_result(Ack) :: + ('empty' | + %% MessageId, AckTag, Remaining_Len + {rabbit_types:msg_id(), Ack, non_neg_integer()})). -type(attempt_recovery() :: boolean()). -type(purged_msg_count() :: non_neg_integer()). -type(async_callback() :: @@ -139,6 +143,10 @@ -callback fetch(true, state()) -> {fetch_result(ack()), state()}; (false, state()) -> {fetch_result(undefined), state()}. +%% Remove the next message. +-callback drop(true, state()) -> {drop_result(ack()), state()}; + (false, state()) -> {drop_result(undefined), state()}. + %% Acktags supplied are for messages which can now be forgotten %% about. Must return 1 msg_id per Ack, in the same order as Acks. -callback ack([ack()], state()) -> {msg_ids(), state()}. diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index df733546..961636b1 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -17,7 +17,8 @@ -module(rabbit_mirror_queue_master). -export([init/3, terminate/2, delete_and_terminate/2, - purge/1, publish/4, publish_delivered/4, discard/3, fetch/2, ack/2, + purge/1, publish/4, publish_delivered/4, + discard/3, fetch/2, drop/2, ack/2, requeue/2, len/1, is_empty/1, depth/1, drain_confirmed/1, dropwhile/3, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, @@ -270,22 +271,30 @@ drain_confirmed(State = #state { backing_queue = BQ, fetch(AckRequired, State = #state { gm = GM, backing_queue = BQ, backing_queue_state = BQS, - set_delivered = SetDelivered, - ack_msg_id = AM }) -> + set_delivered = SetDelivered }) -> {Result, BQS1} = BQ:fetch(AckRequired, BQS), State1 = State #state { backing_queue_state = BQS1 }, case Result of empty -> {Result, State1}; - {#basic_message { id = MsgId } = Message, IsDelivered, AckTag, - Remaining} -> - ok = gm:broadcast(GM, {fetch, AckRequired, MsgId, Remaining}), + {Message, IsDelivered, AckTag, Remaining} -> + ok = gm:broadcast(GM, {drop, Remaining, 1, AckRequired}), IsDelivered1 = IsDelivered orelse SetDelivered > 0, - SetDelivered1 = lists:max([0, SetDelivered - 1]), - AM1 = maybe_store_acktag(AckTag, MsgId, AM), {{Message, IsDelivered1, AckTag, Remaining}, - State1 #state { set_delivered = SetDelivered1, - ack_msg_id = AM1 }} + drop(Message#basic_message.id, AckTag, State1)} + end. + +drop(AckRequired, State = #state { gm = GM, + backing_queue = BQ, + backing_queue_state = BQS }) -> + {Result, BQS1} = BQ:drop(AckRequired, BQS), + State1 = State #state { backing_queue_state = BQS1 }, + case Result of + empty -> + {Result, State1}; + {MsgId, AckTag, Remaining} -> + ok = gm:broadcast(GM, {drop, Remaining, 1, AckRequired}), + {Result, drop(MsgId, AckTag, State1)} end. ack(AckTags, State = #state { gm = GM, @@ -440,6 +449,15 @@ depth_fun() -> end) end. +%% --------------------------------------------------------------------------- +%% Helpers +%% --------------------------------------------------------------------------- + +drop(MsgId, AckTag, State = #state { set_delivered = SetDelivered, + ack_msg_id = AM }) -> + State #state { set_delivered = lists:max([0, SetDelivered - 1]), + ack_msg_id = maybe_store_acktag(AckTag, MsgId, AM) }. + maybe_store_acktag(undefined, _MsgId, AM) -> AM; maybe_store_acktag(AckTag, MsgId, AM) -> dict:store(AckTag, MsgId, AM). diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index bea4758c..3ad8eb77 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -727,8 +727,8 @@ process_instruction({drop, Length, Dropped, AckRequired}, end, State1 = lists:foldl( fun (const, StateN = #state{backing_queue_state = BQSN}) -> - {{#basic_message{id = MsgId}, _, AckTag, _}, BQSN1} = - BQ:fetch(AckRequired, BQSN), + {{MsgId, AckTag, _Remaining}, BQSN1} = + BQ:drop(AckRequired, BQSN), maybe_store_ack( AckRequired, MsgId, AckTag, StateN #state { backing_queue_state = BQSN1 }) @@ -737,21 +737,6 @@ process_instruction({drop, Length, Dropped, AckRequired}, true -> State1; false -> update_delta(ToDrop - Dropped, State1) end}; -process_instruction({fetch, AckRequired, MsgId, Remaining}, - State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - QLen = BQ:len(BQS), - {ok, case QLen - 1 of - Remaining -> - {{#basic_message{id = MsgId}, _IsDelivered, - AckTag, Remaining}, BQS1} = BQ:fetch(AckRequired, BQS), - maybe_store_ack(AckRequired, MsgId, AckTag, - State #state { backing_queue_state = BQS1 }); - _ when QLen =< Remaining andalso AckRequired -> - State; - _ when QLen =< Remaining -> - update_delta(-1, State) - end}; process_instruction({ack, MsgIds}, State = #state { backing_queue = BQ, backing_queue_state = BQS, diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 8a3fd9d9..367b4802 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -18,7 +18,7 @@ -export([init/3, terminate/2, delete_and_terminate/2, purge/1, publish/4, publish_delivered/4, discard/3, drain_confirmed/1, - dropwhile/3, fetch/2, ack/2, requeue/2, len/1, is_empty/1, + dropwhile/3, fetch/2, drop/2, ack/2, requeue/2, len/1, is_empty/1, depth/1, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, is_duplicate/2, multiple_routing_keys/0, fold/3]). @@ -615,6 +615,16 @@ fetch(AckRequired, State) -> {Res, a(State3)} end. +drop(AckRequired, State) -> + case queue_out(State) of + {empty, State1} -> + {empty, a(State1)}; + {{value, MsgStatus}, State1} -> + {{_Msg, _IsDelivered, AckTag, Remaining}, State2} = + internal_fetch(AckRequired, MsgStatus, State1), + {{MsgStatus#msg_status.msg_id, AckTag, Remaining}, a(State2)} + end. + ack([], State) -> {[], State}; ack(AckTags, State) -> -- cgit v1.2.1 From 9bf8bb79fbbaf85434f0383462c0b5d816d1a6a3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 20 Nov 2012 15:12:06 +0000 Subject: remove unused vqstate field --- src/rabbit_variable_queue.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 8a3fd9d9..88270722 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -255,7 +255,6 @@ q4, next_seq_id, pending_ack, - pending_ack_index, ram_ack_index, index_state, msg_store_clients, -- cgit v1.2.1 From a7cd61e79c5ab447f1c02530096610f0021ceef6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 20 Nov 2012 16:54:53 +0000 Subject: add test of vq:drop --- src/rabbit_tests.erl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 096f9490..444c7375 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2300,6 +2300,7 @@ test_variable_queue() -> fun test_variable_queue_partial_segments_delta_thing/1, fun test_variable_queue_all_the_bits_not_covered_elsewhere1/1, fun test_variable_queue_all_the_bits_not_covered_elsewhere2/1, + fun test_drop/1, fun test_dropwhile/1, fun test_dropwhile_varying_ram_duration/1, fun test_variable_queue_ack_limiting/1, @@ -2361,6 +2362,20 @@ test_variable_queue_ack_limiting(VQ0) -> VQ6. +test_drop(VQ0) -> + %% start by sending a messages + VQ1 = variable_queue_publish(false, 1, VQ0), + %% drop message with AckRequired = true + {{MsgId, AckTag, 0}, VQ2} = rabbit_variable_queue:drop(true, VQ1), + true = AckTag =/= undefinded, + %% drop again -> empty + {empty, VQ3} = rabbit_variable_queue:drop(false, VQ2), + %% requeue + {[MsgId], VQ4} = rabbit_variable_queue:requeue([AckTag], VQ3), + %% drop message with AckRequired = false + {{MsgId, undefined, 0}, VQ5} = rabbit_variable_queue:drop(false, VQ4), + VQ5. + test_dropwhile(VQ0) -> Count = 10, -- cgit v1.2.1 From d4e2ab21fb3ea9c5003e051c092aa0df0e451883 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 20 Nov 2012 17:51:33 +0000 Subject: some quick check tests all work ok, but then again that's also the case when I completely break vq. --- src/rabbit_backing_queue_qc.erl | 70 ++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index b37fbb29..df242062 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -85,17 +85,19 @@ backing_queue_test(Cmds) -> %% Commands -%% Command frequencies are tuned so that queues are normally reasonably -%% short, but they may sometimes exceed ?QUEUE_MAXLEN. Publish-multiple -%% and purging cause extreme queue lengths, so these have lower probabilities. -%% Fetches are sufficiently frequent so that commands that need acktags -%% get decent coverage. +%% Command frequencies are tuned so that queues are normally +%% reasonably short, but they may sometimes exceed +%% ?QUEUE_MAXLEN. Publish-multiple and purging cause extreme queue +%% lengths, so these have lower probabilities. Fetches/drops are +%% sufficiently frequent so that commands that need acktags get decent +%% coverage. command(S) -> frequency([{10, qc_publish(S)}, {1, qc_publish_delivered(S)}, {1, qc_publish_multiple(S)}, %% very slow - {15, qc_fetch(S)}, %% needed for ack and requeue + {9, qc_fetch(S)}, %% needed for ack and requeue + {6, qc_drop(S)}, %% {15, qc_ack(S)}, {15, qc_requeue(S)}, {3, qc_set_ram_duration_target(S)}, @@ -124,6 +126,9 @@ qc_publish_delivered(#state{bqstate = BQ}) -> qc_fetch(#state{bqstate = BQ}) -> {call, ?BQMOD, fetch, [boolean(), BQ]}. +qc_drop(#state{bqstate = BQ}) -> + {call, ?BQMOD, drop, [boolean(), BQ]}. + qc_ack(#state{bqstate = BQ, acks = Acks}) -> {call, ?BQMOD, ack, [rand_choice(proplists:get_keys(Acks)), BQ]}. @@ -217,22 +222,10 @@ next_state(S, Res, }; next_state(S, Res, {call, ?BQMOD, fetch, [AckReq, _BQ]}) -> - #state{len = Len, messages = Messages, acks = Acks} = S, - ResultInfo = {call, erlang, element, [1, Res]}, - BQ1 = {call, erlang, element, [2, Res]}, - AckTag = {call, erlang, element, [3, ResultInfo]}, - S1 = S#state{bqstate = BQ1}, - case gb_trees:is_empty(Messages) of - true -> S1; - false -> {SeqId, MsgProp_Msg, M2} = gb_trees:take_smallest(Messages), - S2 = S1#state{len = Len - 1, messages = M2}, - case AckReq of - true -> - S2#state{acks = [{AckTag, {SeqId, MsgProp_Msg}}|Acks]}; - false -> - S2 - end - end; + next_state_fetch_and_drop(S, Res, AckReq, 3); + +next_state(S, Res, {call, ?BQMOD, drop, [AckReq, _BQ]}) -> + next_state_fetch_and_drop(S, Res, AckReq, 2); next_state(S, Res, {call, ?BQMOD, ack, [AcksArg, _BQ]}) -> #state{acks = AcksState} = S, @@ -295,6 +288,21 @@ postcondition(S, {call, ?BQMOD, fetch, _Args}, Res) -> Len =:= 0 end; +postcondition(S, {call, ?BQMOD, drop, _Args}, Res) -> + #state{messages = Messages, len = Len, acks = Acks, confirms = Confrms} = S, + case Res of + {{MsgIdFetched, AckTag, RemainingLen}, _BQ} -> + {_SeqId, {_MsgProps, Msg}} = gb_trees:smallest(Messages), + MsgId = {call, erlang, element, + [?RECORD_INDEX(id, basic_message), Msg]}, + MsgIdFetched =:= MsgId andalso + not proplists:is_defined(AckTag, Acks) andalso + not gb_sets:is_element(AckTag, Confrms) andalso + RemainingLen =:= Len - 1; + {empty, _BQ} -> + Len =:= 0 + end; + postcondition(S, {call, ?BQMOD, publish_delivered, _Args}, {AckTag, _BQ}) -> #state{acks = Acks, confirms = Confrms} = S, not proplists:is_defined(AckTag, Acks) andalso @@ -388,6 +396,24 @@ drop_messages(Messages) -> end end. +next_state_fetch_and_drop(S, Res, AckReq, AckTagIdx) -> + #state{len = Len, messages = Messages, acks = Acks} = S, + ResultInfo = {call, erlang, element, [1, Res]}, + BQ1 = {call, erlang, element, [2, Res]}, + AckTag = {call, erlang, element, [AckTagIdx, ResultInfo]}, + S1 = S#state{bqstate = BQ1}, + case gb_trees:is_empty(Messages) of + true -> S1; + false -> {SeqId, MsgProp_Msg, M2} = gb_trees:take_smallest(Messages), + S2 = S1#state{len = Len - 1, messages = M2}, + case AckReq of + true -> + S2#state{acks = [{AckTag, {SeqId, MsgProp_Msg}}|Acks]}; + false -> + S2 + end + end. + -else. -export([prop_disabled/0]). -- cgit v1.2.1 From bae4a5032ec7897be858647f79c4349be5b8c363 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 21 Nov 2012 12:25:05 +0000 Subject: BQ quickcheck postcondition for drop --- src/rabbit_backing_queue_qc.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index df242062..3168ca5c 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -293,8 +293,8 @@ postcondition(S, {call, ?BQMOD, drop, _Args}, Res) -> case Res of {{MsgIdFetched, AckTag, RemainingLen}, _BQ} -> {_SeqId, {_MsgProps, Msg}} = gb_trees:smallest(Messages), - MsgId = {call, erlang, element, - [?RECORD_INDEX(id, basic_message), Msg]}, + MsgId = eval({call, erlang, element, + [?RECORD_INDEX(id, basic_message), Msg]}), MsgIdFetched =:= MsgId andalso not proplists:is_defined(AckTag, Acks) andalso not gb_sets:is_element(AckTag, Confrms) andalso -- cgit v1.2.1 From 5d308a066213334a009ae2edbb980e974279c9bd Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 21 Nov 2012 15:19:00 +0000 Subject: simplify & optimise rabbit_exchange:callback/4 --- src/rabbit_exchange.erl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index a205b23d..f209b3ca 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -39,8 +39,7 @@ -spec(recover/0 :: () -> [name()]). -spec(callback/4:: (rabbit_types:exchange(), fun_name(), - fun((boolean()) -> non_neg_integer()) | atom(), - [any()]) -> 'ok'). + fun((boolean()) -> non_neg_integer()) | atom(), [any()]) -> 'ok'). -spec(policy_changed/2 :: (rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'). -spec(declare/6 :: @@ -114,14 +113,11 @@ recover() -> [XName || #exchange{name = XName} <- Xs]. callback(X = #exchange{type = XType}, Fun, Serial0, Args) -> - Serial = fun (Bool) -> - case Serial0 of - _ when is_atom(Serial0) -> Serial0; - _ -> Serial0(Bool) - end + Serial = if is_function(Serial0) -> Serial0; + is_atom(Serial0) -> fun (_Bool) -> Serial0 end end, - [ok = apply(M, Fun, [Serial(M:serialise_events(X)) | Args]) - || M <- decorators()], + [ok = apply(M, Fun, [Serial(M:serialise_events(X)) | Args]) || + M <- decorators()], Module = type_to_module(XType), apply(Module, Fun, [Serial(Module:serialise_events()) | Args]). -- cgit v1.2.1 From 45c6dc9aba6266ac2c39f86ca45bf07b44e50805 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 21 Nov 2012 15:47:05 +0000 Subject: refactor: simplify rabbit_exchange:serialise_events --- src/rabbit_exchange.erl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index f209b3ca..e72cbafe 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -124,12 +124,8 @@ callback(X = #exchange{type = XType}, Fun, Serial0, Args) -> policy_changed(X1, X2) -> callback(X1, policy_changed, none, [X1, X2]). serialise_events(X = #exchange{type = Type}) -> - case [Serialise || M <- decorators(), - Serialise <- [M:serialise_events(X)], - Serialise == true] of - [] -> (type_to_module(Type)):serialise_events(); - _ -> true - end. + lists:any(fun (M) -> M:serialise_events(X) end, decorators()) + orelse (type_to_module(Type)):serialise_events(). serial(#exchange{name = XName} = X) -> Serial = case serialise_events(X) of -- cgit v1.2.1 From 96ed5762a614081576249c891c4a5b3dfd0719cc Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 21 Nov 2012 17:53:18 +0000 Subject: Minimal backing queue fold --- src/rabbit_amqqueue_process.erl | 11 ++++-- src/rabbit_backing_queue.erl | 7 ++-- src/rabbit_mirror_queue_master.erl | 15 +++++--- src/rabbit_variable_queue.erl | 72 +++++++++++++++++++++++++++++++------- 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index abdbd24b..ddffd8be 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1178,7 +1178,12 @@ handle_call(force_event_refresh, _From, {Ch, CTag} -> [{Ch, CTag, AckRequired}] = consumers(State), emit_consumer_created(Ch, CTag, true, AckRequired) end, - reply(ok, State). + reply(ok, State); + +handle_call({fold, Fun, Acc}, _From, State = #q{backing_queue = BQ, + backing_queue_state = BQS}) -> + {Acc1, BQS1} = BQ:fold(Fun, Acc, BQS), + reply(Acc1, State#q{backing_queue_state = BQS1}). handle_cast({confirm, MsgSeqNos, QPid}, State = #q{unconfirmed = UC}) -> {MsgSeqNoAckTags, UC1} = dtree:take(MsgSeqNos, QPid, UC), @@ -1224,8 +1229,8 @@ handle_cast({reject, AckTags, false, ChPid}, State) -> ChPid, AckTags, State, fun (State1 = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - BQS1 = BQ:fold(fun(M, A) -> DLXFun([{M, A}]) end, - BQS, AckTags), + BQS1 = BQ:foreach_ack(fun(M, A) -> DLXFun([{M, A}]) end, + BQS, AckTags), State1#q{backing_queue_state = BQS1} end)); diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index af660c60..3f593e4a 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -145,12 +145,15 @@ %% Acktags supplied are for messages which should be processed. The %% provided callback function is called with each message. --callback fold(msg_fun(), state(), [ack()]) -> state(). +-callback foreach_ack(msg_fun(), state(), [ack()]) -> state(). %% Reinsert messages into the queue which have already been delivered %% and were pending acknowledgement. -callback requeue([ack()], state()) -> {msg_ids(), state()}. +-callback fold(fun((rabbit_types:basic_message(), any()) -> any()), + any(), state()) -> {any(), state()}. + %% How long is my queue? -callback len(state()) -> non_neg_integer(). @@ -212,7 +215,7 @@ behaviour_info(callbacks) -> [{start, 1}, {stop, 0}, {init, 3}, {terminate, 2}, {delete_and_terminate, 2}, {purge, 1}, {publish, 4}, {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, {dropwhile, 3}, - {fetch, 2}, {ack, 2}, {fold, 3}, {requeue, 2}, {len, 1}, + {fetch, 2}, {ack, 2}, {foreach_ack, 3}, {requeue, 2}, {fold, 3}, {len, 1}, {is_empty, 1}, {depth, 1}, {set_ram_duration_target, 2}, {ram_duration, 1}, {needs_timeout, 1}, {timeout, 1}, {handle_pre_hibernate, 1}, {status, 1}, {invoke, 3}, {is_duplicate, 2}] ; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index df733546..53d1a173 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -18,10 +18,10 @@ -export([init/3, terminate/2, delete_and_terminate/2, purge/1, publish/4, publish_delivered/4, discard/3, fetch/2, ack/2, - requeue/2, len/1, is_empty/1, depth/1, drain_confirmed/1, + requeue/2, fold/3, len/1, is_empty/1, depth/1, drain_confirmed/1, dropwhile/3, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, - status/1, invoke/3, is_duplicate/2, fold/3]). + status/1, invoke/3, is_duplicate/2, foreach_ack/3]). -export([start/1, stop/0]). @@ -301,9 +301,9 @@ ack(AckTags, State = #state { gm = GM, {MsgIds, State #state { backing_queue_state = BQS1, ack_msg_id = AM1 }}. -fold(MsgFun, State = #state { backing_queue = BQ, - backing_queue_state = BQS }, AckTags) -> - State #state { backing_queue_state = BQ:fold(MsgFun, BQS, AckTags) }. +foreach_ack(MsgFun, State = #state { backing_queue = BQ, + backing_queue_state = BQS }, AckTags) -> + State #state { backing_queue_state = BQ:foreach_ack(MsgFun, BQS, AckTags) }. requeue(AckTags, State = #state { gm = GM, backing_queue = BQ, @@ -312,6 +312,11 @@ requeue(AckTags, State = #state { gm = GM, ok = gm:broadcast(GM, {requeue, MsgIds}), {MsgIds, State #state { backing_queue_state = BQS1 }}. +fold(Fun, Acc, State = #state { backing_queue = BQ, + backing_queue_state = BQS }) -> + {Result, BQS1} = BQ:fold(Fun, Acc, BQS), + {Result, State #state { backing_queue_state = BQS1 }}. + len(#state { backing_queue = BQ, backing_queue_state = BQS }) -> BQ:len(BQS). diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 8a3fd9d9..5a5547ae 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -18,10 +18,10 @@ -export([init/3, terminate/2, delete_and_terminate/2, purge/1, publish/4, publish_delivered/4, discard/3, drain_confirmed/1, - dropwhile/3, fetch/2, ack/2, requeue/2, len/1, is_empty/1, + dropwhile/3, fetch/2, ack/2, requeue/2, fold/3, len/1, is_empty/1, depth/1, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, - is_duplicate/2, multiple_routing_keys/0, fold/3]). + is_duplicate/2, multiple_routing_keys/0, foreach_ack/3]). -export([start/1, stop/0]). @@ -591,7 +591,7 @@ dropwhile(Pred, AckRequired, State, Msgs) -> {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> case {Pred(MsgProps), AckRequired} of {true, true} -> - {MsgStatus1, State2} = read_msg(MsgStatus, State1), + {MsgStatus1, State2} = read_msg(MsgStatus, State1, true), {{Msg, _, AckTag, _}, State3} = internal_fetch(true, MsgStatus1, State2), dropwhile(Pred, AckRequired, State3, [{Msg, AckTag} | Msgs]); @@ -610,7 +610,7 @@ fetch(AckRequired, State) -> {{value, MsgStatus}, State1} -> %% it is possible that the message wasn't read from disk %% at this point, so read it in. - {MsgStatus1, State2} = read_msg(MsgStatus, State1), + {MsgStatus1, State2} = read_msg(MsgStatus, State1, true), {Res, State3} = internal_fetch(AckRequired, MsgStatus1, State2), {Res, a(State3)} end. @@ -638,13 +638,13 @@ ack(AckTags, State) -> persistent_count = PCount1, ack_out_counter = AckOutCount + length(AckTags) })}. -fold(undefined, State, _AckTags) -> +foreach_ack(undefined, State, _AckTags) -> State; -fold(MsgFun, State = #vqstate{pending_ack = PA}, AckTags) -> +foreach_ack(MsgFun, State = #vqstate{pending_ack = PA}, AckTags) -> lists:foldl( fun(SeqId, State1) -> {MsgStatus, State2} = - read_msg(gb_trees:get(SeqId, PA), State1), + read_msg(gb_trees:get(SeqId, PA), State1, true), MsgFun(MsgStatus#msg_status.msg, SeqId), State2 end, State, AckTags). @@ -670,6 +670,53 @@ requeue(AckTags, #vqstate { delta = Delta, in_counter = InCounter + MsgCount, len = Len + MsgCount }))}. +fold(Fun, Acc, #vqstate { q1 = Q1, + q2 = Q2, + delta = Delta, + q3 = Q3, + q4 = Q4} = State) -> + QFun = fun(M, {A, S}) -> + {#msg_status{msg = Msg}, State1} = read_msg(M, S, false), + A1 = Fun(Msg, A), + {A1, State1} + end, + {Acc1, State1} = ?QUEUE:foldl(QFun, {Acc, State}, Q4), + {Acc2, State2} = ?QUEUE:foldl(QFun, {Acc1, State1}, Q3), + {Acc3, State3} = delta_fold (Fun, Acc2, Delta, State2), + {Acc4, State4} = ?QUEUE:foldl(QFun, {Acc3, State3}, Q2), + ?QUEUE:foldl(QFun, {Acc4, State4}, Q1). + +delta_fold(_Fun, Acc, ?BLANK_DELTA_PATTERN(X), State) -> + {Acc, State}; +delta_fold(Fun, Acc, #delta { start_seq_id = DeltaSeqId, + end_seq_id = DeltaSeqIdEnd}, State) -> + {List, State1 = #vqstate { msg_store_clients = MSCState }} = + delta_index(DeltaSeqId, DeltaSeqIdEnd, State), + {Result, MSCState3} = + lists:foldl(fun ({MsgId, _SeqId, _MsgProps, IsPersistent, _IsDelivered}, + {Acc1, MSCState1}) -> + {{ok, Msg = #basic_message {}}, MSCState2} = + msg_store_read(MSCState1, IsPersistent, MsgId), + {Fun(Msg, Acc1), MSCState2} + end, {Acc, MSCState}, List), + {Result, State1 #vqstate { msg_store_clients = MSCState3}}. + +delta_index(DeltaSeqId, DeltaSeqIdEnd, State) -> + delta_index(DeltaSeqId, DeltaSeqIdEnd, State, []). + +delta_index(DeltaSeqIdDone, DeltaSeqIdEnd, State, List) + when DeltaSeqIdDone == DeltaSeqIdEnd -> + {List, State}; +delta_index(DeltaSeqIdDone, DeltaSeqIdEnd, + #vqstate { index_state = IndexState } = State, List) -> + DeltaSeqId1 = lists:min( + [rabbit_queue_index:next_segment_boundary(DeltaSeqIdDone), + DeltaSeqIdEnd]), + {List1, IndexState1} = + rabbit_queue_index:read(DeltaSeqIdDone, DeltaSeqId1, IndexState), + delta_index(DeltaSeqId1, DeltaSeqIdEnd, + State #vqstate { index_state = IndexState1 }, List ++ List1). + len(#vqstate { len = Len }) -> Len. is_empty(State) -> 0 == len(State). @@ -1045,7 +1092,7 @@ in_r(MsgStatus = #msg_status { msg = undefined }, case ?QUEUE:is_empty(Q4) of true -> State #vqstate { q3 = ?QUEUE:in_r(MsgStatus, Q3) }; false -> {MsgStatus1, State1 = #vqstate { q4 = Q4a }} = - read_msg(MsgStatus, State), + read_msg(MsgStatus, State, true), State1 #vqstate { q4 = ?QUEUE:in_r(MsgStatus1, Q4a) } end; in_r(MsgStatus, State = #vqstate { q4 = Q4 }) -> @@ -1066,13 +1113,14 @@ read_msg(MsgStatus = #msg_status { msg = undefined, msg_id = MsgId, is_persistent = IsPersistent }, State = #vqstate { ram_msg_count = RamMsgCount, - msg_store_clients = MSCState}) -> + msg_store_clients = MSCState}, + UpdateRamCount) -> {{ok, Msg = #basic_message {}}, MSCState1} = msg_store_read(MSCState, IsPersistent, MsgId), {MsgStatus #msg_status { msg = Msg }, - State #vqstate { ram_msg_count = RamMsgCount + 1, + State #vqstate { ram_msg_count = RamMsgCount + one_if(UpdateRamCount), msg_store_clients = MSCState1 }}; -read_msg(MsgStatus, State) -> +read_msg(MsgStatus, State, _UpdateRamCount) -> {MsgStatus, State}. internal_fetch(AckRequired, MsgStatus = #msg_status { @@ -1348,7 +1396,7 @@ msg_indices_written_to_disk(Callback, MsgIdSet) -> %%---------------------------------------------------------------------------- publish_alpha(#msg_status { msg = undefined } = MsgStatus, State) -> - read_msg(MsgStatus, State); + read_msg(MsgStatus, State, true); publish_alpha(MsgStatus, #vqstate {ram_msg_count = RamMsgCount } = State) -> {MsgStatus, State #vqstate { ram_msg_count = RamMsgCount + 1 }}. -- cgit v1.2.1 From 26542563459bfd6ca5a3f19e9a04d28f365ea0af Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 22 Nov 2012 13:32:14 +0000 Subject: bq api tweak: don't include remaining message count in fetch/drop result --- src/rabbit_amqqueue_process.erl | 11 +++++------ src/rabbit_backing_queue.erl | 8 ++------ src/rabbit_mirror_queue_master.erl | 31 ++++++++++++++----------------- src/rabbit_mirror_queue_slave.erl | 3 +-- src/rabbit_tests.erl | 23 ++++++++++++++--------- src/rabbit_variable_queue.erl | 13 ++++++------- 6 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index dc258fa6..fe3ed88d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -485,11 +485,10 @@ deliver_msg_to_consumer(DeliverFun, {Stop, State1}. deliver_from_queue_deliver(AckRequired, State) -> - {{Message, IsDelivered, AckTag, _Remaining}, State1} = - fetch(AckRequired, State), + {Result, State1} = fetch(AckRequired, State), State2 = #q{backing_queue = BQ, backing_queue_state = BQS} = drop_expired_messages(State1), - {{Message, IsDelivered, AckTag}, BQ:is_empty(BQS), State2}. + {Result, BQ:is_empty(BQS), State2}. confirm_messages([], State) -> State; @@ -1061,8 +1060,8 @@ handle_call({basic_get, ChPid, NoAck}, _From, case fetch(AckRequired, drop_expired_messages(State1)) of {empty, State2} -> reply(empty, State2); - {{Message, IsDelivered, AckTag, Remaining}, State2} -> - State3 = + {{Message, IsDelivered, AckTag}, State2} -> + State3 = #q{backing_queue = BQ, backing_queue_state = BQS} = case AckRequired of true -> C = #cr{acktags = ChAckTags} = ch_record(ChPid), ChAckTags1 = sets:add_element(AckTag, ChAckTags), @@ -1071,7 +1070,7 @@ handle_call({basic_get, ChPid, NoAck}, _From, false -> State2 end, Msg = {QName, self(), AckTag, IsDelivered, Message}, - reply({ok, Remaining, Msg}, State3) + reply({ok, BQ:len(BQS), Msg}, State3) end; handle_call({basic_consume, NoAck, ChPid, Limiter, diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 00de3e17..871becc5 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -26,13 +26,9 @@ -type(msg_ids() :: [rabbit_types:msg_id()]). -type(fetch_result(Ack) :: - ('empty' | - %% Message, IsDelivered, AckTag, Remaining_Len - {rabbit_types:basic_message(), boolean(), Ack, non_neg_integer()})). + ('empty' | {rabbit_types:basic_message(), boolean(), Ack})). -type(drop_result(Ack) :: - ('empty' | - %% MessageId, AckTag, Remaining_Len - {rabbit_types:msg_id(), Ack, non_neg_integer()})). + ('empty' | {rabbit_types:msg_id(), Ack})). -type(attempt_recovery() :: boolean()). -type(purged_msg_count() :: non_neg_integer()). -type(async_callback() :: diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 961636b1..ac2048b7 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -268,8 +268,7 @@ drain_confirmed(State = #state { backing_queue = BQ, seen_status = SS1, confirmed = [] }}. -fetch(AckRequired, State = #state { gm = GM, - backing_queue = BQ, +fetch(AckRequired, State = #state { backing_queue = BQ, backing_queue_state = BQS, set_delivered = SetDelivered }) -> {Result, BQS1} = BQ:fetch(AckRequired, BQS), @@ -277,25 +276,19 @@ fetch(AckRequired, State = #state { gm = GM, case Result of empty -> {Result, State1}; - {Message, IsDelivered, AckTag, Remaining} -> - ok = gm:broadcast(GM, {drop, Remaining, 1, AckRequired}), - IsDelivered1 = IsDelivered orelse SetDelivered > 0, - {{Message, IsDelivered1, AckTag, Remaining}, + {Message, IsDelivered, AckTag} -> + {{Message, IsDelivered orelse SetDelivered > 0, AckTag}, drop(Message#basic_message.id, AckTag, State1)} end. -drop(AckRequired, State = #state { gm = GM, - backing_queue = BQ, +drop(AckRequired, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> {Result, BQS1} = BQ:drop(AckRequired, BQS), State1 = State #state { backing_queue_state = BQS1 }, - case Result of - empty -> - {Result, State1}; - {MsgId, AckTag, Remaining} -> - ok = gm:broadcast(GM, {drop, Remaining, 1, AckRequired}), - {Result, drop(MsgId, AckTag, State1)} - end. + {Result, case Result of + empty -> State1; + {MsgId, AckTag} -> drop(MsgId, AckTag, State1) + end}. ack(AckTags, State = #state { gm = GM, backing_queue = BQ, @@ -453,8 +446,12 @@ depth_fun() -> %% Helpers %% --------------------------------------------------------------------------- -drop(MsgId, AckTag, State = #state { set_delivered = SetDelivered, - ack_msg_id = AM }) -> +drop(MsgId, AckTag, State = #state { set_delivered = SetDelivered, + ack_msg_id = AM, + gm = GM, + backing_queue = BQ, + backing_queue_state = BQS }) -> + ok = gm:broadcast(GM, {drop, BQ:len(BQS), 1, AckTag =/= undefined}), State #state { set_delivered = lists:max([0, SetDelivered - 1]), ack_msg_id = maybe_store_acktag(AckTag, MsgId, AM) }. diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 3ad8eb77..cb7a2135 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -727,8 +727,7 @@ process_instruction({drop, Length, Dropped, AckRequired}, end, State1 = lists:foldl( fun (const, StateN = #state{backing_queue_state = BQSN}) -> - {{MsgId, AckTag, _Remaining}, BQSN1} = - BQ:drop(AckRequired, BQSN), + {{MsgId, AckTag}, BQSN1} = BQ:drop(AckRequired, BQSN), maybe_store_ack( AckRequired, MsgId, AckTag, StateN #state { backing_queue_state = BQSN1 }) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 444c7375..408bacd8 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2233,8 +2233,9 @@ variable_queue_fetch(Count, IsPersistent, IsDelivered, Len, VQ) -> lists:foldl(fun (N, {VQN, AckTagsAcc}) -> Rem = Len - N, {{#basic_message { is_persistent = IsPersistent }, - IsDelivered, AckTagN, Rem}, VQM} = + IsDelivered, AckTagN}, VQM} = rabbit_variable_queue:fetch(true, VQN), + Rem = rabbit_variable_queue:len(VQM), {VQM, [AckTagN | AckTagsAcc]} end, {VQ, []}, lists:seq(1, Count)). @@ -2326,7 +2327,7 @@ test_variable_queue_requeue(VQ0) -> VQM end, VQ4, Subset), VQ6 = lists:foldl(fun (AckTag, VQa) -> - {{#basic_message{}, true, AckTag, _}, VQb} = + {{#basic_message{}, true, AckTag}, VQb} = rabbit_variable_queue:fetch(true, VQa), VQb end, VQ5, lists:reverse(Acks)), @@ -2366,14 +2367,16 @@ test_drop(VQ0) -> %% start by sending a messages VQ1 = variable_queue_publish(false, 1, VQ0), %% drop message with AckRequired = true - {{MsgId, AckTag, 0}, VQ2} = rabbit_variable_queue:drop(true, VQ1), + {{MsgId, AckTag}, VQ2} = rabbit_variable_queue:drop(true, VQ1), + true = rabbit_variable_queue:is_empty(VQ2), true = AckTag =/= undefinded, %% drop again -> empty {empty, VQ3} = rabbit_variable_queue:drop(false, VQ2), %% requeue {[MsgId], VQ4} = rabbit_variable_queue:requeue([AckTag], VQ3), %% drop message with AckRequired = false - {{MsgId, undefined, 0}, VQ5} = rabbit_variable_queue:drop(false, VQ4), + {{MsgId, undefined}, VQ5} = rabbit_variable_queue:drop(false, VQ4), + true = rabbit_variable_queue:is_empty(VQ5), VQ5. test_dropwhile(VQ0) -> @@ -2392,7 +2395,7 @@ test_dropwhile(VQ0) -> %% fetch five now VQ3 = lists:foldl(fun (_N, VQN) -> - {{#basic_message{}, _, _, _}, VQM} = + {{#basic_message{}, _, _}, VQM} = rabbit_variable_queue:fetch(false, VQN), VQM end, VQ2, lists:seq(6, Count)), @@ -2445,7 +2448,8 @@ publish_fetch_and_ack(0, _Len, VQ0) -> VQ0; publish_fetch_and_ack(N, Len, VQ0) -> VQ1 = variable_queue_publish(false, 1, VQ0), - {{_Msg, false, AckTag, Len}, VQ2} = rabbit_variable_queue:fetch(true, VQ1), + {{_Msg, false, AckTag}, VQ2} = rabbit_variable_queue:fetch(true, VQ1), + Len = rabbit_variable_queue:len(VQ2), {_Guids, VQ3} = rabbit_variable_queue:ack([AckTag], VQ2), publish_fetch_and_ack(N-1, Len, VQ3). @@ -2510,8 +2514,8 @@ test_variable_queue_all_the_bits_not_covered_elsewhere1(VQ0) -> Count, VQ4), _VQ6 = rabbit_variable_queue:terminate(shutdown, VQ5), VQ7 = variable_queue_init(test_amqqueue(true), true), - {{_Msg1, true, _AckTag1, Count1}, VQ8} = - rabbit_variable_queue:fetch(true, VQ7), + {{_Msg1, true, _AckTag1}, VQ8} = rabbit_variable_queue:fetch(true, VQ7), + Count1 = rabbit_variable_queue:len(VQ8), VQ9 = variable_queue_publish(false, 1, VQ8), VQ10 = rabbit_variable_queue:set_ram_duration_target(0, VQ9), {VQ11, _AckTags2} = variable_queue_fetch(Count1, true, true, Count, VQ10), @@ -2551,8 +2555,9 @@ test_queue_recover() -> rabbit_amqqueue:basic_get(Q1, self(), false), exit(QPid1, shutdown), VQ1 = variable_queue_init(Q, true), - {{_Msg1, true, _AckTag1, CountMinusOne}, VQ2} = + {{_Msg1, true, _AckTag1}, VQ2} = rabbit_variable_queue:fetch(true, VQ1), + CountMinusOne = rabbit_variable_queue:len(VQ2), _VQ3 = rabbit_variable_queue:delete_and_terminate(shutdown, VQ2), rabbit_amqqueue:internal_delete(QName, QPid1) end), diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 208eb70f..3a025ba3 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -591,8 +591,8 @@ dropwhile(Pred, AckRequired, State, Msgs) -> case {Pred(MsgProps), AckRequired} of {true, true} -> {MsgStatus1, State2} = read_msg(MsgStatus, State1), - {{Msg, _, AckTag, _}, State3} = - internal_fetch(true, MsgStatus1, State2), + {{Msg, _IsDelivered, AckTag}, State3} = + internal_fetch(true, MsgStatus1, State2), dropwhile(Pred, AckRequired, State3, [{Msg, AckTag} | Msgs]); {true, false} -> {_, State2} = internal_fetch(false, MsgStatus, State1), @@ -619,9 +619,9 @@ drop(AckRequired, State) -> {empty, State1} -> {empty, a(State1)}; {{value, MsgStatus}, State1} -> - {{_Msg, _IsDelivered, AckTag, Remaining}, State2} = + {{_Msg, _IsDelivered, AckTag}, State2} = internal_fetch(AckRequired, MsgStatus, State1), - {{MsgStatus#msg_status.msg_id, AckTag, Remaining}, a(State2)} + {{MsgStatus#msg_status.msg_id, AckTag}, a(State2)} end. ack([], State) -> @@ -1125,14 +1125,13 @@ internal_fetch(AckRequired, MsgStatus = #msg_status { end, PCount1 = PCount - one_if(IsPersistent andalso not AckRequired), - Len1 = Len - 1, RamMsgCount1 = RamMsgCount - one_if(Msg =/= undefined), - {{Msg, IsDelivered, AckTag, Len1}, + {{Msg, IsDelivered, AckTag}, State1 #vqstate { ram_msg_count = RamMsgCount1, out_counter = OutCount + 1, index_state = IndexState2, - len = Len1, + len = Len - 1, persistent_count = PCount1 }}. purge_betas_and_deltas(LensByStore, -- cgit v1.2.1 From 220488f1e8df41403f038394a941dd246ce5afad Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 22 Nov 2012 13:38:36 +0000 Subject: propagate API change --- src/rabbit_backing_queue_qc.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index 3168ca5c..fe014ef5 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -278,12 +278,12 @@ next_state(S, Res, {call, ?BQMOD, purge, _Args}) -> postcondition(S, {call, ?BQMOD, fetch, _Args}, Res) -> #state{messages = Messages, len = Len, acks = Acks, confirms = Confrms} = S, case Res of - {{MsgFetched, _IsDelivered, AckTag, RemainingLen}, _BQ} -> + {{MsgFetched, _IsDelivered, AckTag}, _BQ} -> {_SeqId, {_MsgProps, Msg}} = gb_trees:smallest(Messages), MsgFetched =:= Msg andalso not proplists:is_defined(AckTag, Acks) andalso not gb_sets:is_element(AckTag, Confrms) andalso - RemainingLen =:= Len - 1; + Len =/= 0; {empty, _BQ} -> Len =:= 0 end; @@ -291,14 +291,14 @@ postcondition(S, {call, ?BQMOD, fetch, _Args}, Res) -> postcondition(S, {call, ?BQMOD, drop, _Args}, Res) -> #state{messages = Messages, len = Len, acks = Acks, confirms = Confrms} = S, case Res of - {{MsgIdFetched, AckTag, RemainingLen}, _BQ} -> + {{MsgIdFetched, AckTag}, _BQ} -> {_SeqId, {_MsgProps, Msg}} = gb_trees:smallest(Messages), MsgId = eval({call, erlang, element, [?RECORD_INDEX(id, basic_message), Msg]}), MsgIdFetched =:= MsgId andalso not proplists:is_defined(AckTag, Acks) andalso not gb_sets:is_element(AckTag, Confrms) andalso - RemainingLen =:= Len - 1; + Len =/= 0; {empty, _BQ} -> Len =:= 0 end; -- cgit v1.2.1 From 7aa01b6f4d6e2dd828ae606dc0f2d13a2dd3980c Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 22 Nov 2012 13:54:19 +0000 Subject: Rename backing queue fold --- src/rabbit_amqqueue_process.erl | 2 +- src/rabbit_backing_queue.erl | 4 ++-- src/rabbit_mirror_queue_master.erl | 8 ++++---- src/rabbit_tests.erl | 3 ++- src/rabbit_variable_queue.erl | 6 +++--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index dc258fa6..c41f02e1 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1226,7 +1226,7 @@ handle_cast({reject, AckTags, false, ChPid}, State) -> ChPid, AckTags, State, fun (State1 = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - BQS1 = BQ:fold(fun(M, A) -> DLXFun([{M, A}]) end, + BQS1 = BQ:foreach_ack(fun(M, A) -> DLXFun([{M, A}]) end, BQS, AckTags), State1#q{backing_queue_state = BQS1} end)); diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 00de3e17..329144f9 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -153,7 +153,7 @@ %% Acktags supplied are for messages which should be processed. The %% provided callback function is called with each message. --callback fold(msg_fun(), state(), [ack()]) -> state(). +-callback foreach_ack(msg_fun(), state(), [ack()]) -> state(). %% Reinsert messages into the queue which have already been delivered %% and were pending acknowledgement. @@ -220,7 +220,7 @@ behaviour_info(callbacks) -> [{start, 1}, {stop, 0}, {init, 3}, {terminate, 2}, {delete_and_terminate, 2}, {purge, 1}, {publish, 4}, {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, {dropwhile, 3}, - {fetch, 2}, {ack, 2}, {fold, 3}, {requeue, 2}, {len, 1}, + {fetch, 2}, {ack, 2}, {foreach_ack, 3}, {requeue, 2}, {len, 1}, {is_empty, 1}, {depth, 1}, {set_ram_duration_target, 2}, {ram_duration, 1}, {needs_timeout, 1}, {timeout, 1}, {handle_pre_hibernate, 1}, {status, 1}, {invoke, 3}, {is_duplicate, 2}] ; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 961636b1..39060c09 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -22,7 +22,7 @@ requeue/2, len/1, is_empty/1, depth/1, drain_confirmed/1, dropwhile/3, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, - status/1, invoke/3, is_duplicate/2, fold/3]). + status/1, invoke/3, is_duplicate/2, foreach_ack/3]). -export([start/1, stop/0]). @@ -310,9 +310,9 @@ ack(AckTags, State = #state { gm = GM, {MsgIds, State #state { backing_queue_state = BQS1, ack_msg_id = AM1 }}. -fold(MsgFun, State = #state { backing_queue = BQ, - backing_queue_state = BQS }, AckTags) -> - State #state { backing_queue_state = BQ:fold(MsgFun, BQS, AckTags) }. +foreach_ack(MsgFun, State = #state { backing_queue = BQ, + backing_queue_state = BQS }, AckTags) -> + State #state { backing_queue_state = BQ:foreach_ack(MsgFun, BQS, AckTags) }. requeue(AckTags, State = #state { gm = GM, backing_queue = BQ, diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 912bd3b6..176374ce 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2534,7 +2534,8 @@ test_variable_queue_all_the_bits_not_covered_elsewhere2(VQ0) -> test_variable_queue_fold_msg_on_disk(VQ0) -> VQ1 = variable_queue_publish(true, 1, VQ0), {VQ2, AckTags} = variable_queue_fetch(1, true, false, 1, VQ1), - VQ3 = rabbit_variable_queue:fold(fun (_M, _A) -> ok end, VQ2, AckTags), + VQ3 = rabbit_variable_queue:foreach_ack(fun (_M, _A) -> ok end, + VQ2, AckTags), VQ3. test_queue_recover() -> diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 862e74f6..7813aa7b 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -21,7 +21,7 @@ dropwhile/3, fetch/2, drop/2, ack/2, requeue/2, len/1, is_empty/1, depth/1, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, - is_duplicate/2, multiple_routing_keys/0, fold/3]). + is_duplicate/2, multiple_routing_keys/0, foreach_ack/3]). -export([start/1, stop/0]). @@ -647,9 +647,9 @@ ack(AckTags, State) -> persistent_count = PCount1, ack_out_counter = AckOutCount + length(AckTags) })}. -fold(undefined, State, _AckTags) -> +foreach_ack(undefined, State, _AckTags) -> State; -fold(MsgFun, State = #vqstate{pending_ack = PA}, AckTags) -> +foreach_ack(MsgFun, State = #vqstate{pending_ack = PA}, AckTags) -> a(lists:foldl(fun(SeqId, State1) -> {MsgStatus, State2} = read_msg(gb_trees:get(SeqId, PA), false, State1), -- cgit v1.2.1 From 946623b70b0022ef88c3e4ae87c71c319693c043 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 22 Nov 2012 15:50:08 +0000 Subject: Don't duplicate name. --- src/rabbit_amqqueue_process.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 3bad6864..1f31aa22 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -108,7 +108,7 @@ owner_pid ]). --define(INFO_KEYS, ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS). +-define(INFO_KEYS, ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [name]). %%---------------------------------------------------------------------------- -- cgit v1.2.1 From a01cf9e7870250ccdd0129c5cbb9b881581f4fc8 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 22 Nov 2012 15:50:14 +0000 Subject: Explain --- src/rabbit_channel.erl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 6ccc2e65..b1ef3b6b 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1357,6 +1357,16 @@ deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{ queue_monitors = QMons}) -> Qs = rabbit_amqqueue:lookup(DelQNames), {RoutingRes, DeliveredQPids} = rabbit_amqqueue:deliver_flow(Qs, Delivery), + %% The pmon:monitor_all/2 monitors all queues to which we + %% delivered. But we want to monitor even queues we didn't deliver + %% to, since we need their 'DOWN' messages to clean + %% queue_names. So we also need to monitor each QPid from + %% queues. But that only gets the masters (which is fine for + %% cleaning queue_names), so we need the union of both. + %% + %% ...and we need to add even non-delivered queues to queue_names + %% since alternative algorithms to update queue_names less + %% frequently would in fact be more expensive in the common case. {QNames1, QMons1} = lists:foldl(fun (#amqqueue{pid = QPid, name = QName}, {QNames0, QMons0}) -> -- cgit v1.2.1 From acd1ab8fafab77b4a93222cd8f6dce354fe1b7d8 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 22 Nov 2012 17:30:21 +0000 Subject: QC test for backing queue fold --- src/rabbit_backing_queue_qc.erl | 21 +++++++++++++++++++-- src/rabbit_variable_queue.erl | 6 +++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index 3168ca5c..00e17d02 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -106,7 +106,8 @@ command(S) -> {1, qc_dropwhile(S)}, {1, qc_is_empty(S)}, {1, qc_timeout(S)}, - {1, qc_purge(S)}]). + {1, qc_purge(S)}, + {1, qc_fold(S)}]). qc_publish(#state{bqstate = BQ}) -> {call, ?BQMOD, publish, @@ -157,6 +158,9 @@ qc_timeout(#state{bqstate = BQ}) -> qc_purge(#state{bqstate = BQ}) -> {call, ?BQMOD, purge, [BQ]}. +qc_fold(#state{bqstate = BQ}) -> + {call, ?BQMOD, fold, [fun foldfun/2, foldacc(), BQ]}. + %% Preconditions %% Create long queues by only allowing publishing @@ -271,7 +275,11 @@ next_state(S, BQ, {call, ?MODULE, timeout, _Args}) -> next_state(S, Res, {call, ?BQMOD, purge, _Args}) -> BQ1 = {call, erlang, element, [2, Res]}, - S#state{bqstate = BQ1, len = 0, messages = gb_trees:empty()}. + S#state{bqstate = BQ1, len = 0, messages = gb_trees:empty()}; + +next_state(S, Res, {call, ?BQMOD, fold, _Args}) -> + BQ1 = {call, erlang, element, [2, Res]}, + S#state{bqstate = BQ1}. %% Postconditions @@ -321,6 +329,12 @@ postcondition(S, {call, ?BQMOD, drain_confirmed, _Args}, Res) -> lists:all(fun (M) -> gb_sets:is_element(M, Confirms) end, ReportedConfirmed); +postcondition(S, {call, ?BQMOD, fold, _Args}, {Res, _BQ}) -> + #state{messages = Messages} = S, + lists:foldl(fun ({_SeqId, {_MsgProps, Msg}}, Acc) -> + foldfun(Msg, Acc) + end, foldacc(), gb_trees:to_list(Messages)) =:= Res; + postcondition(#state{bqstate = BQ, len = Len}, {call, _M, _F, _A}, _Res) -> ?BQMOD:len(BQ) =:= Len. @@ -379,6 +393,9 @@ rand_choice(List, Selection, N) -> rand_choice(List -- [Picked], [Picked | Selection], N - 1). +foldfun(Msg, Acc) -> [Msg | Acc]. +foldacc() -> []. + dropfun(Props) -> Expiry = eval({call, erlang, element, [?RECORD_INDEX(expiry, message_properties), Props]}), diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 7636d5ea..f49aa085 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -18,8 +18,8 @@ -export([init/3, terminate/2, delete_and_terminate/2, purge/1, publish/4, publish_delivered/4, discard/3, drain_confirmed/1, - dropwhile/3, fetch/2, drop/2, ack/2, requeue/2, len/1, is_empty/1, - depth/1, set_ram_duration_target/2, ram_duration/1, + dropwhile/3, fetch/2, drop/2, ack/2, requeue/2, fold/3, len/1, + is_empty/1, depth/1, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, is_duplicate/2, multiple_routing_keys/0, foreach_ack/3]). @@ -684,7 +684,7 @@ fold(Fun, Acc, #vqstate { q1 = Q1, q3 = Q3, q4 = Q4} = State) -> QFun = fun(M, {A, S}) -> - {#msg_status{msg = Msg}, State1} = read_msg(M, S, false), + {#msg_status{msg = Msg}, State1} = read_msg(M, false, S), A1 = Fun(Msg, A), {A1, State1} end, -- cgit v1.2.1 From 6cddbaf0135c269e2d3be5d9b7c956cd2fcd60f4 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 23 Nov 2012 12:28:48 +0000 Subject: Refactor backing queue delta fold --- src/rabbit_variable_queue.erl | 71 +++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index f49aa085..10ada2dd 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -680,51 +680,22 @@ requeue(AckTags, #vqstate { delta = Delta, fold(Fun, Acc, #vqstate { q1 = Q1, q2 = Q2, - delta = Delta, + delta = #delta { start_seq_id = DeltaSeqId, + end_seq_id = DeltaSeqIdEnd }, q3 = Q3, - q4 = Q4} = State) -> - QFun = fun(M, {A, S}) -> - {#msg_status{msg = Msg}, State1} = read_msg(M, false, S), - A1 = Fun(Msg, A), - {A1, State1} + q4 = Q4 } = State) -> + QFun = fun(MsgStatus, {Acc0, State0}) -> + {#msg_status { msg = Msg }, State1 } = + read_msg(MsgStatus, false, State0), + Acc1 = Fun(Msg, Acc0), + {Acc1, State1} end, {Acc1, State1} = ?QUEUE:foldl(QFun, {Acc, State}, Q4), {Acc2, State2} = ?QUEUE:foldl(QFun, {Acc1, State1}, Q3), - {Acc3, State3} = delta_fold (Fun, Acc2, Delta, State2), + {Acc3, State3} = delta_fold (Fun, Acc2, DeltaSeqId, DeltaSeqIdEnd, State2), {Acc4, State4} = ?QUEUE:foldl(QFun, {Acc3, State3}, Q2), ?QUEUE:foldl(QFun, {Acc4, State4}, Q1). -delta_fold(_Fun, Acc, ?BLANK_DELTA_PATTERN(X), State) -> - {Acc, State}; -delta_fold(Fun, Acc, #delta { start_seq_id = DeltaSeqId, - end_seq_id = DeltaSeqIdEnd}, State) -> - {List, State1 = #vqstate { msg_store_clients = MSCState }} = - delta_index(DeltaSeqId, DeltaSeqIdEnd, State), - {Result, MSCState3} = - lists:foldl(fun ({MsgId, _SeqId, _MsgProps, IsPersistent, _IsDelivered}, - {Acc1, MSCState1}) -> - {{ok, Msg = #basic_message {}}, MSCState2} = - msg_store_read(MSCState1, IsPersistent, MsgId), - {Fun(Msg, Acc1), MSCState2} - end, {Acc, MSCState}, List), - {Result, State1 #vqstate { msg_store_clients = MSCState3}}. - -delta_index(DeltaSeqId, DeltaSeqIdEnd, State) -> - delta_index(DeltaSeqId, DeltaSeqIdEnd, State, []). - -delta_index(DeltaSeqIdDone, DeltaSeqIdEnd, State, List) - when DeltaSeqIdDone == DeltaSeqIdEnd -> - {List, State}; -delta_index(DeltaSeqIdDone, DeltaSeqIdEnd, - #vqstate { index_state = IndexState } = State, List) -> - DeltaSeqId1 = lists:min( - [rabbit_queue_index:next_segment_boundary(DeltaSeqIdDone), - DeltaSeqIdEnd]), - {List1, IndexState1} = - rabbit_queue_index:read(DeltaSeqIdDone, DeltaSeqId1, IndexState), - delta_index(DeltaSeqId1, DeltaSeqIdEnd, - State #vqstate { index_state = IndexState1 }, List ++ List1). - len(#vqstate { len = Len }) -> Len. is_empty(State) -> 0 == len(State). @@ -1402,7 +1373,7 @@ msg_indices_written_to_disk(Callback, MsgIdSet) -> end). %%---------------------------------------------------------------------------- -%% Internal plumbing for requeue +%% Internal plumbing for requeue and fold %%---------------------------------------------------------------------------- publish_alpha(#msg_status { msg = undefined } = MsgStatus, State) -> @@ -1471,6 +1442,28 @@ beta_limit(Q) -> delta_limit(?BLANK_DELTA_PATTERN(_X)) -> undefined; delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. +delta_fold(_Fun, Acc, DeltaSeqId, DeltaSeqIdEnd, State) + when DeltaSeqId == DeltaSeqIdEnd -> + {Acc, State}; +delta_fold(Fun, Acc, DeltaSeqId, DeltaSeqIdEnd, + #vqstate { index_state = IndexState, + msg_store_clients = MSCState } = State) -> + DeltaSeqId1 = lists:min( + [rabbit_queue_index:next_segment_boundary(DeltaSeqId), + DeltaSeqIdEnd]), + {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, + IndexState), + {Acc1, MSCState1} = + lists:foldl(fun ({MsgId, _SeqId, _MsgProps, IsPersistent, + _IsDelivered}, {Acc0, MSCState0}) -> + {{ok, Msg = #basic_message {}}, MSCState1} = + msg_store_read(MSCState0, IsPersistent, MsgId), + {Fun(Msg, Acc0), MSCState1} + end, {Acc, MSCState}, List), + delta_fold(Fun, Acc1, DeltaSeqId1, DeltaSeqIdEnd, + State #vqstate { index_state = IndexState1, + msg_store_clients = MSCState1 }). + %%---------------------------------------------------------------------------- %% Phase changes %%---------------------------------------------------------------------------- -- cgit v1.2.1 From 75cec9b38c1debbeb4cf5ac37c0c4b781d620485 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 23 Nov 2012 13:22:12 +0000 Subject: Backing queue fold tests --- src/rabbit_tests.erl | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 176374ce..e9d923ac 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2217,6 +2217,10 @@ variable_queue_publish(IsPersistent, Count, VQ) -> variable_queue_publish(IsPersistent, Count, fun (_N, P) -> P end, VQ). variable_queue_publish(IsPersistent, Count, PropFun, VQ) -> + variable_queue_publish(IsPersistent, Count, PropFun, + fun (_N) -> <<>> end, VQ). + +variable_queue_publish(IsPersistent, Count, PropFun, PayloadFun, VQ) -> lists:foldl( fun (N, VQN) -> rabbit_variable_queue:publish( @@ -2225,7 +2229,8 @@ variable_queue_publish(IsPersistent, Count, PropFun, VQ) -> <<>>, #'P_basic'{delivery_mode = case IsPersistent of true -> 2; false -> 1 - end}, <<>>), + end}, + PayloadFun(N)), PropFun(N, #message_properties{}), self(), VQN) end, VQ, lists:seq(1, Count)). @@ -2305,9 +2310,22 @@ test_variable_queue() -> fun test_dropwhile/1, fun test_dropwhile_varying_ram_duration/1, fun test_variable_queue_ack_limiting/1, - fun test_variable_queue_requeue/1]], + fun test_variable_queue_requeue/1, + fun test_variable_queue_fold/1]], passed. +test_variable_queue_fold(VQ0) -> + Count = rabbit_queue_index:next_segment_boundary(0) * 2 + 1, + VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), + VQ2 = variable_queue_publish( + true, Count, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), + {Acc, VQ3} = rabbit_variable_queue:fold(fun (M, A) -> [M | A] end, [], VQ2), + true = [term_to_binary(N) || N <- lists:seq(Count, 1, -1)] == + [list_to_binary(lists:reverse(P)) || + #basic_message{ content = #content{ payload_fragments_rev = P}} <- + Acc], + VQ3. + test_variable_queue_requeue(VQ0) -> Interval = 50, Count = rabbit_queue_index:next_segment_boundary(0) + 2 * Interval, -- cgit v1.2.1 From 0e455cb23ccc8045361b9769f5fbf378254b4013 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 23 Nov 2012 13:50:37 +0000 Subject: Propagate API change --- src/rabbit_amqqueue_process.erl | 5 +++++ src/rabbit_backing_queue.erl | 7 ++++++- src/rabbit_mirror_queue_master.erl | 7 ++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b3c44a50..743d72ef 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1154,6 +1154,11 @@ handle_call({requeue, AckTags, ChPid}, From, State) -> gen_server2:reply(From, ok), noreply(requeue(AckTags, ChPid, State)); +handle_call({fold, Fun, Acc}, _From, State = #q{backing_queue = BQ, + backing_queue_state = BQS}) -> + {Reply, BQS1} = BQ:fold(Fun, Acc, BQS), + reply(Reply, State #q{backing_queue_state = BQS1}); + handle_call(start_mirroring, _From, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> %% lookup again to get policy for init_with_existing_bq diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 329144f9..f0d0b46c 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -159,6 +159,11 @@ %% and were pending acknowledgement. -callback requeue([ack()], state()) -> {msg_ids(), state()}. +%% Fold over all the messages in a queue and return the accumulated +%% results, leaving the queue undisturbed. +-callback fold(fun((rabbit_types:basic_message(), any()) -> any()), + any(), state()) -> {any(), state()}. + %% How long is my queue? -callback len(state()) -> non_neg_integer(). @@ -220,7 +225,7 @@ behaviour_info(callbacks) -> [{start, 1}, {stop, 0}, {init, 3}, {terminate, 2}, {delete_and_terminate, 2}, {purge, 1}, {publish, 4}, {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, {dropwhile, 3}, - {fetch, 2}, {ack, 2}, {foreach_ack, 3}, {requeue, 2}, {len, 1}, + {fetch, 2}, {ack, 2}, {foreach_ack, 3}, {requeue, 2}, {fold, 3}, {len, 1}, {is_empty, 1}, {depth, 1}, {set_ram_duration_target, 2}, {ram_duration, 1}, {needs_timeout, 1}, {timeout, 1}, {handle_pre_hibernate, 1}, {status, 1}, {invoke, 3}, {is_duplicate, 2}] ; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 39060c09..15aeea01 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -19,7 +19,7 @@ -export([init/3, terminate/2, delete_and_terminate/2, purge/1, publish/4, publish_delivered/4, discard/3, fetch/2, drop/2, ack/2, - requeue/2, len/1, is_empty/1, depth/1, drain_confirmed/1, + requeue/2, fold/3, len/1, is_empty/1, depth/1, drain_confirmed/1, dropwhile/3, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, is_duplicate/2, foreach_ack/3]). @@ -321,6 +321,11 @@ requeue(AckTags, State = #state { gm = GM, ok = gm:broadcast(GM, {requeue, MsgIds}), {MsgIds, State #state { backing_queue_state = BQS1 }}. +fold(Fun, Acc, State = #state { backing_queue = BQ, + backing_queue_state = BQS }) -> + {Result, BQS1} = BQ:fold(Fun, Acc, BQS), + {Result, State #state { backing_queue_state = BQS1 }}. + len(#state { backing_queue = BQ, backing_queue_state = BQS }) -> BQ:len(BQS). -- cgit v1.2.1 From e6b612d8265e81363cee4152c54aa4b0354380af Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 23 Nov 2012 14:25:56 +0000 Subject: Better type signature ...and rollback amqqueue_process changes --- src/rabbit_amqqueue_process.erl | 5 ----- src/rabbit_backing_queue.erl | 5 +++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 743d72ef..b3c44a50 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1154,11 +1154,6 @@ handle_call({requeue, AckTags, ChPid}, From, State) -> gen_server2:reply(From, ok), noreply(requeue(AckTags, ChPid, State)); -handle_call({fold, Fun, Acc}, _From, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - {Reply, BQS1} = BQ:fold(Fun, Acc, BQS), - reply(Reply, State #q{backing_queue_state = BQS1}); - handle_call(start_mirroring, _From, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> %% lookup again to get policy for init_with_existing_bq diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index f0d0b46c..e9c014be 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -23,6 +23,7 @@ %% We can't specify a per-queue ack/state with callback signatures -type(ack() :: any()). -type(state() :: any()). +-type(acc() :: any()). -type(msg_ids() :: [rabbit_types:msg_id()]). -type(fetch_result(Ack) :: @@ -161,8 +162,8 @@ %% Fold over all the messages in a queue and return the accumulated %% results, leaving the queue undisturbed. --callback fold(fun((rabbit_types:basic_message(), any()) -> any()), - any(), state()) -> {any(), state()}. +-callback fold(fun((rabbit_types:basic_message(), acc()) -> acc()), + acc(), state()) -> {acc(), state()}. %% How long is my queue? -callback len(state()) -> non_neg_integer(). -- cgit v1.2.1 From 107edf1a80b9e43d960bff62310b1ecee5157499 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 23 Nov 2012 14:43:23 +0000 Subject: refactor: vq:ram_ack_index doesn't need to be a gb_tree a gb_set suffices --- src/rabbit_variable_queue.erl | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 7813aa7b..298c68b6 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -348,7 +348,7 @@ q4 :: ?QUEUE:?QUEUE(), next_seq_id :: seq_id(), pending_ack :: gb_tree(), - ram_ack_index :: gb_tree(), + ram_ack_index :: gb_set(), index_state :: any(), msg_store_clients :: 'undefined' | {{any(), binary()}, {any(), binary()}}, @@ -731,7 +731,7 @@ ram_duration(State = #vqstate { {AvgAckIngressRate, AckIngress1} = update_rate(Now, AckTimestamp, AckInCount, AckIngress), - RamAckCount = gb_trees:size(RamAckIndex), + RamAckCount = gb_sets:size(RamAckIndex), Duration = %% msgs+acks / (msgs+acks/sec) == sec case (AvgEgressRate == 0 andalso AvgIngressRate == 0 andalso @@ -810,7 +810,7 @@ status(#vqstate { {pending_acks , gb_trees:size(PA)}, {target_ram_count , TargetRamCount}, {ram_msg_count , RamMsgCount}, - {ram_ack_count , gb_trees:size(RAI)}, + {ram_ack_count , gb_sets:size(RAI)}, {next_seq_id , NextSeqId}, {persistent_count , PersistentCount}, {avg_ingress_rate , AvgIngressRate}, @@ -1015,7 +1015,7 @@ init(IsDurable, IndexState, DeltaCount, Terms, AsyncCallback, q4 = ?QUEUE:new(), next_seq_id = NextSeqId, pending_ack = gb_trees:empty(), - ram_ack_index = gb_trees:empty(), + ram_ack_index = gb_sets:empty(), index_state = IndexState1, msg_store_clients = {PersistentClient, TransientClient}, durable = IsDurable, @@ -1233,7 +1233,6 @@ maybe_write_to_disk(ForceMsg, ForceIndex, MsgStatus, %%---------------------------------------------------------------------------- record_pending_ack(#msg_status { seq_id = SeqId, - msg_id = MsgId, msg_on_disk = MsgOnDisk } = MsgStatus, State = #vqstate { pending_ack = PA, ram_ack_index = RAI, @@ -1241,7 +1240,7 @@ record_pending_ack(#msg_status { seq_id = SeqId, {AckEntry, RAI1} = case MsgOnDisk of true -> {m(trim_msg_status(MsgStatus)), RAI}; - false -> {MsgStatus, gb_trees:insert(SeqId, MsgId, RAI)} + false -> {MsgStatus, gb_sets:insert(SeqId, RAI)} end, State #vqstate { pending_ack = gb_trees:insert(SeqId, AckEntry, PA), ram_ack_index = RAI1, @@ -1251,7 +1250,7 @@ remove_pending_ack(SeqId, State = #vqstate { pending_ack = PA, ram_ack_index = RAI }) -> {gb_trees:get(SeqId, PA), State #vqstate { pending_ack = gb_trees:delete(SeqId, PA), - ram_ack_index = gb_trees:delete_any(SeqId, RAI) }}. + ram_ack_index = gb_sets:delete_any(SeqId, RAI) }}. purge_pending_ack(KeepPersistent, State = #vqstate { pending_ack = PA, @@ -1262,7 +1261,7 @@ purge_pending_ack(KeepPersistent, accumulate_ack(MsgStatus, Acc) end, accumulate_ack_init(), PA), State1 = State #vqstate { pending_ack = gb_trees:empty(), - ram_ack_index = gb_trees:empty() }, + ram_ack_index = gb_sets:empty() }, case KeepPersistent of true -> case orddict:find(false, MsgIdsByStore) of error -> State1; @@ -1462,7 +1461,7 @@ reduce_memory_use(AlphaBetaFun, BetaDeltaFun, AckFun, }) -> {Reduce, State1 = #vqstate { q2 = Q2, q3 = Q3 }} = - case chunk_size(RamMsgCount + gb_trees:size(RamAckIndex), + case chunk_size(RamMsgCount + gb_sets:size(RamAckIndex), TargetRamCount) of 0 -> {false, State}; %% Reduce memory of pending acks and alphas. The order is @@ -1490,12 +1489,12 @@ limit_ram_acks(0, State) -> {0, State}; limit_ram_acks(Quota, State = #vqstate { pending_ack = PA, ram_ack_index = RAI }) -> - case gb_trees:is_empty(RAI) of + case gb_sets:is_empty(RAI) of true -> {Quota, State}; false -> - {SeqId, MsgId, RAI1} = gb_trees:take_largest(RAI), - MsgStatus = #msg_status { msg_id = MsgId, is_persistent = false} = + {SeqId, RAI1} = gb_sets:take_largest(RAI), + MsgStatus = #msg_status { is_persistent = false} = gb_trees:get(SeqId, PA), {MsgStatus1, State1} = maybe_write_to_disk(true, false, MsgStatus, State), -- cgit v1.2.1 From b872b881f803417ed32e3b31a199a237998eceff Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 23 Nov 2012 14:59:47 +0000 Subject: don't evict messages from RAM when inserting them into pending_ack --- src/rabbit_variable_queue.erl | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 298c68b6..1aea8a3b 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1232,17 +1232,15 @@ maybe_write_to_disk(ForceMsg, ForceIndex, MsgStatus, %% Internal gubbins for acks %%---------------------------------------------------------------------------- -record_pending_ack(#msg_status { seq_id = SeqId, - msg_on_disk = MsgOnDisk } = MsgStatus, +record_pending_ack(#msg_status { seq_id = SeqId, msg = Msg } = MsgStatus, State = #vqstate { pending_ack = PA, ram_ack_index = RAI, ack_in_counter = AckInCount}) -> - {AckEntry, RAI1} = - case MsgOnDisk of - true -> {m(trim_msg_status(MsgStatus)), RAI}; - false -> {MsgStatus, gb_sets:insert(SeqId, RAI)} - end, - State #vqstate { pending_ack = gb_trees:insert(SeqId, AckEntry, PA), + RAI1 = case Msg of + undefined -> RAI; + _ -> gb_sets:insert(SeqId, RAI) + end, + State #vqstate { pending_ack = gb_trees:insert(SeqId, MsgStatus, PA), ram_ack_index = RAI1, ack_in_counter = AckInCount + 1}. @@ -1494,8 +1492,7 @@ limit_ram_acks(Quota, State = #vqstate { pending_ack = PA, {Quota, State}; false -> {SeqId, RAI1} = gb_sets:take_largest(RAI), - MsgStatus = #msg_status { is_persistent = false} = - gb_trees:get(SeqId, PA), + MsgStatus = gb_trees:get(SeqId, PA), {MsgStatus1, State1} = maybe_write_to_disk(true, false, MsgStatus, State), PA1 = gb_trees:update(SeqId, m(trim_msg_status(MsgStatus1)), PA), -- cgit v1.2.1 From ba1fadc5a1c9f4d8cb1e537bfed1365d1e6bbf37 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 23 Nov 2012 15:09:28 +0000 Subject: more precise signature --- src/rabbit_backing_queue.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index e9c014be..2fc10bb2 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -23,7 +23,6 @@ %% We can't specify a per-queue ack/state with callback signatures -type(ack() :: any()). -type(state() :: any()). --type(acc() :: any()). -type(msg_ids() :: [rabbit_types:msg_id()]). -type(fetch_result(Ack) :: @@ -162,8 +161,8 @@ %% Fold over all the messages in a queue and return the accumulated %% results, leaving the queue undisturbed. --callback fold(fun((rabbit_types:basic_message(), acc()) -> acc()), - acc(), state()) -> {acc(), state()}. +-callback fold(fun((rabbit_types:basic_message(), A) -> A), A, state()) + -> {A, state()}. %% How long is my queue? -callback len(state()) -> non_neg_integer(). -- cgit v1.2.1 From 019d70472eae0441a30926e115130bc574f7c406 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 23 Nov 2012 15:28:45 +0000 Subject: cosmetic --- src/rabbit_variable_queue.erl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 10ada2dd..6aab6bf0 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -687,14 +687,14 @@ fold(Fun, Acc, #vqstate { q1 = Q1, QFun = fun(MsgStatus, {Acc0, State0}) -> {#msg_status { msg = Msg }, State1 } = read_msg(MsgStatus, false, State0), - Acc1 = Fun(Msg, Acc0), - {Acc1, State1} + {Fun(Msg, Acc0), State1} end, {Acc1, State1} = ?QUEUE:foldl(QFun, {Acc, State}, Q4), {Acc2, State2} = ?QUEUE:foldl(QFun, {Acc1, State1}, Q3), - {Acc3, State3} = delta_fold (Fun, Acc2, DeltaSeqId, DeltaSeqIdEnd, State2), + {Acc3, State3} = delta_fold(Fun, Acc2, DeltaSeqId, DeltaSeqIdEnd, State2), {Acc4, State4} = ?QUEUE:foldl(QFun, {Acc3, State3}, Q2), - ?QUEUE:foldl(QFun, {Acc4, State4}, Q1). + {Acc5, State5} = ?QUEUE:foldl(QFun, {Acc4, State4}, Q1), + {Acc5, State5}. len(#vqstate { len = Len }) -> Len. @@ -1442,8 +1442,7 @@ beta_limit(Q) -> delta_limit(?BLANK_DELTA_PATTERN(_X)) -> undefined; delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. -delta_fold(_Fun, Acc, DeltaSeqId, DeltaSeqIdEnd, State) - when DeltaSeqId == DeltaSeqIdEnd -> +delta_fold(_Fun, Acc, DeltaSeqIdEnd, DeltaSeqIdEnd, State) -> {Acc, State}; delta_fold(Fun, Acc, DeltaSeqId, DeltaSeqIdEnd, #vqstate { index_state = IndexState, -- cgit v1.2.1 From c0064525834826fa355f47fa42a72e269bf6e4f3 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 23 Nov 2012 16:00:41 +0000 Subject: Eager mirror synchronisation. --- src/rabbit_amqqueue.erl | 8 +++++++- src/rabbit_amqqueue_process.erl | 14 ++++++++++++++ src/rabbit_mirror_queue_master.erl | 29 ++++++++++++++++++++++++++++- src/rabbit_mirror_queue_slave.erl | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 8ce1160c..4d957789 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -31,7 +31,7 @@ -export([notify_down_all/2, limit_all/3]). -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). --export([start_mirroring/1, stop_mirroring/1]). +-export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1]). %% internal -export([internal_declare/2, internal_delete/2, run_backing_queue/3, @@ -591,6 +591,12 @@ set_maximum_since_use(QPid, Age) -> start_mirroring(QPid) -> ok = delegate_call(QPid, start_mirroring). stop_mirroring(QPid) -> ok = delegate_call(QPid, stop_mirroring). +sync_mirrors(Name) -> + case lookup(Name) of + {ok, #amqqueue{pid = QPid}} -> delegate_cast(QPid, sync_mirrors); + _ -> ok + end. + on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( fun () -> QsDels = diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b3c44a50..68ae4816 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1300,6 +1300,20 @@ handle_cast({dead_letter, Msgs, Reason}, State = #q{dlx = XName}) -> cleanup_after_confirm([AckTag || {_, AckTag} <- Msgs], State) end; +handle_cast(sync_mirrors, + State = #q{q = #amqqueue{name = Name}, + backing_queue = BQ, + backing_queue_state = BQS}) -> + case BQ of + rabbit_mirror_queue_master -> + {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = + rabbit_amqqueue:lookup(Name), + rabbit_mirror_queue_master:sync_mirrors(SPids -- SSPids, BQS); + _ -> + ok + end, + noreply(State); + handle_cast(wake_up, State) -> noreply(State). diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 39060c09..bc2d21ac 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -28,7 +28,7 @@ -export([promote_backing_queue_state/7, sender_death_fun/0, depth_fun/0]). --export([init_with_existing_bq/3, stop_mirroring/1]). +-export([init_with_existing_bq/3, stop_mirroring/1, sync_mirrors/2]). -behaviour(rabbit_backing_queue). @@ -127,6 +127,33 @@ stop_mirroring(State = #state { coordinator = CPid, stop_all_slaves(shutdown, State), {BQ, BQS}. +sync_mirrors(SPids, #state { backing_queue = BQ, + backing_queue_state = BQS }) -> + Ref = make_ref(), + SPidsMRefs = [begin + SPid ! {sync_start, Ref, self()}, + MRef = erlang:monitor(process, SPid), + {SPid, MRef} + end || SPid <- SPids], + %% We wait for a reply from the slaves so that we know they are in + %% a receive block and will thus receive messages we send to them + %% *without* those messages ending up in their gen_server2 pqueue. + SPids1 = [SPid1 || {SPid, MRef} <- SPidsMRefs, + SPid1 <- [receive + {'DOWN', MRef, process, SPid, _Reason} -> + dead; + {sync_ready, Ref, SPid} -> + SPid + end], + SPid1 =/= dead], + [erlang:demonitor(MRef) || {_, MRef} <- SPidsMRefs], + BQ:fold(fun (M = #basic_message{}, none) -> + [SPid ! {sync_message, Ref, M} || SPid <- SPids1], + none + end, none, BQS), + [SPid ! {sync_complete, Ref} || SPid <- SPids1], + ok. + terminate({shutdown, dropped} = Reason, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 3ad8eb77..cd2b205c 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -264,6 +264,14 @@ handle_info({bump_credit, Msg}, State) -> credit_flow:handle_bump_msg(Msg), noreply(State); +handle_info({sync_start, Ref, MPid}, + State = #state { backing_queue = BQ, + backing_queue_state = BQS }) -> + MRef = erlang:monitor(process, MPid), + MPid ! {sync_ready, Ref, self()}, + {_MsgCount, BQS1} = BQ:purge(BQS), + noreply(sync_loop(Ref, MRef, State#state{backing_queue_state = BQS1})); + handle_info(Msg, State) -> {stop, {unexpected_info, Msg}, State}. @@ -830,3 +838,27 @@ record_synchronised(#amqqueue { name = QName }) -> ok end end). + +sync_loop(Ref, MRef, State = #state{backing_queue = BQ, + backing_queue_state = BQS}) -> + receive + {'DOWN', MRef, process, _MPid, _Reason} -> + %% If the master dies half way we are not in the usual + %% half-synced state (with messages nearer the tail of the + %% queue; instead we have ones nearer the head. If we then + %% sync with a newly promoted master, or even just receive + %% messages from it, we have a hole in the middle. So the + %% only thing to do here is purge.) + State#state{backing_queue_state = BQ:purge(BQS)}; + {sync_complete, Ref} -> + erlang:demonitor(MRef), + set_delta(0, State); + {sync_message, Ref, M} -> + %% TODO expiry / delivered need fixing + Props = #message_properties{expiry = undefined, + needs_confirming = false, + delivered = false}, + BQS1 = BQ:publish(M, Props, none, BQS), + sync_loop(Ref, MRef, State#state{backing_queue_state = BQS1}) + end. + -- cgit v1.2.1 From 38dad4b998445675d1f91d2cf0d79303ac2ce9e0 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 23 Nov 2012 16:28:26 +0000 Subject: Add message properties to backing queue fold --- src/rabbit_backing_queue.erl | 5 +++-- src/rabbit_backing_queue_qc.erl | 4 ++-- src/rabbit_tests.erl | 4 +++- src/rabbit_variable_queue.erl | 8 ++++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 2fc10bb2..24dd36e1 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -161,8 +161,9 @@ %% Fold over all the messages in a queue and return the accumulated %% results, leaving the queue undisturbed. --callback fold(fun((rabbit_types:basic_message(), A) -> A), A, state()) - -> {A, state()}. +-callback fold(fun(({rabbit_types:basic_message(), + rabbit_types:message_properties()}, A) -> A), + A, state()) -> {A, state()}. %% How long is my queue? -callback len(state()) -> non_neg_integer(). diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index 00e17d02..a6d9b59a 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -331,8 +331,8 @@ postcondition(S, {call, ?BQMOD, drain_confirmed, _Args}, Res) -> postcondition(S, {call, ?BQMOD, fold, _Args}, {Res, _BQ}) -> #state{messages = Messages} = S, - lists:foldl(fun ({_SeqId, {_MsgProps, Msg}}, Acc) -> - foldfun(Msg, Acc) + lists:foldl(fun ({_SeqId, {MsgProps, Msg}}, Acc) -> + foldfun({Msg, MsgProps}, Acc) end, foldacc(), gb_trees:to_list(Messages)) =:= Res; postcondition(#state{bqstate = BQ, len = Len}, {call, _M, _F, _A}, _Res) -> diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index e9d923ac..dffca79d 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2319,7 +2319,9 @@ test_variable_queue_fold(VQ0) -> VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), VQ2 = variable_queue_publish( true, Count, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), - {Acc, VQ3} = rabbit_variable_queue:fold(fun (M, A) -> [M | A] end, [], VQ2), + {Acc, VQ3} = rabbit_variable_queue:fold(fun ({M, _}, A) -> + [M | A] + end, [], VQ2), true = [term_to_binary(N) || N <- lists:seq(Count, 1, -1)] == [list_to_binary(lists:reverse(P)) || #basic_message{ content = #content{ payload_fragments_rev = P}} <- diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 6aab6bf0..644ba182 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -685,9 +685,9 @@ fold(Fun, Acc, #vqstate { q1 = Q1, q3 = Q3, q4 = Q4 } = State) -> QFun = fun(MsgStatus, {Acc0, State0}) -> - {#msg_status { msg = Msg }, State1 } = + {#msg_status { msg = Msg, msg_props = MsgProps }, State1 } = read_msg(MsgStatus, false, State0), - {Fun(Msg, Acc0), State1} + {Fun({Msg, MsgProps}, Acc0), State1} end, {Acc1, State1} = ?QUEUE:foldl(QFun, {Acc, State}, Q4), {Acc2, State2} = ?QUEUE:foldl(QFun, {Acc1, State1}, Q3), @@ -1453,11 +1453,11 @@ delta_fold(Fun, Acc, DeltaSeqId, DeltaSeqIdEnd, {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, IndexState), {Acc1, MSCState1} = - lists:foldl(fun ({MsgId, _SeqId, _MsgProps, IsPersistent, + lists:foldl(fun ({MsgId, _SeqId, MsgProps, IsPersistent, _IsDelivered}, {Acc0, MSCState0}) -> {{ok, Msg = #basic_message {}}, MSCState1} = msg_store_read(MSCState0, IsPersistent, MsgId), - {Fun(Msg, Acc0), MSCState1} + {Fun({Msg, MsgProps}, Acc0), MSCState1} end, {Acc, MSCState}, List), delta_fold(Fun, Acc1, DeltaSeqId1, DeltaSeqIdEnd, State #vqstate { index_state = IndexState1, -- cgit v1.2.1 From 9746f148c5641137438ac79b31b7381e005343e2 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 23 Nov 2012 16:33:30 +0000 Subject: Cosmetic / clearer. --- src/rabbit_mirror_queue_slave.erl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index cd2b205c..ae069898 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -840,7 +840,7 @@ record_synchronised(#amqqueue { name = QName }) -> end). sync_loop(Ref, MRef, State = #state{backing_queue = BQ, - backing_queue_state = BQS}) -> + backing_queue_state = BQS}) -> receive {'DOWN', MRef, process, _MPid, _Reason} -> %% If the master dies half way we are not in the usual @@ -854,11 +854,10 @@ sync_loop(Ref, MRef, State = #state{backing_queue = BQ, erlang:demonitor(MRef), set_delta(0, State); {sync_message, Ref, M} -> - %% TODO expiry / delivered need fixing + %% TODO expiry needs fixing Props = #message_properties{expiry = undefined, needs_confirming = false, - delivered = false}, + delivered = true}, BQS1 = BQ:publish(M, Props, none, BQS), sync_loop(Ref, MRef, State#state{backing_queue_state = BQS1}) end. - -- cgit v1.2.1 From 4f02718feda2495c6ea02ea93ce4de896b9dc102 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 23 Nov 2012 16:52:59 +0000 Subject: Log progress, and an important optimisation. --- src/rabbit_amqqueue_process.erl | 2 +- src/rabbit_mirror_queue_master.erl | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 68ae4816..07f4c3b1 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1308,7 +1308,7 @@ handle_cast(sync_mirrors, rabbit_mirror_queue_master -> {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = rabbit_amqqueue:lookup(Name), - rabbit_mirror_queue_master:sync_mirrors(SPids -- SSPids, BQS); + rabbit_mirror_queue_master:sync_mirrors(SPids -- SSPids, Name, BQS); _ -> ok end, diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index bc2d21ac..05075d0f 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -28,7 +28,7 @@ -export([promote_backing_queue_state/7, sender_death_fun/0, depth_fun/0]). --export([init_with_existing_bq/3, stop_mirroring/1, sync_mirrors/2]). +-export([init_with_existing_bq/3, stop_mirroring/1, sync_mirrors/3]). -behaviour(rabbit_backing_queue). @@ -127,8 +127,14 @@ stop_mirroring(State = #state { coordinator = CPid, stop_all_slaves(shutdown, State), {BQ, BQS}. -sync_mirrors(SPids, #state { backing_queue = BQ, - backing_queue_state = BQS }) -> +sync_mirrors([], Name, _State) -> + rabbit_log:info("Synchronising ~s: nothing to do~n", + [rabbit_misc:rs(Name)]), + ok; +sync_mirrors(SPids, Name, #state { backing_queue = BQ, + backing_queue_state = BQS }) -> + rabbit_log:info("Synchronising ~s with slaves ~p~n", + [rabbit_misc:rs(Name), SPids]), Ref = make_ref(), SPidsMRefs = [begin SPid ! {sync_start, Ref, self()}, @@ -147,11 +153,20 @@ sync_mirrors(SPids, #state { backing_queue = BQ, end], SPid1 =/= dead], [erlang:demonitor(MRef) || {_, MRef} <- SPidsMRefs], - BQ:fold(fun (M = #basic_message{}, none) -> - [SPid ! {sync_message, Ref, M} || SPid <- SPids1], - none - end, none, BQS), + {Total, _BQS} = + BQ:fold(fun (M = #basic_message{}, I) -> + [SPid ! {sync_message, Ref, M} || SPid <- SPids1], + case I rem 1000 of + 0 -> rabbit_log:info( + "Synchronising ~s: ~p messages~n", + [rabbit_misc:rs(Name), I]); + _ -> ok + end, + I + 1 + end, 0, BQS), [SPid ! {sync_complete, Ref} || SPid <- SPids1], + rabbit_log:info("Synchronising ~s: ~p messages; complete~n", + [rabbit_misc:rs(Name), Total]), ok. terminate({shutdown, dropped} = Reason, -- cgit v1.2.1 From e2fb9a87298b9131d24795cf04ce164eb240d6ef Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 23 Nov 2012 17:19:03 +0000 Subject: Flow control for the sync process. --- src/rabbit_mirror_queue_master.erl | 15 ++++++++++++++- src/rabbit_mirror_queue_slave.erl | 13 +++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 05075d0f..dadaef1d 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -155,7 +155,11 @@ sync_mirrors(SPids, Name, #state { backing_queue = BQ, [erlang:demonitor(MRef) || {_, MRef} <- SPidsMRefs], {Total, _BQS} = BQ:fold(fun (M = #basic_message{}, I) -> - [SPid ! {sync_message, Ref, M} || SPid <- SPids1], + wait_for_credit(), + [begin + credit_flow:send(SPid, ?CREDIT_DISC_BOUND), + SPid ! {sync_message, Ref, M} + end || SPid <- SPids1], case I rem 1000 of 0 -> rabbit_log:info( "Synchronising ~s: ~p messages~n", @@ -169,6 +173,15 @@ sync_mirrors(SPids, Name, #state { backing_queue = BQ, [rabbit_misc:rs(Name), Total]), ok. +wait_for_credit() -> + case credit_flow:blocked() of + true -> receive + {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), + wait_for_credit() + end; + false -> ok + end. + terminate({shutdown, dropped} = Reason, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index ae069898..c1d2e8e4 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -270,7 +270,8 @@ handle_info({sync_start, Ref, MPid}, MRef = erlang:monitor(process, MPid), MPid ! {sync_ready, Ref, self()}, {_MsgCount, BQS1} = BQ:purge(BQS), - noreply(sync_loop(Ref, MRef, State#state{backing_queue_state = BQS1})); + noreply( + sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1})); handle_info(Msg, State) -> {stop, {unexpected_info, Msg}, State}. @@ -839,10 +840,10 @@ record_synchronised(#amqqueue { name = QName }) -> end end). -sync_loop(Ref, MRef, State = #state{backing_queue = BQ, +sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, backing_queue_state = BQS}) -> receive - {'DOWN', MRef, process, _MPid, _Reason} -> + {'DOWN', MRef, process, MPid, _Reason} -> %% If the master dies half way we are not in the usual %% half-synced state (with messages nearer the tail of the %% queue; instead we have ones nearer the head. If we then @@ -850,14 +851,18 @@ sync_loop(Ref, MRef, State = #state{backing_queue = BQ, %% messages from it, we have a hole in the middle. So the %% only thing to do here is purge.) State#state{backing_queue_state = BQ:purge(BQS)}; + {bump_credit, Msg} -> + credit_flow:handle_bump_msg(Msg), + sync_loop(Ref, MRef, MPid, State); {sync_complete, Ref} -> erlang:demonitor(MRef), set_delta(0, State); {sync_message, Ref, M} -> + credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), %% TODO expiry needs fixing Props = #message_properties{expiry = undefined, needs_confirming = false, delivered = true}, BQS1 = BQ:publish(M, Props, none, BQS), - sync_loop(Ref, MRef, State#state{backing_queue_state = BQS1}) + sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1}) end. -- cgit v1.2.1 From c08f72e5ca8043eb1199acd09fbf229551e516e2 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 23 Nov 2012 17:28:35 +0000 Subject: Oops --- src/rabbit_mirror_queue_slave.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index c1d2e8e4..a8615cee 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -850,7 +850,8 @@ sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, %% sync with a newly promoted master, or even just receive %% messages from it, we have a hole in the middle. So the %% only thing to do here is purge.) - State#state{backing_queue_state = BQ:purge(BQS)}; + {_MsgCount, BQS1} = BQ:purge(BQS), + State#state{backing_queue_state = BQS1}; {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), sync_loop(Ref, MRef, MPid, State); -- cgit v1.2.1 From fdd2a4bd4c3f84ff80d6450fd23d50f29be24acc Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 23 Nov 2012 17:44:01 +0000 Subject: Fix expiry --- src/rabbit_mirror_queue_master.erl | 4 ++-- src/rabbit_mirror_queue_slave.erl | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index a9f5e5ac..16642604 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -154,11 +154,11 @@ sync_mirrors(SPids, Name, #state { backing_queue = BQ, SPid1 =/= dead], [erlang:demonitor(MRef) || {_, MRef} <- SPidsMRefs], {Total, _BQS} = - BQ:fold(fun (M = #basic_message{}, I) -> + BQ:fold(fun ({Msg, MsgProps}, I) -> wait_for_credit(), [begin credit_flow:send(SPid, ?CREDIT_DISC_BOUND), - SPid ! {sync_message, Ref, M} + SPid ! {sync_message, Ref, Msg, MsgProps} end || SPid <- SPids1], case I rem 1000 of 0 -> rabbit_log:info( diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index a8615cee..d408c56e 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -858,12 +858,10 @@ sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, {sync_complete, Ref} -> erlang:demonitor(MRef), set_delta(0, State); - {sync_message, Ref, M} -> + {sync_message, Ref, Msg, Props0} -> credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), - %% TODO expiry needs fixing - Props = #message_properties{expiry = undefined, - needs_confirming = false, - delivered = true}, - BQS1 = BQ:publish(M, Props, none, BQS), + Props = Props0#message_properties{needs_confirming = false, + delivered = true}, + BQS1 = BQ:publish(Msg, Props, none, BQS), sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1}) end. -- cgit v1.2.1 From dfb04276e78cc67bffa869effe34c77d82cf039c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 24 Nov 2012 10:14:09 +0000 Subject: 'pid' is a valid INFO_KEY and some code, namely the clustering tests, rely on that --- src/rabbit_amqqueue_process.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 476f806d..5ddafba8 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -108,7 +108,7 @@ owner_pid ]). --define(INFO_KEYS, ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [name]). +-define(INFO_KEYS, [pid | ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [name]]). %%---------------------------------------------------------------------------- -- cgit v1.2.1 From ab9ab903487244bcbcb50b982f1c44cfcfbe20f0 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 26 Nov 2012 10:44:36 +0000 Subject: mark all messages enqueued in the slave as 'delivered' which is a much better than the set_delivered logic, which we can now get rid of. In doing so it also becomes clear that having the 'delivered' flag in the #message_properties is less than ideal. It is mutable and we never bothered updating vq s.t. it sets the flag correctly. So lets get rid of it and add a parameter to bq:publish instead --- include/rabbit.hrl | 3 +-- src/rabbit_amqqueue_process.erl | 15 +++++++-------- src/rabbit_backing_queue.erl | 6 +++--- src/rabbit_mirror_queue_master.erl | 39 +++++++++++++------------------------- src/rabbit_mirror_queue_slave.erl | 2 +- src/rabbit_tests.erl | 2 +- src/rabbit_variable_queue.erl | 30 ++++++++++++++--------------- 7 files changed, 40 insertions(+), 57 deletions(-) diff --git a/include/rabbit.hrl b/include/rabbit.hrl index b2832b45..0ccb80bf 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -78,8 +78,7 @@ -record(event, {type, props, timestamp}). --record(message_properties, {expiry, needs_confirming = false, - delivered = false}). +-record(message_properties, {expiry, needs_confirming = false}). -record(plugin, {name, %% atom() version, %% string() diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 5ddafba8..bfc0f418 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -540,8 +540,8 @@ run_message_queue(State) -> State2. attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, - Props = #message_properties{delivered = Delivered}, - State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> + Props, Delivered, State = #q{backing_queue = BQ, + backing_queue_state = BQS}) -> case BQ:is_duplicate(Message, BQS) of {false, BQS1} -> deliver_msgs_to_consumers( @@ -563,15 +563,15 @@ attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, Delivered, State) -> {Confirm, State1} = send_or_record_confirm(Delivery, State), - Props = message_properties(Message, Confirm, Delivered, State), - case attempt_delivery(Delivery, Props, State1) of + Props = message_properties(Message, Confirm, State), + case attempt_delivery(Delivery, Props, Delivered, State1) of {true, State2} -> State2; %% The next one is an optimisation {false, State2 = #q{ttl = 0, dlx = undefined}} -> discard(Delivery, State2); {false, State2 = #q{backing_queue = BQ, backing_queue_state = BQS}} -> - BQS1 = BQ:publish(Message, Props, SenderPid, BQS), + BQS1 = BQ:publish(Message, Props, Delivered, SenderPid, BQS), ensure_ttl_timer(Props#message_properties.expiry, State2#q{backing_queue_state = BQS1}) end. @@ -704,10 +704,9 @@ subtract_acks(ChPid, AckTags, State, Fun) -> Fun(State) end. -message_properties(Message, Confirm, Delivered, #q{ttl = TTL}) -> +message_properties(Message, Confirm, #q{ttl = TTL}) -> #message_properties{expiry = calculate_msg_expiry(Message, TTL), - needs_confirming = Confirm == eventually, - delivered = Delivered}. + needs_confirming = Confirm == eventually}. calculate_msg_expiry(#basic_message{content = Content}, TTL) -> #content{properties = Props} = diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 9e99ca5e..26c63b08 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -78,8 +78,8 @@ %% Publish a message. -callback publish(rabbit_types:basic_message(), - rabbit_types:message_properties(), pid(), state()) -> - state(). + rabbit_types:message_properties(), boolean(), pid(), + state()) -> state(). %% Called for messages which have already been passed straight %% out to a client. The queue will be empty for these calls @@ -219,7 +219,7 @@ behaviour_info(callbacks) -> [{start, 1}, {stop, 0}, {init, 3}, {terminate, 2}, - {delete_and_terminate, 2}, {purge, 1}, {publish, 4}, + {delete_and_terminate, 2}, {purge, 1}, {publish, 5}, {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, {dropwhile, 3}, {fetch, 2}, {ack, 2}, {foreach_ack, 3}, {requeue, 2}, {fold, 3}, {len, 1}, {is_empty, 1}, {depth, 1}, {set_ram_duration_target, 2}, diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 8fcd1893..c8a361b1 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -17,7 +17,7 @@ -module(rabbit_mirror_queue_master). -export([init/3, terminate/2, delete_and_terminate/2, - purge/1, publish/4, publish_delivered/4, + purge/1, publish/5, publish_delivered/4, discard/3, fetch/2, drop/2, ack/2, requeue/2, fold/3, len/1, is_empty/1, depth/1, drain_confirmed/1, dropwhile/3, set_ram_duration_target/2, ram_duration/1, @@ -38,7 +38,6 @@ coordinator, backing_queue, backing_queue_state, - set_delivered, seen_status, confirmed, ack_msg_id, @@ -55,7 +54,6 @@ coordinator :: pid(), backing_queue :: atom(), backing_queue_state :: any(), - set_delivered :: non_neg_integer(), seen_status :: dict(), confirmed :: [rabbit_guid:guid()], ack_msg_id :: dict(), @@ -114,7 +112,6 @@ init_with_existing_bq(Q = #amqqueue{name = QName}, BQ, BQS) -> coordinator = CPid, backing_queue = BQ, backing_queue_state = BQS, - set_delivered = 0, seen_status = dict:new(), confirmed = [], ack_msg_id = dict:new(), @@ -136,8 +133,8 @@ terminate({shutdown, dropped} = Reason, %% in without this node being restarted. Thus we must do the full %% blown delete_and_terminate now, but only locally: we do not %% broadcast delete_and_terminate. - State #state { backing_queue_state = BQ:delete_and_terminate(Reason, BQS), - set_delivered = 0 }; + State#state{backing_queue_state = BQ:delete_and_terminate(Reason, BQS)}; + terminate(Reason, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> %% Backing queue termination. The queue is going down but @@ -148,8 +145,7 @@ terminate(Reason, delete_and_terminate(Reason, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> stop_all_slaves(Reason, State), - State #state { backing_queue_state = BQ:delete_and_terminate(Reason, BQS), - set_delivered = 0 }. + State#state{backing_queue_state = BQ:delete_and_terminate(Reason, BQS)}. stop_all_slaves(Reason, #state{gm = GM}) -> Info = gm:info(GM), @@ -175,17 +171,16 @@ purge(State = #state { gm = GM, backing_queue_state = BQS }) -> ok = gm:broadcast(GM, {drop, 0, BQ:len(BQS), false}), {Count, BQS1} = BQ:purge(BQS), - {Count, State #state { backing_queue_state = BQS1, - set_delivered = 0 }}. + {Count, State #state { backing_queue_state = BQS1 }}. -publish(Msg = #basic_message { id = MsgId }, MsgProps, ChPid, +publish(Msg = #basic_message { id = MsgId }, MsgProps, IsDelivered, ChPid, State = #state { gm = GM, seen_status = SS, backing_queue = BQ, backing_queue_state = BQS }) -> false = dict:is_key(MsgId, SS), %% ASSERTION ok = gm:broadcast(GM, {publish, ChPid, MsgProps, Msg}), - BQS1 = BQ:publish(Msg, MsgProps, ChPid, BQS), + BQS1 = BQ:publish(Msg, MsgProps, IsDelivered, ChPid, BQS), ensure_monitoring(ChPid, State #state { backing_queue_state = BQS1 }). publish_delivered(Msg = #basic_message { id = MsgId }, MsgProps, @@ -224,7 +219,6 @@ discard(MsgId, ChPid, State = #state { gm = GM, dropwhile(Pred, AckRequired, State = #state{gm = GM, backing_queue = BQ, - set_delivered = SetDelivered, backing_queue_state = BQS }) -> Len = BQ:len(BQS), {Next, Msgs, BQS1} = BQ:dropwhile(Pred, AckRequired, BQS), @@ -234,9 +228,7 @@ dropwhile(Pred, AckRequired, 0 -> ok; _ -> ok = gm:broadcast(GM, {drop, Len1, Dropped, AckRequired}) end, - SetDelivered1 = lists:max([0, SetDelivered - Dropped]), - {Next, Msgs, State #state { backing_queue_state = BQS1, - set_delivered = SetDelivered1 } }. + {Next, Msgs, State #state { backing_queue_state = BQS1 } }. drain_confirmed(State = #state { backing_queue = BQ, backing_queue_state = BQS, @@ -269,16 +261,14 @@ drain_confirmed(State = #state { backing_queue = BQ, confirmed = [] }}. fetch(AckRequired, State = #state { backing_queue = BQ, - backing_queue_state = BQS, - set_delivered = SetDelivered }) -> + backing_queue_state = BQS }) -> {Result, BQS1} = BQ:fetch(AckRequired, BQS), State1 = State #state { backing_queue_state = BQS1 }, case Result of empty -> {Result, State1}; - {Message, IsDelivered, AckTag} -> - {{Message, IsDelivered orelse SetDelivered > 0, AckTag}, - drop(Message#basic_message.id, AckTag, State1)} + {#basic_message{id = MsgId}, _IsDelivered, AckTag} -> + {Result, drop(MsgId, AckTag, State1)} end. drop(AckRequired, State = #state { backing_queue = BQ, @@ -416,7 +406,6 @@ promote_backing_queue_state(CPid, BQ, BQS, GM, AckTags, SeenStatus, KS) -> coordinator = CPid, backing_queue = BQ, backing_queue_state = BQS1, - set_delivered = Len, seen_status = SeenStatus, confirmed = [], ack_msg_id = dict:new(), @@ -451,14 +440,12 @@ depth_fun() -> %% Helpers %% --------------------------------------------------------------------------- -drop(MsgId, AckTag, State = #state { set_delivered = SetDelivered, - ack_msg_id = AM, +drop(MsgId, AckTag, State = #state { ack_msg_id = AM, gm = GM, backing_queue = BQ, backing_queue_state = BQS }) -> ok = gm:broadcast(GM, {drop, BQ:len(BQS), 1, AckTag =/= undefined}), - State #state { set_delivered = lists:max([0, SetDelivered - 1]), - ack_msg_id = maybe_store_acktag(AckTag, MsgId, AM) }. + State #state { ack_msg_id = maybe_store_acktag(AckTag, MsgId, AM) }. maybe_store_acktag(undefined, _MsgId, AM) -> AM; maybe_store_acktag(AckTag, MsgId, AM) -> dict:store(AckTag, MsgId, AM). diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index cb7a2135..752dac89 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -703,7 +703,7 @@ process_instruction({publish, ChPid, MsgProps, Msg = #basic_message { id = MsgId }}, State) -> State1 = #state { backing_queue = BQ, backing_queue_state = BQS } = publish_or_discard(published, ChPid, MsgId, State), - BQS1 = BQ:publish(Msg, MsgProps, ChPid, BQS), + BQS1 = BQ:publish(Msg, MsgProps, true, ChPid, BQS), {ok, State1 #state { backing_queue_state = BQS1 }}; process_instruction({publish_delivered, ChPid, MsgProps, Msg = #basic_message { id = MsgId }}, State) -> diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 81180ebe..4a989424 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2230,7 +2230,7 @@ variable_queue_publish(IsPersistent, Count, PropFun, PayloadFun, VQ) -> false -> 1 end}, PayloadFun(N)), - PropFun(N, #message_properties{}), self(), VQN) + PropFun(N, #message_properties{}), false, self(), VQN) end, VQ, lists:seq(1, Count)). variable_queue_fetch(Count, IsPersistent, IsDelivered, Len, VQ) -> diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index e2566e10..be340cdd 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -17,7 +17,7 @@ -module(rabbit_variable_queue). -export([init/3, terminate/2, delete_and_terminate/2, purge/1, - publish/4, publish_delivered/4, discard/3, drain_confirmed/1, + publish/5, publish_delivered/4, discard/3, drain_confirmed/1, dropwhile/3, fetch/2, drop/2, ack/2, requeue/2, fold/3, len/1, is_empty/1, depth/1, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, @@ -520,16 +520,16 @@ purge(State = #vqstate { q4 = Q4, publish(Msg = #basic_message { is_persistent = IsPersistent, id = MsgId }, MsgProps = #message_properties { needs_confirming = NeedsConfirming }, - _ChPid, State = #vqstate { q1 = Q1, q3 = Q3, q4 = Q4, - next_seq_id = SeqId, - len = Len, - in_counter = InCount, - persistent_count = PCount, - durable = IsDurable, - ram_msg_count = RamMsgCount, - unconfirmed = UC }) -> + IsDelivered, _ChPid, State = #vqstate { q1 = Q1, q3 = Q3, q4 = Q4, + next_seq_id = SeqId, + len = Len, + in_counter = InCount, + persistent_count = PCount, + durable = IsDurable, + ram_msg_count = RamMsgCount, + unconfirmed = UC }) -> IsPersistent1 = IsDurable andalso IsPersistent, - MsgStatus = msg_status(IsPersistent1, SeqId, Msg, MsgProps), + MsgStatus = msg_status(IsPersistent1, IsDelivered, SeqId, Msg, MsgProps), {MsgStatus1, State1} = maybe_write_to_disk(false, false, MsgStatus, State), State2 = case ?QUEUE:is_empty(Q3) of false -> State1 #vqstate { q1 = ?QUEUE:in(m(MsgStatus1), Q1) }; @@ -556,8 +556,7 @@ publish_delivered(Msg = #basic_message { is_persistent = IsPersistent, durable = IsDurable, unconfirmed = UC }) -> IsPersistent1 = IsDurable andalso IsPersistent, - MsgStatus = (msg_status(IsPersistent1, SeqId, Msg, MsgProps)) - #msg_status { is_delivered = true }, + MsgStatus = msg_status(IsPersistent1, true, SeqId, Msg, MsgProps), {MsgStatus1, State1} = maybe_write_to_disk(false, false, MsgStatus, State), State2 = record_pending_ack(m(MsgStatus1), State1), PCount1 = PCount + one_if(IsPersistent1), @@ -891,11 +890,10 @@ gb_sets_maybe_insert(false, _Val, Set) -> Set; %% when requeueing, we re-add a msg_id to the unconfirmed set gb_sets_maybe_insert(true, Val, Set) -> gb_sets:add(Val, Set). -msg_status(IsPersistent, SeqId, Msg = #basic_message { id = MsgId }, - MsgProps = #message_properties { delivered = Delivered }) -> - %% TODO would it make sense to remove #msg_status.is_delivered? +msg_status(IsPersistent, IsDelivered, SeqId, + Msg = #basic_message { id = MsgId }, MsgProps) -> #msg_status { seq_id = SeqId, msg_id = MsgId, msg = Msg, - is_persistent = IsPersistent, is_delivered = Delivered, + is_persistent = IsPersistent, is_delivered = IsDelivered, msg_on_disk = false, index_on_disk = false, msg_props = MsgProps }. -- cgit v1.2.1 From 33394fa90b5b5f5596b8dc0e34b58879230b6ee5 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 26 Nov 2012 11:36:57 +0000 Subject: Untuple fold function --- src/rabbit_backing_queue.erl | 4 ++-- src/rabbit_backing_queue_qc.erl | 2 +- src/rabbit_tests.erl | 2 +- src/rabbit_variable_queue.erl | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 24dd36e1..ffa716b6 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -161,8 +161,8 @@ %% Fold over all the messages in a queue and return the accumulated %% results, leaving the queue undisturbed. --callback fold(fun(({rabbit_types:basic_message(), - rabbit_types:message_properties()}, A) -> A), +-callback fold(fun((rabbit_types:basic_message(), + rabbit_types:message_properties(), A) -> A), A, state()) -> {A, state()}. %% How long is my queue? diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index a6d9b59a..fb8b82ea 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -332,7 +332,7 @@ postcondition(S, {call, ?BQMOD, drain_confirmed, _Args}, Res) -> postcondition(S, {call, ?BQMOD, fold, _Args}, {Res, _BQ}) -> #state{messages = Messages} = S, lists:foldl(fun ({_SeqId, {MsgProps, Msg}}, Acc) -> - foldfun({Msg, MsgProps}, Acc) + foldfun(Msg, MsgProps, Acc) end, foldacc(), gb_trees:to_list(Messages)) =:= Res; postcondition(#state{bqstate = BQ, len = Len}, {call, _M, _F, _A}, _Res) -> diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index dffca79d..c4bd1836 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2319,7 +2319,7 @@ test_variable_queue_fold(VQ0) -> VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), VQ2 = variable_queue_publish( true, Count, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), - {Acc, VQ3} = rabbit_variable_queue:fold(fun ({M, _}, A) -> + {Acc, VQ3} = rabbit_variable_queue:fold(fun (M, _, A) -> [M | A] end, [], VQ2), true = [term_to_binary(N) || N <- lists:seq(Count, 1, -1)] == diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 644ba182..b826413a 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -687,7 +687,7 @@ fold(Fun, Acc, #vqstate { q1 = Q1, QFun = fun(MsgStatus, {Acc0, State0}) -> {#msg_status { msg = Msg, msg_props = MsgProps }, State1 } = read_msg(MsgStatus, false, State0), - {Fun({Msg, MsgProps}, Acc0), State1} + {Fun(Msg, MsgProps, Acc0), State1} end, {Acc1, State1} = ?QUEUE:foldl(QFun, {Acc, State}, Q4), {Acc2, State2} = ?QUEUE:foldl(QFun, {Acc1, State1}, Q3), @@ -1457,7 +1457,7 @@ delta_fold(Fun, Acc, DeltaSeqId, DeltaSeqIdEnd, _IsDelivered}, {Acc0, MSCState0}) -> {{ok, Msg = #basic_message {}}, MSCState1} = msg_store_read(MSCState0, IsPersistent, MsgId), - {Fun({Msg, MsgProps}, Acc0), MSCState1} + {Fun(Msg, MsgProps, Acc0), MSCState1} end, {Acc, MSCState}, List), delta_fold(Fun, Acc1, DeltaSeqId1, DeltaSeqIdEnd, State #vqstate { index_state = IndexState1, -- cgit v1.2.1 From 02d367c7f7d1bcdfb492f5e57cfc9b11a99568a0 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 26 Nov 2012 12:02:53 +0000 Subject: Add spec, fix API, refuse to run if there are pending acks, return status, don't throw away state. --- src/rabbit_amqqueue.erl | 8 +++----- src/rabbit_amqqueue_process.erl | 32 ++++++++++++++++++-------------- src/rabbit_mirror_queue_master.erl | 12 ++++++------ src/rabbit_mirror_queue_slave.erl | 1 + 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 4d957789..ad81ba03 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -173,6 +173,8 @@ (rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'). -spec(start_mirroring/1 :: (pid()) -> 'ok'). -spec(stop_mirroring/1 :: (pid()) -> 'ok'). +-spec(sync_mirrors/1 :: (rabbit_types:amqqueue()) -> + 'ok' | error('queue_has_pending_acks') | error('queue_not_mirrored')). -endif. @@ -591,11 +593,7 @@ set_maximum_since_use(QPid, Age) -> start_mirroring(QPid) -> ok = delegate_call(QPid, start_mirroring). stop_mirroring(QPid) -> ok = delegate_call(QPid, stop_mirroring). -sync_mirrors(Name) -> - case lookup(Name) of - {ok, #amqqueue{pid = QPid}} -> delegate_cast(QPid, sync_mirrors); - _ -> ok - end. +sync_mirrors(#amqqueue{pid = QPid}) -> delegate_call(QPid, sync_mirrors). on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 07f4c3b1..19872d8d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1171,6 +1171,24 @@ handle_call(stop_mirroring, _From, State = #q{backing_queue = BQ, reply(ok, State#q{backing_queue = BQ1, backing_queue_state = BQS1}); +handle_call(sync_mirrors, From, + State = #q{q = #amqqueue{name = Name}, + backing_queue = rabbit_mirror_queue_master = BQ, + backing_queue_state = BQS}) -> + case BQ:depth(BQS) - BQ:len(BQS) of + 0 -> + {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = + rabbit_amqqueue:lookup(Name), + gen_server2:reply(From, ok), + noreply(rabbit_mirror_queue_master:sync_mirrors( + SPids -- SSPids, Name, BQS)); + _ -> + reply({error, queue_has_pending_acks}, State) + end; + +handle_call(sync_mirrors, _From, State) -> + reply({error, queue_not_mirrored}, State); + handle_call(force_event_refresh, _From, State = #q{exclusive_consumer = Exclusive}) -> rabbit_event:notify(queue_created, infos(?CREATION_EVENT_KEYS, State)), @@ -1300,20 +1318,6 @@ handle_cast({dead_letter, Msgs, Reason}, State = #q{dlx = XName}) -> cleanup_after_confirm([AckTag || {_, AckTag} <- Msgs], State) end; -handle_cast(sync_mirrors, - State = #q{q = #amqqueue{name = Name}, - backing_queue = BQ, - backing_queue_state = BQS}) -> - case BQ of - rabbit_mirror_queue_master -> - {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = - rabbit_amqqueue:lookup(Name), - rabbit_mirror_queue_master:sync_mirrors(SPids -- SSPids, Name, BQS); - _ -> - ok - end, - noreply(State); - handle_cast(wake_up, State) -> noreply(State). diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 16642604..ea9e04fe 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -127,12 +127,12 @@ stop_mirroring(State = #state { coordinator = CPid, stop_all_slaves(shutdown, State), {BQ, BQS}. -sync_mirrors([], Name, _State) -> +sync_mirrors([], Name, State) -> rabbit_log:info("Synchronising ~s: nothing to do~n", [rabbit_misc:rs(Name)]), - ok; -sync_mirrors(SPids, Name, #state { backing_queue = BQ, - backing_queue_state = BQS }) -> + State; +sync_mirrors(SPids, Name, State = #state { backing_queue = BQ, + backing_queue_state = BQS }) -> rabbit_log:info("Synchronising ~s with slaves ~p~n", [rabbit_misc:rs(Name), SPids]), Ref = make_ref(), @@ -153,7 +153,7 @@ sync_mirrors(SPids, Name, #state { backing_queue = BQ, end], SPid1 =/= dead], [erlang:demonitor(MRef) || {_, MRef} <- SPidsMRefs], - {Total, _BQS} = + {Total, BQS1} = BQ:fold(fun ({Msg, MsgProps}, I) -> wait_for_credit(), [begin @@ -171,7 +171,7 @@ sync_mirrors(SPids, Name, #state { backing_queue = BQ, [SPid ! {sync_complete, Ref} || SPid <- SPids1], rabbit_log:info("Synchronising ~s: ~p messages; complete~n", [rabbit_misc:rs(Name), Total]), - ok. + State#state{backing_queue_state = BQS1}. wait_for_credit() -> case credit_flow:blocked() of diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index d408c56e..f4130e02 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -857,6 +857,7 @@ sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, sync_loop(Ref, MRef, MPid, State); {sync_complete, Ref} -> erlang:demonitor(MRef), + %% We can only sync when there are no pending acks set_delta(0, State); {sync_message, Ref, Msg, Props0} -> credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), -- cgit v1.2.1 From 37eabc35f816bf08e3bc9372e5a1528ec099661d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 26 Nov 2012 12:06:20 +0000 Subject: Cosmetic --- src/rabbit_amqqueue_process.erl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 19872d8d..7b167a9a 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1176,14 +1176,12 @@ handle_call(sync_mirrors, From, backing_queue = rabbit_mirror_queue_master = BQ, backing_queue_state = BQS}) -> case BQ:depth(BQS) - BQ:len(BQS) of - 0 -> - {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = - rabbit_amqqueue:lookup(Name), - gen_server2:reply(From, ok), - noreply(rabbit_mirror_queue_master:sync_mirrors( - SPids -- SSPids, Name, BQS)); - _ -> - reply({error, queue_has_pending_acks}, State) + 0 -> {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = + rabbit_amqqueue:lookup(Name), + gen_server2:reply(From, ok), + noreply(rabbit_mirror_queue_master:sync_mirrors( + SPids -- SSPids, Name, BQS)); + _ -> reply({error, queue_has_pending_acks}, State) end; handle_call(sync_mirrors, _From, State) -> -- cgit v1.2.1 From c568567524756c0d156599e8f9cbf33dcbcce78e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 26 Nov 2012 12:08:21 +0000 Subject: cosmetic --- src/rabbit_tests.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index c4bd1836..d5c096a1 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2319,9 +2319,8 @@ test_variable_queue_fold(VQ0) -> VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), VQ2 = variable_queue_publish( true, Count, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), - {Acc, VQ3} = rabbit_variable_queue:fold(fun (M, _, A) -> - [M | A] - end, [], VQ2), + {Acc, VQ3} = rabbit_variable_queue:fold( + fun (M, _, A) -> [M | A] end, [], VQ2), true = [term_to_binary(N) || N <- lists:seq(Count, 1, -1)] == [list_to_binary(lists:reverse(P)) || #basic_message{ content = #content{ payload_fragments_rev = P}} <- -- cgit v1.2.1 From bbe8ed714286772fed5c7326b6e819284888de53 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Mon, 26 Nov 2012 13:21:26 +0000 Subject: managed restarts, try-again-restart, cosmetics --- src/supervisor2.erl | 378 ++++++++++++++++++++++++++++------------------------ 1 file changed, 205 insertions(+), 173 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 2256b98e..be6319d6 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -79,6 +79,7 @@ %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-export([try_again_restart/2]). %%-------------------------------------------------------------------------- @@ -112,13 +113,13 @@ %%-------------------------------------------------------------------------- -record(child, {% pid is undefined when child is not running - pid = undefined :: child() | {restarting,pid()} | [pid()], - name :: child_id(), - mfa :: mfargs(), + pid = undefined :: child() | {restarting,pid()} | [pid()], + name :: child_id(), + mfargs :: mfargs(), restart_type :: restart(), shutdown :: shutdown(), - child_type :: worker(), - modules = [] :: modules()}). + child_type :: worker(), + modules = [] :: modules()}). -type child_rec() :: #child{}. -define(DICT, dict). @@ -191,7 +192,8 @@ start_child(Supervisor, ChildSpec) -> Result :: {'ok', Child :: child()} | {'ok', Child :: child(), Info :: term()} | {'error', Error}, - Error :: 'running' | 'not_found' | 'simple_one_for_one' | term(). + Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one' | + term(). restart_child(Supervisor, Name) -> call(Supervisor, {restart_child, Name}). @@ -199,7 +201,7 @@ restart_child(Supervisor, Name) -> SupRef :: sup_ref(), Id :: child_id(), Result :: 'ok' | {'error', Error}, - Error :: 'running' | 'not_found' | 'simple_one_for_one'. + Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one'. delete_child(Supervisor, Name) -> call(Supervisor, {delete_child, Name}). @@ -221,7 +223,7 @@ terminate_child(Supervisor, Name) -> -spec which_children(SupRef) -> [{Id,Child,Type,Modules}] when SupRef :: sup_ref(), Id :: child_id() | undefined, - Child :: child(), + Child :: child() | 'restarting', Type :: worker(), Modules :: modules(). which_children(Supervisor) -> @@ -252,11 +254,14 @@ check_childspecs(X) -> {error, {badarg, X}}. %%%----------------------------------------------------------------- %%% Called by timer:apply_after from restart/2 -%-spec try_again_restart(SupRef, Child) -> ok when -% SupRef :: sup_ref(), -% Child :: child_id() | pid(). -%try_again_restart(Supervisor, Child) -> -% cast(Supervisor, {try_again_restart, Child}). +-spec try_again_restart(SupRef, Child) -> ok when + SupRef :: sup_ref(), + Child :: child_id() | pid(). +try_again_restart(Supervisor, Child) -> + cast(Supervisor, {try_again_restart, Child}). + +cast(Supervisor, Req) -> + gen_server:cast(Supervisor, Req). find_child(Supervisor, Name) -> [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), @@ -334,6 +339,8 @@ start_children(Children, SupName) -> start_children(Children, [], SupName). start_children([Child|Chs], NChildren, SupName) -> case do_start_child(SupName, Child) of + {ok, undefined} when Child#child.restart_type =:= temporary -> + start_children(Chs, NChildren, SupName); {ok, Pid} -> start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); {ok, Pid, _Extra} -> @@ -346,8 +353,8 @@ start_children([], NChildren, _SupName) -> {ok, NChildren}. do_start_child(SupName, Child) -> - #child{mfa = {M, F, A}} = Child, - case catch apply(M, F, A) of + #child{mfargs = {M, F, Args}} = Child, + case catch apply(M, F, Args) of {ok, Pid} when is_pid(Pid) -> NChild = Child#child{pid = Pid}, report_progress(NChild, SupName), @@ -386,11 +393,11 @@ do_start_child_i(M, F, A) -> handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> Child = hd(State#state.children), - #child{mfa = {M, F, A}} = Child, + #child{mfargs = {M, F, A}} = Child, Args = A ++ EArgs, case do_start_child_i(M, F, Args) of - {ok, undefined} -> - {reply, {ok, undefined}, State}; + {ok, undefined} when Child#child.restart_type =:= temporary -> + {reply, {ok, undefined}, State}; {ok, Pid} -> NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State), {reply, {ok, Pid}, NState}; @@ -407,11 +414,15 @@ handle_call({terminate_child, Name}, _From, State) when not is_pid(Name), {reply, {error, simple_one_for_one}, State}; handle_call({terminate_child, Name}, _From, State) -> - case get_child(Name, State) of + case get_child(Name, State, ?is_simple(State)) of {value, Child} -> - NChild = do_terminate(Child, State#state.name), - {reply, ok, replace_child(NChild, State)}; - _ -> + case do_terminate(Child, State#state.name) of + #child{restart_type=RT} when RT=:=temporary; ?is_simple(State) -> + {reply, ok, state_del_child(Child, State)}; + NChild -> + {reply, ok, replace_child(NChild, State)} + end; + false -> {reply, {error, not_found}, State} end; @@ -442,6 +453,8 @@ handle_call({restart_child, Name}, _From, State) -> Error -> {reply, Error, State} end; + {value, #child{pid=?restarting(_)}} -> + {reply, {error, restarting}, State}; {value, _} -> {reply, {error, running}, State}; _ -> @@ -453,6 +466,8 @@ handle_call({delete_child, Name}, _From, State) -> {value, Child} when Child#child.pid =:= undefined -> NState = remove_child(Child, State), {reply, ok, NState}; + {value, #child{pid=?restarting(_)}} -> + {reply, {error, restarting}, State}; {value, _} -> {reply, {error, running}, State}; _ -> @@ -471,19 +486,24 @@ handle_call(which_children, _From, #state{children = [#child{restart_type = RTyp child_type = CT, modules = Mods}]} = State) when ?is_simple(State) -> - Reply = lists:map(fun ({Pid, _}) -> {undefined, Pid, CT, Mods} end, + Reply = lists:map(fun({?restarting(_),_}) -> {undefined,restarting,CT,Mods}; + ({Pid, _}) -> {undefined, Pid, CT, Mods} end, ?DICT:to_list(dynamics_db(RType, State#state.dynamics))), {reply, Reply, State}; handle_call(which_children, _From, State) -> Resp = - lists:map(fun (#child{pid = Pid, name = Name, + lists:map(fun(#child{pid = ?restarting(_), name = Name, + child_type = ChildType, modules = Mods}) -> + {Name, restarting, ChildType, Mods}; + (#child{pid = Pid, name = Name, child_type = ChildType, modules = Mods}) -> - {Name, Pid, ChildType, Mods} + {Name, Pid, ChildType, Mods} end, State#state.children), {reply, Resp, State}; + handle_call(count_children, _From, #state{children = [#child{restart_type = temporary, child_type = CT}]} = State) when ?is_simple(State) -> @@ -535,13 +555,6 @@ handle_call(count_children, _From, State) -> {supervisors, Supers}, {workers, Workers}], {reply, Reply, State}. --spec handle_cast('null', state()) -> {'noreply', state()}. -%%% Hopefully cause a function-clause as there is no API function -%%% that utilizes cast. -handle_cast(null, State) -> - error_logger:error_msg("ERROR: Supervisor received cast-message 'null'~n", - []), - {noreply, State}. count_child(#child{pid = Pid, child_type = worker}, {Specs, Active, Supers, Workers}) -> @@ -556,6 +569,44 @@ count_child(#child{pid = Pid, child_type = supervisor}, false -> {Specs+1, Active, Supers+1, Workers} end. + +%%% If a restart attempt failed, this message is sent via +%%% timer:apply_after(0,...) in order to give gen_server the chance to +%%% check it's inbox before trying again. +-spec handle_cast({try_again_restart, child_id() | pid()}, state()) -> + {'noreply', state()} | {stop, shutdown, state()}. + +handle_cast({try_again_restart,Pid}, #state{children=[Child]}=State) + when ?is_simple(State) -> + RT = Child#child.restart_type, + RPid = restarting(Pid), + case dynamic_child_args(RPid, dynamics_db(RT, State#state.dynamics)) of + {ok, Args} -> + {M, F, _} = Child#child.mfargs, + NChild = Child#child{pid = RPid, mfargs = {M, F, Args}}, + case restart(NChild,State) of + {ok, State1} -> + {noreply, State1}; + {shutdown, State1} -> + {stop, shutdown, State1} + end; + error -> + {noreply, State} + end; + +handle_cast({try_again_restart,Name}, State) -> + case lists:keyfind(Name,#child.name,State#state.children) of + Child = #child{pid=?restarting(_)} -> + case restart(Child,State) of + {ok, State1} -> + {noreply, State1}; + {shutdown, State1} -> + {stop, shutdown, State1} + end; + _ -> + {noreply,State} + end. + %% %% Take care of terminated children. %% @@ -584,7 +635,7 @@ handle_info({delayed_restart, {RestartType, Reason, Child}}, State) -> end; handle_info(Msg, State) -> - error_logger:error_msg("Supervisor received unexpected message: ~p~n", + error_logger:error_msg("Supervisor received unexpected message: ~p~n", [Msg]), {noreply, State}. @@ -688,6 +739,8 @@ handle_start_child(Child, State) -> case get_child(Child#child.name, State) of false -> case do_start_child(State#state.name, Child) of + {ok, undefined} when Child#child.restart_type =:= temporary -> + {{ok, undefined}, State}; {ok, Pid} -> {{ok, Pid}, save_child(Child#child{pid = Pid}, State)}; {ok, Pid, Extra} -> @@ -695,7 +748,7 @@ handle_start_child(Child, State) -> {error, What} -> {{error, {What, Child}}, State} end; - {value, OldChild} when OldChild#child.pid =/= undefined -> + {value, OldChild} when is_pid(OldChild#child.pid) -> {{error, {already_started, OldChild#child.pid}}, State}; {value, _OldChild} -> {{error, already_present}, State} @@ -710,8 +763,8 @@ restart_child(Pid, Reason, #state{children = [Child]} = State) when ?is_simple(S RestartType = Child#child.restart_type, case dynamic_child_args(Pid, dynamics_db(RestartType, State#state.dynamics)) of {ok, Args} -> - {M, F, _} = Child#child.mfa, - NChild = Child#child{pid = Pid, mfa = {M, F, Args}}, + {M, F, _} = Child#child.mfargs, + NChild = Child#child{pid = Pid, mfargs = {M, F, Args}}, do_restart(RestartType, Reason, NChild, State); error -> {ok, State} @@ -753,7 +806,7 @@ do_restart(temporary, Reason, Child, State) -> {ok, NState}. do_restart_delay({RestartType, Delay}, Reason, Child, State) -> - case restart1(Child, State) of + case restart(Child, State) of {ok, NState} -> {ok, NState}; {terminate, NState} -> @@ -773,32 +826,32 @@ del_child_and_maybe_shutdown(_, Child, State) -> restart(Child, State) -> case add_restart(State) of {ok, NState} -> - restart(NState#state.strategy, Child, NState, fun restart/2); + case restart(NState#state.strategy, Child, NState) of + {try_again,NState2} -> + %% Leaving control back to gen_server before + %% trying again. This way other incoming requsts + %% for the supervisor can be handled - e.g. a + %% shutdown request for the supervisor or the + %% child. + Id = if ?is_simple(State) -> Child#child.pid; + true -> Child#child.name + end, + timer:apply_after(0,?MODULE,try_again_restart,[self(),Id]), + {ok,NState2}; + Other -> + Other + end; {terminate, NState} -> report_error(shutdown, reached_max_restart_intensity, Child, State#state.name), - {shutdown, state_del_child(Child, NState)} + {shutdown, remove_child(Child, NState)} end. -restart1(Child, State) -> - case add_restart(State) of - {ok, NState} -> - restart(NState#state.strategy, Child, NState, fun restart1/2); - {terminate, _NState} -> - %% we've reached the max restart intensity, but the - %% add_restart will have added to the restarts - %% field. Given we don't want to die here, we need to go - %% back to the old restarts field otherwise we'll never - %% attempt to restart later. - {terminate, State} - end. - -restart(simple_one_for_one, Child, State, Restart) -> - #child{mfa = {M, F, A}} = Child, - Dynamics = ?DICT:erase(Child#child.pid, State#state.dynamics), +restart(simple_one_for_one, Child, State) -> + #child{pid = OldPid, mfargs = {M, F, A}} = Child, + Dynamics = ?DICT:erase(OldPid, dynamics_db(Child#child.restart_type, + State#state.dynamics)), case do_start_child_i(M, F, A) of - {ok, undefined} -> - {ok, State}; {ok, Pid} -> NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)}, {ok, NState}; @@ -806,10 +859,13 @@ restart(simple_one_for_one, Child, State, Restart) -> NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)}, {ok, NState}; {error, Error} -> + NState = State#state{dynamics = ?DICT:store(restarting(OldPid), A, + Dynamics)}, report_error(start_error, Error, Child, State#state.name), - Restart(Child, State) + {try_again, NState} end; -restart(one_for_one, Child, State, Restart) -> +restart(one_for_one, Child, State) -> + OldPid = Child#child.pid, case do_start_child(State#state.name, Child) of {ok, Pid} -> NState = replace_child(Child#child{pid = Pid}, State), @@ -818,149 +874,90 @@ restart(one_for_one, Child, State, Restart) -> NState = replace_child(Child#child{pid = Pid}, State), {ok, NState}; {error, Reason} -> + NState = replace_child(Child#child{pid = restarting(OldPid)}, State), report_error(start_error, Reason, Child, State#state.name), - Restart(Child, State) + {try_again, NState} end; -restart(rest_for_one, Child, State, Restart) -> +restart(rest_for_one, Child, State) -> {ChAfter, ChBefore} = split_child(Child#child.pid, State#state.children), ChAfter2 = terminate_children(ChAfter, State#state.name), case start_children(ChAfter2, State#state.name) of {ok, ChAfter3} -> {ok, State#state{children = ChAfter3 ++ ChBefore}}; {error, ChAfter3} -> - Restart(Child, State#state{children = ChAfter3 ++ ChBefore}) + NChild = Child#child{pid=restarting(Child#child.pid)}, + NState = State#state{children = ChAfter3 ++ ChBefore}, + {try_again, replace_child(NChild,NState)} end; -restart(one_for_all, Child, State, Restart) -> +restart(one_for_all, Child, State) -> Children1 = del_child(Child#child.pid, State#state.children), Children2 = terminate_children(Children1, State#state.name), case start_children(Children2, State#state.name) of {ok, NChs} -> {ok, State#state{children = NChs}}; {error, NChs} -> - Restart(Child, State#state{children = NChs}) + NChild = Child#child{pid=restarting(Child#child.pid)}, + NState = State#state{children = NChs}, + {try_again, replace_child(NChild,NState)} end. +restarting(Pid) when is_pid(Pid) -> ?restarting(Pid); +restarting(RPid) -> RPid. + %%----------------------------------------------------------------- %% Func: terminate_children/2 -%% Args: Children = [#child] in termination order +%% Args: Children = [child_rec()] in termination order %% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} -%% Returns: NChildren = [#child] in +%% Returns: NChildren = [child_rec()] in %% startup order (reversed termination order) %%----------------------------------------------------------------- terminate_children(Children, SupName) -> terminate_children(Children, SupName, []). +%% Temporary children should not be restarted and thus should +%% be skipped when building the list of terminated children, although +%% we do want them to be shut down as many functions from this module +%% use this function to just clear everything. +terminate_children([Child = #child{restart_type=temporary} | Children], SupName, Res) -> + do_terminate(Child, SupName), + terminate_children(Children, SupName, Res); terminate_children([Child | Children], SupName, Res) -> NChild = do_terminate(Child, SupName), terminate_children(Children, SupName, [NChild | Res]); terminate_children([], _SupName, Res) -> Res. -terminate_simple_children(Child, Dynamics, SupName) -> - Pids = monitor_children(Child, Dynamics), - TimeoutMsg = {timeout, make_ref()}, - TRef = timeout_start(Child, TimeoutMsg), - {Replies, Timedout} = - lists:foldl( - fun (_Pid, {Replies, Timedout}) -> - {Pid1, Reason1, Timedout1} = - receive - TimeoutMsg -> - Remaining = Pids -- [P || {P, _} <- Replies], - [exit(P, kill) || P <- Remaining], - receive - {'DOWN', _MRef, process, Pid, Reason} -> - {Pid, Reason, true} - end; - {'DOWN', _MRef, process, Pid, Reason} -> - {Pid, Reason, Timedout} - end, - {[{Pid1, child_res(Child, Reason1, Timedout1)} | Replies], - Timedout1} - end, {[], false}, Pids), - timeout_stop(Child, TRef, TimeoutMsg, Timedout), - ReportError = shutdown_error_reporter(SupName), - Report = fun(_, ok) -> ok; - (Pid, {error, R}) -> ReportError(R, Child#child{pid = Pid}) - end, - [receive - {'EXIT', Pid, Reason} -> - Report(Pid, child_res(Child, Reason, Timedout)) - after - 0 -> Report(Pid, Reply) - end || {Pid, Reply} <- Replies], - ok. - -monitor_children(Child=#child{restart_type=temporary}, Dynamics) -> - ?SETS:fold(fun (Pid, _Args, Pids) -> - erlang:monitor(process, Pid), - unlink(Pid), - exit(Pid, child_exit_reason(Child)), - [Pid | Pids] - end, [], Dynamics); -monitor_children(Child, Dynamics) -> - dict:fold(fun (Pid, _Args, Pids) -> - erlang:monitor(process, Pid), - unlink(Pid), - exit(Pid, child_exit_reason(Child)), - [Pid | Pids] - end, [], Dynamics). - -child_exit_reason(#child{shutdown = brutal_kill}) -> kill; -child_exit_reason(#child{}) -> shutdown. - -child_res(#child{shutdown=brutal_kill}, killed, false) -> ok; -child_res(#child{}, shutdown, false) -> ok; -child_res(#child{restart_type=permanent}, normal, false) -> {error, normal}; -child_res(#child{restart_type={permanent,_}},normal, false) -> {error, normal}; -child_res(#child{}, normal, false) -> ok; -child_res(#child{}, R, _) -> {error, R}. - -timeout_start(#child{shutdown = Time}, Msg) when is_integer(Time) -> - erlang:send_after(Time, self(), Msg); -timeout_start(#child{}, _Msg) -> - ok. - -timeout_stop(#child{shutdown = Time}, TRef, Msg, false) when is_integer(Time) -> - erlang:cancel_timer(TRef), - receive - Msg -> ok - after - 0 -> ok - end; -timeout_stop(#child{}, _TRef, _Msg, _Timedout) -> - ok. - -do_terminate(Child, SupName) when Child#child.pid =/= undefined -> - ReportError = shutdown_error_reporter(SupName), +do_terminate(Child, SupName) when is_pid(Child#child.pid) -> case shutdown(Child#child.pid, Child#child.shutdown) of ok -> ok; {error, normal} -> case Child#child.restart_type of - permanent -> ReportError(normal, Child); - {permanent, _Delay} -> ReportError(normal, Child); - _ -> ok + permanent -> + report_error(shutdown_error, normal, Child, SupName); + {permanent, _Delay} -> + report_error(shutdown_error, normal, Child, SupName); + _ -> + ok end; {error, OtherReason} -> - ReportError(OtherReason, Child) + report_error(shutdown_error, OtherReason, Child, SupName) end, Child#child{pid = undefined}; do_terminate(Child, _SupName) -> - Child. + Child#child{pid = undefined}. %%----------------------------------------------------------------- -%% Shutdowns a child. We must check the EXIT value +%% Shutdowns a child. We must check the EXIT value %% of the child, because it might have died with another reason than -%% the wanted. In that case we want to report the error. We put a -%% monitor on the child an check for the 'DOWN' message instead of -%% checking for the 'EXIT' message, because if we check the 'EXIT' -%% message a "naughty" child, who does unlink(Sup), could hang the -%% supervisor. +%% the wanted. In that case we want to report the error. We put a +%% monitor on the child an check for the 'DOWN' message instead of +%% checking for the 'EXIT' message, because if we check the 'EXIT' +%% message a "naughty" child, who does unlink(Sup), could hang the +%% supervisor. %% Returns: ok | {error, OtherReason} (this should be reported) %%----------------------------------------------------------------- shutdown(Pid, brutal_kill) -> - case monitor_child(Pid) of ok -> exit(Pid, kill), @@ -973,9 +970,7 @@ shutdown(Pid, brutal_kill) -> {error, Reason} -> {error, Reason} end; - shutdown(Pid, Time) -> - case monitor_child(Pid) of ok -> exit(Pid, shutdown), %% Try to shutdown gracefully @@ -1015,7 +1010,7 @@ monitor_child(Pid) -> end after 0 -> %% If a naughty child did unlink and the child dies before - %% monitor the result will be that shutdown/2 receives a + %% monitor the result will be that shutdown/2 receives a %% 'DOWN'-message with reason noproc. %% If the child should die after the unlink there %% will be a 'DOWN'-message with a correct reason @@ -1137,8 +1132,8 @@ wait_dynamic_children(#child{restart_type=RType} = Child, Pids, Sz, %% it could become very costly as it is not uncommon to spawn %% very many such processes. save_child(#child{restart_type = temporary, - mfa = {M, F, _}} = Child, #state{children = Children} = State) -> - State#state{children = [Child#child{mfa = {M, F, undefined}} |Children]}; + mfargs = {M, F, _}} = Child, #state{children = Children} = State) -> + State#state{children = [Child#child{mfargs = {M, F, undefined}} |Children]}; save_child(Child, #state{children = Children} = State) -> State#state{children = [Child |Children]}. @@ -1172,8 +1167,12 @@ state_del_child(Child, State) -> NChildren = del_child(Child#child.name, State#state.children), State#state{children = NChildren}. +del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name, Ch#child.restart_type =:= temporary -> + Chs; del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name -> [Ch#child{pid = undefined} | Chs]; +del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid, Ch#child.restart_type =:= temporary -> + Chs; del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid -> [Ch#child{pid = undefined} | Chs]; del_child(Name, [Ch|Chs]) -> @@ -1196,8 +1195,38 @@ split_child(_, [], After) -> {lists:reverse(After), []}. get_child(Name, State) -> + get_child(Name, State, false). +get_child(Pid, State, AllowPid) when AllowPid, is_pid(Pid) -> + get_dynamic_child(Pid, State); +get_child(Name, State, _) -> lists:keysearch(Name, #child.name, State#state.children). +get_dynamic_child(Pid, #state{children=[Child], dynamics=Dynamics}) -> + DynamicsDb = dynamics_db(Child#child.restart_type, Dynamics), + case is_dynamic_pid(Pid, DynamicsDb) of + true -> + {value, Child#child{pid=Pid}}; + false -> + RPid = restarting(Pid), + case is_dynamic_pid(RPid, DynamicsDb) of + true -> + {value, Child#child{pid=RPid}}; + false -> + case erlang:is_process_alive(Pid) of + true -> false; + false -> {value, Child} + end + end + end. + +is_dynamic_pid(Pid, Dynamics) -> + case ?SETS:is_set(Dynamics) of + true -> + ?SETS:is_element(Pid, Dynamics); + false -> + ?DICT:is_key(Pid, Dynamics) + end. + replace_child(Child, State) -> Chs = do_replace_child(Child, State#state.children), State#state{children = Chs}. @@ -1245,11 +1274,11 @@ init_state1(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) -> init_state1(_SupName, Type, _, _) -> {invalid_type, Type}. -validStrategy(simple_one_for_one) -> true; -validStrategy(one_for_one) -> true; -validStrategy(one_for_all) -> true; -validStrategy(rest_for_one) -> true; -validStrategy(What) -> throw({invalid_strategy, What}). +validStrategy(simple_one_for_one) -> true; +validStrategy(one_for_one) -> true; +validStrategy(one_for_all) -> true; +validStrategy(rest_for_one) -> true; +validStrategy(What) -> throw({invalid_strategy, What}). validIntensity(Max) when is_integer(Max), Max >= 0 -> true; @@ -1303,7 +1332,7 @@ check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods) -> validChildType(ChildType), validShutdown(Shutdown, ChildType), validMods(Mods), - {ok, #child{name = Name, mfa = Func, restart_type = RestartType, + {ok, #child{name = Name, mfargs = Func, restart_type = RestartType, shutdown = Shutdown, child_type = ChildType, modules = Mods}}. validChildType(supervisor) -> true; @@ -1312,8 +1341,8 @@ validChildType(What) -> throw({invalid_child_type, What}). validName(_Name) -> true. -validFunc({M, F, A}) when is_atom(M), - is_atom(F), +validFunc({M, F, A}) when is_atom(M), + is_atom(F), is_list(A) -> true; validFunc(Func) -> throw({invalid_mfa, Func}). @@ -1413,15 +1442,18 @@ report_error(Error, Reason, Child, SupName) -> {offender, extract_child(Child)}], error_logger:error_report(supervisor_report, ErrorMsg). -shutdown_error_reporter(SupName) -> - fun(Reason, Child) -> - report_error(shutdown_error, Reason, Child, SupName) - end. +extract_child(Child) when is_list(Child#child.pid) -> + [{nb_children, length(Child#child.pid)}, + {name, Child#child.name}, + {mfargs, Child#child.mfargs}, + {restart_type, Child#child.restart_type}, + {shutdown, Child#child.shutdown}, + {child_type, Child#child.child_type}]; extract_child(Child) -> [{pid, Child#child.pid}, {name, Child#child.name}, - {mfa, Child#child.mfa}, + {mfargs, Child#child.mfargs}, {restart_type, Child#child.restart_type}, {shutdown, Child#child.shutdown}, {child_type, Child#child.child_type}]. -- cgit v1.2.1 From e0fcf0d437ed5f8b71e555ece79d34ed4797e23c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 26 Nov 2012 14:03:09 +0000 Subject: Fix bugs in previous commit, and be rather more thorough about monitoring and cleaning up credit flow. --- src/rabbit_amqqueue.erl | 3 +- src/rabbit_amqqueue_process.erl | 5 +-- src/rabbit_mirror_queue_master.erl | 65 +++++++++++++++++++++++++------------- src/rabbit_mirror_queue_slave.erl | 3 ++ 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index ad81ba03..e5f1cb90 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -174,7 +174,8 @@ -spec(start_mirroring/1 :: (pid()) -> 'ok'). -spec(stop_mirroring/1 :: (pid()) -> 'ok'). -spec(sync_mirrors/1 :: (rabbit_types:amqqueue()) -> - 'ok' | error('queue_has_pending_acks') | error('queue_not_mirrored')). + 'ok' | rabbit_types:error('queue_has_pending_acks') + | rabbit_types:error('queue_not_mirrored')). -endif. diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 7b167a9a..8c2fafa6 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1179,8 +1179,9 @@ handle_call(sync_mirrors, From, 0 -> {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = rabbit_amqqueue:lookup(Name), gen_server2:reply(From, ok), - noreply(rabbit_mirror_queue_master:sync_mirrors( - SPids -- SSPids, Name, BQS)); + noreply(State#q{backing_queue_state = + rabbit_mirror_queue_master:sync_mirrors( + SPids -- SSPids, Name, BQS)}); _ -> reply({error, queue_has_pending_acks}, State) end; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index ea9e04fe..542d724a 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -144,44 +144,65 @@ sync_mirrors(SPids, Name, State = #state { backing_queue = BQ, %% We wait for a reply from the slaves so that we know they are in %% a receive block and will thus receive messages we send to them %% *without* those messages ending up in their gen_server2 pqueue. - SPids1 = [SPid1 || {SPid, MRef} <- SPidsMRefs, - SPid1 <- [receive - {'DOWN', MRef, process, SPid, _Reason} -> - dead; - {sync_ready, Ref, SPid} -> - SPid - end], - SPid1 =/= dead], - [erlang:demonitor(MRef) || {_, MRef} <- SPidsMRefs], - {Total, BQS1} = - BQ:fold(fun ({Msg, MsgProps}, I) -> - wait_for_credit(), + SPidsMRefs1 = sync_foreach(SPidsMRefs, Ref, fun sync_receive_ready/3), + {{Total, SPidsMRefs2}, BQS1} = + BQ:fold(fun ({Msg, MsgProps}, {I, SPMR}) -> + SPMR1 = wait_for_credit(SPMR, Ref), [begin credit_flow:send(SPid, ?CREDIT_DISC_BOUND), SPid ! {sync_message, Ref, Msg, MsgProps} - end || SPid <- SPids1], + end || {SPid, _} <- SPMR1], case I rem 1000 of 0 -> rabbit_log:info( "Synchronising ~s: ~p messages~n", [rabbit_misc:rs(Name), I]); _ -> ok end, - I + 1 - end, 0, BQS), - [SPid ! {sync_complete, Ref} || SPid <- SPids1], + {I + 1, SPMR1} + end, {0, SPidsMRefs1}, BQS), + sync_foreach(SPidsMRefs2, Ref, fun sync_receive_complete/3), rabbit_log:info("Synchronising ~s: ~p messages; complete~n", [rabbit_misc:rs(Name), Total]), State#state{backing_queue_state = BQS1}. -wait_for_credit() -> +wait_for_credit(SPidsMRefs, Ref) -> case credit_flow:blocked() of - true -> receive - {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), - wait_for_credit() - end; - false -> ok + true -> wait_for_credit(sync_foreach(SPidsMRefs, Ref, + fun sync_receive_credit/3), Ref); + false -> SPidsMRefs end. +sync_foreach(SPidsMRefs, Ref, Fun) -> + [{SPid, MRef} || {SPid, MRef} <- SPidsMRefs, + SPid1 <- [Fun(SPid, MRef, Ref)], + SPid1 =/= dead]. + +sync_receive_ready(SPid, MRef, Ref) -> + receive + {sync_ready, Ref, SPid} -> SPid; + {'DOWN', MRef, _, SPid, _} -> dead + end. + +sync_receive_credit(SPid, MRef, Ref) -> + receive + {bump_credit, {SPid, _} = Msg} -> credit_flow:handle_bump_msg(Msg), + sync_receive_credit(SPid, MRef, Ref); + {'DOWN', MRef, _, SPid, _} -> credit_flow:peer_down(SPid), + dead + after 0 -> + SPid + end. + +sync_receive_complete(SPid, MRef, Ref) -> + SPid ! {sync_complete, Ref}, + receive + {sync_complete_ok, Ref, SPid} -> ok; + {'DOWN', MRef, _, SPid, _} -> ok + end, + erlang:demonitor(MRef, [flush]), + credit_flow:peer_down(SPid). + + terminate({shutdown, dropped} = Reason, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index f4130e02..311c6ca6 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -851,12 +851,15 @@ sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, %% messages from it, we have a hole in the middle. So the %% only thing to do here is purge.) {_MsgCount, BQS1} = BQ:purge(BQS), + credit_flow:peer_down(MPid), State#state{backing_queue_state = BQS1}; {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), sync_loop(Ref, MRef, MPid, State); {sync_complete, Ref} -> + MPid ! {sync_complete_ok, Ref, self()}, erlang:demonitor(MRef), + credit_flow:peer_down(MPid), %% We can only sync when there are no pending acks set_delta(0, State); {sync_message, Ref, Msg, Props0} -> -- cgit v1.2.1 From 16bf93483b233c9689ef9163a4826d19584ccd22 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 26 Nov 2012 14:13:54 +0000 Subject: Send the sync_start over GM to flush out any other messages that we might have sent that way already. --- src/rabbit_mirror_queue_master.erl | 7 +++++-- src/rabbit_mirror_queue_slave.erl | 23 ++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 542d724a..9527ff30 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -131,13 +131,16 @@ sync_mirrors([], Name, State) -> rabbit_log:info("Synchronising ~s: nothing to do~n", [rabbit_misc:rs(Name)]), State; -sync_mirrors(SPids, Name, State = #state { backing_queue = BQ, +sync_mirrors(SPids, Name, State = #state { gm = GM, + backing_queue = BQ, backing_queue_state = BQS }) -> rabbit_log:info("Synchronising ~s with slaves ~p~n", [rabbit_misc:rs(Name), SPids]), Ref = make_ref(), + %% We send the start over GM to flush out any other messages that + %% we might have sent that way already. + gm:broadcast(GM, {sync_start, Ref, self(), SPids}), SPidsMRefs = [begin - SPid ! {sync_start, Ref, self()}, MRef = erlang:monitor(process, SPid), {SPid, MRef} end || SPid <- SPids], diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 311c6ca6..e7f26e6b 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -222,6 +222,15 @@ handle_cast({deliver, Delivery = #delivery{sender = Sender}, true, Flow}, end, noreply(maybe_enqueue_message(Delivery, State)); +handle_cast({sync_start, Ref, MPid}, + State = #state { backing_queue = BQ, + backing_queue_state = BQS }) -> + MRef = erlang:monitor(process, MPid), + MPid ! {sync_ready, Ref, self()}, + {_MsgCount, BQS1} = BQ:purge(BQS), + noreply( + sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1})); + handle_cast({set_maximum_since_use, Age}, State) -> ok = file_handle_cache:set_maximum_since_use(Age), noreply(State); @@ -264,15 +273,6 @@ handle_info({bump_credit, Msg}, State) -> credit_flow:handle_bump_msg(Msg), noreply(State); -handle_info({sync_start, Ref, MPid}, - State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - MRef = erlang:monitor(process, MPid), - MPid ! {sync_ready, Ref, self()}, - {_MsgCount, BQS1} = BQ:purge(BQS), - noreply( - sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1})); - handle_info(Msg, State) -> {stop, {unexpected_info, Msg}, State}. @@ -367,6 +367,11 @@ handle_msg([_SPid], _From, process_death) -> handle_msg([CPid], _From, {delete_and_terminate, _Reason} = Msg) -> ok = gen_server2:cast(CPid, {gm, Msg}), {stop, {shutdown, ring_shutdown}}; +handle_msg([SPid], _From, {sync_start, Ref, MPid, SPids}) -> + case lists:member(SPid, SPids) of + true -> ok = gen_server2:cast(SPid, {sync_start, Ref, MPid}); + false -> ok + end; handle_msg([SPid], _From, Msg) -> ok = gen_server2:cast(SPid, {gm, Msg}). -- cgit v1.2.1 From 01ed88848239bae9c2f281f6c8dfcd14d93b866b Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 26 Nov 2012 14:23:33 +0000 Subject: Improve progfess logging. --- src/rabbit_mirror_queue_master.erl | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 9527ff30..cae46706 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -45,6 +45,8 @@ known_senders }). +-define(SYNC_PROGRESS_INTERVAL, 1000000). + -ifdef(use_specs). -export_type([death_fun/0, depth_fun/0]). @@ -134,8 +136,8 @@ sync_mirrors([], Name, State) -> sync_mirrors(SPids, Name, State = #state { gm = GM, backing_queue = BQ, backing_queue_state = BQS }) -> - rabbit_log:info("Synchronising ~s with slaves ~p~n", - [rabbit_misc:rs(Name), SPids]), + rabbit_log:info("Synchronising ~s with slaves ~p: ~p messages to do~n", + [rabbit_misc:rs(Name), SPids, BQ:len(BQS)]), Ref = make_ref(), %% We send the start over GM to flush out any other messages that %% we might have sent that way already. @@ -148,24 +150,26 @@ sync_mirrors(SPids, Name, State = #state { gm = GM, %% a receive block and will thus receive messages we send to them %% *without* those messages ending up in their gen_server2 pqueue. SPidsMRefs1 = sync_foreach(SPidsMRefs, Ref, fun sync_receive_ready/3), - {{Total, SPidsMRefs2}, BQS1} = - BQ:fold(fun ({Msg, MsgProps}, {I, SPMR}) -> + {{_, SPidsMRefs2, _}, BQS1} = + BQ:fold(fun ({Msg, MsgProps}, {I, SPMR, Last}) -> SPMR1 = wait_for_credit(SPMR, Ref), [begin credit_flow:send(SPid, ?CREDIT_DISC_BOUND), SPid ! {sync_message, Ref, Msg, MsgProps} end || {SPid, _} <- SPMR1], - case I rem 1000 of - 0 -> rabbit_log:info( - "Synchronising ~s: ~p messages~n", - [rabbit_misc:rs(Name), I]); - _ -> ok - end, - {I + 1, SPMR1} - end, {0, SPidsMRefs1}, BQS), + {I + 1, SPMR1, + case timer:now_diff(erlang:now(), Last) > + ?SYNC_PROGRESS_INTERVAL of + true -> rabbit_log:info( + "Synchronising ~s: ~p messages~n", + [rabbit_misc:rs(Name), I]), + erlang:now(); + false -> Last + end} + end, {0, SPidsMRefs1, erlang:now()}, BQS), sync_foreach(SPidsMRefs2, Ref, fun sync_receive_complete/3), - rabbit_log:info("Synchronising ~s: ~p messages; complete~n", - [rabbit_misc:rs(Name), Total]), + rabbit_log:info("Synchronising ~s: complete~n", + [rabbit_misc:rs(Name)]), State#state{backing_queue_state = BQS1}. wait_for_credit(SPidsMRefs, Ref) -> -- cgit v1.2.1 From ed976b130333fc65d81ab1bddb5711e5afdca3b9 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 26 Nov 2012 15:02:53 +0000 Subject: React to memory monitor and FHC messages. --- src/rabbit_mirror_queue_slave.erl | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 5ea14698..965ea090 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -241,15 +241,8 @@ handle_cast({set_ram_duration_target, Duration}, BQS1 = BQ:set_ram_duration_target(Duration, BQS), noreply(State #state { backing_queue_state = BQS1 }). -handle_info(update_ram_duration, - State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - {RamDuration, BQS1} = BQ:ram_duration(BQS), - DesiredDuration = - rabbit_memory_monitor:report_ram_duration(self(), RamDuration), - BQS2 = BQ:set_ram_duration_target(DesiredDuration, BQS1), - noreply(State #state { rate_timer_ref = just_measured, - backing_queue_state = BQS2 }); +handle_info(update_ram_duration, State) -> + noreply(update_ram_duration(State)); handle_info(sync_timeout, State) -> noreply(backing_queue_timeout( @@ -830,6 +823,15 @@ update_delta( DeltaChange, State = #state { depth_delta = Delta }) -> true = DeltaChange =< 0, %% assertion: we cannot become 'less' sync'ed set_delta(Delta + DeltaChange, State #state { depth_delta = undefined }). +update_ram_duration(State = #state { backing_queue = BQ, + backing_queue_state = BQS }) -> + {RamDuration, BQS1} = BQ:ram_duration(BQS), + DesiredDuration = + rabbit_memory_monitor:report_ram_duration(self(), RamDuration), + BQS2 = BQ:set_ram_duration_target(DesiredDuration, BQS1), + State #state { rate_timer_ref = just_measured, + backing_queue_state = BQS2 }. + record_synchronised(#amqqueue { name = QName }) -> Self = self(), rabbit_misc:execute_mnesia_transaction( @@ -845,7 +847,7 @@ record_synchronised(#amqqueue { name = QName }) -> end). sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, - backing_queue_state = BQS}) -> + backing_queue_state = BQS}) -> receive {'DOWN', MRef, process, MPid, _Reason} -> %% If the master dies half way we are not in the usual @@ -866,6 +868,15 @@ sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, credit_flow:peer_down(MPid), %% We can only sync when there are no pending acks set_delta(0, State); + {'$gen_cast', {set_maximum_since_use, Age}} -> + ok = file_handle_cache:set_maximum_since_use(Age), + sync_loop(Ref, MRef, MPid, State); + {'$gen_cast', {set_ram_duration_target, Duration}} -> + BQS1 = BQ:set_ram_duration_target(Duration, BQS), + sync_loop(Ref, MRef, MPid, + State#state{backing_queue_state = BQS1}); + update_ram_duration -> + sync_loop(Ref, MRef, MPid, update_ram_duration(State)); {sync_message, Ref, Msg, Props0} -> credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), Props = Props0#message_properties{needs_confirming = false, -- cgit v1.2.1 From e9ae5b7fabd7070f542b50942927c987c019341e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 26 Nov 2012 15:36:10 +0000 Subject: remove cruft --- src/rabbit_mirror_queue_slave.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index cb7a2135..982768e8 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -318,7 +318,6 @@ prioritise_cast(Msg, _State) -> {set_maximum_since_use, _Age} -> 8; {run_backing_queue, _Mod, _Fun} -> 6; {gm, _Msg} -> 5; - {post_commit, _Txn, _AckTags} -> 4; _ -> 0 end. -- cgit v1.2.1 From d7a5c44d559c54d17ebee808523e4c474a4675cd Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 26 Nov 2012 15:58:02 +0000 Subject: Don't hang on shutdown --- src/rabbit_amqqueue_process.erl | 11 ++++++++--- src/rabbit_mirror_queue_master.erl | 6 ++++++ src/rabbit_mirror_queue_slave.erl | 11 ++++++----- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 60857e7e..f7bb4453 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1162,9 +1162,14 @@ handle_call(sync_mirrors, From, 0 -> {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = rabbit_amqqueue:lookup(Name), gen_server2:reply(From, ok), - noreply(State#q{backing_queue_state = - rabbit_mirror_queue_master:sync_mirrors( - SPids -- SSPids, Name, BQS)}); + try + noreply(State#q{backing_queue_state = + rabbit_mirror_queue_master:sync_mirrors( + SPids -- SSPids, Name, BQS)}) + catch + {time_to_shutdown, Reason} -> + {stop, Reason, State} + end; _ -> reply({error, queue_has_pending_acks}, State) end; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 64b78fbb..a695d6f2 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -152,6 +152,12 @@ sync_mirrors(SPids, Name, State = #state { gm = GM, SPidsMRefs1 = sync_foreach(SPidsMRefs, Ref, fun sync_receive_ready/3), {{_, SPidsMRefs2, _}, BQS1} = BQ:fold(fun (Msg, MsgProps, {I, SPMR, Last}) -> + receive + {'EXIT', _Pid, Reason} -> + throw({time_to_shutdown, Reason}) + after 0 -> + ok + end, SPMR1 = wait_for_credit(SPMR, Ref), [begin credit_flow:send(SPid, ?CREDIT_DISC_BOUND), diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 965ea090..bb8def3d 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -228,8 +228,7 @@ handle_cast({sync_start, Ref, MPid}, MRef = erlang:monitor(process, MPid), MPid ! {sync_ready, Ref, self()}, {_MsgCount, BQS1} = BQ:purge(BQS), - noreply( - sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1})); + sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1}); handle_cast({set_maximum_since_use, Age}, State) -> ok = file_handle_cache:set_maximum_since_use(Age), @@ -858,7 +857,7 @@ sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, %% only thing to do here is purge.) {_MsgCount, BQS1} = BQ:purge(BQS), credit_flow:peer_down(MPid), - State#state{backing_queue_state = BQS1}; + noreply(State#state{backing_queue_state = BQS1}); {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), sync_loop(Ref, MRef, MPid, State); @@ -867,7 +866,7 @@ sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, erlang:demonitor(MRef), credit_flow:peer_down(MPid), %% We can only sync when there are no pending acks - set_delta(0, State); + noreply(set_delta(0, State)); {'$gen_cast', {set_maximum_since_use, Age}} -> ok = file_handle_cache:set_maximum_since_use(Age), sync_loop(Ref, MRef, MPid, State); @@ -882,5 +881,7 @@ sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, Props = Props0#message_properties{needs_confirming = false, delivered = true}, BQS1 = BQ:publish(Msg, Props, none, BQS), - sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1}) + sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1}); + {'EXIT', _Pid, Reason} -> + {stop, Reason, State} end. -- cgit v1.2.1 From 99dbfffc3429340e04f20e340d9d527bfbfefeec Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 26 Nov 2012 18:08:47 +0000 Subject: Move the sync stuff to its own module --- src/rabbit_mirror_queue_master.erl | 73 +------------------ src/rabbit_mirror_queue_slave.erl | 63 ++++------------ src/rabbit_mirror_queue_sync.erl | 144 +++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 120 deletions(-) create mode 100644 src/rabbit_mirror_queue_sync.erl diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index a695d6f2..cba3def7 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -45,8 +45,6 @@ known_senders }). --define(SYNC_PROGRESS_INTERVAL, 1000000). - -ifdef(use_specs). -export_type([death_fun/0, depth_fun/0]). @@ -142,80 +140,11 @@ sync_mirrors(SPids, Name, State = #state { gm = GM, %% We send the start over GM to flush out any other messages that %% we might have sent that way already. gm:broadcast(GM, {sync_start, Ref, self(), SPids}), - SPidsMRefs = [begin - MRef = erlang:monitor(process, SPid), - {SPid, MRef} - end || SPid <- SPids], - %% We wait for a reply from the slaves so that we know they are in - %% a receive block and will thus receive messages we send to them - %% *without* those messages ending up in their gen_server2 pqueue. - SPidsMRefs1 = sync_foreach(SPidsMRefs, Ref, fun sync_receive_ready/3), - {{_, SPidsMRefs2, _}, BQS1} = - BQ:fold(fun (Msg, MsgProps, {I, SPMR, Last}) -> - receive - {'EXIT', _Pid, Reason} -> - throw({time_to_shutdown, Reason}) - after 0 -> - ok - end, - SPMR1 = wait_for_credit(SPMR, Ref), - [begin - credit_flow:send(SPid, ?CREDIT_DISC_BOUND), - SPid ! {sync_message, Ref, Msg, MsgProps} - end || {SPid, _} <- SPMR1], - {I + 1, SPMR1, - case timer:now_diff(erlang:now(), Last) > - ?SYNC_PROGRESS_INTERVAL of - true -> rabbit_log:info( - "Synchronising ~s: ~p messages~n", - [rabbit_misc:rs(Name), I]), - erlang:now(); - false -> Last - end} - end, {0, SPidsMRefs1, erlang:now()}, BQS), - sync_foreach(SPidsMRefs2, Ref, fun sync_receive_complete/3), + BQS1 = rabbit_mirror_queue_sync:master(Name, Ref, SPids, BQ, BQS), rabbit_log:info("Synchronising ~s: complete~n", [rabbit_misc:rs(Name)]), State#state{backing_queue_state = BQS1}. -wait_for_credit(SPidsMRefs, Ref) -> - case credit_flow:blocked() of - true -> wait_for_credit(sync_foreach(SPidsMRefs, Ref, - fun sync_receive_credit/3), Ref); - false -> SPidsMRefs - end. - -sync_foreach(SPidsMRefs, Ref, Fun) -> - [{SPid, MRef} || {SPid, MRef} <- SPidsMRefs, - SPid1 <- [Fun(SPid, MRef, Ref)], - SPid1 =/= dead]. - -sync_receive_ready(SPid, MRef, Ref) -> - receive - {sync_ready, Ref, SPid} -> SPid; - {'DOWN', MRef, _, SPid, _} -> dead - end. - -sync_receive_credit(SPid, MRef, Ref) -> - receive - {bump_credit, {SPid, _} = Msg} -> credit_flow:handle_bump_msg(Msg), - sync_receive_credit(SPid, MRef, Ref); - {'DOWN', MRef, _, SPid, _} -> credit_flow:peer_down(SPid), - dead - after 0 -> - SPid - end. - -sync_receive_complete(SPid, MRef, Ref) -> - SPid ! {sync_complete, Ref}, - receive - {sync_complete_ok, Ref, SPid} -> ok; - {'DOWN', MRef, _, SPid, _} -> ok - end, - erlang:demonitor(MRef, [flush]), - credit_flow:peer_down(SPid). - - terminate({shutdown, dropped} = Reason, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index bb8def3d..93ba882b 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -225,10 +225,16 @@ handle_cast({deliver, Delivery = #delivery{sender = Sender}, true, Flow}, handle_cast({sync_start, Ref, MPid}, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> - MRef = erlang:monitor(process, MPid), - MPid ! {sync_ready, Ref, self()}, - {_MsgCount, BQS1} = BQ:purge(BQS), - sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1}); + S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, + %% [0] We can only sync when there are no pending acks + %% [1] The master died so we do not need to set_delta even though + %% we purged since we will get a depth instruction soon. + case rabbit_mirror_queue_sync:slave(Ref, MPid, BQ, BQS, + fun update_ram_duration/2) of + {ok, BQS1} -> noreply(set_delta(0, S(BQS1))); %% [0] + {failed, BQS1} -> noreply(S(BQS1)); %% [1] + {stop, R, BQS1} -> {stop, R, S(BQS1)} + end; handle_cast({set_maximum_since_use, Age}, State) -> ok = file_handle_cache:set_maximum_since_use(Age), @@ -824,12 +830,14 @@ update_delta( DeltaChange, State = #state { depth_delta = Delta }) -> update_ram_duration(State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> + State#state{rate_timer_ref = just_measured, + backing_queue_state = update_ram_duration(BQ, BQS)}. + +update_ram_duration(BQ, BQS) -> {RamDuration, BQS1} = BQ:ram_duration(BQS), DesiredDuration = rabbit_memory_monitor:report_ram_duration(self(), RamDuration), - BQS2 = BQ:set_ram_duration_target(DesiredDuration, BQS1), - State #state { rate_timer_ref = just_measured, - backing_queue_state = BQS2 }. + BQ:set_ram_duration_target(DesiredDuration, BQS1). record_synchronised(#amqqueue { name = QName }) -> Self = self(), @@ -844,44 +852,3 @@ record_synchronised(#amqqueue { name = QName }) -> ok end end). - -sync_loop(Ref, MRef, MPid, State = #state{backing_queue = BQ, - backing_queue_state = BQS}) -> - receive - {'DOWN', MRef, process, MPid, _Reason} -> - %% If the master dies half way we are not in the usual - %% half-synced state (with messages nearer the tail of the - %% queue; instead we have ones nearer the head. If we then - %% sync with a newly promoted master, or even just receive - %% messages from it, we have a hole in the middle. So the - %% only thing to do here is purge.) - {_MsgCount, BQS1} = BQ:purge(BQS), - credit_flow:peer_down(MPid), - noreply(State#state{backing_queue_state = BQS1}); - {bump_credit, Msg} -> - credit_flow:handle_bump_msg(Msg), - sync_loop(Ref, MRef, MPid, State); - {sync_complete, Ref} -> - MPid ! {sync_complete_ok, Ref, self()}, - erlang:demonitor(MRef), - credit_flow:peer_down(MPid), - %% We can only sync when there are no pending acks - noreply(set_delta(0, State)); - {'$gen_cast', {set_maximum_since_use, Age}} -> - ok = file_handle_cache:set_maximum_since_use(Age), - sync_loop(Ref, MRef, MPid, State); - {'$gen_cast', {set_ram_duration_target, Duration}} -> - BQS1 = BQ:set_ram_duration_target(Duration, BQS), - sync_loop(Ref, MRef, MPid, - State#state{backing_queue_state = BQS1}); - update_ram_duration -> - sync_loop(Ref, MRef, MPid, update_ram_duration(State)); - {sync_message, Ref, Msg, Props0} -> - credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), - Props = Props0#message_properties{needs_confirming = false, - delivered = true}, - BQS1 = BQ:publish(Msg, Props, none, BQS), - sync_loop(Ref, MRef, MPid, State#state{backing_queue_state = BQS1}); - {'EXIT', _Pid, Reason} -> - {stop, Reason, State} - end. diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl new file mode 100644 index 00000000..1a7cdbb9 --- /dev/null +++ b/src/rabbit_mirror_queue_sync.erl @@ -0,0 +1,144 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is VMware, Inc. +%% Copyright (c) 2010-2012 VMware, Inc. All rights reserved. +%% + +-module(rabbit_mirror_queue_sync). + +-include("rabbit.hrl"). + +-export([master/5, slave/5]). + +-define(SYNC_PROGRESS_INTERVAL, 1000000). + +%% --------------------------------------------------------------------------- + +master(Name, Ref, SPids, BQ, BQS) -> + SPidsMRefs = [begin + MRef = erlang:monitor(process, SPid), + {SPid, MRef} + end || SPid <- SPids], + %% We wait for a reply from the slaves so that we know they are in + %% a receive block and will thus receive messages we send to them + %% *without* those messages ending up in their gen_server2 pqueue. + SPidsMRefs1 = sync_foreach(SPidsMRefs, Ref, fun sync_receive_ready/3), + {{_, SPidsMRefs2, _}, BQS1} = + BQ:fold(fun (Msg, MsgProps, {I, SPMR, Last}) -> + receive + {'EXIT', _Pid, Reason} -> + throw({time_to_shutdown, Reason}) + after 0 -> + ok + end, + SPMR1 = wait_for_credit(SPMR, Ref), + [begin + credit_flow:send(SPid, ?CREDIT_DISC_BOUND), + SPid ! {sync_message, Ref, Msg, MsgProps} + end || {SPid, _} <- SPMR1], + {I + 1, SPMR1, + case timer:now_diff(erlang:now(), Last) > + ?SYNC_PROGRESS_INTERVAL of + true -> rabbit_log:info( + "Synchronising ~s: ~p messages~n", + [rabbit_misc:rs(Name), I]), + erlang:now(); + false -> Last + end} + end, {0, SPidsMRefs1, erlang:now()}, BQS), + sync_foreach(SPidsMRefs2, Ref, fun sync_receive_complete/3), + BQS1. + +wait_for_credit(SPidsMRefs, Ref) -> + case credit_flow:blocked() of + true -> wait_for_credit(sync_foreach(SPidsMRefs, Ref, + fun sync_receive_credit/3), Ref); + false -> SPidsMRefs + end. + +sync_foreach(SPidsMRefs, Ref, Fun) -> + [{SPid, MRef} || {SPid, MRef} <- SPidsMRefs, + SPid1 <- [Fun(SPid, MRef, Ref)], + SPid1 =/= dead]. + +sync_receive_ready(SPid, MRef, Ref) -> + receive + {sync_ready, Ref, SPid} -> SPid; + {'DOWN', MRef, _, SPid, _} -> dead + end. + +sync_receive_credit(SPid, MRef, Ref) -> + receive + {bump_credit, {SPid, _} = Msg} -> credit_flow:handle_bump_msg(Msg), + sync_receive_credit(SPid, MRef, Ref); + {'DOWN', MRef, _, SPid, _} -> credit_flow:peer_down(SPid), + dead + after 0 -> + SPid + end. + +sync_receive_complete(SPid, MRef, Ref) -> + SPid ! {sync_complete, Ref}, + receive + {sync_complete_ok, Ref, SPid} -> ok; + {'DOWN', MRef, _, SPid, _} -> ok + end, + erlang:demonitor(MRef, [flush]), + credit_flow:peer_down(SPid). + +%% --------------------------------------------------------------------------- + +slave(Ref, MPid, BQ, BQS, UpdateRamDuration) -> + MRef = erlang:monitor(process, MPid), + MPid ! {sync_ready, Ref, self()}, + {_MsgCount, BQS1} = BQ:purge(BQS), + slave_sync_loop(Ref, MRef, MPid, BQ, BQS1, UpdateRamDuration). + +slave_sync_loop(Ref, MRef, MPid, BQ, BQS, UpdateRamDuration) -> + receive + {'DOWN', MRef, process, MPid, _Reason} -> + %% If the master dies half way we are not in the usual + %% half-synced state (with messages nearer the tail of the + %% queue; instead we have ones nearer the head. If we then + %% sync with a newly promoted master, or even just receive + %% messages from it, we have a hole in the middle. So the + %% only thing to do here is purge.) + {_MsgCount, BQS1} = BQ:purge(BQS), + credit_flow:peer_down(MPid), + {failed, BQS1}; + {bump_credit, Msg} -> + credit_flow:handle_bump_msg(Msg), + slave_sync_loop(Ref, MRef, MPid, BQ, BQS, UpdateRamDuration); + {sync_complete, Ref} -> + MPid ! {sync_complete_ok, Ref, self()}, + erlang:demonitor(MRef), + credit_flow:peer_down(MPid), + {ok, BQS}; + {'$gen_cast', {set_maximum_since_use, Age}} -> + ok = file_handle_cache:set_maximum_since_use(Age), + slave_sync_loop(Ref, MRef, MPid, BQ, BQS, UpdateRamDuration); + {'$gen_cast', {set_ram_duration_target, Duration}} -> + BQS1 = BQ:set_ram_duration_target(Duration, BQS), + slave_sync_loop(Ref, MRef, MPid, BQ, BQS1, UpdateRamDuration); + update_ram_duration -> + BQS1 = UpdateRamDuration(BQ, BQS), + slave_sync_loop(Ref, MRef, MPid, BQ, BQS1, UpdateRamDuration); + {sync_message, Ref, Msg, Props0} -> + credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), + Props = Props0#message_properties{needs_confirming = false, + delivered = true}, + BQS1 = BQ:publish(Msg, Props, none, BQS), + slave_sync_loop(Ref, MRef, MPid, BQ, BQS1, UpdateRamDuration); + {'EXIT', _Pid, Reason} -> + {stop, Reason, BQS} + end. -- cgit v1.2.1 From 87faa2fbc8a551838dee3a5114f0ae526a1d8642 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 27 Nov 2012 10:46:33 +0000 Subject: cosmetic --- src/rabbit.erl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index c3a6d283..7b8348fc 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -400,13 +400,11 @@ status() -> is_running() -> is_running(node()). -is_running(Node) -> - rabbit_nodes:is_running(Node, rabbit). +is_running(Node) -> rabbit_nodes:is_running(Node, rabbit). environment() -> - lists:keysort( - 1, [P || P = {K, _} <- application:get_all_env(rabbit), - K =/= default_pass]). + lists:keysort(1, [P || P = {K, _} <- application:get_all_env(rabbit), + K =/= default_pass]). rotate_logs(BinarySuffix) -> Suffix = binary_to_list(BinarySuffix), -- cgit v1.2.1 From 721de630fdd3e7817ece0e788475a0081fcbc749 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Tue, 27 Nov 2012 12:42:10 +0000 Subject: tweak for {Mode, Delay} restart types; tweak test timings a little --- src/supervisor2.erl | 29 +++++++++++++++++++++++++---- src/test_sup.erl | 4 ++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index be6319d6..c5a16a9f 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -338,8 +338,12 @@ init_dynamic(_State, StartSpec) -> start_children(Children, SupName) -> start_children(Children, [], SupName). start_children([Child|Chs], NChildren, SupName) -> + Restart = case Child#child.restart_type of + A when is_atom(A) -> A; + {N, _} when is_atom(N) -> N + end, case do_start_child(SupName, Child) of - {ok, undefined} when Child#child.restart_type =:= temporary -> + {ok, undefined} when Restart =:= temporary -> start_children(Chs, NChildren, SupName); {ok, Pid} -> start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); @@ -394,9 +398,13 @@ do_start_child_i(M, F, A) -> handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> Child = hd(State#state.children), #child{mfargs = {M, F, A}} = Child, + Restart = case Child#child.restart_type of + Name when is_atom(Name) -> Name; + {Type, _} when is_atom(Type) -> Type + end, Args = A ++ EArgs, case do_start_child_i(M, F, Args) of - {ok, undefined} when Child#child.restart_type =:= temporary -> + {ok, undefined} when Restart =:= temporary -> {reply, {ok, undefined}, State}; {ok, Pid} -> NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State), @@ -630,7 +638,7 @@ handle_info({delayed_restart, {RestartType, Reason, Child}}, State) -> {value, Child1} -> {ok, NState} = do_restart(RestartType, Reason, Child1, State), {noreply, NState}; - _ -> + What -> {noreply, State} end; @@ -806,7 +814,7 @@ do_restart(temporary, Reason, Child, State) -> {ok, NState}. do_restart_delay({RestartType, Delay}, Reason, Child, State) -> - case restart(Child, State) of + case restart1(Child, State) of {ok, NState} -> {ok, NState}; {terminate, NState} -> @@ -816,6 +824,19 @@ do_restart_delay({RestartType, Delay}, Reason, Child, State) -> {ok, state_del_child(Child, NState)} end. +restart1(Child, State) -> + case add_restart(State) of + {ok, NState} -> + restart(NState#state.strategy, Child, NState); + {terminate, _NState} -> + %% we've reached the max restart intensity, but the + %% add_restart will have added to the restarts + %% field. Given we don't want to die here, we need to go + %% back to the old restarts field otherwise we'll never + %% attempt to restart later. + {terminate, State} + end. + del_child_and_maybe_shutdown(intrinsic, Child, State) -> {shutdown, state_del_child(Child, State)}; del_child_and_maybe_shutdown({intrinsic, _Delay}, Child, State) -> diff --git a/src/test_sup.erl b/src/test_sup.erl index 6a56e64a..b84acdb4 100644 --- a/src/test_sup.erl +++ b/src/test_sup.erl @@ -50,7 +50,7 @@ test_supervisor_delayed_restart(SupPid) -> ok = exit_child(SupPid), timer:sleep(100), timeout = ping_child(SupPid), - timer:sleep(1010), + timer:sleep(1100), ok = ping_child(SupPid), passed. @@ -73,7 +73,7 @@ ping_child(SupPid) -> Ref = make_ref(), with_child_pid(SupPid, fun(ChildPid) -> ChildPid ! {ping, Ref, self()} end), receive {pong, Ref} -> ok - after 1000 -> timeout + after 1100 -> timeout end. exit_child(SupPid) -> -- cgit v1.2.1 From 1d30f6b9e685c534be827d265779a945a890ef57 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 27 Nov 2012 16:41:39 +0000 Subject: Handle update_ram_duration correctly. --- src/rabbit_mirror_queue_slave.erl | 20 ++++++++++++++------ src/rabbit_mirror_queue_sync.erl | 30 +++++++++++++++--------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 93ba882b..06cda0b8 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -225,15 +225,17 @@ handle_cast({deliver, Delivery = #delivery{sender = Sender}, true, Flow}, handle_cast({sync_start, Ref, MPid}, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> - S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, + State1 = #state{rate_timer_ref = TRef} = ensure_rate_timer(State), + S = fun({TRefN, BQSN}) -> State1#state{rate_timer_ref = TRefN, + backing_queue_state = BQSN} end, %% [0] We can only sync when there are no pending acks %% [1] The master died so we do not need to set_delta even though %% we purged since we will get a depth instruction soon. - case rabbit_mirror_queue_sync:slave(Ref, MPid, BQ, BQS, - fun update_ram_duration/2) of - {ok, BQS1} -> noreply(set_delta(0, S(BQS1))); %% [0] - {failed, BQS1} -> noreply(S(BQS1)); %% [1] - {stop, R, BQS1} -> {stop, R, S(BQS1)} + case rabbit_mirror_queue_sync:slave(Ref, TRef, MPid, BQ, BQS, + fun update_ram_duration_sync/2) of + {ok, Res} -> noreply(set_delta(0, S(Res))); %% [0] + {failed, Res} -> noreply(S(Res)); %% [1] + {stop, Reason, Res} -> {stop, Reason, S(Res)} end; handle_cast({set_maximum_since_use, Age}, State) -> @@ -833,6 +835,12 @@ update_ram_duration(State = #state { backing_queue = BQ, State#state{rate_timer_ref = just_measured, backing_queue_state = update_ram_duration(BQ, BQS)}. +update_ram_duration_sync(BQ, BQS) -> + BQS1 = update_ram_duration(BQ, BQS), + TRef = erlang:send_after(?RAM_DURATION_UPDATE_INTERVAL, + self(), update_ram_duration), + {TRef, BQS1}. + update_ram_duration(BQ, BQS) -> {RamDuration, BQS1} = BQ:ram_duration(BQS), DesiredDuration = diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 1a7cdbb9..cdf35eb2 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -18,7 +18,7 @@ -include("rabbit.hrl"). --export([master/5, slave/5]). +-export([master/5, slave/6]). -define(SYNC_PROGRESS_INTERVAL, 1000000). @@ -98,47 +98,47 @@ sync_receive_complete(SPid, MRef, Ref) -> %% --------------------------------------------------------------------------- -slave(Ref, MPid, BQ, BQS, UpdateRamDuration) -> +slave(Ref, TRef, MPid, BQ, BQS, UpdateRamDuration) -> MRef = erlang:monitor(process, MPid), MPid ! {sync_ready, Ref, self()}, {_MsgCount, BQS1} = BQ:purge(BQS), - slave_sync_loop(Ref, MRef, MPid, BQ, BQS1, UpdateRamDuration). + slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS1, UpdateRamDuration). -slave_sync_loop(Ref, MRef, MPid, BQ, BQS, UpdateRamDuration) -> +slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS, UpdateRamDur) -> receive {'DOWN', MRef, process, MPid, _Reason} -> %% If the master dies half way we are not in the usual %% half-synced state (with messages nearer the tail of the - %% queue; instead we have ones nearer the head. If we then + %% queue); instead we have ones nearer the head. If we then %% sync with a newly promoted master, or even just receive %% messages from it, we have a hole in the middle. So the - %% only thing to do here is purge.) + %% only thing to do here is purge. {_MsgCount, BQS1} = BQ:purge(BQS), credit_flow:peer_down(MPid), - {failed, BQS1}; + {failed, {TRef, BQS1}}; {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), - slave_sync_loop(Ref, MRef, MPid, BQ, BQS, UpdateRamDuration); + slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS, UpdateRamDur); {sync_complete, Ref} -> MPid ! {sync_complete_ok, Ref, self()}, erlang:demonitor(MRef), credit_flow:peer_down(MPid), - {ok, BQS}; + {ok, {TRef, BQS}}; {'$gen_cast', {set_maximum_since_use, Age}} -> ok = file_handle_cache:set_maximum_since_use(Age), - slave_sync_loop(Ref, MRef, MPid, BQ, BQS, UpdateRamDuration); + slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS, UpdateRamDur); {'$gen_cast', {set_ram_duration_target, Duration}} -> BQS1 = BQ:set_ram_duration_target(Duration, BQS), - slave_sync_loop(Ref, MRef, MPid, BQ, BQS1, UpdateRamDuration); + slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS1, UpdateRamDur); update_ram_duration -> - BQS1 = UpdateRamDuration(BQ, BQS), - slave_sync_loop(Ref, MRef, MPid, BQ, BQS1, UpdateRamDuration); + {TRef2, BQS1} = UpdateRamDur(BQ, BQS), + slave_sync_loop(Ref, TRef2, MRef, MPid, BQ, BQS1, UpdateRamDur); {sync_message, Ref, Msg, Props0} -> credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), Props = Props0#message_properties{needs_confirming = false, delivered = true}, BQS1 = BQ:publish(Msg, Props, none, BQS), - slave_sync_loop(Ref, MRef, MPid, BQ, BQS1, UpdateRamDuration); + slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS1, UpdateRamDur); {'EXIT', _Pid, Reason} -> - {stop, Reason, BQS} + {stop, Reason, {TRef, BQS}} end. -- cgit v1.2.1 From 23122e968b13fc958534f59996e3d49a9353cd4f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 27 Nov 2012 18:20:34 +0000 Subject: simplify errors --- src/rabbit_amqqueue.erl | 3 +-- src/rabbit_amqqueue_process.erl | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index c1884118..4bdab0bc 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -174,8 +174,7 @@ -spec(start_mirroring/1 :: (pid()) -> 'ok'). -spec(stop_mirroring/1 :: (pid()) -> 'ok'). -spec(sync_mirrors/1 :: (rabbit_types:amqqueue()) -> - 'ok' | rabbit_types:error('queue_has_pending_acks') - | rabbit_types:error('queue_not_mirrored')). + 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). -endif. diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index f7bb4453..3f9894f8 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1170,11 +1170,11 @@ handle_call(sync_mirrors, From, {time_to_shutdown, Reason} -> {stop, Reason, State} end; - _ -> reply({error, queue_has_pending_acks}, State) + _ -> reply({error, pending_acks}, State) end; handle_call(sync_mirrors, _From, State) -> - reply({error, queue_not_mirrored}, State); + reply({error, not_mirrored}, State); handle_call(force_event_refresh, _From, State = #q{exclusive_consumer = Exclusive}) -> -- cgit v1.2.1 From 9e8f4fc04ed36a6d7243cf050ce590e14375e15e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 27 Nov 2012 18:22:45 +0000 Subject: fix bug --- src/rabbit_backing_queue_qc.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index fb8b82ea..764911b9 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -159,7 +159,7 @@ qc_purge(#state{bqstate = BQ}) -> {call, ?BQMOD, purge, [BQ]}. qc_fold(#state{bqstate = BQ}) -> - {call, ?BQMOD, fold, [fun foldfun/2, foldacc(), BQ]}. + {call, ?BQMOD, fold, [fun foldfun/3, foldacc(), BQ]}. %% Preconditions @@ -393,7 +393,7 @@ rand_choice(List, Selection, N) -> rand_choice(List -- [Picked], [Picked | Selection], N - 1). -foldfun(Msg, Acc) -> [Msg | Acc]. +foldfun(Msg, _MsgProps, Acc) -> [Msg | Acc]. foldacc() -> []. dropfun(Props) -> -- cgit v1.2.1 From cc25f470115cfd68e8f474ee9252d1f3eb30ae88 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 11:45:33 +0000 Subject: Various QA cleanups --- src/rabbit_amqqueue_process.erl | 12 +++------ src/rabbit_mirror_queue_master.erl | 53 ++++++++++++++++++++++---------------- src/rabbit_mirror_queue_slave.erl | 29 ++++++++++----------- src/rabbit_mirror_queue_sync.erl | 29 ++++++++++----------- 4 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 70dc8aee..10efc798 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1154,17 +1154,13 @@ handle_call({requeue, AckTags, ChPid}, From, State) -> noreply(requeue(AckTags, ChPid, State)); handle_call(sync_mirrors, From, - State = #q{q = #amqqueue{name = Name}, - backing_queue = rabbit_mirror_queue_master = BQ, + State = #q{backing_queue = rabbit_mirror_queue_master = BQ, backing_queue_state = BQS}) -> case BQ:depth(BQS) - BQ:len(BQS) of - 0 -> {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = - rabbit_amqqueue:lookup(Name), - gen_server2:reply(From, ok), + 0 -> gen_server2:reply(From, ok), try - noreply(State#q{backing_queue_state = - rabbit_mirror_queue_master:sync_mirrors( - SPids -- SSPids, Name, BQS)}) + BQS1 = rabbit_mirror_queue_master:sync_mirrors(BQS), + noreply(State#q{backing_queue_state = BQS1}) catch {time_to_shutdown, Reason} -> {stop, Reason, State} diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index e19c1a09..545f2219 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -26,15 +26,16 @@ -export([start/1, stop/0]). --export([promote_backing_queue_state/7, sender_death_fun/0, depth_fun/0]). +-export([promote_backing_queue_state/8, sender_death_fun/0, depth_fun/0]). --export([init_with_existing_bq/3, stop_mirroring/1, sync_mirrors/3]). +-export([init_with_existing_bq/3, stop_mirroring/1, sync_mirrors/1]). -behaviour(rabbit_backing_queue). -include("rabbit.hrl"). --record(state, { gm, +-record(state, { name, + gm, coordinator, backing_queue, backing_queue_state, @@ -50,7 +51,8 @@ -type(death_fun() :: fun ((pid()) -> 'ok')). -type(depth_fun() :: fun (() -> 'ok')). --type(master_state() :: #state { gm :: pid(), +-type(master_state() :: #state { name :: rabbit_amqqueue:name(), + gm :: pid(), coordinator :: pid(), backing_queue :: atom(), backing_queue_state :: any(), @@ -60,9 +62,9 @@ known_senders :: set() }). --spec(promote_backing_queue_state/7 :: - (pid(), atom(), any(), pid(), [any()], dict(), [pid()]) -> - master_state()). +-spec(promote_backing_queue_state/8 :: + (rabbit_amqqueue:name(), pid(), atom(), any(), pid(), [any()], dict(), + [pid()]) -> master_state()). -spec(sender_death_fun/0 :: () -> death_fun()). -spec(depth_fun/0 :: () -> depth_fun()). -spec(init_with_existing_bq/3 :: (rabbit_types:amqqueue(), atom(), any()) -> @@ -108,7 +110,8 @@ init_with_existing_bq(Q = #amqqueue{name = QName}, BQ, BQS) -> end), {_MNode, SNodes} = rabbit_mirror_queue_misc:suggested_queue_nodes(Q), rabbit_mirror_queue_misc:add_mirrors(QName, SNodes), - #state { gm = GM, + #state { name = QName, + gm = GM, coordinator = CPid, backing_queue = BQ, backing_queue_state = BQS, @@ -124,13 +127,19 @@ stop_mirroring(State = #state { coordinator = CPid, stop_all_slaves(shutdown, State), {BQ, BQS}. -sync_mirrors([], Name, State) -> +sync_mirrors(State = #state{name = Name}) -> + {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = + rabbit_amqqueue:lookup(Name), + sync_mirrors(SPids -- SSPids, State). + +sync_mirrors([], State = #state{name = Name}) -> rabbit_log:info("Synchronising ~s: nothing to do~n", [rabbit_misc:rs(Name)]), State; -sync_mirrors(SPids, Name, State = #state { gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> +sync_mirrors(SPids, State = #state { name = Name, + gm = GM, + backing_queue = BQ, + backing_queue_state = BQS }) -> rabbit_log:info("Synchronising ~s with slaves ~p: ~p messages to do~n", [rabbit_misc:rs(Name), SPids, BQ:len(BQS)]), Ref = make_ref(), @@ -165,24 +174,23 @@ delete_and_terminate(Reason, State = #state { backing_queue = BQ, stop_all_slaves(Reason, State), State#state{backing_queue_state = BQ:delete_and_terminate(Reason, BQS)}. -stop_all_slaves(Reason, #state{gm = GM}) -> - Info = gm:info(GM), - Slaves = [Pid || Pid <- proplists:get_value(group_members, Info), - node(Pid) =/= node()], +stop_all_slaves(Reason, #state{name = Name, + gm = GM}) -> + {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(Name), + Slaves = [Pid || Pid <- SPids], MRefs = [erlang:monitor(process, S) || S <- Slaves], ok = gm:broadcast(GM, {delete_and_terminate, Reason}), [receive {'DOWN', MRef, process, _Pid, _Info} -> ok end || MRef <- MRefs], %% Normally when we remove a slave another slave or master will %% notice and update Mnesia. But we just removed them all, and %% have stopped listening ourselves. So manually clean up. - QName = proplists:get_value(group_name, Info), rabbit_misc:execute_mnesia_transaction( fun () -> - [Q] = mnesia:read({rabbit_queue, QName}), + [Q] = mnesia:read({rabbit_queue, Name}), rabbit_mirror_queue_misc:store_updated_slaves( Q #amqqueue { gm_pids = [], slave_pids = [] }) end), - ok = gm:forget_group(QName). + ok = gm:forget_group(Name). purge(State = #state { gm = GM, backing_queue = BQ, @@ -414,17 +422,18 @@ is_duplicate(Message = #basic_message { id = MsgId }, %% Other exported functions %% --------------------------------------------------------------------------- -promote_backing_queue_state(CPid, BQ, BQS, GM, AckTags, SeenStatus, KS) -> +promote_backing_queue_state(QName, CPid, BQ, BQS, GM, AckTags, Seen, KS) -> {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), Len = BQ:len(BQS1), Depth = BQ:depth(BQS1), true = Len == Depth, %% ASSERTION: everything must have been requeued ok = gm:broadcast(GM, {depth, Depth}), - #state { gm = GM, + #state { name = QName, + gm = GM, coordinator = CPid, backing_queue = BQ, backing_queue_state = BQS1, - seen_status = SeenStatus, + seen_status = Seen, confirmed = [], ack_msg_id = dict:new(), known_senders = sets:from_list(KS) }. diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 320c07a6..1ba6774a 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -231,8 +231,14 @@ handle_cast({sync_start, Ref, MPid}, %% [0] We can only sync when there are no pending acks %% [1] The master died so we do not need to set_delta even though %% we purged since we will get a depth instruction soon. - case rabbit_mirror_queue_sync:slave(Ref, TRef, MPid, BQ, BQS, - fun update_ram_duration_sync/2) of + case rabbit_mirror_queue_sync:slave( + Ref, TRef, MPid, BQ, BQS, + fun (BQN, BQSN) -> + BQSN1 = update_ram_duration(BQN, BQSN), + TRef = erlang:send_after(?RAM_DURATION_UPDATE_INTERVAL, + self(), update_ram_duration), + {TRef, BQSN1} + end) of {ok, Res} -> noreply(set_delta(0, S(Res))); %% [0] {failed, Res} -> noreply(S(Res)); %% [1] {stop, Reason, Res} -> {stop, Reason, S(Res)} @@ -248,8 +254,10 @@ handle_cast({set_ram_duration_target, Duration}, BQS1 = BQ:set_ram_duration_target(Duration, BQS), noreply(State #state { backing_queue_state = BQS1 }). -handle_info(update_ram_duration, State) -> - noreply(update_ram_duration(State)); +handle_info(update_ram_duration, State = #state{backing_queue = BQ, + backing_queue_state = BQS}) -> + noreply(State#state{rate_timer_ref = just_measured, + backing_queue_state = update_ram_duration(BQ, BQS)}); handle_info(sync_timeout, State) -> noreply(backing_queue_timeout( @@ -542,7 +550,7 @@ promote_me(From, #state { q = Q = #amqqueue { name = QName }, AckTags = [AckTag || {_MsgId, AckTag} <- dict:to_list(MA)], MasterState = rabbit_mirror_queue_master:promote_backing_queue_state( - CPid, BQ, BQS, GM, AckTags, SS, MPids), + QName, CPid, BQ, BQS, GM, AckTags, SS, MPids), MTC = dict:fold(fun (MsgId, {published, ChPid, MsgSeqNo}, MTC0) -> gb_trees:insert(MsgId, {ChPid, MsgSeqNo}, MTC0); @@ -829,17 +837,6 @@ update_delta( DeltaChange, State = #state { depth_delta = Delta }) -> true = DeltaChange =< 0, %% assertion: we cannot become 'less' sync'ed set_delta(Delta + DeltaChange, State #state { depth_delta = undefined }). -update_ram_duration(State = #state { backing_queue = BQ, - backing_queue_state = BQS }) -> - State#state{rate_timer_ref = just_measured, - backing_queue_state = update_ram_duration(BQ, BQS)}. - -update_ram_duration_sync(BQ, BQS) -> - BQS1 = update_ram_duration(BQ, BQS), - TRef = erlang:send_after(?RAM_DURATION_UPDATE_INTERVAL, - self(), update_ram_duration), - {TRef, BQS1}. - update_ram_duration(BQ, BQS) -> {RamDuration, BQS1} = BQ:ram_duration(BQS), DesiredDuration = diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index e1216120..36e9f1eb 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -32,7 +32,7 @@ master(Name, Ref, SPids, BQ, BQS) -> %% We wait for a reply from the slaves so that we know they are in %% a receive block and will thus receive messages we send to them %% *without* those messages ending up in their gen_server2 pqueue. - SPidsMRefs1 = sync_foreach(SPidsMRefs, Ref, fun sync_receive_ready/3), + SPidsMRefs1 = foreach_slave(SPidsMRefs, Ref, fun sync_receive_ready/3), {{_, SPidsMRefs2, _}, BQS1} = BQ:fold(fun (Msg, MsgProps, {I, SPMR, Last}) -> receive @@ -56,20 +56,19 @@ master(Name, Ref, SPids, BQ, BQS) -> false -> Last end} end, {0, SPidsMRefs1, erlang:now()}, BQS), - sync_foreach(SPidsMRefs2, Ref, fun sync_receive_complete/3), + foreach_slave(SPidsMRefs2, Ref, fun sync_receive_complete/3), BQS1. wait_for_credit(SPidsMRefs, Ref) -> case credit_flow:blocked() of - true -> wait_for_credit(sync_foreach(SPidsMRefs, Ref, - fun sync_receive_credit/3), Ref); + true -> wait_for_credit(foreach_slave(SPidsMRefs, Ref, + fun sync_receive_credit/3), Ref); false -> SPidsMRefs end. -sync_foreach(SPidsMRefs, Ref, Fun) -> +foreach_slave(SPidsMRefs, Ref, Fun) -> [{SPid, MRef} || {SPid, MRef} <- SPidsMRefs, - SPid1 <- [Fun(SPid, MRef, Ref)], - SPid1 =/= dead]. + Fun(SPid, MRef, Ref) =/= dead]. sync_receive_ready(SPid, MRef, Ref) -> receive @@ -102,9 +101,9 @@ slave(Ref, TRef, MPid, BQ, BQS, UpdateRamDuration) -> MRef = erlang:monitor(process, MPid), MPid ! {sync_ready, Ref, self()}, {_MsgCount, BQS1} = BQ:purge(BQS), - slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS1, UpdateRamDuration). + slave_sync_loop({Ref, MRef, MPid, BQ, UpdateRamDuration}, TRef, BQS1). -slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS, UpdateRamDur) -> +slave_sync_loop(Args = {Ref, MRef, MPid, BQ, UpdateRamDur}, TRef, BQS) -> receive {'DOWN', MRef, process, MPid, _Reason} -> %% If the master dies half way we are not in the usual @@ -118,7 +117,7 @@ slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS, UpdateRamDur) -> {failed, {TRef, BQS1}}; {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), - slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS, UpdateRamDur); + slave_sync_loop(Args, TRef, BQS); {sync_complete, Ref} -> MPid ! {sync_complete_ok, Ref, self()}, erlang:demonitor(MRef), @@ -126,18 +125,18 @@ slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS, UpdateRamDur) -> {ok, {TRef, BQS}}; {'$gen_cast', {set_maximum_since_use, Age}} -> ok = file_handle_cache:set_maximum_since_use(Age), - slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS, UpdateRamDur); + slave_sync_loop(Args, TRef, BQS); {'$gen_cast', {set_ram_duration_target, Duration}} -> BQS1 = BQ:set_ram_duration_target(Duration, BQS), - slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS1, UpdateRamDur); + slave_sync_loop(Args, TRef, BQS1); update_ram_duration -> {TRef2, BQS1} = UpdateRamDur(BQ, BQS), - slave_sync_loop(Ref, TRef2, MRef, MPid, BQ, BQS1, UpdateRamDur); + slave_sync_loop(Args, TRef2, BQS1); {sync_message, Ref, Msg, Props} -> credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), Props1 = Props#message_properties{needs_confirming = false}, - BQS1 = BQ:publish(Msg, Props1, none, BQS), - slave_sync_loop(Ref, TRef, MRef, MPid, BQ, BQS1, UpdateRamDur); + BQS1 = BQ:publish(Msg, Props1, true, none, BQS), + slave_sync_loop(Args, TRef, BQS1); {'EXIT', _Pid, Reason} -> {stop, Reason, {TRef, BQS}} end. -- cgit v1.2.1 From f4786168c90dd2ffa1a4e8a1b54eae1c61e2a7eb Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 12:09:37 +0000 Subject: Oops --- src/rabbit_mirror_queue_slave.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 1ba6774a..11490b9c 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -235,9 +235,9 @@ handle_cast({sync_start, Ref, MPid}, Ref, TRef, MPid, BQ, BQS, fun (BQN, BQSN) -> BQSN1 = update_ram_duration(BQN, BQSN), - TRef = erlang:send_after(?RAM_DURATION_UPDATE_INTERVAL, - self(), update_ram_duration), - {TRef, BQSN1} + TRefN = erlang:send_after(?RAM_DURATION_UPDATE_INTERVAL, + self(), update_ram_duration), + {TRefN, BQSN1} end) of {ok, Res} -> noreply(set_delta(0, S(Res))); %% [0] {failed, Res} -> noreply(S(Res)); %% [1] -- cgit v1.2.1 From 566125a445ab33c08d55bdd4b7f3a5bc2bc58311 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 13:02:50 +0000 Subject: Spawn a separate process to send out messages from the master. This lets us simplify some monitor and credit_flow handling. Thus also stop sync_receive_credit from being a spinloop. --- src/rabbit_mirror_queue_master.erl | 5 ++-- src/rabbit_mirror_queue_sync.erl | 56 +++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 545f2219..ff1eccaf 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -145,8 +145,9 @@ sync_mirrors(SPids, State = #state { name = Name, Ref = make_ref(), %% We send the start over GM to flush out any other messages that %% we might have sent that way already. - gm:broadcast(GM, {sync_start, Ref, self(), SPids}), - BQS1 = rabbit_mirror_queue_sync:master(Name, Ref, SPids, BQ, BQS), + Syncer = rabbit_mirror_queue_sync:master_prepare(Name, Ref, SPids, BQ, BQS), + gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), + BQS1 = rabbit_mirror_queue_sync:master_go(Syncer, Ref), rabbit_log:info("Synchronising ~s: complete~n", [rabbit_misc:rs(Name)]), State#state{backing_queue_state = BQS1}. diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 36e9f1eb..978d940c 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -18,13 +18,26 @@ -include("rabbit.hrl"). --export([master/5, slave/6]). +-export([master_prepare/5, master_go/2, slave/6]). -define(SYNC_PROGRESS_INTERVAL, 1000000). %% --------------------------------------------------------------------------- -master(Name, Ref, SPids, BQ, BQS) -> +master_prepare(Name, Ref, SPids, BQ, BQS) -> + MPid = self(), + spawn_link(fun () -> master(Name, Ref, MPid, SPids, BQ, BQS) end). + +master_go(Syncer, Ref) -> + Syncer ! {go, Ref}, + receive + {done, Ref, BQS1} -> BQS1 + end. + +master(Name, Ref, MPid, SPids, BQ, BQS) -> + receive + {go, Ref} -> ok + end, SPidsMRefs = [begin MRef = erlang:monitor(process, SPid), {SPid, MRef} @@ -57,7 +70,8 @@ master(Name, Ref, SPids, BQ, BQS) -> end} end, {0, SPidsMRefs1, erlang:now()}, BQS), foreach_slave(SPidsMRefs2, Ref, fun sync_receive_complete/3), - BQS1. + MPid ! {done, Ref, BQS1}, + unlink(MPid). wait_for_credit(SPidsMRefs, Ref) -> case credit_flow:blocked() of @@ -76,36 +90,28 @@ sync_receive_ready(SPid, MRef, Ref) -> {'DOWN', MRef, _, SPid, _} -> dead end. -sync_receive_credit(SPid, MRef, Ref) -> +sync_receive_credit(SPid, MRef, _Ref) -> receive {bump_credit, {SPid, _} = Msg} -> credit_flow:handle_bump_msg(Msg), - sync_receive_credit(SPid, MRef, Ref); + SPid; {'DOWN', MRef, _, SPid, _} -> credit_flow:peer_down(SPid), dead - after 0 -> - SPid end. -sync_receive_complete(SPid, MRef, Ref) -> - SPid ! {sync_complete, Ref}, - receive - {sync_complete_ok, Ref, SPid} -> ok; - {'DOWN', MRef, _, SPid, _} -> ok - end, - erlang:demonitor(MRef, [flush]), - credit_flow:peer_down(SPid). +sync_receive_complete(SPid, _MRef, Ref) -> + SPid ! {sync_complete, Ref}. %% --------------------------------------------------------------------------- -slave(Ref, TRef, MPid, BQ, BQS, UpdateRamDuration) -> - MRef = erlang:monitor(process, MPid), - MPid ! {sync_ready, Ref, self()}, +slave(Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> + MRef = erlang:monitor(process, Syncer), + Syncer ! {sync_ready, Ref, self()}, {_MsgCount, BQS1} = BQ:purge(BQS), - slave_sync_loop({Ref, MRef, MPid, BQ, UpdateRamDuration}, TRef, BQS1). + slave_sync_loop({Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS1). -slave_sync_loop(Args = {Ref, MRef, MPid, BQ, UpdateRamDur}, TRef, BQS) -> +slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDur}, TRef, BQS) -> receive - {'DOWN', MRef, process, MPid, _Reason} -> + {'DOWN', MRef, process, Syncer, _Reason} -> %% If the master dies half way we are not in the usual %% half-synced state (with messages nearer the tail of the %% queue); instead we have ones nearer the head. If we then @@ -113,15 +119,15 @@ slave_sync_loop(Args = {Ref, MRef, MPid, BQ, UpdateRamDur}, TRef, BQS) -> %% messages from it, we have a hole in the middle. So the %% only thing to do here is purge. {_MsgCount, BQS1} = BQ:purge(BQS), - credit_flow:peer_down(MPid), + credit_flow:peer_down(Syncer), {failed, {TRef, BQS1}}; {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), slave_sync_loop(Args, TRef, BQS); {sync_complete, Ref} -> - MPid ! {sync_complete_ok, Ref, self()}, + Syncer ! {sync_complete_ok, Ref, self()}, erlang:demonitor(MRef), - credit_flow:peer_down(MPid), + credit_flow:peer_down(Syncer), {ok, {TRef, BQS}}; {'$gen_cast', {set_maximum_since_use, Age}} -> ok = file_handle_cache:set_maximum_since_use(Age), @@ -133,7 +139,7 @@ slave_sync_loop(Args = {Ref, MRef, MPid, BQ, UpdateRamDur}, TRef, BQS) -> {TRef2, BQS1} = UpdateRamDur(BQ, BQS), slave_sync_loop(Args, TRef2, BQS1); {sync_message, Ref, Msg, Props} -> - credit_flow:ack(MPid, ?CREDIT_DISC_BOUND), + credit_flow:ack(Syncer, ?CREDIT_DISC_BOUND), Props1 = Props#message_properties{needs_confirming = false}, BQS1 = BQ:publish(Msg, Props1, true, none, BQS), slave_sync_loop(Args, TRef, BQS1); -- cgit v1.2.1 From 8f3a8f6de8c86783e7f22d76d099955b7e58dc5b Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 13:41:34 +0000 Subject: Don't expose the BQ to the syncer. --- src/rabbit_mirror_queue_master.erl | 4 +- src/rabbit_mirror_queue_sync.erl | 88 ++++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index ff1eccaf..04e868bc 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -145,9 +145,9 @@ sync_mirrors(SPids, State = #state { name = Name, Ref = make_ref(), %% We send the start over GM to flush out any other messages that %% we might have sent that way already. - Syncer = rabbit_mirror_queue_sync:master_prepare(Name, Ref, SPids, BQ, BQS), + Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, SPids), gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), - BQS1 = rabbit_mirror_queue_sync:master_go(Syncer, Ref), + BQS1 = rabbit_mirror_queue_sync:master_go(Syncer, Ref, Name, BQ, BQS), rabbit_log:info("Synchronising ~s: complete~n", [rabbit_misc:rs(Name)]), State#state{backing_queue_state = BQS1}. diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 978d940c..560e0d43 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -18,26 +18,45 @@ -include("rabbit.hrl"). --export([master_prepare/5, master_go/2, slave/6]). +-export([master_prepare/2, master_go/5, slave/6]). -define(SYNC_PROGRESS_INTERVAL, 1000000). %% --------------------------------------------------------------------------- +%% Master -master_prepare(Name, Ref, SPids, BQ, BQS) -> +master_prepare(Ref, SPids) -> MPid = self(), - spawn_link(fun () -> master(Name, Ref, MPid, SPids, BQ, BQS) end). - -master_go(Syncer, Ref) -> - Syncer ! {go, Ref}, + spawn_link(fun () -> syncer(Ref, MPid, SPids) end). + +master_go(Syncer, Ref, Name, BQ, BQS) -> + SendArgs = {Syncer, Ref, Name}, + {_, BQS1} = + BQ:fold(fun (Msg, MsgProps, {I, Last}) -> + {I + 1, master_send(SendArgs, I, Last, Msg, MsgProps)} + end, {0, erlang:now()}, BQS), + Syncer ! {done, Ref}, + BQS1. + +master_send({Syncer, Ref, Name}, I, Last, Msg, MsgProps) -> + Syncer ! {msg, Ref, Msg, MsgProps}, receive - {done, Ref, BQS1} -> BQS1 + {msg_ok, Ref} -> ok; + {'EXIT', _Pid, Reason} -> throw({time_to_shutdown, Reason}) + end, + case timer:now_diff(erlang:now(), Last) > + ?SYNC_PROGRESS_INTERVAL of + true -> rabbit_log:info("Synchronising ~s: ~p messages~n", + [rabbit_misc:rs(Name), I]), + erlang:now(); + false -> Last end. -master(Name, Ref, MPid, SPids, BQ, BQS) -> - receive - {go, Ref} -> ok - end, +%% Master +%% --------------------------------------------------------------------------- +%% Syncer + +syncer(Ref, MPid, SPids) -> SPidsMRefs = [begin MRef = erlang:monitor(process, SPid), {SPid, MRef} @@ -46,33 +65,24 @@ master(Name, Ref, MPid, SPids, BQ, BQS) -> %% a receive block and will thus receive messages we send to them %% *without* those messages ending up in their gen_server2 pqueue. SPidsMRefs1 = foreach_slave(SPidsMRefs, Ref, fun sync_receive_ready/3), - {{_, SPidsMRefs2, _}, BQS1} = - BQ:fold(fun (Msg, MsgProps, {I, SPMR, Last}) -> - receive - {'EXIT', _Pid, Reason} -> - throw({time_to_shutdown, Reason}) - after 0 -> - ok - end, - SPMR1 = wait_for_credit(SPMR, Ref), - [begin - credit_flow:send(SPid, ?CREDIT_DISC_BOUND), - SPid ! {sync_message, Ref, Msg, MsgProps} - end || {SPid, _} <- SPMR1], - {I + 1, SPMR1, - case timer:now_diff(erlang:now(), Last) > - ?SYNC_PROGRESS_INTERVAL of - true -> rabbit_log:info( - "Synchronising ~s: ~p messages~n", - [rabbit_misc:rs(Name), I]), - erlang:now(); - false -> Last - end} - end, {0, SPidsMRefs1, erlang:now()}, BQS), - foreach_slave(SPidsMRefs2, Ref, fun sync_receive_complete/3), - MPid ! {done, Ref, BQS1}, + SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), + foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3), unlink(MPid). +syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> + receive + {msg, Ref, Msg, MsgProps} -> + MPid ! {msg_ok, Ref}, + SPidsMRefs1 = wait_for_credit(SPidsMRefs, Ref), + [begin + credit_flow:send(SPid, ?CREDIT_DISC_BOUND), + SPid ! {sync_msg, Ref, Msg, MsgProps} + end || {SPid, _} <- SPidsMRefs1], + syncer_loop(Args, SPidsMRefs1); + {done, Ref} -> + SPidsMRefs + end. + wait_for_credit(SPidsMRefs, Ref) -> case credit_flow:blocked() of true -> wait_for_credit(foreach_slave(SPidsMRefs, Ref, @@ -98,10 +108,12 @@ sync_receive_credit(SPid, MRef, _Ref) -> dead end. -sync_receive_complete(SPid, _MRef, Ref) -> +sync_send_complete(SPid, _MRef, Ref) -> SPid ! {sync_complete, Ref}. +%% Syncer %% --------------------------------------------------------------------------- +%% Slave slave(Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> MRef = erlang:monitor(process, Syncer), @@ -138,7 +150,7 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDur}, TRef, BQS) -> update_ram_duration -> {TRef2, BQS1} = UpdateRamDur(BQ, BQS), slave_sync_loop(Args, TRef2, BQS1); - {sync_message, Ref, Msg, Props} -> + {sync_msg, Ref, Msg, Props} -> credit_flow:ack(Syncer, ?CREDIT_DISC_BOUND), Props1 = Props#message_properties{needs_confirming = false}, BQS1 = BQ:publish(Msg, Props1, true, none, BQS), -- cgit v1.2.1 From d3de0a33eff8467bc584c72e095b8865ce3159a5 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 13:56:50 +0000 Subject: Diagram. --- src/rabbit_mirror_queue_master.erl | 2 -- src/rabbit_mirror_queue_sync.erl | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 04e868bc..e8734a8c 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -143,8 +143,6 @@ sync_mirrors(SPids, State = #state { name = Name, rabbit_log:info("Synchronising ~s with slaves ~p: ~p messages to do~n", [rabbit_misc:rs(Name), SPids, BQ:len(BQS)]), Ref = make_ref(), - %% We send the start over GM to flush out any other messages that - %% we might have sent that way already. Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, SPids), gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), BQS1 = rabbit_mirror_queue_sync:master_go(Syncer, Ref, Name, BQ, BQS), diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 560e0d43..f7901d9c 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -22,6 +22,32 @@ -define(SYNC_PROGRESS_INTERVAL, 1000000). +%% There are three processes around, the master, the syncer and the +%% slave(s). The syncer is an intermediary, linked to the master in +%% order to make sure we do not mess with the master's credit flow or +%% set of monitors. +%% +%% Interactions +%% ------------ +%% +%% '*' indicates repeating messages. All are standard Erlang messages +%% except sync_start which is sent over GM to flush out any other +%% messages that we might have sent that way already. (credit) is the +%% usual credit_flow bump message every so often. +%% +%% Master Syncer Slave(s) +%% sync_mirrors -> || || +%% (from channel) || -- (spawns) --> || || +%% || --------- sync_start (over GM) -------> || +%% || || <--- sync_ready ---- || +%% || ----- msg* ---> || || } +%% || <-- msg_ok* --- || || } loop +%% || || ----- sync_msg* ---> || } +%% || || <---- (credit)* ---- || } +%% || ---- done ----> || || +%% || || -- sync_complete --> || +%% || (Dies) || + %% --------------------------------------------------------------------------- %% Master -- cgit v1.2.1 From e6e360d8d76c2cc38e38b824d9ba35af2ac5f0e8 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 13:57:24 +0000 Subject: Oops --- src/rabbit_mirror_queue_sync.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index f7901d9c..8d0407f2 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -35,7 +35,7 @@ %% messages that we might have sent that way already. (credit) is the %% usual credit_flow bump message every so often. %% -%% Master Syncer Slave(s) +%% Master Syncer Slave(s) %% sync_mirrors -> || || %% (from channel) || -- (spawns) --> || || %% || --------- sync_start (over GM) -------> || -- cgit v1.2.1 From 8f9c5c2a0428a9dac54e3b0ff0a514cf740316b4 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 14:04:18 +0000 Subject: We should flush here too, to not leave a junk 'DOWN' message when the syncer exits. Also there's no sync_complete_ok any more... --- src/rabbit_mirror_queue_sync.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 8d0407f2..6626ee2e 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -163,8 +163,7 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDur}, TRef, BQS) -> credit_flow:handle_bump_msg(Msg), slave_sync_loop(Args, TRef, BQS); {sync_complete, Ref} -> - Syncer ! {sync_complete_ok, Ref, self()}, - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), credit_flow:peer_down(Syncer), {ok, {TRef, BQS}}; {'$gen_cast', {set_maximum_since_use, Age}} -> -- cgit v1.2.1 From 45d09eb000f05523a384f2f5875ee57c9d44b19c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 14:23:33 +0000 Subject: Unshorten name. --- src/rabbit_mirror_queue_sync.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 6626ee2e..33c7bccc 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -147,7 +147,7 @@ slave(Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> {_MsgCount, BQS1} = BQ:purge(BQS), slave_sync_loop({Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS1). -slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDur}, TRef, BQS) -> +slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS) -> receive {'DOWN', MRef, process, Syncer, _Reason} -> %% If the master dies half way we are not in the usual @@ -173,7 +173,7 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDur}, TRef, BQS) -> BQS1 = BQ:set_ram_duration_target(Duration, BQS), slave_sync_loop(Args, TRef, BQS1); update_ram_duration -> - {TRef2, BQS1} = UpdateRamDur(BQ, BQS), + {TRef2, BQS1} = UpdateRamDuration(BQ, BQS), slave_sync_loop(Args, TRef2, BQS1); {sync_msg, Ref, Msg, Props} -> credit_flow:ack(Syncer, ?CREDIT_DISC_BOUND), -- cgit v1.2.1 From 048b669947b70f08f282905318be6dc78b04cfa5 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 28 Nov 2012 14:24:39 +0000 Subject: There is no TRef1. --- src/rabbit_mirror_queue_sync.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 33c7bccc..0fd50e40 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -173,8 +173,8 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS) -> BQS1 = BQ:set_ram_duration_target(Duration, BQS), slave_sync_loop(Args, TRef, BQS1); update_ram_duration -> - {TRef2, BQS1} = UpdateRamDuration(BQ, BQS), - slave_sync_loop(Args, TRef2, BQS1); + {TRef1, BQS1} = UpdateRamDuration(BQ, BQS), + slave_sync_loop(Args, TRef1, BQS1); {sync_msg, Ref, Msg, Props} -> credit_flow:ack(Syncer, ?CREDIT_DISC_BOUND), Props1 = Props#message_properties{needs_confirming = false}, -- cgit v1.2.1 From 2b12e2a85add3f01290b4984268a994940890707 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 28 Nov 2012 14:37:44 +0000 Subject: Provide early termination for backing queue fold --- src/rabbit_backing_queue.erl | 4 +-- src/rabbit_backing_queue_qc.erl | 5 ++-- src/rabbit_tests.erl | 43 ++++++++++++++++++++------------ src/rabbit_variable_queue.erl | 54 ++++++++++++++++++++++++++++------------- 4 files changed, 70 insertions(+), 36 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index ffa716b6..071962a5 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -162,8 +162,8 @@ %% Fold over all the messages in a queue and return the accumulated %% results, leaving the queue undisturbed. -callback fold(fun((rabbit_types:basic_message(), - rabbit_types:message_properties(), A) -> A), - A, state()) -> {A, state()}. + rabbit_types:message_properties(), A) -> + {('stop' | 'cont'), A}), A, state()) -> {A, state()}. %% How long is my queue? -callback len(state()) -> non_neg_integer(). diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index 764911b9..982b2479 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -332,7 +332,8 @@ postcondition(S, {call, ?BQMOD, drain_confirmed, _Args}, Res) -> postcondition(S, {call, ?BQMOD, fold, _Args}, {Res, _BQ}) -> #state{messages = Messages} = S, lists:foldl(fun ({_SeqId, {MsgProps, Msg}}, Acc) -> - foldfun(Msg, MsgProps, Acc) + {cont, Acc1} = foldfun(Msg, MsgProps, Acc), + Acc1 end, foldacc(), gb_trees:to_list(Messages)) =:= Res; postcondition(#state{bqstate = BQ, len = Len}, {call, _M, _F, _A}, _Res) -> @@ -393,7 +394,7 @@ rand_choice(List, Selection, N) -> rand_choice(List -- [Picked], [Picked | Selection], N - 1). -foldfun(Msg, _MsgProps, Acc) -> [Msg | Acc]. +foldfun(Msg, _MsgProps, Acc) -> {cont, [Msg | Acc]}. foldacc() -> []. dropfun(Props) -> diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index d5c096a1..6b45b021 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1697,6 +1697,7 @@ test_backing_queue() -> passed = test_queue_index(), passed = test_queue_index_props(), passed = test_variable_queue(), + passed = test_variable_queue_fold(), passed = test_variable_queue_delete_msg_store_files_callback(), passed = test_queue_recover(), application:set_env(rabbit, queue_index_max_journal_entries, @@ -2299,6 +2300,32 @@ wait_for_confirms(Unconfirmed) -> end end. +test_variable_queue_fold() -> + Count = rabbit_queue_index:next_segment_boundary(0), + [passed = with_fresh_variable_queue( + fun (VQ) -> test_variable_queue_fold_shortcut(VQ, Cut) end) || + Cut <- [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]], + passed. + +test_variable_queue_fold_shortcut(VQ0, Cut) -> + Count = rabbit_queue_index:next_segment_boundary(0), + Msg2Int = fun (#basic_message{ + content = #content{ payload_fragments_rev = P}}) -> + binary_to_term(list_to_binary(lists:reverse(P))) + end, + VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), + VQ2 = variable_queue_publish( + true, Count, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), + {Acc, VQ3} = rabbit_variable_queue:fold(fun (M, _, A) -> + case Msg2Int(M) =< Cut of + true -> {cont, [M | A]}; + false -> {stop, A} + end + end, [], VQ2), + true = [N || N <- lists:seq(lists:min([Cut, Count]), 1, -1)] == + [Msg2Int(M) || M <- Acc], + VQ3. + test_variable_queue() -> [passed = with_fresh_variable_queue(F) || F <- [fun test_variable_queue_dynamic_duration_change/1, @@ -2310,23 +2337,9 @@ test_variable_queue() -> fun test_dropwhile/1, fun test_dropwhile_varying_ram_duration/1, fun test_variable_queue_ack_limiting/1, - fun test_variable_queue_requeue/1, - fun test_variable_queue_fold/1]], + fun test_variable_queue_requeue/1]], passed. -test_variable_queue_fold(VQ0) -> - Count = rabbit_queue_index:next_segment_boundary(0) * 2 + 1, - VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), - VQ2 = variable_queue_publish( - true, Count, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), - {Acc, VQ3} = rabbit_variable_queue:fold( - fun (M, _, A) -> [M | A] end, [], VQ2), - true = [term_to_binary(N) || N <- lists:seq(Count, 1, -1)] == - [list_to_binary(lists:reverse(P)) || - #basic_message{ content = #content{ payload_fragments_rev = P}} <- - Acc], - VQ3. - test_variable_queue_requeue(VQ0) -> Interval = 50, Count = rabbit_queue_index:next_segment_boundary(0) + 2 * Interval, diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index b826413a..f1b72036 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -687,13 +687,15 @@ fold(Fun, Acc, #vqstate { q1 = Q1, QFun = fun(MsgStatus, {Acc0, State0}) -> {#msg_status { msg = Msg, msg_props = MsgProps }, State1 } = read_msg(MsgStatus, false, State0), - {Fun(Msg, MsgProps, Acc0), State1} + {StopGo, AccNext} = Fun(Msg, MsgProps, Acc0), + {StopGo, {AccNext, State1}} end, - {Acc1, State1} = ?QUEUE:foldl(QFun, {Acc, State}, Q4), - {Acc2, State2} = ?QUEUE:foldl(QFun, {Acc1, State1}, Q3), - {Acc3, State3} = delta_fold(Fun, Acc2, DeltaSeqId, DeltaSeqIdEnd, State2), - {Acc4, State4} = ?QUEUE:foldl(QFun, {Acc3, State3}, Q2), - {Acc5, State5} = ?QUEUE:foldl(QFun, {Acc4, State4}, Q1), + {Cont1, {Acc1, State1}} = shortcut_qfold(QFun, {cont, {Acc, State}}, Q4), + {Cont2, {Acc2, State2}} = shortcut_qfold(QFun, {Cont1, {Acc1, State1}}, Q3), + {Cont3, {Acc3, State3}} = delta_fold(Fun, {Cont2, Acc2}, + DeltaSeqId, DeltaSeqIdEnd, State2), + {Cont4, {Acc4, State4}} = shortcut_qfold(QFun, {Cont3, {Acc3, State3}}, Q2), + {_, {Acc5, State5}} = shortcut_qfold(QFun, {Cont4, {Acc4, State4}}, Q1), {Acc5, State5}. len(#vqstate { len = Len }) -> Len. @@ -1442,9 +1444,26 @@ beta_limit(Q) -> delta_limit(?BLANK_DELTA_PATTERN(_X)) -> undefined; delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. -delta_fold(_Fun, Acc, DeltaSeqIdEnd, DeltaSeqIdEnd, State) -> - {Acc, State}; -delta_fold(Fun, Acc, DeltaSeqId, DeltaSeqIdEnd, +shortcut_qfold(_Fun, {stop, _Acc} = A, _Q) -> + A; +shortcut_qfold(Fun, {cont, Acc} = A, Q) -> + case ?QUEUE:out(Q) of + {empty, _Q} -> A; + {{value, V}, Q1} -> shortcut_qfold(Fun, Fun(V, Acc), Q1) + end. + +shortcut_lfold(_Fun, {stop, _Acc} = A, _List) -> + A; +shortcut_lfold(_Fun, {cont, _Acc} = A, []) -> + A; +shortcut_lfold(Fun, {cont, Acc}, [H | Rest]) -> + shortcut_lfold(Fun, Fun(H, Acc), Rest). + +delta_fold(_Fun, {stop, Acc}, _DeltaSeqId, _DeltaSeqIdEnd, State) -> + {stop, {Acc, State}}; +delta_fold(_Fun, {cont, Acc}, DeltaSeqIdEnd, DeltaSeqIdEnd, State) -> + {cont, {Acc, State}}; +delta_fold(Fun, {cont, Acc}, DeltaSeqId, DeltaSeqIdEnd, #vqstate { index_state = IndexState, msg_store_clients = MSCState } = State) -> DeltaSeqId1 = lists:min( @@ -1452,14 +1471,15 @@ delta_fold(Fun, Acc, DeltaSeqId, DeltaSeqIdEnd, DeltaSeqIdEnd]), {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, IndexState), - {Acc1, MSCState1} = - lists:foldl(fun ({MsgId, _SeqId, MsgProps, IsPersistent, - _IsDelivered}, {Acc0, MSCState0}) -> - {{ok, Msg = #basic_message {}}, MSCState1} = - msg_store_read(MSCState0, IsPersistent, MsgId), - {Fun(Msg, MsgProps, Acc0), MSCState1} - end, {Acc, MSCState}, List), - delta_fold(Fun, Acc1, DeltaSeqId1, DeltaSeqIdEnd, + {StopCont, {Acc1, MSCState1}} = + shortcut_lfold(fun ({MsgId, _SeqId, MsgProps, IsPersistent, + _IsDelivered}, {Acc0, MSCState0}) -> + {{ok, Msg = #basic_message {}}, MSCState1} = + msg_store_read(MSCState0, IsPersistent, MsgId), + {StopCont, AccNext} = Fun(Msg, MsgProps, Acc0), + {StopCont, {AccNext, MSCState1}} + end, {cont, {Acc, MSCState}}, List), + delta_fold(Fun, {StopCont, Acc1}, DeltaSeqId1, DeltaSeqIdEnd, State #vqstate { index_state = IndexState1, msg_store_clients = MSCState1 }). -- cgit v1.2.1 From 30df1be6eb28a2a914c45c75e0e92508dd82ef14 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 28 Nov 2012 15:51:43 +0100 Subject: removes unreachable expression --- packaging/standalone/src/rabbit_release.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packaging/standalone/src/rabbit_release.erl b/packaging/standalone/src/rabbit_release.erl index 877fd73e..787ec9ec 100644 --- a/packaging/standalone/src/rabbit_release.erl +++ b/packaging/standalone/src/rabbit_release.erl @@ -75,8 +75,7 @@ start() -> systools:script2boot(RootName), %% Make release tarfile make_tar(RootName, RabbitHome), - rabbit_misc:quit(0), - ok. + rabbit_misc:quit(0). make_tar(Release, RabbitHome) -> systools:make_tar(Release, -- cgit v1.2.1 From 1fed273a420f6bd5ed543fc9c9cb18b31aca6398 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 28 Nov 2012 15:52:20 +0100 Subject: removes unnecessary comment --- packaging/standalone/src/rabbit_release.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/packaging/standalone/src/rabbit_release.erl b/packaging/standalone/src/rabbit_release.erl index 787ec9ec..c759d60d 100644 --- a/packaging/standalone/src/rabbit_release.erl +++ b/packaging/standalone/src/rabbit_release.erl @@ -22,7 +22,6 @@ -define(BaseApps, [rabbit]). -define(ERROR_CODE, 1). -%% This module is based on rabbit_prelaunch.erl from rabbitmq-server source code %% We need to calculate all the ERTS apps we need to ship with a %% standalone rabbit. To acomplish that we need to unpack and load the plugins %% apps that are shiped with rabbit. -- cgit v1.2.1 From 05c66c2565da495571cbf20553a469f69bcef015 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 28 Nov 2012 15:33:48 +0000 Subject: cosmetic --- src/rabbit_mirror_queue_master.erl | 28 +++++++++++++--------------- src/rabbit_mirror_queue_sync.erl | 5 +---- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index e8734a8c..0e8748fd 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -127,27 +127,27 @@ stop_mirroring(State = #state { coordinator = CPid, stop_all_slaves(shutdown, State), {BQ, BQS}. -sync_mirrors(State = #state{name = Name}) -> +sync_mirrors(State = #state{name = QName}) -> {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = - rabbit_amqqueue:lookup(Name), + rabbit_amqqueue:lookup(QName), sync_mirrors(SPids -- SSPids, State). -sync_mirrors([], State = #state{name = Name}) -> +sync_mirrors([], State = #state{name = QName}) -> rabbit_log:info("Synchronising ~s: nothing to do~n", - [rabbit_misc:rs(Name)]), + [rabbit_misc:rs(QName)]), State; -sync_mirrors(SPids, State = #state { name = Name, +sync_mirrors(SPids, State = #state { name = QName, gm = GM, backing_queue = BQ, backing_queue_state = BQS }) -> rabbit_log:info("Synchronising ~s with slaves ~p: ~p messages to do~n", - [rabbit_misc:rs(Name), SPids, BQ:len(BQS)]), + [rabbit_misc:rs(QName), SPids, BQ:len(BQS)]), Ref = make_ref(), Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, SPids), gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), - BQS1 = rabbit_mirror_queue_sync:master_go(Syncer, Ref, Name, BQ, BQS), + BQS1 = rabbit_mirror_queue_sync:master_go(Syncer, Ref, QName, BQ, BQS), rabbit_log:info("Synchronising ~s: complete~n", - [rabbit_misc:rs(Name)]), + [rabbit_misc:rs(QName)]), State#state{backing_queue_state = BQS1}. terminate({shutdown, dropped} = Reason, @@ -173,11 +173,9 @@ delete_and_terminate(Reason, State = #state { backing_queue = BQ, stop_all_slaves(Reason, State), State#state{backing_queue_state = BQ:delete_and_terminate(Reason, BQS)}. -stop_all_slaves(Reason, #state{name = Name, - gm = GM}) -> - {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(Name), - Slaves = [Pid || Pid <- SPids], - MRefs = [erlang:monitor(process, S) || S <- Slaves], +stop_all_slaves(Reason, #state{name = QName, gm = GM}) -> + {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(QName), + MRefs = [erlang:monitor(process, SPid) || SPid <- SPids], ok = gm:broadcast(GM, {delete_and_terminate, Reason}), [receive {'DOWN', MRef, process, _Pid, _Info} -> ok end || MRef <- MRefs], %% Normally when we remove a slave another slave or master will @@ -185,11 +183,11 @@ stop_all_slaves(Reason, #state{name = Name, %% have stopped listening ourselves. So manually clean up. rabbit_misc:execute_mnesia_transaction( fun () -> - [Q] = mnesia:read({rabbit_queue, Name}), + [Q] = mnesia:read({rabbit_queue, QName}), rabbit_mirror_queue_misc:store_updated_slaves( Q #amqqueue { gm_pids = [], slave_pids = [] }) end), - ok = gm:forget_group(Name). + ok = gm:forget_group(QName). purge(State = #state { gm = GM, backing_queue = BQ, diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 0fd50e40..b9fb6cb6 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -83,10 +83,7 @@ master_send({Syncer, Ref, Name}, I, Last, Msg, MsgProps) -> %% Syncer syncer(Ref, MPid, SPids) -> - SPidsMRefs = [begin - MRef = erlang:monitor(process, SPid), - {SPid, MRef} - end || SPid <- SPids], + SPidsMRefs = [{SPid, erlang:monitor(process, SPid)} || SPid <- SPids], %% We wait for a reply from the slaves so that we know they are in %% a receive block and will thus receive messages we send to them %% *without* those messages ending up in their gen_server2 pqueue. -- cgit v1.2.1 From d09b83297be1b7520515dbc1289a6ee25c8339e0 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 28 Nov 2012 16:06:53 +0000 Subject: cosmetic --- src/rabbit_variable_queue.erl | 44 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index f1b72036..5bcac0dc 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -690,12 +690,12 @@ fold(Fun, Acc, #vqstate { q1 = Q1, {StopGo, AccNext} = Fun(Msg, MsgProps, Acc0), {StopGo, {AccNext, State1}} end, - {Cont1, {Acc1, State1}} = shortcut_qfold(QFun, {cont, {Acc, State}}, Q4), - {Cont2, {Acc2, State2}} = shortcut_qfold(QFun, {Cont1, {Acc1, State1}}, Q3), + {Cont1, {Acc1, State1}} = qfoldl(QFun, {cont, {Acc, State }}, Q4), + {Cont2, {Acc2, State2}} = qfoldl(QFun, {Cont1, {Acc1, State1}}, Q3), {Cont3, {Acc3, State3}} = delta_fold(Fun, {Cont2, Acc2}, DeltaSeqId, DeltaSeqIdEnd, State2), - {Cont4, {Acc4, State4}} = shortcut_qfold(QFun, {Cont3, {Acc3, State3}}, Q2), - {_, {Acc5, State5}} = shortcut_qfold(QFun, {Cont4, {Acc4, State4}}, Q1), + {Cont4, {Acc4, State4}} = qfoldl(QFun, {Cont3, {Acc3, State3}}, Q2), + {_, {Acc5, State5}} = qfoldl(QFun, {Cont4, {Acc4, State4}}, Q1), {Acc5, State5}. len(#vqstate { len = Len }) -> Len. @@ -1444,26 +1444,22 @@ beta_limit(Q) -> delta_limit(?BLANK_DELTA_PATTERN(_X)) -> undefined; delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. -shortcut_qfold(_Fun, {stop, _Acc} = A, _Q) -> - A; -shortcut_qfold(Fun, {cont, Acc} = A, Q) -> +qfoldl(_Fun, {stop, _Acc} = A, _Q) -> A; +qfoldl( Fun, {cont, Acc} = A, Q) -> case ?QUEUE:out(Q) of {empty, _Q} -> A; - {{value, V}, Q1} -> shortcut_qfold(Fun, Fun(V, Acc), Q1) + {{value, V}, Q1} -> qfoldl(Fun, Fun(V, Acc), Q1) end. -shortcut_lfold(_Fun, {stop, _Acc} = A, _List) -> - A; -shortcut_lfold(_Fun, {cont, _Acc} = A, []) -> - A; -shortcut_lfold(Fun, {cont, Acc}, [H | Rest]) -> - shortcut_lfold(Fun, Fun(H, Acc), Rest). +lfoldl(_Fun, {stop, _Acc} = A, _L) -> A; +lfoldl(_Fun, {cont, _Acc} = A, []) -> A; +lfoldl( Fun, {cont, Acc}, [H | T]) -> lfoldl(Fun, Fun(H, Acc), T). -delta_fold(_Fun, {stop, Acc}, _DeltaSeqId, _DeltaSeqIdEnd, State) -> +delta_fold(_Fun, {stop, Acc}, _DeltaSeqId, _DeltaSeqIdEnd, State) -> {stop, {Acc, State}}; -delta_fold(_Fun, {cont, Acc}, DeltaSeqIdEnd, DeltaSeqIdEnd, State) -> +delta_fold(_Fun, {cont, Acc}, DeltaSeqIdEnd, DeltaSeqIdEnd, State) -> {cont, {Acc, State}}; -delta_fold(Fun, {cont, Acc}, DeltaSeqId, DeltaSeqIdEnd, +delta_fold( Fun, {cont, Acc}, DeltaSeqId, DeltaSeqIdEnd, #vqstate { index_state = IndexState, msg_store_clients = MSCState } = State) -> DeltaSeqId1 = lists:min( @@ -1472,13 +1468,13 @@ delta_fold(Fun, {cont, Acc}, DeltaSeqId, DeltaSeqIdEnd, {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, IndexState), {StopCont, {Acc1, MSCState1}} = - shortcut_lfold(fun ({MsgId, _SeqId, MsgProps, IsPersistent, - _IsDelivered}, {Acc0, MSCState0}) -> - {{ok, Msg = #basic_message {}}, MSCState1} = - msg_store_read(MSCState0, IsPersistent, MsgId), - {StopCont, AccNext} = Fun(Msg, MsgProps, Acc0), - {StopCont, {AccNext, MSCState1}} - end, {cont, {Acc, MSCState}}, List), + lfoldl(fun ({MsgId, _SeqId, MsgProps, IsPersistent, _IsDelivered}, + {Acc0, MSCState0}) -> + {{ok, Msg = #basic_message {}}, MSCState1} = + msg_store_read(MSCState0, IsPersistent, MsgId), + {StopCont, AccNext} = Fun(Msg, MsgProps, Acc0), + {StopCont, {AccNext, MSCState1}} + end, {cont, {Acc, MSCState}}, List), delta_fold(Fun, {StopCont, Acc1}, DeltaSeqId1, DeltaSeqIdEnd, State #vqstate { index_state = IndexState1, msg_store_clients = MSCState1 }). -- cgit v1.2.1 From f28de3bf301671f9a383273b92a487f91eba6da1 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 28 Nov 2012 16:37:06 +0000 Subject: Better quickcheck test of interruptable backing queue fold --- src/rabbit_backing_queue_qc.erl | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index 982b2479..61ec02fb 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -159,7 +159,7 @@ qc_purge(#state{bqstate = BQ}) -> {call, ?BQMOD, purge, [BQ]}. qc_fold(#state{bqstate = BQ}) -> - {call, ?BQMOD, fold, [fun foldfun/3, foldacc(), BQ]}. + {call, ?BQMOD, fold, [makefoldfun(pos_integer()), foldacc(), BQ]}. %% Preconditions @@ -329,12 +329,14 @@ postcondition(S, {call, ?BQMOD, drain_confirmed, _Args}, Res) -> lists:all(fun (M) -> gb_sets:is_element(M, Confirms) end, ReportedConfirmed); -postcondition(S, {call, ?BQMOD, fold, _Args}, {Res, _BQ}) -> +postcondition(S, {call, ?BQMOD, fold, [FoldFun, Acc0, _BQ0]}, {Res, _BQ1}) -> #state{messages = Messages} = S, - lists:foldl(fun ({_SeqId, {MsgProps, Msg}}, Acc) -> - {cont, Acc1} = foldfun(Msg, MsgProps, Acc), - Acc1 - end, foldacc(), gb_trees:to_list(Messages)) =:= Res; + {_, Model} = lists:foldl(fun ({_SeqId, {_MsgProps, _Msg}}, {stop, Acc}) -> + {stop, Acc}; + ({_SeqId, {MsgProps, Msg}}, {cont, Acc}) -> + FoldFun(Msg, MsgProps, Acc) + end, {cont, Acc0}, gb_trees:to_list(Messages)), + true = Model =:= Res; postcondition(#state{bqstate = BQ, len = Len}, {call, _M, _F, _A}, _Res) -> ?BQMOD:len(BQ) =:= Len. @@ -394,7 +396,13 @@ rand_choice(List, Selection, N) -> rand_choice(List -- [Picked], [Picked | Selection], N - 1). -foldfun(Msg, _MsgProps, Acc) -> {cont, [Msg | Acc]}. +makefoldfun(Size) -> + fun (Msg, _MsgProps, Acc) -> + case length(Acc) > Size of + false -> {cont, [Msg | Acc]}; + true -> {stop, Acc} + end + end. foldacc() -> []. dropfun(Props) -> -- cgit v1.2.1 From aabcfbb00fb7b914bde0b28dabbde81b4e7e233e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 28 Nov 2012 16:43:47 +0000 Subject: cosmetic --- src/rabbit_backing_queue.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 071962a5..6315a56a 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -162,8 +162,9 @@ %% Fold over all the messages in a queue and return the accumulated %% results, leaving the queue undisturbed. -callback fold(fun((rabbit_types:basic_message(), - rabbit_types:message_properties(), A) -> - {('stop' | 'cont'), A}), A, state()) -> {A, state()}. + rabbit_types:message_properties(), + A) -> {('stop' | 'cont'), A}), + A, state()) -> {A, state()}. %% How long is my queue? -callback len(state()) -> non_neg_integer(). -- cgit v1.2.1 From 853e52016edf7bc637269554ebdd2bcb92e7947f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 28 Nov 2012 17:01:30 +0000 Subject: tweak --- src/rabbit_tests.erl | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 6b45b021..3f32b003 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1697,7 +1697,6 @@ test_backing_queue() -> passed = test_queue_index(), passed = test_queue_index_props(), passed = test_variable_queue(), - passed = test_variable_queue_fold(), passed = test_variable_queue_delete_msg_store_files_callback(), passed = test_queue_recover(), application:set_env(rabbit, queue_index_max_journal_entries, @@ -2300,15 +2299,25 @@ wait_for_confirms(Unconfirmed) -> end end. -test_variable_queue_fold() -> +test_variable_queue() -> + [passed = with_fresh_variable_queue(F) || + F <- [fun test_variable_queue_dynamic_duration_change/1, + fun test_variable_queue_partial_segments_delta_thing/1, + fun test_variable_queue_all_the_bits_not_covered_elsewhere1/1, + fun test_variable_queue_all_the_bits_not_covered_elsewhere2/1, + fun test_drop/1, + fun test_variable_queue_fold_msg_on_disk/1, + fun test_dropwhile/1, + fun test_dropwhile_varying_ram_duration/1, + fun test_variable_queue_ack_limiting/1, + fun test_variable_queue_requeue/1]], Count = rabbit_queue_index:next_segment_boundary(0), [passed = with_fresh_variable_queue( - fun (VQ) -> test_variable_queue_fold_shortcut(VQ, Cut) end) || + fun (VQ) -> test_variable_queue_fold(Cut, Count, VQ) end) || Cut <- [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]], passed. -test_variable_queue_fold_shortcut(VQ0, Cut) -> - Count = rabbit_queue_index:next_segment_boundary(0), +test_variable_queue_fold(Cut, Count, VQ0) -> Msg2Int = fun (#basic_message{ content = #content{ payload_fragments_rev = P}}) -> binary_to_term(list_to_binary(lists:reverse(P))) @@ -2326,20 +2335,6 @@ test_variable_queue_fold_shortcut(VQ0, Cut) -> [Msg2Int(M) || M <- Acc], VQ3. -test_variable_queue() -> - [passed = with_fresh_variable_queue(F) || - F <- [fun test_variable_queue_dynamic_duration_change/1, - fun test_variable_queue_partial_segments_delta_thing/1, - fun test_variable_queue_all_the_bits_not_covered_elsewhere1/1, - fun test_variable_queue_all_the_bits_not_covered_elsewhere2/1, - fun test_drop/1, - fun test_variable_queue_fold_msg_on_disk/1, - fun test_dropwhile/1, - fun test_dropwhile_varying_ram_duration/1, - fun test_variable_queue_ack_limiting/1, - fun test_variable_queue_requeue/1]], - passed. - test_variable_queue_requeue(VQ0) -> Interval = 50, Count = rabbit_queue_index:next_segment_boundary(0) + 2 * Interval, -- cgit v1.2.1 From 68d625779bde08a5e1f5100f312c3d0f8d42a043 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 28 Nov 2012 18:09:27 +0100 Subject: refactors code to extract/load plugin code --- packaging/standalone/src/rabbit_release.erl | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packaging/standalone/src/rabbit_release.erl b/packaging/standalone/src/rabbit_release.erl index c759d60d..03b18133 100644 --- a/packaging/standalone/src/rabbit_release.erl +++ b/packaging/standalone/src/rabbit_release.erl @@ -34,19 +34,18 @@ start() -> init:get_plain_arguments(), RootName = UnpackedPluginDir ++ "/rabbit", + %% extract the plugins so we can load their apps later prepare_plugins(PluginsDistDir, UnpackedPluginDir), PluginAppNames = [P#plugin.name || P <- rabbit_plugins:list(PluginsDistDir)], - %% we need to call find_plugins because it has the secondary effect of - %% adding the plugin ebin folder to the code path. - %% We need that in order to load the plugin app - RequiredApps = find_plugins(UnpackedPluginDir), + %% add the plugin ebin folder to the code path. + add_plugins_to_path(UnpackedPluginDir), %% Build the entire set of dependencies - this will load the %% applications along the way - AllApps = case catch sets:to_list(expand_dependencies(RequiredApps)) of + AllApps = case catch sets:to_list(expand_dependencies(PluginAppNames)) of {failed_to_load_app, App, Err} -> terminate("failed to load application ~s:~n~p", [App, Err]); @@ -139,19 +138,14 @@ expand_dependencies(Current, [Next|Rest]) -> expand_dependencies(sets:add_element(Next, Current), Rest ++ Unique) end. -find_plugins(PluginDir) -> - [prepare_dir_plugin(PluginName) || +add_plugins_to_path(PluginDir) -> + [add_plugin_to_path(PluginName) || PluginName <- filelib:wildcard(PluginDir ++ "/*/ebin/*.app")]. -prepare_dir_plugin(PluginAppDescFn) -> +add_plugin_to_path(PluginAppDescFn) -> %% Add the plugin ebin directory to the load path PluginEBinDirN = filename:dirname(PluginAppDescFn), - code:add_path(PluginEBinDirN), - - %% We want the second-last token - NameTokens = string:tokens(PluginAppDescFn,"/."), - PluginNameString = lists:nth(length(NameTokens) - 1, NameTokens), - list_to_atom(PluginNameString). + code:add_path(PluginEBinDirN). terminate(Fmt, Args) -> io:format("ERROR: " ++ Fmt ++ "~n", Args), -- cgit v1.2.1 From ffac65f860a497ad7eed12763e3eb79f2a3f1ff6 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 28 Nov 2012 18:30:34 +0100 Subject: removes empty folder from the release --- packaging/standalone/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index cd85ffca..2730eb66 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -38,6 +38,9 @@ dist: # move rabbitmq files to top level folder mv $(RLS_DIR)/lib/rabbit-$(VERSION)/* $(RLS_DIR) +# remove empty lib/rabbit-$(VERSION) folder + rm -rf $(RLS_DIR)/lib/rabbit-$(VERSION) + tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) rm -rf $(SOURCE_DIR) $(TARGET_DIR) -- cgit v1.2.1 From 262927855c8fefd66527a373eb158598efd35766 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 28 Nov 2012 18:33:21 +0100 Subject: cosmetics --- packaging/standalone/src/rabbit_release.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/standalone/src/rabbit_release.erl b/packaging/standalone/src/rabbit_release.erl index 03b18133..26f36d68 100644 --- a/packaging/standalone/src/rabbit_release.erl +++ b/packaging/standalone/src/rabbit_release.erl @@ -37,12 +37,12 @@ start() -> %% extract the plugins so we can load their apps later prepare_plugins(PluginsDistDir, UnpackedPluginDir), - PluginAppNames = [P#plugin.name || - P <- rabbit_plugins:list(PluginsDistDir)], - %% add the plugin ebin folder to the code path. add_plugins_to_path(UnpackedPluginDir), + PluginAppNames = [P#plugin.name || + P <- rabbit_plugins:list(PluginsDistDir)], + %% Build the entire set of dependencies - this will load the %% applications along the way AllApps = case catch sets:to_list(expand_dependencies(PluginAppNames)) of -- cgit v1.2.1 From 8d88d5f511c31f8957038d3fb2b247472cb677c1 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 28 Nov 2012 18:46:19 +0100 Subject: renames package to include "mac" in it --- packaging/standalone/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 2730eb66..65c72c4d 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -1,7 +1,7 @@ VERSION=0.0.0 SOURCE_DIR=rabbitmq-server-$(VERSION) TARGET_DIR=rabbitmq_server-$(VERSION) -TARGET_TARBALL=rabbitmq-server-standalone-$(VERSION) +TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) RLS_DIR=$(TARGET_DIR)/release/$(TARGET_DIR) ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') @@ -45,7 +45,7 @@ dist: rm -rf $(SOURCE_DIR) $(TARGET_DIR) clean: clean_partial - rm -f rabbitmq-server-standalone-*.tar.gz + rm -f rabbitmq-server-mac-standalone-*.tar.gz clean_partial: rm -rf $(SOURCE_DIR) -- cgit v1.2.1 From 270140dce3540332bbeb8dc52921cc081caa5755 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 28 Nov 2012 19:40:01 +0000 Subject: expand test coverage to at least two iterations of delta_fold previously it had no test coverage at all Also, re-use the same vq for all fold tests, for efficiency. --- src/rabbit_tests.erl | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 3f32b003..91d620a7 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2310,30 +2310,33 @@ test_variable_queue() -> fun test_dropwhile/1, fun test_dropwhile_varying_ram_duration/1, fun test_variable_queue_ack_limiting/1, - fun test_variable_queue_requeue/1]], - Count = rabbit_queue_index:next_segment_boundary(0), - [passed = with_fresh_variable_queue( - fun (VQ) -> test_variable_queue_fold(Cut, Count, VQ) end) || - Cut <- [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]], + fun test_variable_queue_requeue/1, + fun test_variable_queue_fold/1]], passed. -test_variable_queue_fold(Cut, Count, VQ0) -> - Msg2Int = fun (#basic_message{ - content = #content{ payload_fragments_rev = P}}) -> - binary_to_term(list_to_binary(lists:reverse(P))) - end, +test_variable_queue_fold(VQ0) -> + Count = rabbit_queue_index:next_segment_boundary(0) * 2 + 64, VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), VQ2 = variable_queue_publish( true, Count, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), - {Acc, VQ3} = rabbit_variable_queue:fold(fun (M, _, A) -> - case Msg2Int(M) =< Cut of - true -> {cont, [M | A]}; - false -> {stop, A} - end - end, [], VQ2), + lists:foldl( + fun (Cut, VQ3) -> test_variable_queue_fold(Cut, Count, VQ3) end, + VQ2, [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]). + +test_variable_queue_fold(Cut, Count, VQ0) -> + {Acc, VQ1} = rabbit_variable_queue:fold( + fun (M, _, A) -> + case msg2int(M) =< Cut of + true -> {cont, [M | A]}; + false -> {stop, A} + end + end, [], VQ0), true = [N || N <- lists:seq(lists:min([Cut, Count]), 1, -1)] == - [Msg2Int(M) || M <- Acc], - VQ3. + [msg2int(M) || M <- Acc], + VQ1. + +msg2int(#basic_message{content = #content{ payload_fragments_rev = P}}) -> + binary_to_term(list_to_binary(lists:reverse(P))). test_variable_queue_requeue(VQ0) -> Interval = 50, -- cgit v1.2.1 From 7d9da3ee2ce62a2b684c40e416c8df39ccd27895 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 28 Nov 2012 23:49:58 +0100 Subject: moves sed invocation to bash script --- packaging/standalone/Makefile | 18 +----------------- packaging/standalone/fix-rabbitmq-defaults.sh | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100755 packaging/standalone/fix-rabbitmq-defaults.sh diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 65c72c4d..fced396f 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -22,7 +22,7 @@ dist: MAN_DIR=`pwd`/$(TARGET_DIR)/share/man \ install - $(MAKE) fix_rabbitmq_defaults + ./fix-rabbitmq-defaults.sh $(TARGET_DIR) $(ERTS_VSN) $(VERSION) mkdir -p $(TARGET_DIR)/etc/rabbitmq @@ -62,19 +62,3 @@ generate_release: -s rabbit_release \ -extra "$(RABBITMQ_PLUGINS_DIR)" "$(RABBITMQ_PLUGINS_EXPAND_DIR)" "$(RABBITMQ_HOME)" rm src/rabbit_release.beam - -## Here we set the RABBITMQ_HOME variable, -## then we make ERL_DIR point to our released erl -## and we add the paths to our released start_clean and start_sasl boot scripts -.PHONY : fix_rabbitmq_defaults -fix_rabbitmq_defaults: - sed -e 's:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 \ - && sed -e 's:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp1 >$(TARGET_DIR)/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":' \ - $(TARGET_DIR)/sbin/rabbitmq-defaults.tmp >$(TARGET_DIR)/sbin/rabbitmq-defaults - - chmod 0755 $(TARGET_DIR)/sbin/rabbitmq-defaults diff --git a/packaging/standalone/fix-rabbitmq-defaults.sh b/packaging/standalone/fix-rabbitmq-defaults.sh new file mode 100755 index 00000000..021c47bc --- /dev/null +++ b/packaging/standalone/fix-rabbitmq-defaults.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +TARGET_DIR=$1 +ERTS_VSN=$2 +VERSION=$3 + +## Here we set the RABBITMQ_HOME variable, +## then we make ERL_DIR point to our released erl +## and we add the paths to our released start_clean and start_sasl boot scripts + +sed -e 's:^SYS_PREFIX=$:SYS_PREFIX=\${RABBITMQ_HOME}:' \ + "${TARGET_DIR}"/sbin/rabbitmq-defaults >"${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:^ERL_DIR=$:ERL_DIR=\${RABBITMQ_HOME}/erts-'"${ERTS_VSN}"'/bin/:' \ + "${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp >"${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp1 \ + && sed -e 's:start_clean$:"\${SYS_PREFIX}/releases/'"${VERSION}"'/start_clean":' \ + "${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp1 >"${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp \ + && sed -e 's:start_sasl:"\${SYS_PREFIX}/releases/'"${VERSION}"'/start_sasl":' \ + "${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp >"${TARGET_DIR}"/sbin/rabbitmq-defaults + +chmod 0755 "${TARGET_DIR}"/sbin/rabbitmq-defaults -- cgit v1.2.1 From 3ba3ce551da8594a7d9fcb1895c37b53dc1ebc19 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 11:22:07 +0000 Subject: Oops, didn't even compile that last one. --- src/rabbit_amqqueue_process.erl | 9 ++++----- src/rabbit_mirror_queue_master.erl | 13 ++++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c932249e..acbea4e9 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1156,13 +1156,12 @@ handle_call({requeue, AckTags, ChPid}, From, State) -> handle_call(sync_mirrors, From, State = #q{backing_queue = rabbit_mirror_queue_master = BQ, backing_queue_state = BQS}) -> - S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, + S = fun(BQSN) -> State#q{backing_queue_state = BQSN} end, case BQ:depth(BQS) - BQ:len(BQS) of 0 -> gen_server2:reply(From, ok), - case rabbit_mirror_queue_master:sync_mirrors(BQS) of - {shutdown, Reason, BQS1} -> {stop, Reason, S(BQS1)}; - {ok, BQS1} -> noreply(S(BQS1)) - end + case rabbit_mirror_queue_master:sync_mirrors(BQS) of + {shutdown, Reason, BQS1} -> {stop, Reason, S(BQS1)}; + {ok, BQS1} -> noreply(S(BQS1)) end; _ -> reply({error, pending_acks}, State) end; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 0e8748fd..439d1f4b 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -135,7 +135,7 @@ sync_mirrors(State = #state{name = QName}) -> sync_mirrors([], State = #state{name = QName}) -> rabbit_log:info("Synchronising ~s: nothing to do~n", [rabbit_misc:rs(QName)]), - State; + {ok, State}; sync_mirrors(SPids, State = #state { name = QName, gm = GM, backing_queue = BQ, @@ -145,10 +145,13 @@ sync_mirrors(SPids, State = #state { name = QName, Ref = make_ref(), Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, SPids), gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), - BQS1 = rabbit_mirror_queue_sync:master_go(Syncer, Ref, QName, BQ, BQS), - rabbit_log:info("Synchronising ~s: complete~n", - [rabbit_misc:rs(QName)]), - State#state{backing_queue_state = BQS1}. + S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, + case rabbit_mirror_queue_sync:master_go(Syncer, Ref, QName, BQ, BQS) of + {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; + {ok, BQS1} -> rabbit_log:info("Synchronising ~s: complete~n", + [rabbit_misc:rs(QName)]), + {ok, S(BQS1)} + end. terminate({shutdown, dropped} = Reason, State = #state { backing_queue = BQ, -- cgit v1.2.1 From a1b7f66d6cce9b53356ec0092925dfcc7cc8413d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 11:24:47 +0000 Subject: Reverse master/syncer communication. --- src/rabbit_mirror_queue_sync.erl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index b6d93c0d..acf3e3e3 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -40,8 +40,8 @@ %% (from channel) || -- (spawns) --> || || %% || --------- sync_start (over GM) -------> || %% || || <--- sync_ready ---- || -%% || ----- msg* ---> || || } -%% || <-- msg_ok* --- || || } loop +%% || <--- next* ---- || || } +%% || ---- msg* ----> || || } loop %% || || ----- sync_msg* ---> || } %% || || <---- (credit)* ---- || } %% || ---- done ----> || || @@ -62,13 +62,15 @@ master_go(Syncer, Ref, Name, BQ, BQS) -> master_send(SendArgs, I, Last, Msg, MsgProps) end, {0, erlang:now()}, BQS), Syncer ! {done, Ref}, + receive + {next, Ref} -> ok + end, case Acc of {shutdown, Reason} -> {shutdown, Reason, BQS1}; _ -> {ok, BQS1} end. master_send({Syncer, Ref, Name}, I, Last, Msg, MsgProps) -> - Syncer ! {msg, Ref, Msg, MsgProps}, Acc = {I + 1, case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of true -> rabbit_log:info("Synchronising ~s: ~p messages~n", @@ -77,7 +79,8 @@ master_send({Syncer, Ref, Name}, I, Last, Msg, MsgProps) -> false -> Last end}, receive - {msg_ok, Ref} -> {cont, Acc}; + {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, + {cont, Acc}; {'EXIT', _Pid, Reason} -> {stop, {shutdown, Reason}} end. @@ -96,9 +99,9 @@ syncer(Ref, MPid, SPids) -> unlink(MPid). syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> + MPid ! {next, Ref}, receive {msg, Ref, Msg, MsgProps} -> - MPid ! {msg_ok, Ref}, SPidsMRefs1 = wait_for_credit(SPidsMRefs, Ref), [begin credit_flow:send(SPid, ?CREDIT_DISC_BOUND), -- cgit v1.2.1 From 9102940de6158c4829156322318aaf33c40c56cb Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 11:49:28 +0000 Subject: Have slaves determine whether they need sync. --- src/rabbit_mirror_queue_master.erl | 26 ++++++++-------------- src/rabbit_mirror_queue_slave.erl | 14 ++++++------ src/rabbit_mirror_queue_sync.erl | 44 +++++++++++++++++++++++++------------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 439d1f4b..0820f3f9 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -127,24 +127,16 @@ stop_mirroring(State = #state { coordinator = CPid, stop_all_slaves(shutdown, State), {BQ, BQS}. -sync_mirrors(State = #state{name = QName}) -> - {ok, #amqqueue{slave_pids = SPids, sync_slave_pids = SSPids}} = - rabbit_amqqueue:lookup(QName), - sync_mirrors(SPids -- SSPids, State). - -sync_mirrors([], State = #state{name = QName}) -> - rabbit_log:info("Synchronising ~s: nothing to do~n", - [rabbit_misc:rs(QName)]), - {ok, State}; -sync_mirrors(SPids, State = #state { name = QName, - gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> - rabbit_log:info("Synchronising ~s with slaves ~p: ~p messages to do~n", - [rabbit_misc:rs(QName), SPids, BQ:len(BQS)]), +sync_mirrors(State = #state { name = QName, + gm = GM, + backing_queue = BQ, + backing_queue_state = BQS }) -> + rabbit_log:info("Synchronising ~s: ~p messages to synchronise~n", + [rabbit_misc:rs(QName), BQ:len(BQS)]), + {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(QName), Ref = make_ref(), - Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, SPids), - gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), + Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, QName, SPids), + gm:broadcast(GM, {sync_start, Ref, Syncer}), S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, case rabbit_mirror_queue_sync:master_go(Syncer, Ref, QName, BQ, BQS) of {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 11490b9c..2b216a5f 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -222,8 +222,9 @@ handle_cast({deliver, Delivery = #delivery{sender = Sender}, true, Flow}, end, noreply(maybe_enqueue_message(Delivery, State)); -handle_cast({sync_start, Ref, MPid}, - State = #state { backing_queue = BQ, +handle_cast({sync_start, Ref, Syncer}, + State = #state { depth_delta = DD, + backing_queue = BQ, backing_queue_state = BQS }) -> State1 = #state{rate_timer_ref = TRef} = ensure_rate_timer(State), S = fun({TRefN, BQSN}) -> State1#state{rate_timer_ref = TRefN, @@ -232,7 +233,7 @@ handle_cast({sync_start, Ref, MPid}, %% [1] The master died so we do not need to set_delta even though %% we purged since we will get a depth instruction soon. case rabbit_mirror_queue_sync:slave( - Ref, TRef, MPid, BQ, BQS, + DD, Ref, TRef, Syncer, BQ, BQS, fun (BQN, BQSN) -> BQSN1 = update_ram_duration(BQN, BQSN), TRefN = erlang:send_after(?RAM_DURATION_UPDATE_INTERVAL, @@ -374,11 +375,8 @@ handle_msg([_SPid], _From, process_death) -> handle_msg([CPid], _From, {delete_and_terminate, _Reason} = Msg) -> ok = gen_server2:cast(CPid, {gm, Msg}), {stop, {shutdown, ring_shutdown}}; -handle_msg([SPid], _From, {sync_start, Ref, MPid, SPids}) -> - case lists:member(SPid, SPids) of - true -> ok = gen_server2:cast(SPid, {sync_start, Ref, MPid}); - false -> ok - end; +handle_msg([SPid], _From, {sync_start, Ref, Syncer}) -> + gen_server2:cast(SPid, {sync_start, Ref, Syncer}); handle_msg([SPid], _From, Msg) -> ok = gen_server2:cast(SPid, {gm, Msg}). diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index acf3e3e3..9ff853d5 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -18,7 +18,7 @@ -include("rabbit.hrl"). --export([master_prepare/2, master_go/5, slave/6]). +-export([master_prepare/3, master_go/5, slave/7]). -define(SYNC_PROGRESS_INTERVAL, 1000000). @@ -51,12 +51,12 @@ %% --------------------------------------------------------------------------- %% Master -master_prepare(Ref, SPids) -> +master_prepare(Ref, QName, SPids) -> MPid = self(), - spawn_link(fun () -> syncer(Ref, MPid, SPids) end). + spawn_link(fun () -> syncer(Ref, QName, MPid, SPids) end). -master_go(Syncer, Ref, Name, BQ, BQS) -> - SendArgs = {Syncer, Ref, Name}, +master_go(Syncer, Ref, QName, BQ, BQS) -> + SendArgs = {Syncer, Ref, QName}, {Acc, BQS1} = BQ:fold(fun (Msg, MsgProps, {I, Last}) -> master_send(SendArgs, I, Last, Msg, MsgProps) @@ -70,11 +70,11 @@ master_go(Syncer, Ref, Name, BQ, BQS) -> _ -> {ok, BQS1} end. -master_send({Syncer, Ref, Name}, I, Last, Msg, MsgProps) -> +master_send({Syncer, Ref, QName}, I, Last, Msg, MsgProps) -> Acc = {I + 1, case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of true -> rabbit_log:info("Synchronising ~s: ~p messages~n", - [rabbit_misc:rs(Name), I]), + [rabbit_misc:rs(QName), I]), erlang:now(); false -> Last end}, @@ -88,14 +88,23 @@ master_send({Syncer, Ref, Name}, I, Last, Msg, MsgProps) -> %% --------------------------------------------------------------------------- %% Syncer -syncer(Ref, MPid, SPids) -> +syncer(Ref, QName, MPid, SPids) -> SPidsMRefs = [{SPid, erlang:monitor(process, SPid)} || SPid <- SPids], %% We wait for a reply from the slaves so that we know they are in %% a receive block and will thus receive messages we send to them %% *without* those messages ending up in their gen_server2 pqueue. - SPidsMRefs1 = foreach_slave(SPidsMRefs, Ref, fun sync_receive_ready/3), - SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), - foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3), + case foreach_slave(SPidsMRefs, Ref, fun sync_receive_ready/3) of + [] -> + rabbit_log:info("Synchronising ~s: all slaves already synced~n", + [rabbit_misc:rs(QName)]); + SPidsMRefs1 -> + rabbit_log:info("Synchronising ~s: ~p require sync~n", + [rabbit_misc:rs(QName), + [rabbit_misc:pid_to_string(S) || + {S, _} <- SPidsMRefs1]]), + SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), + foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3) + end, unlink(MPid). syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> @@ -121,12 +130,13 @@ wait_for_credit(SPidsMRefs, Ref) -> foreach_slave(SPidsMRefs, Ref, Fun) -> [{SPid, MRef} || {SPid, MRef} <- SPidsMRefs, - Fun(SPid, MRef, Ref) =/= dead]. + Fun(SPid, MRef, Ref) =/= ignore]. sync_receive_ready(SPid, MRef, Ref) -> receive {sync_ready, Ref, SPid} -> SPid; - {'DOWN', MRef, _, SPid, _} -> dead + {sync_deny, Ref, SPid} -> ignore; + {'DOWN', MRef, _, SPid, _} -> ignore end. sync_receive_credit(SPid, MRef, _Ref) -> @@ -134,7 +144,7 @@ sync_receive_credit(SPid, MRef, _Ref) -> {bump_credit, {SPid, _} = Msg} -> credit_flow:handle_bump_msg(Msg), SPid; {'DOWN', MRef, _, SPid, _} -> credit_flow:peer_down(SPid), - dead + ignore end. sync_send_complete(SPid, _MRef, Ref) -> @@ -144,7 +154,11 @@ sync_send_complete(SPid, _MRef, Ref) -> %% --------------------------------------------------------------------------- %% Slave -slave(Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> +slave(0, Ref, TRef, Syncer, _BQ, BQS, _UpdateRamDuration) -> + Syncer ! {sync_deny, Ref, self()}, + {ok, {TRef, BQS}}; + +slave(_DD, Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> MRef = erlang:monitor(process, Syncer), Syncer ! {sync_ready, Ref, self()}, {_MsgCount, BQS1} = BQ:purge(BQS), -- cgit v1.2.1 From 4debb61fb625758cb850d2cf27af93e2cd00b0ea Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 12:05:23 +0000 Subject: Only respond to 'EXIT's from parent / syncer. --- src/rabbit_mirror_queue_master.erl | 11 +++++---- src/rabbit_mirror_queue_sync.erl | 16 ++++++++----- src/rabbit_misc.erl | 46 +++++++++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 0820f3f9..2f9f4c02 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -139,10 +139,13 @@ sync_mirrors(State = #state { name = QName, gm:broadcast(GM, {sync_start, Ref, Syncer}), S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, case rabbit_mirror_queue_sync:master_go(Syncer, Ref, QName, BQ, BQS) of - {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; - {ok, BQS1} -> rabbit_log:info("Synchronising ~s: complete~n", - [rabbit_misc:rs(QName)]), - {ok, S(BQS1)} + {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; + {sync_died, R, BQS1} -> rabbit_log:info("Synchronising ~s: ~p~n", + [rabbit_misc:rs(QName), R]), + {ok, S(BQS1)}; + {ok, BQS1} -> rabbit_log:info("Synchronising ~s: complete~n", + [rabbit_misc:rs(QName)]), + {ok, S(BQS1)} end. terminate({shutdown, dropped} = Reason, diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 9ff853d5..94f5ae0f 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -66,8 +66,9 @@ master_go(Syncer, Ref, QName, BQ, BQS) -> {next, Ref} -> ok end, case Acc of - {shutdown, Reason} -> {shutdown, Reason, BQS1}; - _ -> {ok, BQS1} + {shutdown, Reason} -> {shutdown, Reason, BQS1}; + {sync_died, Reason} -> {sync_died, Reason, BQS1}; + _ -> {ok, BQS1} end. master_send({Syncer, Ref, QName}, I, Last, Msg, MsgProps) -> @@ -78,10 +79,12 @@ master_send({Syncer, Ref, QName}, I, Last, Msg, MsgProps) -> erlang:now(); false -> Last end}, + Parent = rabbit_misc:get_parent(), receive - {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, - {cont, Acc}; - {'EXIT', _Pid, Reason} -> {stop, {shutdown, Reason}} + {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, + {cont, Acc}; + {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}}; + {'EXIT', Parent, Reason} -> {stop, {shutdown, Reason}} end. %% Master @@ -165,6 +168,7 @@ slave(_DD, Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> slave_sync_loop({Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS1). slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS) -> + Parent = rabbit_misc:get_parent(), receive {'DOWN', MRef, process, Syncer, _Reason} -> %% If the master dies half way we are not in the usual @@ -197,6 +201,6 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS) -> Props1 = Props#message_properties{needs_confirming = false}, BQS1 = BQ:publish(Msg, Props1, true, none, BQS), slave_sync_loop(Args, TRef, BQS1); - {'EXIT', _Pid, Reason} -> + {'EXIT', Parent, Reason} -> {stop, Reason, {TRef, BQS}} end. diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 81bb6769..cd83e3b8 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -66,6 +66,7 @@ -export([check_expiry/1]). -export([base64url/1]). -export([interval_operation/4]). +-export([get_parent/0]). %% Horrible macro to use in guards -define(IS_BENIGN_EXIT(R), @@ -239,7 +240,7 @@ -spec(interval_operation/4 :: ({atom(), atom(), any()}, float(), non_neg_integer(), non_neg_integer()) -> {any(), non_neg_integer()}). - +-spec(get_parent/0 :: () -> pid()). -endif. %%---------------------------------------------------------------------------- @@ -1034,3 +1035,46 @@ interval_operation({M, F, A}, MaxRatio, IdealInterval, LastInterval) -> {false, false} -> lists:max([IdealInterval, round(LastInterval / 1.5)]) end}. + +%% ------------------------------------------------------------------------- +%% Begin copypasta from gen_server.erl + +get_parent() -> + case get('$ancestors') of + [Parent | _] when is_pid(Parent)-> + Parent; + [Parent | _] when is_atom(Parent)-> + name_to_pid(Parent); + _ -> + exit(process_was_not_started_by_proc_lib) + end. + +name_to_pid(Name) -> + case whereis(Name) of + undefined -> + case whereis_name(Name) of + undefined -> + exit(could_not_find_registerd_name); + Pid -> + Pid + end; + Pid -> + Pid + end. + +whereis_name(Name) -> + case ets:lookup(global_names, Name) of + [{_Name, Pid, _Method, _RPid, _Ref}] -> + if node(Pid) == node() -> + case erlang:is_process_alive(Pid) of + true -> Pid; + false -> undefined + end; + true -> + Pid + end; + [] -> undefined + end. + +%% End copypasta from gen_server.erl +%% ------------------------------------------------------------------------- -- cgit v1.2.1 From e6768457d3788fc3e6576a0468c98a3733afefbb Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 12:09:57 +0000 Subject: Respond to FHC. --- src/rabbit_mirror_queue_sync.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 94f5ae0f..0b9bd42a 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -80,6 +80,12 @@ master_send({Syncer, Ref, QName}, I, Last, Msg, MsgProps) -> false -> Last end}, Parent = rabbit_misc:get_parent(), + receive + {'$gen_cast', {set_maximum_since_use, Age}} -> + ok = file_handle_cache:set_maximum_since_use(Age) + after 0 -> + ok + end, receive {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, {cont, Acc}; @@ -101,7 +107,7 @@ syncer(Ref, QName, MPid, SPids) -> rabbit_log:info("Synchronising ~s: all slaves already synced~n", [rabbit_misc:rs(QName)]); SPidsMRefs1 -> - rabbit_log:info("Synchronising ~s: ~p require sync~n", + rabbit_log:info("Synchronising ~s: ~p to sync~n", [rabbit_misc:rs(QName), [rabbit_misc:pid_to_string(S) || {S, _} <- SPidsMRefs1]]), -- cgit v1.2.1 From 0675c23b66b5f8804cee5d20ce586aa7082471f3 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 12:14:13 +0000 Subject: Call get_parent/0 less often... --- src/rabbit_mirror_queue_sync.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 0b9bd42a..29f6af2c 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -56,7 +56,7 @@ master_prepare(Ref, QName, SPids) -> spawn_link(fun () -> syncer(Ref, QName, MPid, SPids) end). master_go(Syncer, Ref, QName, BQ, BQS) -> - SendArgs = {Syncer, Ref, QName}, + SendArgs = {Syncer, Ref, QName, rabbit_misc:get_parent()}, {Acc, BQS1} = BQ:fold(fun (Msg, MsgProps, {I, Last}) -> master_send(SendArgs, I, Last, Msg, MsgProps) @@ -71,7 +71,7 @@ master_go(Syncer, Ref, QName, BQ, BQS) -> _ -> {ok, BQS1} end. -master_send({Syncer, Ref, QName}, I, Last, Msg, MsgProps) -> +master_send({Syncer, Ref, QName, Parent}, I, Last, Msg, MsgProps) -> Acc = {I + 1, case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of true -> rabbit_log:info("Synchronising ~s: ~p messages~n", @@ -79,7 +79,6 @@ master_send({Syncer, Ref, QName}, I, Last, Msg, MsgProps) -> erlang:now(); false -> Last end}, - Parent = rabbit_misc:get_parent(), receive {'$gen_cast', {set_maximum_since_use, Age}} -> ok = file_handle_cache:set_maximum_since_use(Age) @@ -171,10 +170,11 @@ slave(_DD, Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> MRef = erlang:monitor(process, Syncer), Syncer ! {sync_ready, Ref, self()}, {_MsgCount, BQS1} = BQ:purge(BQS), - slave_sync_loop({Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS1). + slave_sync_loop({Ref, MRef, Syncer, BQ, UpdateRamDuration, + rabbit_misc:get_parent()}, TRef, BQS1). -slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration}, TRef, BQS) -> - Parent = rabbit_misc:get_parent(), +slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, + TRef, BQS) -> receive {'DOWN', MRef, process, Syncer, _Reason} -> %% If the master dies half way we are not in the usual -- cgit v1.2.1 From 3f7332481753ef93e37ef768747e30fa89fc66ff Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 12:35:00 +0000 Subject: Make sure newly-started slaves don't respond and confuse things. --- src/rabbit_mirror_queue_master.erl | 2 +- src/rabbit_mirror_queue_slave.erl | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 2f9f4c02..d8737938 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -136,7 +136,7 @@ sync_mirrors(State = #state { name = QName, {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(QName), Ref = make_ref(), Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, QName, SPids), - gm:broadcast(GM, {sync_start, Ref, Syncer}), + gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, case rabbit_mirror_queue_sync:master_go(Syncer, Ref, QName, BQ, BQS) of {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 2b216a5f..1658b2ad 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -375,8 +375,11 @@ handle_msg([_SPid], _From, process_death) -> handle_msg([CPid], _From, {delete_and_terminate, _Reason} = Msg) -> ok = gen_server2:cast(CPid, {gm, Msg}), {stop, {shutdown, ring_shutdown}}; -handle_msg([SPid], _From, {sync_start, Ref, Syncer}) -> - gen_server2:cast(SPid, {sync_start, Ref, Syncer}); +handle_msg([SPid], _From, {sync_start, Ref, Syncer, SPids}) -> + case lists:member(SPid, SPids) of + true -> gen_server2:cast(SPid, {sync_start, Ref, Syncer}); + false -> ok + end; handle_msg([SPid], _From, Msg) -> ok = gen_server2:cast(SPid, {gm, Msg}). -- cgit v1.2.1 From 64b0a9b0ae9a9e5137bbc10436547c4b6457b4e0 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 12:36:24 +0000 Subject: Update diagram. --- src/rabbit_mirror_queue_sync.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 29f6af2c..b717f1f7 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -40,10 +40,12 @@ %% (from channel) || -- (spawns) --> || || %% || --------- sync_start (over GM) -------> || %% || || <--- sync_ready ---- || +%% || || (or) || +%% || || <--- sync_deny ----- || %% || <--- next* ---- || || } %% || ---- msg* ----> || || } loop -%% || || ----- sync_msg* ---> || } -%% || || <---- (credit)* ---- || } +%% || || ---- sync_msg* ----> || } +%% || || <--- (credit)* ----- || } %% || ---- done ----> || || %% || || -- sync_complete --> || %% || (Dies) || -- cgit v1.2.1 From 36f4adc8c01bf680d64ea1cce8bbda2ac757d494 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 29 Nov 2012 12:51:21 +0000 Subject: cosmetic, plus correctly identify provenance --- src/rabbit_misc.erl | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index cd83e3b8..ccf3d8be 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -1037,44 +1037,35 @@ interval_operation({M, F, A}, MaxRatio, IdealInterval, LastInterval) -> end}. %% ------------------------------------------------------------------------- -%% Begin copypasta from gen_server.erl +%% Begin copypasta from gen_server2.erl get_parent() -> case get('$ancestors') of - [Parent | _] when is_pid(Parent)-> - Parent; - [Parent | _] when is_atom(Parent)-> - name_to_pid(Parent); - _ -> - exit(process_was_not_started_by_proc_lib) + [Parent | _] when is_pid (Parent) -> Parent; + [Parent | _] when is_atom(Parent) -> name_to_pid(Parent); + _ -> exit(process_was_not_started_by_proc_lib) end. name_to_pid(Name) -> case whereis(Name) of - undefined -> - case whereis_name(Name) of - undefined -> - exit(could_not_find_registerd_name); - Pid -> - Pid - end; - Pid -> - Pid + undefined -> case whereis_name(Name) of + undefined -> exit(could_not_find_registerd_name); + Pid -> Pid + end; + Pid -> Pid end. whereis_name(Name) -> case ets:lookup(global_names, Name) of - [{_Name, Pid, _Method, _RPid, _Ref}] -> - if node(Pid) == node() -> - case erlang:is_process_alive(Pid) of - true -> Pid; - false -> undefined + [{_Name, Pid, _Method, _RPid, _Ref}] -> + if node(Pid) == node() -> case erlang:is_process_alive(Pid) of + true -> Pid; + false -> undefined + end; + true -> Pid end; - true -> - Pid - end; - [] -> undefined + [] -> undefined end. -%% End copypasta from gen_server.erl +%% End copypasta from gen_server2.erl %% ------------------------------------------------------------------------- -- cgit v1.2.1 From 3e605d8d940c708f4586b5dd1064bff44d97b88c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 12:54:28 +0000 Subject: Set depth_delta to undefined if something goes wrong. Or indeed if something goes right, but in that case we are about to overwrite it. --- src/rabbit_mirror_queue_slave.erl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 1658b2ad..b12f85b8 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -227,11 +227,10 @@ handle_cast({sync_start, Ref, Syncer}, backing_queue = BQ, backing_queue_state = BQS }) -> State1 = #state{rate_timer_ref = TRef} = ensure_rate_timer(State), - S = fun({TRefN, BQSN}) -> State1#state{rate_timer_ref = TRefN, + S = fun({TRefN, BQSN}) -> State1#state{depth_delta = undefined, + rate_timer_ref = TRefN, backing_queue_state = BQSN} end, %% [0] We can only sync when there are no pending acks - %% [1] The master died so we do not need to set_delta even though - %% we purged since we will get a depth instruction soon. case rabbit_mirror_queue_sync:slave( DD, Ref, TRef, Syncer, BQ, BQS, fun (BQN, BQSN) -> @@ -241,7 +240,7 @@ handle_cast({sync_start, Ref, Syncer}, {TRefN, BQSN1} end) of {ok, Res} -> noreply(set_delta(0, S(Res))); %% [0] - {failed, Res} -> noreply(S(Res)); %% [1] + {failed, Res} -> noreply(S(Res)); {stop, Reason, Res} -> {stop, Reason, S(Res)} end; -- cgit v1.2.1 From 0d619d9d9c4eaa5c4477f41aaf4818ceaab0b9c6 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Thu, 29 Nov 2012 12:57:37 +0000 Subject: reduce distance to OTP --- src/supervisor2.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index c5a16a9f..76dc4e83 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -74,7 +74,7 @@ start_child/2, restart_child/2, delete_child/2, terminate_child/2, which_children/1, count_children/1, - find_child/2, check_childspecs/1]). + find_child/2, check_childspecs/1]). %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -437,7 +437,7 @@ handle_call({terminate_child, Name}, _From, State) -> %%% The requests delete_child and restart_child are invalid for %%% simple_one_for_one supervisors. handle_call({_Req, _Data}, _From, State) when ?is_simple(State) -> - {reply, {error, State#state.strategy}, State}; + {reply, {error, simple_one_for_one}, State}; handle_call({start_child, ChildSpec}, _From, State) -> case check_childspec(ChildSpec) of @@ -777,6 +777,7 @@ restart_child(Pid, Reason, #state{children = [Child]} = State) when ?is_simple(S error -> {ok, State} end; + restart_child(Pid, Reason, State) -> Children = State#state.children, case lists:keysearch(Pid, #child.pid, Children) of -- cgit v1.2.1 From 854b886d2e1f9f905abba6838b54dee61747ed8a Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Thu, 29 Nov 2012 12:58:40 +0000 Subject: explain why we're not using lists:keyfind/3 --- src/supervisor2.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 76dc4e83..8a47c67d 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -780,6 +780,7 @@ restart_child(Pid, Reason, #state{children = [Child]} = State) when ?is_simple(S restart_child(Pid, Reason, State) -> Children = State#state.children, + %% we still support >= R12-B3 in which lists:keyfind/3 doesn't exist case lists:keysearch(Pid, #child.pid, Children) of {value, Child} -> RestartType = Child#child.restart_type, -- cgit v1.2.1 From b81fd50dda7dd65f97903a85615d684804bed501 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 29 Nov 2012 13:09:15 +0000 Subject: Don't do anything if we decided not to do anything. --- src/rabbit_mirror_queue_slave.erl | 1 + src/rabbit_mirror_queue_sync.erl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index b12f85b8..53564f09 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -239,6 +239,7 @@ handle_cast({sync_start, Ref, Syncer}, self(), update_ram_duration), {TRefN, BQSN1} end) of + denied -> noreply(State1); {ok, Res} -> noreply(set_delta(0, S(Res))); %% [0] {failed, Res} -> noreply(S(Res)); {stop, Reason, Res} -> {stop, Reason, S(Res)} diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index b717f1f7..266465ec 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -166,7 +166,7 @@ sync_send_complete(SPid, _MRef, Ref) -> slave(0, Ref, TRef, Syncer, _BQ, BQS, _UpdateRamDuration) -> Syncer ! {sync_deny, Ref, self()}, - {ok, {TRef, BQS}}; + denied; slave(_DD, Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> MRef = erlang:monitor(process, Syncer), -- cgit v1.2.1 From 76e832a6c6e451d049a1c5439251179444bbc966 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Thu, 29 Nov 2012 13:16:04 +0000 Subject: observe use_specs --- src/supervisor2.erl | 77 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 8a47c67d..8e01fe74 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -82,11 +82,12 @@ -export([try_again_restart/2]). %%-------------------------------------------------------------------------- - +-ifdef(use_specs). -export_type([child_spec/0, startchild_ret/0, strategy/0]). - +-endif. %%-------------------------------------------------------------------------- +-ifdef(use_specs). -type child() :: 'undefined' | pid(). -type child_id() :: term(). -type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. @@ -109,9 +110,11 @@ -type strategy() :: 'one_for_all' | 'one_for_one' | 'rest_for_one' | 'simple_one_for_one'. +-endif. %%-------------------------------------------------------------------------- +-ifdef(use_specs). -record(child, {% pid is undefined when child is not running pid = undefined :: child() | {restarting,pid()} | [pid()], name :: child_id(), @@ -121,11 +124,22 @@ child_type :: worker(), modules = [] :: modules()}). -type child_rec() :: #child{}. +-else. +-record(child, { + pid = undefined, + name, + mfargs, + restart_type, + shutdown, + child_type, + modules = []}). +-endif. -define(DICT, dict). -define(SETS, sets). -define(SET, set). +-ifdef(use_specs). -record(state, {name, strategy :: strategy(), children = [] :: [child_rec()], @@ -136,16 +150,28 @@ module, args}). -type state() :: #state{}. +-else. +-record(state, {name, + strategy, + children = [], + dynamics, + intensity, + period, + restarts = [], + module, + args}). +-endif. -define(is_simple(State), State#state.strategy =:= simple_one_for_one). +-ifdef(use_specs). -callback init(Args :: term()) -> {ok, {{RestartStrategy :: strategy(), MaxR :: non_neg_integer(), MaxT :: non_neg_integer()}, [ChildSpec :: child_spec()]}} | ignore. - +-endif. -define(restarting(_Pid_), {restarting,_Pid_}). %%% --------------------------------------------------- @@ -153,27 +179,31 @@ %%% Servers/processes should/could also be built using gen_server.erl. %%% SupName = {local, atom()} | {global, atom()}. %%% --------------------------------------------------- - +-ifdef(use_specs). -type startlink_err() :: {'already_started', pid()} | 'shutdown' | term(). -type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}. -spec start_link(Module, Args) -> startlink_ret() when Module :: module(), Args :: term(). + +-endif. start_link(Mod, Args) -> gen_server:start_link(supervisor2, {self, Mod, Args}, []). +-ifdef(use_specs). -spec start_link(SupName, Module, Args) -> startlink_ret() when SupName :: sup_name(), Module :: module(), Args :: term(). +-endif. start_link(SupName, Mod, Args) -> gen_server:start_link(SupName, ?MODULE, {SupName, Mod, Args}, []). %%% --------------------------------------------------- %%% Interface functions. %%% --------------------------------------------------- - +-ifdef(use_specs). -type startchild_err() :: 'already_present' | {'already_started', Child :: child()} | term(). -type startchild_ret() :: {'ok', Child :: child()} @@ -183,9 +213,11 @@ start_link(SupName, Mod, Args) -> -spec start_child(SupRef, ChildSpec) -> startchild_ret() when SupRef :: sup_ref(), ChildSpec :: child_spec() | (List :: [term()]). +-endif. start_child(Supervisor, ChildSpec) -> call(Supervisor, {start_child, ChildSpec}). +-ifdef(use_specs). -spec restart_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: child_id(), @@ -194,14 +226,17 @@ start_child(Supervisor, ChildSpec) -> | {'error', Error}, Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one' | term(). +-endif. restart_child(Supervisor, Name) -> call(Supervisor, {restart_child, Name}). +-ifdef(use_specs). -spec delete_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: child_id(), Result :: 'ok' | {'error', Error}, Error :: 'running' | 'restarting' | 'not_found' | 'simple_one_for_one'. +-endif. delete_child(Supervisor, Name) -> call(Supervisor, {delete_child, Name}). @@ -211,24 +246,28 @@ delete_child(Supervisor, Name) -> %% Note that the child is *always* terminated in some %% way (maybe killed). %%----------------------------------------------------------------- - +-ifdef(use_specs). -spec terminate_child(SupRef, Id) -> Result when SupRef :: sup_ref(), Id :: pid() | child_id(), Result :: 'ok' | {'error', Error}, Error :: 'not_found' | 'simple_one_for_one'. +-endif. terminate_child(Supervisor, Name) -> call(Supervisor, {terminate_child, Name}). +-ifdef(use_specs). -spec which_children(SupRef) -> [{Id,Child,Type,Modules}] when SupRef :: sup_ref(), Id :: child_id() | undefined, Child :: child() | 'restarting', Type :: worker(), Modules :: modules(). +-endif. which_children(Supervisor) -> call(Supervisor, which_children). +-ifdef(use_specs). -spec count_children(SupRef) -> PropListOfCounts when SupRef :: sup_ref(), PropListOfCounts :: [Count], @@ -236,15 +275,18 @@ which_children(Supervisor) -> | {active, ActiveProcessCount :: non_neg_integer()} | {supervisors, ChildSupervisorCount :: non_neg_integer()} |{workers, ChildWorkerCount :: non_neg_integer()}. +-endif. count_children(Supervisor) -> call(Supervisor, count_children). call(Supervisor, Req) -> gen_server:call(Supervisor, Req, infinity). +-ifdef(use_specs). -spec check_childspecs(ChildSpecs) -> Result when ChildSpecs :: [child_spec()], Result :: 'ok' | {'error', Error :: term()}. +-endif. check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> case check_startspec(ChildSpecs) of {ok, _} -> ok; @@ -254,15 +296,22 @@ check_childspecs(X) -> {error, {badarg, X}}. %%%----------------------------------------------------------------- %%% Called by timer:apply_after from restart/2 +-ifdef(use_specs). -spec try_again_restart(SupRef, Child) -> ok when SupRef :: sup_ref(), Child :: child_id() | pid(). +-endif. try_again_restart(Supervisor, Child) -> cast(Supervisor, {try_again_restart, Child}). cast(Supervisor, Req) -> gen_server:cast(Supervisor, Req). +-ifdef(use_specs). +-spec find_child(Supervisor, Name) -> [pid()] when + Supervisor :: sup_ref(), + Name :: child_id(). +-endif. find_child(Supervisor, Name) -> [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), Name1 =:= Name]. @@ -272,7 +321,7 @@ find_child(Supervisor, Name) -> %%% Initialize the supervisor. %%% %%% --------------------------------------------------- - +-ifdef(use_specs). -type init_sup_name() :: sup_name() | 'self'. -type stop_rsn() :: 'shutdown' | {'bad_return', {module(),'init', term()}} @@ -281,7 +330,7 @@ find_child(Supervisor, Name) -> -spec init({init_sup_name(), module(), [term()]}) -> {'ok', state()} | 'ignore' | {'stop', stop_rsn()}. - +-endif. init({SupName, Mod, Args}) -> process_flag(trap_exit, true), case Mod:init(Args) of @@ -581,9 +630,10 @@ count_child(#child{pid = Pid, child_type = supervisor}, %%% If a restart attempt failed, this message is sent via %%% timer:apply_after(0,...) in order to give gen_server the chance to %%% check it's inbox before trying again. +-ifdef(use_specs). -spec handle_cast({try_again_restart, child_id() | pid()}, state()) -> {'noreply', state()} | {stop, shutdown, state()}. - +-endif. handle_cast({try_again_restart,Pid}, #state{children=[Child]}=State) when ?is_simple(State) -> RT = Child#child.restart_type, @@ -618,9 +668,10 @@ handle_cast({try_again_restart,Name}, State) -> %% %% Take care of terminated children. %% +-ifdef(use_specs). -spec handle_info(term(), state()) -> {'noreply', state()} | {'stop', 'shutdown', state()}. - +-endif. handle_info({'EXIT', Pid, Reason}, State) -> case restart_child(Pid, Reason, State) of {ok, State1} -> @@ -650,8 +701,9 @@ handle_info(Msg, State) -> %% %% Terminate this server. %% +-ifdef(use_specs). -spec terminate(term(), state()) -> 'ok'. - +-endif. terminate(_Reason, #state{children=[Child]} = State) when ?is_simple(State) -> terminate_dynamic_children(Child, dynamics_db(Child#child.restart_type, State#state.dynamics), @@ -668,9 +720,10 @@ terminate(_Reason, State) -> %% NOTE: This requires that the init function of the call-back module %% does not have any side effects. %% +-ifdef(use_specs). -spec code_change(term(), state(), term()) -> {'ok', state()} | {'error', term()}. - +-endif. code_change(_, State, _) -> case (State#state.module):init(State#state.args) of {ok, {SupFlags, StartSpec}} -> -- cgit v1.2.1 From 0ce06f9e0bac7a52785ce7d242206ef43f4235d2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 29 Nov 2012 13:25:38 +0000 Subject: no more unused vars --- src/rabbit_mirror_queue_sync.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 266465ec..d838d636 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -164,7 +164,7 @@ sync_send_complete(SPid, _MRef, Ref) -> %% --------------------------------------------------------------------------- %% Slave -slave(0, Ref, TRef, Syncer, _BQ, BQS, _UpdateRamDuration) -> +slave(0, Ref, _TRef, Syncer, _BQ, _BQS, _UpdateRamDuration) -> Syncer ! {sync_deny, Ref, self()}, denied; -- cgit v1.2.1 From 8a2c6dac21a2a99b7fcbc73ed2831c37475802a5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 29 Nov 2012 14:00:16 +0000 Subject: extract logging --- src/rabbit_mirror_queue_master.erl | 17 +++++++++-------- src/rabbit_mirror_queue_sync.erl | 30 ++++++++++++------------------ 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index d8737938..3d7f902c 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -131,20 +131,21 @@ sync_mirrors(State = #state { name = QName, gm = GM, backing_queue = BQ, backing_queue_state = BQS }) -> - rabbit_log:info("Synchronising ~s: ~p messages to synchronise~n", - [rabbit_misc:rs(QName), BQ:len(BQS)]), + Log = fun (Fmt, Params) -> + rabbit_log:info("Synchronising ~s: " ++ Fmt ++ "~n", + [rabbit_misc:rs(QName) | Params]) + end, + Log("~p messages to synchronise", [BQ:len(BQS)]), {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(QName), Ref = make_ref(), - Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, QName, SPids), + Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, Log, SPids), gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, - case rabbit_mirror_queue_sync:master_go(Syncer, Ref, QName, BQ, BQS) of + case rabbit_mirror_queue_sync:master_go(Syncer, Ref, Log, BQ, BQS) of {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; - {sync_died, R, BQS1} -> rabbit_log:info("Synchronising ~s: ~p~n", - [rabbit_misc:rs(QName), R]), + {sync_died, R, BQS1} -> Log("~p", [R]), {ok, S(BQS1)}; - {ok, BQS1} -> rabbit_log:info("Synchronising ~s: complete~n", - [rabbit_misc:rs(QName)]), + {ok, BQS1} -> Log("complete", []), {ok, S(BQS1)} end. diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index d838d636..bddfb9dc 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -53,12 +53,12 @@ %% --------------------------------------------------------------------------- %% Master -master_prepare(Ref, QName, SPids) -> +master_prepare(Ref, Log, SPids) -> MPid = self(), - spawn_link(fun () -> syncer(Ref, QName, MPid, SPids) end). + spawn_link(fun () -> syncer(Ref, Log, MPid, SPids) end). -master_go(Syncer, Ref, QName, BQ, BQS) -> - SendArgs = {Syncer, Ref, QName, rabbit_misc:get_parent()}, +master_go(Syncer, Ref, Log, BQ, BQS) -> + SendArgs = {Syncer, Ref, Log, rabbit_misc:get_parent()}, {Acc, BQS1} = BQ:fold(fun (Msg, MsgProps, {I, Last}) -> master_send(SendArgs, I, Last, Msg, MsgProps) @@ -73,11 +73,10 @@ master_go(Syncer, Ref, QName, BQ, BQS) -> _ -> {ok, BQS1} end. -master_send({Syncer, Ref, QName, Parent}, I, Last, Msg, MsgProps) -> +master_send({Syncer, Ref, Log, Parent}, I, Last, Msg, MsgProps) -> Acc = {I + 1, case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of - true -> rabbit_log:info("Synchronising ~s: ~p messages~n", - [rabbit_misc:rs(QName), I]), + true -> Log("~p messages", [I]), erlang:now(); false -> Last end}, @@ -98,22 +97,17 @@ master_send({Syncer, Ref, QName, Parent}, I, Last, Msg, MsgProps) -> %% --------------------------------------------------------------------------- %% Syncer -syncer(Ref, QName, MPid, SPids) -> +syncer(Ref, Log, MPid, SPids) -> SPidsMRefs = [{SPid, erlang:monitor(process, SPid)} || SPid <- SPids], %% We wait for a reply from the slaves so that we know they are in %% a receive block and will thus receive messages we send to them %% *without* those messages ending up in their gen_server2 pqueue. case foreach_slave(SPidsMRefs, Ref, fun sync_receive_ready/3) of - [] -> - rabbit_log:info("Synchronising ~s: all slaves already synced~n", - [rabbit_misc:rs(QName)]); - SPidsMRefs1 -> - rabbit_log:info("Synchronising ~s: ~p to sync~n", - [rabbit_misc:rs(QName), - [rabbit_misc:pid_to_string(S) || - {S, _} <- SPidsMRefs1]]), - SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), - foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3) + [] -> Log("all slaves already synced", []); + SPidsMRefs1 -> Log("~p to sync", [[rabbit_misc:pid_to_string(S) || + {S, _} <- SPidsMRefs1]]), + SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), + foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3) end, unlink(MPid). -- cgit v1.2.1 From 69f561481424051a2e456c4138af9e0d22790d7b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 29 Nov 2012 14:11:56 +0000 Subject: cosmetic --- src/rabbit_mirror_queue_sync.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index bddfb9dc..3a8a68b8 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -63,10 +63,10 @@ master_go(Syncer, Ref, Log, BQ, BQS) -> BQ:fold(fun (Msg, MsgProps, {I, Last}) -> master_send(SendArgs, I, Last, Msg, MsgProps) end, {0, erlang:now()}, BQS), - Syncer ! {done, Ref}, receive {next, Ref} -> ok end, + Syncer ! {done, Ref}, case Acc of {shutdown, Reason} -> {shutdown, Reason, BQS1}; {sync_died, Reason} -> {sync_died, Reason, BQS1}; @@ -89,8 +89,8 @@ master_send({Syncer, Ref, Log, Parent}, I, Last, Msg, MsgProps) -> receive {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, {cont, Acc}; - {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}}; - {'EXIT', Parent, Reason} -> {stop, {shutdown, Reason}} + {'EXIT', Parent, Reason} -> {stop, {shutdown, Reason}}; + {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}} end. %% Master @@ -139,7 +139,7 @@ foreach_slave(SPidsMRefs, Ref, Fun) -> sync_receive_ready(SPid, MRef, Ref) -> receive {sync_ready, Ref, SPid} -> SPid; - {sync_deny, Ref, SPid} -> ignore; + {sync_deny, Ref, SPid} -> ignore; {'DOWN', MRef, _, SPid, _} -> ignore end. -- cgit v1.2.1 From d9a00cfa67c05583ffcddbfa0a003cecc102bee9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 29 Nov 2012 14:37:13 +0000 Subject: handle the case of the Syncer dying right at the end which could previously leave the master blocked, waiting for 'next'. And move the unlinking, which allows us to ensure we don't end up with stray 'EXIT's. --- src/rabbit_mirror_queue_sync.erl | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 3a8a68b8..c654cde5 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -58,19 +58,13 @@ master_prepare(Ref, Log, SPids) -> spawn_link(fun () -> syncer(Ref, Log, MPid, SPids) end). master_go(Syncer, Ref, Log, BQ, BQS) -> - SendArgs = {Syncer, Ref, Log, rabbit_misc:get_parent()}, - {Acc, BQS1} = - BQ:fold(fun (Msg, MsgProps, {I, Last}) -> - master_send(SendArgs, I, Last, Msg, MsgProps) - end, {0, erlang:now()}, BQS), - receive - {next, Ref} -> ok - end, - Syncer ! {done, Ref}, - case Acc of - {shutdown, Reason} -> {shutdown, Reason, BQS1}; - {sync_died, Reason} -> {sync_died, Reason, BQS1}; - _ -> {ok, BQS1} + Args = {Syncer, Ref, Log, rabbit_misc:get_parent()}, + case BQ:fold(fun (Msg, MsgProps, {I, Last}) -> + master_send(Args, I, Last, Msg, MsgProps) + end, {0, erlang:now()}, BQS) of + {{shutdown, Reason}, BQS1} -> {shutdown, Reason, BQS1}; + {{sync_died, Reason}, BQS1} -> {sync_died, Reason, BQS1}; + {_, BQS1} -> master_done(Args, BQS1) end. master_send({Syncer, Ref, Log, Parent}, I, Last, Msg, MsgProps) -> @@ -93,6 +87,18 @@ master_send({Syncer, Ref, Log, Parent}, I, Last, Msg, MsgProps) -> {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}} end. +master_done({Syncer, Ref, _Log, Parent}, BQS) -> + receive + {next, Ref} -> unlink(Syncer), + Syncer ! {done, Ref}, + receive {'EXIT', Syncer, _} -> ok + after 0 -> ok + end, + {ok, BQS}; + {'EXIT', Parent, Reason} -> {shutdown, Reason, BQS}; + {'EXIT', Syncer, Reason} -> {sync_died, Reason, BQS} + end. + %% Master %% --------------------------------------------------------------------------- %% Syncer @@ -108,8 +114,7 @@ syncer(Ref, Log, MPid, SPids) -> {S, _} <- SPidsMRefs1]]), SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3) - end, - unlink(MPid). + end. syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> MPid ! {next, Ref}, -- cgit v1.2.1 From 66f11afe7300881f00496fc5431c7d362aadf8f0 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 29 Nov 2012 14:39:17 +0000 Subject: correct docs for final handshake --- src/rabbit_mirror_queue_sync.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index c654cde5..4cb534ff 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -46,6 +46,7 @@ %% || ---- msg* ----> || || } loop %% || || ---- sync_msg* ----> || } %% || || <--- (credit)* ----- || } +%% || <--- next ---- || || %% || ---- done ----> || || %% || || -- sync_complete --> || %% || (Dies) || -- cgit v1.2.1 From a8139d50954a567b3f2ad33fb34a04b32bf9cf90 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Thu, 29 Nov 2012 14:43:54 +0000 Subject: handle {permanent, Delay} for dynamic children --- src/supervisor2.erl | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 8e01fe74..b1a4a6c1 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -163,6 +163,10 @@ -endif. -define(is_simple(State), State#state.strategy =:= simple_one_for_one). +-define(is_permanent(R), ((R =:= permanent) orelse + (is_tuple(R) andalso + tuple_size(R) == 2 andalso + element(1, R) =:= permanent))). -ifdef(use_specs). -callback init(Args :: term()) -> @@ -1007,15 +1011,8 @@ do_terminate(Child, SupName) when is_pid(Child#child.pid) -> case shutdown(Child#child.pid, Child#child.shutdown) of ok -> ok; - {error, normal} -> - case Child#child.restart_type of - permanent -> - report_error(shutdown_error, normal, Child, SupName); - {permanent, _Delay} -> - report_error(shutdown_error, normal, Child, SupName); - _ -> - ok - end; + {error, normal} when not ?is_permanent(Child#child.restart_type) -> + ok; {error, OtherReason} -> report_error(shutdown_error, OtherReason, Child, SupName) end, @@ -1145,7 +1142,7 @@ monitor_dynamic_children(#child{restart_type=RType}, Dynamics) -> case monitor_child(P) of ok -> {?SETS:add_element(P, Pids), EStack}; - {error, normal} when RType =/= permanent -> + {error, normal} when ?is_permanent(RType) -> {Pids, EStack}; {error, Reason} -> {Pids, ?DICT:append(Reason, P, EStack)} @@ -1154,7 +1151,6 @@ monitor_dynamic_children(#child{restart_type=RType}, Dynamics) -> {Pids, EStack} end, {?SETS:new(), ?DICT:new()}, Dynamics). - wait_dynamic_children(_Child, _Pids, 0, undefined, EStack) -> EStack; wait_dynamic_children(_Child, _Pids, 0, TRef, EStack) -> @@ -1185,7 +1181,7 @@ wait_dynamic_children(#child{restart_type=RType} = Child, Pids, Sz, wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, TRef, EStack); - {'DOWN', _MRef, process, Pid, normal} when RType =/= permanent -> + {'DOWN', _MRef, process, Pid, normal} when ?is_permanent(RType) -> wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, TRef, EStack); -- cgit v1.2.1 From ff172f664ccb1271fbd934bd1c7748ef007f0490 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 29 Nov 2012 22:51:37 +0000 Subject: return to a simpler, better BQ:dropwhile, and introduce 'fetchwhile' ...to cover the remaining required functionality, including the ability to process messages along the way, pass around an accumulator, and get hold of the IsDelivered flag (not needed in our use case but included for similarity with 'fetch'). --- src/rabbit_amqqueue_process.erl | 15 ++++++++----- src/rabbit_backing_queue.erl | 32 +++++++++++++++++--------- src/rabbit_backing_queue_qc.erl | 4 ++-- src/rabbit_mirror_queue_master.erl | 46 ++++++++++++++++++++++---------------- src/rabbit_tests.erl | 14 +++++------- src/rabbit_variable_queue.erl | 40 ++++++++++++++++++--------------- 6 files changed, 87 insertions(+), 64 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 74717ace..a6b3829b 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -725,14 +725,15 @@ drop_expired_messages(State = #q{dlx = DLX, Now = now_micros(), ExpirePred = fun (#message_properties{expiry = Exp}) -> Now >= Exp end, {Props, BQS1} = case DLX of - undefined -> {Next, undefined, BQS2} = - BQ:dropwhile(ExpirePred, false, BQS), - {Next, BQS2}; - _ -> {Next, Msgs, BQS2} = - BQ:dropwhile(ExpirePred, true, BQS), + undefined -> BQ:dropwhile(ExpirePred, BQS); + _ -> {Next, Msgs, BQS2} = + BQ:fetchwhile(ExpirePred, + fun accumulate_msgs/4, + [], BQS), case Msgs of [] -> ok; - _ -> (dead_letter_fun(expired))(Msgs) + _ -> (dead_letter_fun(expired))( + lists:reverse(Msgs)) end, {Next, BQS2} end, @@ -741,6 +742,8 @@ drop_expired_messages(State = #q{dlx = DLX, #message_properties{expiry = Exp} -> Exp end, State#q{backing_queue_state = BQS1}). +accumulate_msgs(Msg, _IsDelivered, AckTag, Acc) -> [{Msg, AckTag} | Acc]. + ensure_ttl_timer(undefined, State) -> State; ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = undefined}) -> diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 96c58cb9..272df5c1 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -124,16 +124,25 @@ %% be ignored. -callback drain_confirmed(state()) -> {msg_ids(), state()}. -%% Drop messages from the head of the queue while the supplied predicate returns -%% true. Also accepts a boolean parameter that determines whether the messages -%% necessitate an ack or not. If they do, the function returns a list of -%% messages with the respective acktags. --callback dropwhile(msg_pred(), true, state()) - -> {rabbit_types:message_properties() | undefined, - [{rabbit_types:basic_message(), ack()}], state()}; - (msg_pred(), false, state()) - -> {rabbit_types:message_properties() | undefined, - undefined, state()}. +%% Drop messages from the head of the queue while the supplied +%% predicate on message properties returns true. Returns the first +%% message properties for which the predictate returned false, or +%% 'undefined' if the whole backing queue was traversed w/o the +%% predicate ever returning false. +-callback dropwhile(msg_pred(), state()) + -> {rabbit_types:message_properties() | undefined, state()}. + +%% Like dropwhile, except messages are fetched in "require +%% acknowledgement" mode and are passed, together with their Delivered +%% flag and ack tag, to the supplied function. The function is also +%% fed an accumulator. The result of fetchwhile is as for dropwhile +%% plus the accumulator. +-callback fetchwhile(msg_pred(), + fun ((rabbit_types:basic_message(), boolean(), ack(), A) + -> A), + A, state()) + -> {rabbit_types:message_properties() | undefined, + A, state()}. %% Produce the next message. -callback fetch(true, state()) -> {fetch_result(ack()), state()}; @@ -222,7 +231,8 @@ behaviour_info(callbacks) -> [{start, 1}, {stop, 0}, {init, 3}, {terminate, 2}, {delete_and_terminate, 2}, {purge, 1}, {publish, 5}, - {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, {dropwhile, 3}, + {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, + {dropwhile, 2}, {fetchwhile, 4}, {fetch, 2}, {ack, 2}, {foreach_ack, 3}, {requeue, 2}, {fold, 3}, {len, 1}, {is_empty, 1}, {depth, 1}, {set_ram_duration_target, 2}, {ram_duration, 1}, {needs_timeout, 1}, {timeout, 1}, diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index a5d0a008..e337580c 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -147,7 +147,7 @@ qc_drain_confirmed(#state{bqstate = BQ}) -> {call, ?BQMOD, drain_confirmed, [BQ]}. qc_dropwhile(#state{bqstate = BQ}) -> - {call, ?BQMOD, dropwhile, [fun dropfun/1, false, BQ]}. + {call, ?BQMOD, dropwhile, [fun dropfun/1, BQ]}. qc_is_empty(#state{bqstate = BQ}) -> {call, ?BQMOD, is_empty, [BQ]}. @@ -262,7 +262,7 @@ next_state(S, Res, {call, ?BQMOD, drain_confirmed, _Args}) -> S#state{bqstate = BQ1}; next_state(S, Res, {call, ?BQMOD, dropwhile, _Args}) -> - BQ = {call, erlang, element, [3, Res]}, + BQ = {call, erlang, element, [2, Res]}, #state{messages = Messages} = S, Msgs1 = drop_messages(Messages), S#state{bqstate = BQ, len = gb_trees:size(Msgs1), messages = Msgs1}; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index c8a361b1..0ae10d89 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -20,7 +20,7 @@ purge/1, publish/5, publish_delivered/4, discard/3, fetch/2, drop/2, ack/2, requeue/2, fold/3, len/1, is_empty/1, depth/1, drain_confirmed/1, - dropwhile/3, set_ram_duration_target/2, ram_duration/1, + dropwhile/2, fetchwhile/4, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, is_duplicate/2, foreach_ack/3]). @@ -216,19 +216,17 @@ discard(MsgId, ChPid, State = #state { gm = GM, State end. -dropwhile(Pred, AckRequired, - State = #state{gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> +dropwhile(Pred, State = #state{backing_queue = BQ, + backing_queue_state = BQS }) -> Len = BQ:len(BQS), - {Next, Msgs, BQS1} = BQ:dropwhile(Pred, AckRequired, BQS), - Len1 = BQ:len(BQS1), - Dropped = Len - Len1, - case Dropped of - 0 -> ok; - _ -> ok = gm:broadcast(GM, {drop, Len1, Dropped, AckRequired}) - end, - {Next, Msgs, State #state { backing_queue_state = BQS1 } }. + {Next, BQS1} = BQ:dropwhile(Pred, BQS), + {Next, drop(Len, false, State #state { backing_queue_state = BQS1 })}. + +fetchwhile(Pred, Fun, Acc, State = #state{backing_queue = BQ, + backing_queue_state = BQS }) -> + Len = BQ:len(BQS), + {Next, Acc1, BQS1} = BQ:fetchwhile(Pred, Fun, Acc, BQS), + {Next, Acc1, drop(Len, true, State #state { backing_queue_state = BQS1 })}. drain_confirmed(State = #state { backing_queue = BQ, backing_queue_state = BQS, @@ -268,7 +266,7 @@ fetch(AckRequired, State = #state { backing_queue = BQ, empty -> {Result, State1}; {#basic_message{id = MsgId}, _IsDelivered, AckTag} -> - {Result, drop(MsgId, AckTag, State1)} + {Result, drop_one(MsgId, AckTag, State1)} end. drop(AckRequired, State = #state { backing_queue = BQ, @@ -277,7 +275,7 @@ drop(AckRequired, State = #state { backing_queue = BQ, State1 = State #state { backing_queue_state = BQS1 }, {Result, case Result of empty -> State1; - {MsgId, AckTag} -> drop(MsgId, AckTag, State1) + {MsgId, AckTag} -> drop_one(MsgId, AckTag, State1) end}. ack(AckTags, State = #state { gm = GM, @@ -440,13 +438,23 @@ depth_fun() -> %% Helpers %% --------------------------------------------------------------------------- -drop(MsgId, AckTag, State = #state { ack_msg_id = AM, - gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> +drop_one(MsgId, AckTag, State = #state { ack_msg_id = AM, + gm = GM, + backing_queue = BQ, + backing_queue_state = BQS }) -> ok = gm:broadcast(GM, {drop, BQ:len(BQS), 1, AckTag =/= undefined}), State #state { ack_msg_id = maybe_store_acktag(AckTag, MsgId, AM) }. +drop(PrevLen, AckRequired, State = #state { gm = GM, + backing_queue = BQ, + backing_queue_state = BQS }) -> + Len = BQ:len(BQS), + case PrevLen - Len of + 0 -> State; + Dropped -> ok = gm:broadcast(GM, {drop, Len, Dropped, AckRequired}), + State + end. + maybe_store_acktag(undefined, _MsgId, AM) -> AM; maybe_store_acktag(AckTag, MsgId, AM) -> dict:store(AckTag, MsgId, AM). diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index df8544a4..d6d40b14 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2418,10 +2418,10 @@ test_dropwhile(VQ0) -> fun (N, Props) -> Props#message_properties{expiry = N} end, VQ0), %% drop the first 5 messages - {_, undefined, VQ2} = rabbit_variable_queue:dropwhile( - fun(#message_properties { expiry = Expiry }) -> - Expiry =< 5 - end, false, VQ1), + {_, VQ2} = rabbit_variable_queue:dropwhile( + fun(#message_properties { expiry = Expiry }) -> + Expiry =< 5 + end, VQ1), %% fetch five now VQ3 = lists:foldl(fun (_N, VQN) -> @@ -2438,12 +2438,10 @@ test_dropwhile(VQ0) -> test_dropwhile_varying_ram_duration(VQ0) -> VQ1 = variable_queue_publish(false, 1, VQ0), VQ2 = rabbit_variable_queue:set_ram_duration_target(0, VQ1), - {_, undefined, VQ3} = rabbit_variable_queue:dropwhile( - fun(_) -> false end, false, VQ2), + {_, VQ3} = rabbit_variable_queue:dropwhile(fun(_) -> false end, VQ2), VQ4 = rabbit_variable_queue:set_ram_duration_target(infinity, VQ3), VQ5 = variable_queue_publish(false, 1, VQ4), - {_, undefined, VQ6} = - rabbit_variable_queue:dropwhile(fun(_) -> false end, false, VQ5), + {_, VQ6} = rabbit_variable_queue:dropwhile(fun(_) -> false end, VQ5), VQ6. test_variable_queue_dynamic_duration_change(VQ0) -> diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 30ab96f5..3e4c7c86 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -18,7 +18,8 @@ -export([init/3, terminate/2, delete_and_terminate/2, purge/1, publish/5, publish_delivered/4, discard/3, drain_confirmed/1, - dropwhile/3, fetch/2, drop/2, ack/2, requeue/2, fold/3, len/1, + dropwhile/2, fetchwhile/4, + fetch/2, drop/2, ack/2, requeue/2, fold/3, len/1, is_empty/1, depth/1, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, is_duplicate/2, multiple_routing_keys/0, foreach_ack/3]). @@ -577,27 +578,30 @@ drain_confirmed(State = #vqstate { confirmed = C }) -> confirmed = gb_sets:new() }} end. -dropwhile(Pred, AckRequired, State) -> dropwhile(Pred, AckRequired, State, []). +dropwhile(Pred, State) -> + case queue_out(State) of + {empty, State1} -> + {undefined, a(State1)}; + {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> + case Pred(MsgProps) of + true -> {_, State2} = internal_fetch(false, MsgStatus, State1), + dropwhile(Pred, State2); + false -> {MsgProps, a(in_r(MsgStatus, State1))} + end + end. -dropwhile(Pred, AckRequired, State, Msgs) -> - End = fun(Next, S) when AckRequired -> {Next, lists:reverse(Msgs), S}; - (Next, S) -> {Next, undefined, S} - end, +fetchwhile(Pred, Fun, Acc, State) -> case queue_out(State) of {empty, State1} -> - End(undefined, a(State1)); + {undefined, Acc, a(State1)}; {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> - case {Pred(MsgProps), AckRequired} of - {true, true} -> - {MsgStatus1, State2} = read_msg(MsgStatus, State1), - {{Msg, _IsDelivered, AckTag}, State3} = - internal_fetch(true, MsgStatus1, State2), - dropwhile(Pred, AckRequired, State3, [{Msg, AckTag} | Msgs]); - {true, false} -> - {_, State2} = internal_fetch(false, MsgStatus, State1), - dropwhile(Pred, AckRequired, State2, undefined); - {false, _} -> - End(MsgProps, a(in_r(MsgStatus, State1))) + case Pred(MsgProps) of + true -> {MsgStatus1, State2} = read_msg(MsgStatus, State1), + {{Msg, IsDelivered, AckTag}, State3} = + internal_fetch(true, MsgStatus1, State2), + Acc1 = Fun(Msg, IsDelivered, AckTag, Acc), + fetchwhile(Pred, Fun, Acc1, State3); + false -> {MsgProps, Acc, a(in_r(MsgStatus, State1))} end end. -- cgit v1.2.1 From bdee19cebc803a712999ea7d201b45ef6e78f238 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 30 Nov 2012 00:28:05 +0000 Subject: remove unused state var We were diligently maintaining ack_msg_id, but never actually using it for anything. And it's always been like that. --- src/rabbit_mirror_queue_master.erl | 43 +++++++++++++------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index c8a361b1..009971bb 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -40,7 +40,6 @@ backing_queue_state, seen_status, confirmed, - ack_msg_id, known_senders }). @@ -56,7 +55,6 @@ backing_queue_state :: any(), seen_status :: dict(), confirmed :: [rabbit_guid:guid()], - ack_msg_id :: dict(), known_senders :: set() }). @@ -114,7 +112,6 @@ init_with_existing_bq(Q = #amqqueue{name = QName}, BQ, BQS) -> backing_queue_state = BQS, seen_status = dict:new(), confirmed = [], - ack_msg_id = dict:new(), known_senders = sets:new() }. stop_mirroring(State = #state { coordinator = CPid, @@ -187,13 +184,11 @@ publish_delivered(Msg = #basic_message { id = MsgId }, MsgProps, ChPid, State = #state { gm = GM, seen_status = SS, backing_queue = BQ, - backing_queue_state = BQS, - ack_msg_id = AM }) -> + backing_queue_state = BQS }) -> false = dict:is_key(MsgId, SS), %% ASSERTION ok = gm:broadcast(GM, {publish_delivered, ChPid, MsgProps, Msg}), {AckTag, BQS1} = BQ:publish_delivered(Msg, MsgProps, ChPid, BQS), - AM1 = maybe_store_acktag(AckTag, MsgId, AM), - State1 = State #state { backing_queue_state = BQS1, ack_msg_id = AM1 }, + State1 = State #state { backing_queue_state = BQS1 }, {AckTag, ensure_monitoring(ChPid, State1)}. discard(MsgId, ChPid, State = #state { gm = GM, @@ -264,34 +259,29 @@ fetch(AckRequired, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> {Result, BQS1} = BQ:fetch(AckRequired, BQS), State1 = State #state { backing_queue_state = BQS1 }, - case Result of - empty -> - {Result, State1}; - {#basic_message{id = MsgId}, _IsDelivered, AckTag} -> - {Result, drop(MsgId, AckTag, State1)} - end. + {Result, case Result of + empty -> State1; + {_MsgId, _IsDelivered, AckTag} -> drop_one(AckTag, State1) + end}. drop(AckRequired, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> {Result, BQS1} = BQ:drop(AckRequired, BQS), State1 = State #state { backing_queue_state = BQS1 }, {Result, case Result of - empty -> State1; - {MsgId, AckTag} -> drop(MsgId, AckTag, State1) + empty -> State1; + {_MsgId, AckTag} -> drop_one(AckTag, State1) end}. ack(AckTags, State = #state { gm = GM, backing_queue = BQ, - backing_queue_state = BQS, - ack_msg_id = AM }) -> + backing_queue_state = BQS }) -> {MsgIds, BQS1} = BQ:ack(AckTags, BQS), case MsgIds of [] -> ok; _ -> ok = gm:broadcast(GM, {ack, MsgIds}) end, - AM1 = lists:foldl(fun dict:erase/2, AM, AckTags), - {MsgIds, State #state { backing_queue_state = BQS1, - ack_msg_id = AM1 }}. + {MsgIds, State #state { backing_queue_state = BQS1 }}. foreach_ack(MsgFun, State = #state { backing_queue = BQ, backing_queue_state = BQS }, AckTags) -> @@ -408,7 +398,6 @@ promote_backing_queue_state(CPid, BQ, BQS, GM, AckTags, SeenStatus, KS) -> backing_queue_state = BQS1, seen_status = SeenStatus, confirmed = [], - ack_msg_id = dict:new(), known_senders = sets:from_list(KS) }. sender_death_fun() -> @@ -440,15 +429,11 @@ depth_fun() -> %% Helpers %% --------------------------------------------------------------------------- -drop(MsgId, AckTag, State = #state { ack_msg_id = AM, - gm = GM, - backing_queue = BQ, - backing_queue_state = BQS }) -> +drop_one(AckTag, State = #state { gm = GM, + backing_queue = BQ, + backing_queue_state = BQS }) -> ok = gm:broadcast(GM, {drop, BQ:len(BQS), 1, AckTag =/= undefined}), - State #state { ack_msg_id = maybe_store_acktag(AckTag, MsgId, AM) }. - -maybe_store_acktag(undefined, _MsgId, AM) -> AM; -maybe_store_acktag(AckTag, MsgId, AM) -> dict:store(AckTag, MsgId, AM). + State. ensure_monitoring(ChPid, State = #state { coordinator = CPid, known_senders = KS }) -> -- cgit v1.2.1 From fb7011f1e3b1487431b0e72031f4d844e53041af Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 30 Nov 2012 00:53:15 +0000 Subject: unbreak qc (hopefully) --- src/rabbit_backing_queue_qc.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index a5d0a008..a7e0a5e7 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -115,7 +115,7 @@ qc_publish(#state{bqstate = BQ}) -> #message_properties{needs_confirming = frequency([{1, true}, {20, false}]), expiry = oneof([undefined | lists:seq(1, 10)])}, - self(), BQ]}. + false, self(), BQ]}. qc_publish_multiple(#state{}) -> {call, ?MODULE, publish_multiple, [resize(?QUEUE_MAXLEN, pos_integer())]}. @@ -182,7 +182,7 @@ precondition(#state{len = Len}, {call, ?MODULE, publish_multiple, _Arg}) -> %% Model updates -next_state(S, BQ, {call, ?BQMOD, publish, [Msg, MsgProps, _Pid, _BQ]}) -> +next_state(S, BQ, {call, ?BQMOD, publish, [Msg, MsgProps, _Del, _Pid, _BQ]}) -> #state{len = Len, messages = Messages, confirms = Confirms, -- cgit v1.2.1 From dcb5df1ee084f3c158812d480e469ae856115dc6 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 30 Nov 2012 10:55:31 +0000 Subject: oops - we mean 'not is_permanent' here --- src/supervisor2.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index b1a4a6c1..251d0d51 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -1142,7 +1142,7 @@ monitor_dynamic_children(#child{restart_type=RType}, Dynamics) -> case monitor_child(P) of ok -> {?SETS:add_element(P, Pids), EStack}; - {error, normal} when ?is_permanent(RType) -> + {error, normal} when not ?is_permanent(RType) -> {Pids, EStack}; {error, Reason} -> {Pids, ?DICT:append(Reason, P, EStack)} @@ -1181,7 +1181,7 @@ wait_dynamic_children(#child{restart_type=RType} = Child, Pids, Sz, wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, TRef, EStack); - {'DOWN', _MRef, process, Pid, normal} when ?is_permanent(RType) -> + {'DOWN', _MRef, process, Pid, normal} when not ?is_permanent(RType) -> wait_dynamic_children(Child, ?SETS:del_element(Pid, Pids), Sz-1, TRef, EStack); -- cgit v1.2.1 From 1ad3a10e7ef1ec75bb367af02ebd0b447eaeae41 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 30 Nov 2012 11:01:16 +0000 Subject: document addition of find_child/2 --- src/supervisor2.erl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 251d0d51..0da00e58 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -3,7 +3,9 @@ %% %% 1) the module name is supervisor2 %% -%% 2) child specifications can contain, as the restart type, a tuple +%% 2) a find_child/2 utility function has been added +%% +%% 3) child specifications can contain, as the restart type, a tuple %% {permanent, Delay} | {transient, Delay} | {intrinsic, Delay} %% where Delay >= 0 (see point (4) below for intrinsic). The delay, %% in seconds, indicates what should happen if a child, upon being @@ -36,14 +38,14 @@ %% perspective it's a normal exit, whilst from supervisor's %% perspective, it's an abnormal exit. %% -%% 3) Added an 'intrinsic' restart type. Like the transient type, this +%% 4) Added an 'intrinsic' restart type. Like the transient type, this %% type means the child should only be restarted if the child exits %% abnormally. Unlike the transient type, if the child exits %% normally, the supervisor itself also exits normally. If the %% child is a supervisor and it exits normally (i.e. with reason of %% 'shutdown') then the child's parent also exits normally. %% -%% 4) normal, and {shutdown, _} exit reasons are all treated the same +%% 5) normal, and {shutdown, _} exit reasons are all treated the same %% (i.e. are regarded as normal exits) %% %% All modifications are (C) 2010-2012 VMware, Inc. -- cgit v1.2.1 From 91a39d3368f2a6590776d7defc896ba325b7fc8a Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 30 Nov 2012 16:37:45 +0000 Subject: Emit events on vhost creation / deletion. --- src/rabbit_vhost.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rabbit_vhost.erl b/src/rabbit_vhost.erl index 297fa56f..4fe5c169 100644 --- a/src/rabbit_vhost.erl +++ b/src/rabbit_vhost.erl @@ -70,6 +70,7 @@ add(VHostPath) -> {<<"amq.rabbitmq.trace">>, topic}]], ok end), + rabbit_event:notify(vhost_created, info(VHostPath)), R. delete(VHostPath) -> @@ -87,6 +88,7 @@ delete(VHostPath) -> with(VHostPath, fun () -> ok = internal_delete(VHostPath) end)), + ok = rabbit_event:notify(vhost_deleted, [{name, VHostPath}]), R. internal_delete(VHostPath) -> -- cgit v1.2.1 From cf229b48130743509783e65927c6ca77928c6dc8 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 3 Dec 2012 13:02:30 +0000 Subject: Whitespace --- src/rabbit_amqqueue_process.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index a6b3829b..2ffa2a1a 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -743,7 +743,7 @@ drop_expired_messages(State = #q{dlx = DLX, end, State#q{backing_queue_state = BQS1}). accumulate_msgs(Msg, _IsDelivered, AckTag, Acc) -> [{Msg, AckTag} | Acc]. - + ensure_ttl_timer(undefined, State) -> State; ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = undefined}) -> -- cgit v1.2.1 From cfb072ffe9519536bcee15efdd846f73886aeb98 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 3 Dec 2012 18:44:11 +0000 Subject: add test for vq:fetchwhile --- src/rabbit_tests.erl | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index d6d40b14..d9ef4439 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2307,7 +2307,7 @@ test_variable_queue() -> fun test_variable_queue_all_the_bits_not_covered_elsewhere2/1, fun test_drop/1, fun test_variable_queue_fold_msg_on_disk/1, - fun test_dropwhile/1, + fun test_dropfetchwhile/1, fun test_dropwhile_varying_ram_duration/1, fun test_variable_queue_ack_limiting/1, fun test_variable_queue_requeue/1, @@ -2409,7 +2409,7 @@ test_drop(VQ0) -> true = rabbit_variable_queue:is_empty(VQ5), VQ5. -test_dropwhile(VQ0) -> +test_dropfetchwhile(VQ0) -> Count = 10, %% add messages with sequential expiry @@ -2417,23 +2417,32 @@ test_dropwhile(VQ0) -> false, Count, fun (N, Props) -> Props#message_properties{expiry = N} end, VQ0), + %% fetch the first 5 messages + {#message_properties{expiry = 6}, AckTags, VQ2} = + rabbit_variable_queue:fetchwhile( + fun (#message_properties{expiry = Expiry}) -> Expiry =< 5 end, + fun (_Msg, _Delivered, AckTag, Acc) -> [AckTag | Acc] end, [], VQ1), + 5 = length(AckTags), + + %% requeue them + {_MsgIds, VQ3} = rabbit_variable_queue:requeue(AckTags, VQ2), + %% drop the first 5 messages - {_, VQ2} = rabbit_variable_queue:dropwhile( - fun(#message_properties { expiry = Expiry }) -> - Expiry =< 5 - end, VQ1), + {#message_properties{expiry = 6}, VQ4} = + rabbit_variable_queue:dropwhile( + fun (#message_properties {expiry = Expiry}) -> Expiry =< 5 end, VQ3), - %% fetch five now - VQ3 = lists:foldl(fun (_N, VQN) -> + %% fetch 5 now + VQ5 = lists:foldl(fun (_N, VQN) -> {{#basic_message{}, _, _}, VQM} = rabbit_variable_queue:fetch(false, VQN), VQM - end, VQ2, lists:seq(6, Count)), + end, VQ4, lists:seq(6, Count)), %% should be empty now - {empty, VQ4} = rabbit_variable_queue:fetch(false, VQ3), + {empty, VQ6} = rabbit_variable_queue:fetch(false, VQ5), - VQ4. + VQ6. test_dropwhile_varying_ram_duration(VQ0) -> VQ1 = variable_queue_publish(false, 1, VQ0), -- cgit v1.2.1 From ccd92d8620f0441002c184d65a60275abb066cbf Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 3 Dec 2012 18:56:33 +0000 Subject: test more --- src/rabbit_tests.erl | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index d9ef4439..42c16b34 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2415,14 +2415,17 @@ test_dropfetchwhile(VQ0) -> %% add messages with sequential expiry VQ1 = variable_queue_publish( false, Count, - fun (N, Props) -> Props#message_properties{expiry = N} end, VQ0), + fun (N, Props) -> Props#message_properties{expiry = N} end, + fun erlang:term_to_binary/1, VQ0), %% fetch the first 5 messages - {#message_properties{expiry = 6}, AckTags, VQ2} = + {#message_properties{expiry = 6}, {Msgs, AckTags}, VQ2} = rabbit_variable_queue:fetchwhile( fun (#message_properties{expiry = Expiry}) -> Expiry =< 5 end, - fun (_Msg, _Delivered, AckTag, Acc) -> [AckTag | Acc] end, [], VQ1), - 5 = length(AckTags), + fun (Msg, _Delivered, AckTag, {MsgAcc, AckAcc}) -> + {[Msg | MsgAcc], [AckTag | AckAcc]} + end, {[], []}, VQ1), + true = lists:seq(1, 5) == [msg2int(M) || M <- lists:reverse(Msgs)], %% requeue them {_MsgIds, VQ3} = rabbit_variable_queue:requeue(AckTags, VQ2), @@ -2432,17 +2435,18 @@ test_dropfetchwhile(VQ0) -> rabbit_variable_queue:dropwhile( fun (#message_properties {expiry = Expiry}) -> Expiry =< 5 end, VQ3), - %% fetch 5 now - VQ5 = lists:foldl(fun (_N, VQN) -> - {{#basic_message{}, _, _}, VQM} = + %% fetch 5 + VQ5 = lists:foldl(fun (N, VQN) -> + {{Msg, _, _}, VQM} = rabbit_variable_queue:fetch(false, VQN), + true = msg2int(Msg) == N, VQM end, VQ4, lists:seq(6, Count)), %% should be empty now - {empty, VQ6} = rabbit_variable_queue:fetch(false, VQ5), + true = rabbit_variable_queue:is_empty(VQ5), - VQ6. + VQ5. test_dropwhile_varying_ram_duration(VQ0) -> VQ1 = variable_queue_publish(false, 1, VQ0), -- cgit v1.2.1 From a0cf7e4ab2261a85299deaaa3ae6229795c2df28 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 3 Dec 2012 19:14:41 +0000 Subject: yet more tests --- src/rabbit_tests.erl | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 42c16b34..2226f445 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2309,6 +2309,7 @@ test_variable_queue() -> fun test_variable_queue_fold_msg_on_disk/1, fun test_dropfetchwhile/1, fun test_dropwhile_varying_ram_duration/1, + fun test_fetchwhile_varying_ram_duration/1, fun test_variable_queue_ack_limiting/1, fun test_variable_queue_requeue/1, fun test_variable_queue_fold/1]], @@ -2449,12 +2450,30 @@ test_dropfetchwhile(VQ0) -> VQ5. test_dropwhile_varying_ram_duration(VQ0) -> + test_dropfetchwhile_varying_ram_duration( + fun (VQ1) -> + {_, VQ2} = rabbit_variable_queue:dropwhile( + fun (_) -> false end, VQ1), + VQ2 + end, VQ0). + +test_fetchwhile_varying_ram_duration(VQ0) -> + test_dropfetchwhile_varying_ram_duration( + fun (VQ1) -> + {_, ok, VQ2} = rabbit_variable_queue:fetchwhile( + fun (_) -> false end, + fun (_, _, _, A) -> A end, + ok, VQ1), + VQ2 + end, VQ0). + +test_dropfetchwhile_varying_ram_duration(Fun, VQ0) -> VQ1 = variable_queue_publish(false, 1, VQ0), VQ2 = rabbit_variable_queue:set_ram_duration_target(0, VQ1), - {_, VQ3} = rabbit_variable_queue:dropwhile(fun(_) -> false end, VQ2), + VQ3 = Fun(VQ2), VQ4 = rabbit_variable_queue:set_ram_duration_target(infinity, VQ3), VQ5 = variable_queue_publish(false, 1, VQ4), - {_, VQ6} = rabbit_variable_queue:dropwhile(fun(_) -> false end, VQ5), + VQ6 = Fun(VQ5), VQ6. test_variable_queue_dynamic_duration_change(VQ0) -> -- cgit v1.2.1 From 89f60604808dbbf7e0d6ea88192bf68e4cafe113 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 4 Dec 2012 12:01:14 +0000 Subject: remove pre-0-9-1 cruft --- src/rabbit_channel.erl | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index b1ef3b6b..a3c82865 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -529,16 +529,12 @@ check_not_default_exchange(_) -> %% check that an exchange/queue name does not contain the reserved %% "amq." prefix. %% -%% One, quite reasonable, interpretation of the spec, taken by the -%% QPid M1 Java client, is that the exclusion of "amq." prefixed names +%% As per the AMQP 0-9-1 spec, the exclusion of "amq." prefixed names %% only applies on actual creation, and not in the cases where the -%% entity already exists. This is how we use this function in the code -%% below. However, AMQP JIRA 123 changes that in 0-10, and possibly -%% 0-9SP1, making it illegal to attempt to declare an exchange/queue -%% with an amq.* name when passive=false. So this will need -%% revisiting. +%% entity already exists or passive=true. %% -%% TODO: enforce other constraints on name. See AMQP JIRA 69. +%% NB: We deliberately do not enforce the other constraints on names +%% required by the spec. check_name(Kind, NameBin = <<"amq.", _/binary>>) -> rabbit_misc:protocol_error( access_refused, -- cgit v1.2.1 From 427e44ed9712db32a020ffc2357bb04aef09fbc0 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Tue, 4 Dec 2012 14:13:42 +0000 Subject: change comments to reflect merging with R15B-03 --- src/supervisor2.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 0da00e58..da37ba39 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -1,4 +1,4 @@ -%% This file is a copy of supervisor.erl from the R13B-3 Erlang/OTP +%% This file is a copy of supervisor.erl from the R15B-3 Erlang/OTP %% distribution, with the following modifications: %% %% 1) the module name is supervisor2 -- cgit v1.2.1 From 265af7bac59bc588e53b9e59cd546922d6983e86 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 5 Dec 2012 12:04:08 +0000 Subject: cosmetic - reduce distance to OTP --- src/supervisor2.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 0da00e58..4e7cbe43 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -92,12 +92,12 @@ -ifdef(use_specs). -type child() :: 'undefined' | pid(). -type child_id() :: term(). --type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. --type modules() :: [module()] | 'dynamic'. --type delay() :: non_neg_integer(). --type restart() :: 'permanent' | 'transient' | 'temporary' | 'intrinsic' | {'permanent', delay()} | {'transient', delay()} | {'intrinsic', delay()}. +-type mfargs() :: {M :: module(), F :: atom(), A :: [term()] | undefined}. +-type modules() :: [module()] | 'dynamic'. +-type delay() :: non_neg_integer(). +-type restart() :: 'permanent' | 'transient' | 'temporary' | 'intrinsic' | {'permanent', delay()} | {'transient', delay()} | {'intrinsic', delay()}. -type shutdown() :: 'brutal_kill' | timeout(). --type worker() :: 'worker' | 'supervisor'. +-type worker() :: 'worker' | 'supervisor'. -type sup_name() :: {'local', Name :: atom()} | {'global', Name :: atom()}. -type sup_ref() :: (Name :: atom()) | {Name :: atom(), Node :: node()} -- cgit v1.2.1 From 9803385de90f12f5d1dbdc1a6f716927d977b066 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 5 Dec 2012 12:04:59 +0000 Subject: use ?MODULE macro when appropriate --- src/supervisor2.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 4e7cbe43..d4d9e106 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -195,7 +195,7 @@ -endif. start_link(Mod, Args) -> - gen_server:start_link(supervisor2, {self, Mod, Args}, []). + gen_server:start_link(?MODULE, {self, Mod, Args}, []). -ifdef(use_specs). -spec start_link(SupName, Module, Args) -> startlink_ret() when -- cgit v1.2.1 From 9d43c1ef36cae496223e5058afc927ebdf800ab4 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 5 Dec 2012 12:07:08 +0000 Subject: reduce distance to OTP - {temporary, Delay} is not a valid restart type --- src/supervisor2.erl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index d4d9e106..e7e13672 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -393,12 +393,8 @@ init_dynamic(_State, StartSpec) -> start_children(Children, SupName) -> start_children(Children, [], SupName). start_children([Child|Chs], NChildren, SupName) -> - Restart = case Child#child.restart_type of - A when is_atom(A) -> A; - {N, _} when is_atom(N) -> N - end, case do_start_child(SupName, Child) of - {ok, undefined} when Restart =:= temporary -> + {ok, undefined} when Child#child.restart_type =:= temporary -> start_children(Chs, NChildren, SupName); {ok, Pid} -> start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); @@ -453,13 +449,9 @@ do_start_child_i(M, F, A) -> handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> Child = hd(State#state.children), #child{mfargs = {M, F, A}} = Child, - Restart = case Child#child.restart_type of - Name when is_atom(Name) -> Name; - {Type, _} when is_atom(Type) -> Type - end, Args = A ++ EArgs, case do_start_child_i(M, F, Args) of - {ok, undefined} when Restart =:= temporary -> + {ok, undefined} when Child#child.restart_type =:= temporary -> {reply, {ok, undefined}, State}; {ok, Pid} -> NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State), -- cgit v1.2.1 From 369a5aeda3532ed9bbd1657c07a87c1cdda07a92 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 5 Dec 2012 12:08:09 +0000 Subject: oops - remember to guard specs for R12B03 compatibility --- src/supervisor2.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index e7e13672..36d00732 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -443,9 +443,10 @@ do_start_child_i(M, F, A) -> %%% Callback functions. %%% %%% --------------------------------------------------- +-ifdef(use_specs). -type call() :: 'which_children' | 'count_children' | {_, _}. % XXX: refine -spec handle_call(call(), term(), state()) -> {'reply', term(), state()}. - +-endif. handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> Child = hd(State#state.children), #child{mfargs = {M, F, A}} = Child, -- cgit v1.2.1 From 9e7064134d5c71a208bd8e4cb2f13fd8e186fa12 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 5 Dec 2012 12:14:19 +0000 Subject: reduce distance to OTP - pattern match in case, not-found is always 'false' --- src/supervisor2.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 36d00732..8d6eaa37 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -834,10 +834,9 @@ restart_child(Pid, Reason, State) -> Children = State#state.children, %% we still support >= R12-B3 in which lists:keyfind/3 doesn't exist case lists:keysearch(Pid, #child.pid, Children) of - {value, Child} -> - RestartType = Child#child.restart_type, + {value, #child{restart_type = RestartType} = Child} -> do_restart(RestartType, Reason, Child, State); - _ -> + false -> {ok, State} end. -- cgit v1.2.1 From 598b0e0f907ea6e51241d7858eab04acc6830fbf Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 5 Dec 2012 12:15:56 +0000 Subject: infinity is always a valid shutdown reason --- src/supervisor2.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 8d6eaa37..f0182822 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -1428,7 +1428,7 @@ validDelay(What) -> throw({invalid_delay, What}). validShutdown(Shutdown, _) when is_integer(Shutdown), Shutdown > 0 -> true; -validShutdown(infinity, supervisor) -> true; +validShutdown(infinity, _) -> true; validShutdown(brutal_kill, _) -> true; validShutdown(Shutdown, _) -> throw({invalid_shutdown, Shutdown}). -- cgit v1.2.1 From bd4dad1150be114357b8c5730c9942b43b425c5b Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 5 Dec 2012 13:50:04 +0000 Subject: Normalise credit flow quantities. --- src/rabbit_mirror_queue_sync.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 4cb534ff..a80f8f50 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -123,7 +123,7 @@ syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> {msg, Ref, Msg, MsgProps} -> SPidsMRefs1 = wait_for_credit(SPidsMRefs, Ref), [begin - credit_flow:send(SPid, ?CREDIT_DISC_BOUND), + credit_flow:send(SPid), SPid ! {sync_msg, Ref, Msg, MsgProps} end || {SPid, _} <- SPidsMRefs1], syncer_loop(Args, SPidsMRefs1); @@ -205,7 +205,7 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, {TRef1, BQS1} = UpdateRamDuration(BQ, BQS), slave_sync_loop(Args, TRef1, BQS1); {sync_msg, Ref, Msg, Props} -> - credit_flow:ack(Syncer, ?CREDIT_DISC_BOUND), + credit_flow:ack(Syncer), Props1 = Props#message_properties{needs_confirming = false}, BQS1 = BQ:publish(Msg, Props1, true, none, BQS), slave_sync_loop(Args, TRef, BQS1); -- cgit v1.2.1 From 3761e87b40caaf58962655130bb5eea3f9d20860 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 5 Dec 2012 16:28:25 +0000 Subject: send expired messages to self() one at a time which simplifies the code a fair bit and also means we don't build up a potentially huge intermediate data structure. --- src/rabbit_amqqueue_process.erl | 69 ++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 2ffa2a1a..2bb476fb 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -724,26 +724,24 @@ drop_expired_messages(State = #q{dlx = DLX, backing_queue = BQ }) -> Now = now_micros(), ExpirePred = fun (#message_properties{expiry = Exp}) -> Now >= Exp end, - {Props, BQS1} = case DLX of - undefined -> BQ:dropwhile(ExpirePred, BQS); - _ -> {Next, Msgs, BQS2} = - BQ:fetchwhile(ExpirePred, - fun accumulate_msgs/4, - [], BQS), - case Msgs of - [] -> ok; - _ -> (dead_letter_fun(expired))( - lists:reverse(Msgs)) - end, - {Next, BQS2} - end, + {Props, BQS1} = + case DLX of + undefined -> BQ:dropwhile(ExpirePred, BQS); + _ -> DLXFun = dead_letter_fun(expired), + {Next, ok, BQS2} = + BQ:fetchwhile( + ExpirePred, + fun (Msg, _IsDelivered, AckTag, Acc) -> + DLXFun(Msg, AckTag), + Acc + end, ok, BQS), + {Next, BQS2} + end, ensure_ttl_timer(case Props of undefined -> undefined; #message_properties{expiry = Exp} -> Exp end, State#q{backing_queue_state = BQS1}). -accumulate_msgs(Msg, _IsDelivered, AckTag, Acc) -> [{Msg, AckTag} | Acc]. - ensure_ttl_timer(undefined, State) -> State; ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = undefined}) -> @@ -764,7 +762,9 @@ ensure_ttl_timer(_Expiry, State) -> State. dead_letter_fun(Reason) -> - fun(Msgs) -> gen_server2:cast(self(), {dead_letter, Msgs, Reason}) end. + fun(Msg, AckTag) -> + gen_server2:cast(self(), {dead_letter, Msg, AckTag, Reason}) + end. dead_letter_publish(Msg, Reason, X, State = #q{publish_seqno = MsgSeqNo}) -> DLMsg = make_dead_letter_msg(Reason, Msg, State), @@ -1213,8 +1213,7 @@ handle_cast({reject, AckTags, false, ChPid}, State) -> ChPid, AckTags, State, fun (State1 = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - BQS1 = BQ:foreach_ack(fun(M, A) -> DLXFun([{M, A}]) end, - BQS, AckTags), + BQS1 = BQ:foreach_ack(DLXFun, BQS, AckTags), State1#q{backing_queue_state = BQS1} end)); @@ -1262,29 +1261,23 @@ handle_cast({set_maximum_since_use, Age}, State) -> ok = file_handle_cache:set_maximum_since_use(Age), noreply(State); -handle_cast({dead_letter, Msgs, Reason}, State = #q{dlx = XName}) -> +handle_cast({dead_letter, Msg, AckTag, Reason}, + State = #q{dlx = XName, + publish_seqno = SeqNo, + unconfirmed = UC, + queue_monitors = QMons}) -> case rabbit_exchange:lookup(XName) of {ok, X} -> - {AckImmediately, State2} = - lists:foldl( - fun({Msg, AckTag}, - {Acks, State1 = #q{publish_seqno = SeqNo, - unconfirmed = UC, - queue_monitors = QMons}}) -> - case dead_letter_publish(Msg, Reason, X, State1) of - [] -> {[AckTag | Acks], State1}; - QPids -> UC1 = dtree:insert( - SeqNo, QPids, AckTag, UC), - QMons1 = pmon:monitor_all(QPids, QMons), - {Acks, - State1#q{publish_seqno = SeqNo + 1, - unconfirmed = UC1, - queue_monitors = QMons1}} - end - end, {[], State}, Msgs), - cleanup_after_confirm(AckImmediately, State2); + case dead_letter_publish(Msg, Reason, X, State) of + [] -> cleanup_after_confirm([AckTag], State); + QPids -> UC1 = dtree:insert(SeqNo, QPids, AckTag, UC), + QMons1 = pmon:monitor_all(QPids, QMons), + State#q{publish_seqno = SeqNo + 1, + unconfirmed = UC1, + queue_monitors = QMons1} + end; {error, not_found} -> - cleanup_after_confirm([AckTag || {_, AckTag} <- Msgs], State) + cleanup_after_confirm([AckTag], State) end; handle_cast(start_mirroring, State = #q{backing_queue = BQ, -- cgit v1.2.1 From 1ca44d16f3f9bd4fe9933a01ffcc84a990bd0320 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Thu, 6 Dec 2012 16:46:54 +0100 Subject: refactors the sed bits inside the Makefile --- packaging/standalone/Makefile | 14 +++++++++++++- packaging/standalone/fix-rabbitmq-defaults.sh | 20 -------------------- 2 files changed, 13 insertions(+), 21 deletions(-) delete mode 100755 packaging/standalone/fix-rabbitmq-defaults.sh diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index fced396f..50d1385a 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -13,6 +13,10 @@ RABBITMQ_EBIN_ROOT=$(RABBITMQ_HOME)/ebin RABBITMQ_PLUGINS_DIR=$(RABBITMQ_HOME)/plugins RABBITMQ_PLUGINS_EXPAND_DIR=$(RABBITMQ_PLUGINS_DIR)/expand +RABBITMQ_DEFAULTS=$(TARGET_DIR)/sbin/rabbitmq-defaults +fix_defaults = sed -e $(1) $(RABBITMQ_DEFAULTS) > $(RABBITMQ_DEFAULTS).tmp \ + && mv $(RABBITMQ_DEFAULTS).tmp $(RABBITMQ_DEFAULTS) + dist: tar -zxf ../../dist/$(SOURCE_DIR).tar.gz @@ -22,7 +26,15 @@ dist: MAN_DIR=`pwd`/$(TARGET_DIR)/share/man \ install - ./fix-rabbitmq-defaults.sh $(TARGET_DIR) $(ERTS_VSN) $(VERSION) +## Here we set the RABBITMQ_HOME variable, +## then we make ERL_DIR point to our released erl +## and we add the paths to our released start_clean and start_sasl boot scripts + $(call fix_defaults,'s:^SYS_PREFIX=$$:SYS_PREFIX=\$${RABBITMQ_HOME}:') + $(call fix_defaults,'s:^ERL_DIR=$$:ERL_DIR=\$${RABBITMQ_HOME}/erts-$(ERTS_VSN)/bin/:') + $(call fix_defaults,'s:start_clean$$:"\$${SYS_PREFIX}/releases/$(VERSION)/start_clean":') + $(call fix_defaults,'s:start_sasl:"\$${SYS_PREFIX}/releases/$(VERSION)/start_sasl":') + + chmod 0755 $(RABBITMQ_DEFAULTS) mkdir -p $(TARGET_DIR)/etc/rabbitmq diff --git a/packaging/standalone/fix-rabbitmq-defaults.sh b/packaging/standalone/fix-rabbitmq-defaults.sh deleted file mode 100755 index 021c47bc..00000000 --- a/packaging/standalone/fix-rabbitmq-defaults.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -TARGET_DIR=$1 -ERTS_VSN=$2 -VERSION=$3 - -## Here we set the RABBITMQ_HOME variable, -## then we make ERL_DIR point to our released erl -## and we add the paths to our released start_clean and start_sasl boot scripts - -sed -e 's:^SYS_PREFIX=$:SYS_PREFIX=\${RABBITMQ_HOME}:' \ - "${TARGET_DIR}"/sbin/rabbitmq-defaults >"${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:^ERL_DIR=$:ERL_DIR=\${RABBITMQ_HOME}/erts-'"${ERTS_VSN}"'/bin/:' \ - "${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp >"${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp1 \ - && sed -e 's:start_clean$:"\${SYS_PREFIX}/releases/'"${VERSION}"'/start_clean":' \ - "${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp1 >"${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp \ - && sed -e 's:start_sasl:"\${SYS_PREFIX}/releases/'"${VERSION}"'/start_sasl":' \ - "${TARGET_DIR}"/sbin/rabbitmq-defaults.tmp >"${TARGET_DIR}"/sbin/rabbitmq-defaults - -chmod 0755 "${TARGET_DIR}"/sbin/rabbitmq-defaults -- cgit v1.2.1 From 5b6b87c7b519688bdd4eecfeae983dac66fb6c88 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Thu, 6 Dec 2012 17:18:51 +0100 Subject: cosmetics --- packaging/standalone/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 50d1385a..0dde6fa6 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -65,7 +65,10 @@ clean_partial: .PHONY : generate_release generate_release: - erlc -I $(TARGET_DIR)/include/ -o src -Wall -v +debug_info -Duse_specs -Duse_proper_qc -pa $(TARGET_DIR)/ebin/ src/rabbit_release.erl + erlc \ + -I $(TARGET_DIR)/include/ -o src -Wall \ + -v +debug_info -Duse_specs -Duse_proper_qc \ + -pa $(TARGET_DIR)/ebin/ src/rabbit_release.erl erl \ -pa "$(RABBITMQ_EBIN_ROOT)" \ -pa src \ -- cgit v1.2.1 From 8f088d8fe0403274b417bf7baad16de317ac23bb Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 6 Dec 2012 16:52:13 +0000 Subject: Add rabbitmqctl sync_queue, and make it synchronous. Also fix the fact that we were treating "no queues to sync" as a 'normal' crash of the syncer, which... no. --- docs/rabbitmqctl.1.xml | 31 +++++++++++++++++++++++++++++++ src/rabbit_amqqueue.erl | 14 ++++++++++---- src/rabbit_amqqueue_process.erl | 7 +++---- src/rabbit_control_main.erl | 7 +++++++ src/rabbit_mirror_queue_master.erl | 11 ++++++----- src/rabbit_mirror_queue_sync.erl | 11 ++++++++++- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/docs/rabbitmqctl.1.xml b/docs/rabbitmqctl.1.xml index 34947b66..1583ab98 100644 --- a/docs/rabbitmqctl.1.xml +++ b/docs/rabbitmqctl.1.xml @@ -446,6 +446,37 @@ + + sync_queue queue + + + + + queue + + + The name of the queue to synchronise. + + + + + + Instructs a mirrored queue with unsynchronised slaves to + synchronise itself. The queue will block while + synchronisation takes place (all publishers to and + consumers from the queue will block). The queue must be + mirrored, must have unsynchronised slaves, and must not + have any pending unacknowledged messages for this + command to succeed. + + + Note that unsynchronised queues from which messages are + being drained will become synchronised eventually. This + command is primarily useful for queues which are not + being drained. + + + diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 4bdab0bc..ae96f739 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -31,7 +31,7 @@ -export([notify_down_all/2, limit_all/3]). -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). --export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1]). +-export([start_mirroring/1, stop_mirroring/1, sync/2]). %% internal -export([internal_declare/2, internal_delete/1, run_backing_queue/3, @@ -173,8 +173,9 @@ (rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'). -spec(start_mirroring/1 :: (pid()) -> 'ok'). -spec(stop_mirroring/1 :: (pid()) -> 'ok'). --spec(sync_mirrors/1 :: (rabbit_types:amqqueue()) -> - 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). +-spec(sync/2 :: (binary(), rabbit_types:vhost()) -> + 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored' | + 'already_synced')). -endif. @@ -592,7 +593,12 @@ set_maximum_since_use(QPid, Age) -> start_mirroring(QPid) -> ok = delegate_cast(QPid, start_mirroring). stop_mirroring(QPid) -> ok = delegate_cast(QPid, stop_mirroring). -sync_mirrors(#amqqueue{pid = QPid}) -> delegate_call(QPid, sync_mirrors). +sync(QNameBin, VHostBin) -> + QName = rabbit_misc:r(VHostBin, queue, QNameBin), + case lookup(QName) of + {ok, #amqqueue{pid = QPid}} -> delegate_call(QPid, sync_mirrors); + E -> E + end. on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index acbea4e9..730c235e 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1153,15 +1153,14 @@ handle_call({requeue, AckTags, ChPid}, From, State) -> gen_server2:reply(From, ok), noreply(requeue(AckTags, ChPid, State)); -handle_call(sync_mirrors, From, +handle_call(sync_mirrors, _From, State = #q{backing_queue = rabbit_mirror_queue_master = BQ, backing_queue_state = BQS}) -> S = fun(BQSN) -> State#q{backing_queue_state = BQSN} end, case BQ:depth(BQS) - BQ:len(BQS) of - 0 -> gen_server2:reply(From, ok), - case rabbit_mirror_queue_master:sync_mirrors(BQS) of + 0 -> case rabbit_mirror_queue_master:sync_mirrors(BQS) of {shutdown, Reason, BQS1} -> {stop, Reason, S(BQS1)}; - {ok, BQS1} -> noreply(S(BQS1)) + {Result, BQS1} -> reply(Result, S(BQS1)) end; _ -> reply({error, pending_acks}, State) end; diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index 669a0787..b4272555 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -50,6 +50,7 @@ update_cluster_nodes, {forget_cluster_node, [?OFFLINE_DEF]}, cluster_status, + {sync_queue, [?VHOST_DEF]}, add_user, delete_user, @@ -280,6 +281,12 @@ action(forget_cluster_node, Node, [ClusterNodeS], Opts, Inform) -> rpc_call(Node, rabbit_mnesia, forget_cluster_node, [ClusterNode, RemoveWhenOffline]); +action(sync_queue, Node, [Queue], Opts, Inform) -> + VHost = proplists:get_value(?VHOST_OPT, Opts), + Inform("Synchronising queue ~s in ~s", [Queue, VHost]), + rpc_call(Node, rabbit_amqqueue, sync, + [list_to_binary(Queue), list_to_binary(VHost)]); + action(wait, Node, [PidFile], _Opts, Inform) -> Inform("Waiting for ~p", [Node]), wait_for_application(Node, PidFile, rabbit_and_plugins, Inform); diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 3d7f902c..03a712d6 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -142,11 +142,12 @@ sync_mirrors(State = #state { name = QName, gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, case rabbit_mirror_queue_sync:master_go(Syncer, Ref, Log, BQ, BQS) of - {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; - {sync_died, R, BQS1} -> Log("~p", [R]), - {ok, S(BQS1)}; - {ok, BQS1} -> Log("complete", []), - {ok, S(BQS1)} + {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; + {sync_died, R, BQS1} -> Log("~p", [R]), + {ok, S(BQS1)}; + {already_synced, BQS1} -> {{error, already_synced}, S(BQS1)}; + {ok, BQS1} -> Log("complete", []), + {ok, S(BQS1)} end. terminate({shutdown, dropped} = Reason, diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index a80f8f50..f56cf5b3 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -42,6 +42,7 @@ %% || || <--- sync_ready ---- || %% || || (or) || %% || || <--- sync_deny ----- || +%% || <--- ready ---- || || %% || <--- next* ---- || || } %% || ---- msg* ----> || || } loop %% || || ---- sync_msg* ----> || } @@ -60,6 +61,13 @@ master_prepare(Ref, Log, SPids) -> master_go(Syncer, Ref, Log, BQ, BQS) -> Args = {Syncer, Ref, Log, rabbit_misc:get_parent()}, + receive + {'EXIT', Syncer, normal} -> {already_synced, BQS}; + {'EXIT', Syncer, Reason} -> {sync_died, Reason, BQS}; + {ready, Syncer} -> master_go0(Args, BQ, BQS) + end. + +master_go0(Args, BQ, BQS) -> case BQ:fold(fun (Msg, MsgProps, {I, Last}) -> master_send(Args, I, Last, Msg, MsgProps) end, {0, erlang:now()}, BQS) of @@ -111,7 +119,8 @@ syncer(Ref, Log, MPid, SPids) -> %% *without* those messages ending up in their gen_server2 pqueue. case foreach_slave(SPidsMRefs, Ref, fun sync_receive_ready/3) of [] -> Log("all slaves already synced", []); - SPidsMRefs1 -> Log("~p to sync", [[rabbit_misc:pid_to_string(S) || + SPidsMRefs1 -> MPid ! {ready, self()}, + Log("~p to sync", [[rabbit_misc:pid_to_string(S) || {S, _} <- SPidsMRefs1]]), SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3) -- cgit v1.2.1 From 02d057d4ecb12ecc53c0bc43c08ea7552d7e812a Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 6 Dec 2012 17:00:37 +0000 Subject: Make syncing idempotent. --- src/rabbit_amqqueue.erl | 3 +-- src/rabbit_mirror_queue_master.erl | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index ae96f739..3169948b 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -174,8 +174,7 @@ -spec(start_mirroring/1 :: (pid()) -> 'ok'). -spec(stop_mirroring/1 :: (pid()) -> 'ok'). -spec(sync/2 :: (binary(), rabbit_types:vhost()) -> - 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored' | - 'already_synced')). + 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). -endif. diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 03a712d6..c9b6269b 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -145,7 +145,7 @@ sync_mirrors(State = #state { name = QName, {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; {sync_died, R, BQS1} -> Log("~p", [R]), {ok, S(BQS1)}; - {already_synced, BQS1} -> {{error, already_synced}, S(BQS1)}; + {already_synced, BQS1} -> {ok, S(BQS1)}; {ok, BQS1} -> Log("complete", []), {ok, S(BQS1)} end. -- cgit v1.2.1 From 46eb0f2520a82055dfb88151982111b6e56d06e4 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Thu, 6 Dec 2012 18:07:00 +0100 Subject: passes the release OS as a variable --- packaging/standalone/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 0dde6fa6..1e548789 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -1,7 +1,7 @@ VERSION=0.0.0 SOURCE_DIR=rabbitmq-server-$(VERSION) TARGET_DIR=rabbitmq_server-$(VERSION) -TARGET_TARBALL=rabbitmq-server-mac-standalone-$(VERSION) +TARGET_TARBALL=rabbitmq-server-$(OS)-standalone-$(VERSION) RLS_DIR=$(TARGET_DIR)/release/$(TARGET_DIR) ERTS_VSN=$(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(version)]), halt().') @@ -57,7 +57,7 @@ dist: rm -rf $(SOURCE_DIR) $(TARGET_DIR) clean: clean_partial - rm -f rabbitmq-server-mac-standalone-*.tar.gz + rm -f rabbitmq-server-$(OS)-standalone-*.tar.gz clean_partial: rm -rf $(SOURCE_DIR) -- cgit v1.2.1 From d11ec980f2224650a5c1936db7ff69d2ca1dc032 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 6 Dec 2012 17:33:01 +0000 Subject: fix spec bugs --- src/rabbit_auth_backend_internal.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rabbit_auth_backend_internal.erl b/src/rabbit_auth_backend_internal.erl index 7b9df81e..919be3f3 100644 --- a/src/rabbit_auth_backend_internal.erl +++ b/src/rabbit_auth_backend_internal.erl @@ -49,7 +49,7 @@ -spec(hash_password/1 :: (rabbit_types:password()) -> rabbit_types:password_hash()). -spec(set_tags/2 :: (rabbit_types:username(), [atom()]) -> 'ok'). --spec(list_users/0 :: () -> rabbit_types:infos()). +-spec(list_users/0 :: () -> [rabbit_types:infos()]). -spec(user_info_keys/0 :: () -> rabbit_types:info_keys()). -spec(lookup_user/1 :: (rabbit_types:username()) -> rabbit_types:ok(rabbit_types:internal_user()) @@ -58,14 +58,14 @@ regexp(), regexp(), regexp()) -> 'ok'). -spec(clear_permissions/2 :: (rabbit_types:username(), rabbit_types:vhost()) -> 'ok'). --spec(list_permissions/0 :: () -> rabbit_types:infos()). +-spec(list_permissions/0 :: () -> [rabbit_types:infos()]). -spec(list_vhost_permissions/1 :: - (rabbit_types:vhost()) -> rabbit_types:infos()). + (rabbit_types:vhost()) -> [rabbit_types:infos()]). -spec(list_user_permissions/1 :: - (rabbit_types:username()) -> rabbit_types:infos()). + (rabbit_types:username()) -> [rabbit_types:infos()]). -spec(list_user_vhost_permissions/2 :: (rabbit_types:username(), rabbit_types:vhost()) - -> rabbit_types:infos()). + -> [rabbit_types:infos()]). -spec(perms_info_keys/0 :: () -> rabbit_types:info_keys()). -spec(vhost_perms_info_keys/0 :: () -> rabbit_types:info_keys()). -spec(user_perms_info_keys/0 :: () -> rabbit_types:info_keys()). -- cgit v1.2.1 From 2052456394635806458b9d73bc5dc7e38c5cb0fd Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 7 Dec 2012 13:15:38 +0000 Subject: remove compiler warning --- src/supervisor2.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index a1f758ff..89e78703 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -688,7 +688,7 @@ handle_info({delayed_restart, {RestartType, Reason, Child}}, State) -> {value, Child1} -> {ok, NState} = do_restart(RestartType, Reason, Child1, State), {noreply, NState}; - What -> + _What -> {noreply, State} end; -- cgit v1.2.1 From 1031bf128dfce310530ca3fc063bcd6e8d629ff9 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 10 Dec 2012 13:30:19 +0000 Subject: Fix a couple of partition-related specs. --- src/rabbit_mnesia.erl | 3 ++- src/rabbit_node_monitor.erl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 6576ba52..6a442fec 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -68,7 +68,8 @@ %% Various queries to get the status of the db -spec(status/0 :: () -> [{'nodes', [{node_type(), [node()]}]} | - {'running_nodes', [node()]}]). + {'running_nodes', [node()]} | + {'partitions', [{node(), [node()]}]}]). -spec(is_clustered/0 :: () -> boolean()). -spec(cluster_nodes/1 :: ('all' | 'disc' | 'ram' | 'running') -> [node()]). -spec(node_type/0 :: () -> node_type()). diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 8d0e4456..258ac0ce 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -53,7 +53,7 @@ -spec(notify_joined_cluster/0 :: () -> 'ok'). -spec(notify_left_cluster/1 :: (node()) -> 'ok'). --spec(partitions/0 :: () -> {node(), [{atom(), node()}]}). +-spec(partitions/0 :: () -> {node(), [node()]}). -endif. -- cgit v1.2.1 From a381c38a2965c0888589635c233afd7298838be9 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 12 Dec 2012 17:48:56 +0000 Subject: Limit queue depth Without dead-lettering yet --- src/rabbit_amqqueue.erl | 9 ++++++++- src/rabbit_amqqueue_process.erl | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 1b6cc223..9c089973 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -387,7 +387,8 @@ check_declare_arguments(QueueName, Args) -> Checks = [{<<"x-expires">>, fun check_expires_arg/2}, {<<"x-message-ttl">>, fun check_message_ttl_arg/2}, {<<"x-dead-letter-exchange">>, fun check_string_arg/2}, - {<<"x-dead-letter-routing-key">>, fun check_dlxrk_arg/2}], + {<<"x-dead-letter-routing-key">>, fun check_dlxrk_arg/2}, + {<<"x-maxdepth">>, fun check_maxdepth_arg/2}], [case rabbit_misc:table_lookup(Args, Key) of undefined -> ok; TypeVal -> case Fun(TypeVal, Args) of @@ -410,6 +411,12 @@ check_int_arg({Type, _}, _) -> false -> {error, {unacceptable_type, Type}} end. +check_maxdepth_arg({Type, Val}, Args) -> + case check_int_arg({Type, Val}, Args) of + ok when Val =< 0 -> {error, {value_non_positive, Val}}; + X -> X + end. + check_expires_arg({Type, Val}, Args) -> case check_int_arg({Type, Val}, Args) of ok when Val == 0 -> {error, {value_zero, Val}}; diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 2ffa2a1a..0de9b4e4 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -54,7 +54,8 @@ delayed_stop, queue_monitors, dlx, - dlx_routing_key + dlx_routing_key, + max_depth }). -record(consumer, {tag, ack_required}). @@ -134,6 +135,7 @@ init(Q) -> senders = pmon:new(), dlx = undefined, dlx_routing_key = undefined, + max_depth = undefined, publish_seqno = 1, unconfirmed = dtree:empty(), delayed_stop = undefined, @@ -159,6 +161,7 @@ init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS, rate_timer_ref = RateTRef, expiry_timer_ref = undefined, ttl = undefined, + max_depth = undefined, senders = Senders, publish_seqno = 1, unconfirmed = dtree:empty(), @@ -258,7 +261,8 @@ process_args(State = #q{q = #amqqueue{arguments = Arguments}}) -> [{<<"x-expires">>, fun init_expires/2}, {<<"x-dead-letter-exchange">>, fun init_dlx/2}, {<<"x-dead-letter-routing-key">>, fun init_dlx_routing_key/2}, - {<<"x-message-ttl">>, fun init_ttl/2}]). + {<<"x-message-ttl">>, fun init_ttl/2}, + {<<"x-maxdepth">>, fun init_maxdepth/2}]). init_expires(Expires, State) -> ensure_expiry_timer(State#q{expires = Expires}). @@ -270,6 +274,9 @@ init_dlx(DLX, State = #q{q = #amqqueue{name = QName}}) -> init_dlx_routing_key(RoutingKey, State) -> State#q{dlx_routing_key = RoutingKey}. +init_maxdepth(MaxDepth, State) -> + State#q{max_depth = MaxDepth}. + terminate_shutdown(Fun, State) -> State1 = #q{backing_queue_state = BQS} = stop_sync_timer(stop_rate_timer(State)), @@ -571,12 +578,36 @@ deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, %% The next one is an optimisation {false, State2 = #q{ttl = 0, dlx = undefined}} -> discard(Delivery, State2); - {false, State2 = #q{backing_queue = BQ, backing_queue_state = BQS}} -> - BQS1 = BQ:publish(Message, Props, Delivered, SenderPid, BQS), + {false, State2} -> + BQS1 = publish_max(Message, Props, Delivered, SenderPid, State2), ensure_ttl_timer(Props#message_properties.expiry, State2#q{backing_queue_state = BQS1}) end. +publish_max(Message, Props, Delivered, SenderPid, + State = #q{backing_queue = BQ, + backing_queue_state = BQS, + max_depth = undefined }) -> + BQ:publish(Message, Props, Delivered, SenderPid, BQS); +publish_max(Message, Props, Delivered, SenderPid, + State = #q{backing_queue = BQ, + backing_queue_state = BQS, + max_depth = MaxDepth }) -> + Depth = BQ:depth(BQS), + case Depth >= MaxDepth of + true -> + Length = BQ:len(BQS), + case Length >= MaxDepth of + false -> + BQS; + true -> + {M, BQS1} = BQ:fetch(false, BQS), + BQ:publish(Message, Props, Delivered, SenderPid, BQS1) + end; + false-> + BQ:publish(Message, Props, Delivered, SenderPid, BQS) + end. + requeue_and_run(AckTags, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), -- cgit v1.2.1 From c0dd43566ea63c1ac36c4c0109100f8b7e57d681 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 14 Dec 2012 11:33:45 +0000 Subject: It's important to respond to this too. --- src/rabbit_mirror_queue_sync.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index f56cf5b3..bb12cf49 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -219,5 +219,8 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, BQS1 = BQ:publish(Msg, Props1, true, none, BQS), slave_sync_loop(Args, TRef, BQS1); {'EXIT', Parent, Reason} -> - {stop, Reason, {TRef, BQS}} + {stop, Reason, {TRef, BQS}}; + {'$gen_cast', {gm, {delete_and_terminate, Reason}}} -> + BQS1 = BQ:delete_and_terminate(Reason, BQS), + {stop, Reason, {TRef, BQS1}} end. -- cgit v1.2.1 From 05988c78f535247c15415766117f28fc93ab1390 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 14 Dec 2012 11:36:02 +0000 Subject: Ensure rabbitmqctl list_queues remains responsive during sync, and emit stats events too. --- docs/rabbitmqctl.1.xml | 6 ++++++ src/rabbit_amqqueue_process.erl | 30 +++++++++++++++++++++++++----- src/rabbit_mirror_queue_master.erl | 8 +++++--- src/rabbit_mirror_queue_sync.erl | 18 ++++++++++-------- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/docs/rabbitmqctl.1.xml b/docs/rabbitmqctl.1.xml index 1583ab98..d18a0f9e 100644 --- a/docs/rabbitmqctl.1.xml +++ b/docs/rabbitmqctl.1.xml @@ -1140,6 +1140,12 @@ i.e. those which could take over from the master without message loss. + + status + The status of the queue. Normally + 'running', but may be different if the queue is + synchronising. + If no queueinfoitems are specified then queue name and depth are diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 730c235e..5d9da204 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -54,7 +54,8 @@ delayed_stop, queue_monitors, dlx, - dlx_routing_key + dlx_routing_key, + status }). -record(consumer, {tag, ack_required}). @@ -97,7 +98,8 @@ memory, slave_pids, synchronised_slave_pids, - backing_queue_status + backing_queue_status, + status ]). -define(CREATION_EVENT_KEYS, @@ -138,7 +140,8 @@ init(Q) -> unconfirmed = dtree:empty(), delayed_stop = undefined, queue_monitors = pmon:new(), - msg_id_to_channel = gb_trees:empty()}, + msg_id_to_channel = gb_trees:empty(), + status = running}, {ok, rabbit_event:init_stats_timer(State, #q.stats_timer), hibernate, {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. @@ -164,7 +167,8 @@ init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS, unconfirmed = dtree:empty(), delayed_stop = undefined, queue_monitors = pmon:new(), - msg_id_to_channel = MTC}, + msg_id_to_channel = MTC, + status = running}, State1 = process_args(rabbit_event:init_stats_timer(State, #q.stats_timer)), lists:foldl(fun (Delivery, StateN) -> deliver_or_enqueue(Delivery, true, StateN) @@ -934,6 +938,8 @@ i(synchronised_slave_pids, #q{q = #amqqueue{name = Name}}) -> false -> ''; true -> SSPids end; +i(status, #q{status = Status}) -> + Status; i(backing_queue_status, #q{backing_queue_state = BQS, backing_queue = BQ}) -> BQ:status(BQS); i(Item, _) -> @@ -1157,8 +1163,22 @@ handle_call(sync_mirrors, _From, State = #q{backing_queue = rabbit_mirror_queue_master = BQ, backing_queue_state = BQS}) -> S = fun(BQSN) -> State#q{backing_queue_state = BQSN} end, + InfoPull = fun (Status) -> + receive {'$gen_call', From, {info, Items}} -> + Infos = infos(Items, State#q{status = Status}), + gen_server2:reply(From, {ok, Infos}) + after 0 -> + ok + end + end, + InfoPush = fun (Status) -> + rabbit_event:if_enabled( + State, #q.stats_timer, + fun() -> emit_stats(State#q{status = Status}) end) + end, case BQ:depth(BQS) - BQ:len(BQS) of - 0 -> case rabbit_mirror_queue_master:sync_mirrors(BQS) of + 0 -> case rabbit_mirror_queue_master:sync_mirrors( + InfoPull, InfoPush, BQS) of {shutdown, Reason, BQS1} -> {stop, Reason, S(BQS1)}; {Result, BQS1} -> reply(Result, S(BQS1)) end; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index c9b6269b..601649ef 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -28,7 +28,7 @@ -export([promote_backing_queue_state/8, sender_death_fun/0, depth_fun/0]). --export([init_with_existing_bq/3, stop_mirroring/1, sync_mirrors/1]). +-export([init_with_existing_bq/3, stop_mirroring/1, sync_mirrors/3]). -behaviour(rabbit_backing_queue). @@ -127,7 +127,8 @@ stop_mirroring(State = #state { coordinator = CPid, stop_all_slaves(shutdown, State), {BQ, BQS}. -sync_mirrors(State = #state { name = QName, +sync_mirrors(InfoPull, InfoPush, + State = #state { name = QName, gm = GM, backing_queue = BQ, backing_queue_state = BQS }) -> @@ -141,7 +142,8 @@ sync_mirrors(State = #state { name = QName, Syncer = rabbit_mirror_queue_sync:master_prepare(Ref, Log, SPids), gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, - case rabbit_mirror_queue_sync:master_go(Syncer, Ref, Log, BQ, BQS) of + case rabbit_mirror_queue_sync:master_go( + Syncer, Ref, Log, InfoPull, InfoPush, BQ, BQS) of {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; {sync_died, R, BQS1} -> Log("~p", [R]), {ok, S(BQS1)}; diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index bb12cf49..c10d8fd3 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -18,7 +18,7 @@ -include("rabbit.hrl"). --export([master_prepare/3, master_go/5, slave/7]). +-export([master_prepare/3, master_go/7, slave/7]). -define(SYNC_PROGRESS_INTERVAL, 1000000). @@ -59,16 +59,17 @@ master_prepare(Ref, Log, SPids) -> MPid = self(), spawn_link(fun () -> syncer(Ref, Log, MPid, SPids) end). -master_go(Syncer, Ref, Log, BQ, BQS) -> - Args = {Syncer, Ref, Log, rabbit_misc:get_parent()}, +master_go(Syncer, Ref, Log, InfoPull, InfoPush, BQ, BQS) -> + Args = {Syncer, Ref, Log, InfoPush, rabbit_misc:get_parent()}, receive {'EXIT', Syncer, normal} -> {already_synced, BQS}; {'EXIT', Syncer, Reason} -> {sync_died, Reason, BQS}; - {ready, Syncer} -> master_go0(Args, BQ, BQS) + {ready, Syncer} -> master_go0(InfoPull, Args, BQ, BQS) end. -master_go0(Args, BQ, BQS) -> +master_go0(InfoPull, Args, BQ, BQS) -> case BQ:fold(fun (Msg, MsgProps, {I, Last}) -> + InfoPull({synchronising, I}), master_send(Args, I, Last, Msg, MsgProps) end, {0, erlang:now()}, BQS) of {{shutdown, Reason}, BQS1} -> {shutdown, Reason, BQS1}; @@ -76,10 +77,11 @@ master_go0(Args, BQ, BQS) -> {_, BQS1} -> master_done(Args, BQS1) end. -master_send({Syncer, Ref, Log, Parent}, I, Last, Msg, MsgProps) -> +master_send({Syncer, Ref, Log, InfoPush, Parent}, I, Last, Msg, MsgProps) -> Acc = {I + 1, case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of - true -> Log("~p messages", [I]), + true -> InfoPush({synchronising, I}), + Log("~p messages", [I]), erlang:now(); false -> Last end}, @@ -96,7 +98,7 @@ master_send({Syncer, Ref, Log, Parent}, I, Last, Msg, MsgProps) -> {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}} end. -master_done({Syncer, Ref, _Log, Parent}, BQS) -> +master_done({Syncer, Ref, _Log, _InfoPush, Parent}, BQS) -> receive {next, Ref} -> unlink(Syncer), Syncer ! {done, Ref}, -- cgit v1.2.1 From 01db5e5c77b2bccf0133ff17c836b269d5931562 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 14 Dec 2012 11:51:41 +0000 Subject: Better maxdepth checking --- src/rabbit_amqqueue_process.erl | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 0de9b4e4..b61df6d6 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -584,28 +584,23 @@ deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, State2#q{backing_queue_state = BQS1}) end. -publish_max(Message, Props, Delivered, SenderPid, - State = #q{backing_queue = BQ, - backing_queue_state = BQS, - max_depth = undefined }) -> +publish_max(Message, Props, Delivered, SenderPid, #q{backing_queue = BQ, + backing_queue_state = BQS, + max_depth = undefined }) -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); -publish_max(Message, Props, Delivered, SenderPid, - State = #q{backing_queue = BQ, - backing_queue_state = BQS, - max_depth = MaxDepth }) -> - Depth = BQ:depth(BQS), - case Depth >= MaxDepth of - true -> - Length = BQ:len(BQS), - case Length >= MaxDepth of - false -> - BQS; - true -> - {M, BQS1} = BQ:fetch(false, BQS), - BQ:publish(Message, Props, Delivered, SenderPid, BQS1) - end; - false-> - BQ:publish(Message, Props, Delivered, SenderPid, BQS) +publish_max(Message, Props, Delivered, SenderPid, #q{backing_queue = BQ, + backing_queue_state = BQS, + dlx = XName, + max_depth = MaxDepth }) -> + {Depth, Len} = {BQ:depth(BQS), BQ:len(BQS)}, + case {Depth >= MaxDepth, Len =:= 0} of + {false, _} -> + BQ:publish(Message, Props, Delivered, SenderPid, BQS); + {true, true} -> + BQS; + {true, false} -> + {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(false, BQS), + BQ:publish(Message, Props, Delivered, SenderPid, BQS1) end. requeue_and_run(AckTags, State = #q{backing_queue = BQ, -- cgit v1.2.1 From c52adc5c6768378e2a876ec116e96f70493931c3 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 14 Dec 2012 11:53:25 +0000 Subject: Shorter --- src/rabbit_mirror_queue_sync.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index c10d8fd3..457f658b 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -69,7 +69,7 @@ master_go(Syncer, Ref, Log, InfoPull, InfoPush, BQ, BQS) -> master_go0(InfoPull, Args, BQ, BQS) -> case BQ:fold(fun (Msg, MsgProps, {I, Last}) -> - InfoPull({synchronising, I}), + InfoPull({syncing, I}), master_send(Args, I, Last, Msg, MsgProps) end, {0, erlang:now()}, BQS) of {{shutdown, Reason}, BQS1} -> {shutdown, Reason, BQS1}; @@ -80,7 +80,7 @@ master_go0(InfoPull, Args, BQ, BQS) -> master_send({Syncer, Ref, Log, InfoPush, Parent}, I, Last, Msg, MsgProps) -> Acc = {I + 1, case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of - true -> InfoPush({synchronising, I}), + true -> InfoPush({syncing, I}), Log("~p messages", [I]), erlang:now(); false -> Last -- cgit v1.2.1 From 1db2d04c860e4299b0f0722488f6244f2932113a Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 14 Dec 2012 13:07:21 +0000 Subject: Cancel sync --- docs/rabbitmqctl.1.xml | 20 ++++++++++++++++++++ src/rabbit_amqqueue.erl | 11 ++++++++++- src/rabbit_amqqueue_process.erl | 4 ++++ src/rabbit_control_main.erl | 7 +++++++ src/rabbit_mirror_queue_sync.erl | 26 ++++++++++++++++++++++---- 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/docs/rabbitmqctl.1.xml b/docs/rabbitmqctl.1.xml index d18a0f9e..56fce227 100644 --- a/docs/rabbitmqctl.1.xml +++ b/docs/rabbitmqctl.1.xml @@ -477,6 +477,26 @@ + + cancel_sync_queue queue + + + + + queue + + + The name of the queue to cancel synchronisation for. + + + + + + Instructs a synchronising mirrored queue to stop + synchronising itself. + + + diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 3169948b..c49d5bee 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -31,7 +31,7 @@ -export([notify_down_all/2, limit_all/3]). -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). --export([start_mirroring/1, stop_mirroring/1, sync/2]). +-export([start_mirroring/1, stop_mirroring/1, sync/2, cancel_sync/2]). %% internal -export([internal_declare/2, internal_delete/1, run_backing_queue/3, @@ -175,6 +175,8 @@ -spec(stop_mirroring/1 :: (pid()) -> 'ok'). -spec(sync/2 :: (binary(), rabbit_types:vhost()) -> 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). +-spec(cancel_sync/2 :: (binary(), rabbit_types:vhost()) -> + 'ok' | rabbit_types:error('not_mirrored')). -endif. @@ -599,6 +601,13 @@ sync(QNameBin, VHostBin) -> E -> E end. +cancel_sync(QNameBin, VHostBin) -> + QName = rabbit_misc:r(VHostBin, queue, QNameBin), + case lookup(QName) of + {ok, #amqqueue{pid = QPid}} -> delegate_call(QPid, cancel_sync_mirrors); + E -> E + end. + on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( fun () -> QsDels = diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 5d9da204..26c0edbe 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1188,6 +1188,10 @@ handle_call(sync_mirrors, _From, handle_call(sync_mirrors, _From, State) -> reply({error, not_mirrored}, State); +%% By definition if we get this message here we do not have to do anything. +handle_call(cancel_sync_mirrors, _From, State) -> + reply({error, not_syncing}, State); + handle_call(force_event_refresh, _From, State = #q{exclusive_consumer = Exclusive}) -> rabbit_event:notify(queue_created, infos(?CREATION_EVENT_KEYS, State)), diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index b4272555..12096ff5 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -51,6 +51,7 @@ {forget_cluster_node, [?OFFLINE_DEF]}, cluster_status, {sync_queue, [?VHOST_DEF]}, + {cancel_sync_queue, [?VHOST_DEF]}, add_user, delete_user, @@ -287,6 +288,12 @@ action(sync_queue, Node, [Queue], Opts, Inform) -> rpc_call(Node, rabbit_amqqueue, sync, [list_to_binary(Queue), list_to_binary(VHost)]); +action(cancel_sync_queue, Node, [Queue], Opts, Inform) -> + VHost = proplists:get_value(?VHOST_OPT, Opts), + Inform("Stopping synchronising queue ~s in ~s", [Queue, VHost]), + rpc_call(Node, rabbit_amqqueue, cancel_sync, + [list_to_binary(Queue), list_to_binary(VHost)]); + action(wait, Node, [PidFile], _Opts, Inform) -> Inform("Waiting for ~p", [Node]), wait_for_application(Node, PidFile, rabbit_and_plugins, Inform); diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 457f658b..53f18c5b 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -92,6 +92,14 @@ master_send({Syncer, Ref, Log, InfoPush, Parent}, I, Last, Msg, MsgProps) -> ok end, receive + {'$gen_call', From, + cancel_sync_mirrors} -> unlink(Syncer), + Syncer ! {cancel, Ref}, + receive {'EXIT', Syncer, _} -> ok + after 0 -> ok + end, + gen_server2:reply(From, ok), + {stop, cancelled}; {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, {cont, Acc}; {'EXIT', Parent, Reason} -> {stop, {shutdown, Reason}}; @@ -124,8 +132,16 @@ syncer(Ref, Log, MPid, SPids) -> SPidsMRefs1 -> MPid ! {ready, self()}, Log("~p to sync", [[rabbit_misc:pid_to_string(S) || {S, _} <- SPidsMRefs1]]), - SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), - foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3) + case syncer_loop({Ref, MPid}, SPidsMRefs1) of + {done, SPidsMRefs2} -> + foreach_slave(SPidsMRefs2, Ref, + fun sync_send_complete/3); + cancelled -> + %% We don't tell the slaves we will die + %% - so when we do they interpret that + %% as a failure, which is what we want. + ok + end end. syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> @@ -135,11 +151,13 @@ syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> SPidsMRefs1 = wait_for_credit(SPidsMRefs, Ref), [begin credit_flow:send(SPid), - SPid ! {sync_msg, Ref, Msg, MsgProps} + SPid ! Msg end || {SPid, _} <- SPidsMRefs1], syncer_loop(Args, SPidsMRefs1); + {cancel, Ref} -> + cancelled; {done, Ref} -> - SPidsMRefs + {done, SPidsMRefs} end. wait_for_credit(SPidsMRefs, Ref) -> -- cgit v1.2.1 From 952e5a00af0a217bbc29b6dac1eacd42b3c625ba Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 14 Dec 2012 14:25:44 +0000 Subject: Fix accidental change --- src/rabbit_mirror_queue_sync.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 53f18c5b..b39f7a35 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -151,7 +151,7 @@ syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> SPidsMRefs1 = wait_for_credit(SPidsMRefs, Ref), [begin credit_flow:send(SPid), - SPid ! Msg + SPid ! {sync_msg, Ref, Msg, MsgProps} end || {SPid, _} <- SPidsMRefs1], syncer_loop(Args, SPidsMRefs1); {cancel, Ref} -> -- cgit v1.2.1 From 46fddb9b1b910869f90480b4fb8549b26fca0cf4 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 14 Dec 2012 15:16:43 +0000 Subject: Emit an info item as soon as we start, for greater responsiveness. --- src/rabbit_mirror_queue_sync.erl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index b39f7a35..a9aea966 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -60,24 +60,26 @@ master_prepare(Ref, Log, SPids) -> spawn_link(fun () -> syncer(Ref, Log, MPid, SPids) end). master_go(Syncer, Ref, Log, InfoPull, InfoPush, BQ, BQS) -> - Args = {Syncer, Ref, Log, InfoPush, rabbit_misc:get_parent()}, + Args = {Syncer, Ref, Log, rabbit_misc:get_parent()}, receive {'EXIT', Syncer, normal} -> {already_synced, BQS}; {'EXIT', Syncer, Reason} -> {sync_died, Reason, BQS}; - {ready, Syncer} -> master_go0(InfoPull, Args, BQ, BQS) + {ready, Syncer} -> master_go0( + InfoPull, InfoPush, Args, BQ, BQS) end. -master_go0(InfoPull, Args, BQ, BQS) -> +master_go0(InfoPull, InfoPush, Args, BQ, BQS) -> + InfoPush({syncing, 0}), case BQ:fold(fun (Msg, MsgProps, {I, Last}) -> InfoPull({syncing, I}), - master_send(Args, I, Last, Msg, MsgProps) + master_send(Args, InfoPush, I, Last, Msg, MsgProps) end, {0, erlang:now()}, BQS) of {{shutdown, Reason}, BQS1} -> {shutdown, Reason, BQS1}; {{sync_died, Reason}, BQS1} -> {sync_died, Reason, BQS1}; {_, BQS1} -> master_done(Args, BQS1) end. -master_send({Syncer, Ref, Log, InfoPush, Parent}, I, Last, Msg, MsgProps) -> +master_send({Syncer, Ref, Log, Parent}, InfoPush, I, Last, Msg, MsgProps) -> Acc = {I + 1, case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of true -> InfoPush({syncing, I}), @@ -106,7 +108,7 @@ master_send({Syncer, Ref, Log, InfoPush, Parent}, I, Last, Msg, MsgProps) -> {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}} end. -master_done({Syncer, Ref, _Log, _InfoPush, Parent}, BQS) -> +master_done({Syncer, Ref, _Log, Parent}, BQS) -> receive {next, Ref} -> unlink(Syncer), Syncer ! {done, Ref}, -- cgit v1.2.1 From dedee77a913c7b2f1f6d11e6f649a163797f12c4 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 14 Dec 2012 16:26:07 +0000 Subject: Maxdepth checking with confirms and DLX --- src/rabbit_amqqueue_process.erl | 43 +++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b61df6d6..bcbdb0ad 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -568,7 +568,9 @@ attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, {false, State#q{backing_queue_state = BQS1}} end. -deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, +deliver_or_enqueue(Delivery = #delivery{message = Message, + msg_seq_no = MsgSeqNo, + sender = SenderPid}, Delivered, State) -> {Confirm, State1} = send_or_record_confirm(Delivery, State), Props = message_properties(Message, Confirm, State), @@ -579,27 +581,48 @@ deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, {false, State2 = #q{ttl = 0, dlx = undefined}} -> discard(Delivery, State2); {false, State2} -> - BQS1 = publish_max(Message, Props, Delivered, SenderPid, State2), + BQS1 = publish_max(Delivery, Props, Delivered, State2), ensure_ttl_timer(Props#message_properties.expiry, State2#q{backing_queue_state = BQS1}) end. -publish_max(Message, Props, Delivered, SenderPid, #q{backing_queue = BQ, - backing_queue_state = BQS, - max_depth = undefined }) -> +publish_max(#delivery{message = Message, + sender = SenderPid}, + Props, Delivered, State = #q{backing_queue = BQ, + backing_queue_state = BQS, + max_depth = undefined}) -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); -publish_max(Message, Props, Delivered, SenderPid, #q{backing_queue = BQ, - backing_queue_state = BQS, - dlx = XName, - max_depth = MaxDepth }) -> +publish_max(#delivery{message = Message, + msg_seq_no = MsgSeqNo, + sender = SenderPid}, + Props = #message_properties{needs_confirming = Confirm}, + Delivered, + State = #q{backing_queue = BQ, + backing_queue_state = BQS, + dlx = XName, + max_depth = MaxDepth }) -> {Depth, Len} = {BQ:depth(BQS), BQ:len(BQS)}, case {Depth >= MaxDepth, Len =:= 0} of {false, _} -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); {true, true} -> + case XName of + undefined -> + ok; + _ -> + case rabbit_exchange:lookup(XName) of + {ok, X} -> dead_letter_publish(Message, maxdepth, X, State); + {error, not_found} -> ok + end + end, + case Confirm of + true -> rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]); + _ -> ok + end, BQS; {true, false} -> - {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(false, BQS), + {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), + (dead_letter_fun(maxdepth))([{Msg, AckTag}]), BQ:publish(Message, Props, Delivered, SenderPid, BQS1) end. -- cgit v1.2.1 From 5fc15bf23c0911a9fed976620264a42afd61cd9b Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 14 Dec 2012 16:47:14 +0000 Subject: Shortcut --- src/rabbit_amqqueue_process.erl | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index bcbdb0ad..90e58f25 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -568,9 +568,7 @@ attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, {false, State#q{backing_queue_state = BQS1}} end. -deliver_or_enqueue(Delivery = #delivery{message = Message, - msg_seq_no = MsgSeqNo, - sender = SenderPid}, +deliver_or_enqueue(Delivery = #delivery{message = Message}, Delivered, State) -> {Confirm, State1} = send_or_record_confirm(Delivery, State), Props = message_properties(Message, Confirm, State), @@ -606,18 +604,13 @@ publish_max(#delivery{message = Message, {false, _} -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); {true, true} -> - case XName of - undefined -> - ok; - _ -> - case rabbit_exchange:lookup(XName) of - {ok, X} -> dead_letter_publish(Message, maxdepth, X, State); - {error, not_found} -> ok - end + case rabbit_exchange:lookup(XName) of + {ok, X} -> dead_letter_publish(Message, maxdepth, X, State); + {error, _} -> ok end, case Confirm of - true -> rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]); - _ -> ok + true -> rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]); + false -> ok end, BQS; {true, false} -> -- cgit v1.2.1 From 19e6ab32a1b4da7c16dc10161b11f513c1383436 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 17 Dec 2012 11:52:57 +0000 Subject: Ensure TTL conditionally --- src/rabbit_amqqueue_process.erl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 90e58f25..ffff464d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -579,9 +579,13 @@ deliver_or_enqueue(Delivery = #delivery{message = Message}, {false, State2 = #q{ttl = 0, dlx = undefined}} -> discard(Delivery, State2); {false, State2} -> - BQS1 = publish_max(Delivery, Props, Delivered, State2), - ensure_ttl_timer(Props#message_properties.expiry, - State2#q{backing_queue_state = BQS1}) + case publish_max(Delivery, Props, Delivered, State2) of + nopub -> + State2; + BQS1 -> + ensure_ttl_timer(Props#message_properties.expiry, + State2#q{backing_queue_state = BQS1}) + end end. publish_max(#delivery{message = Message, @@ -612,7 +616,7 @@ publish_max(#delivery{message = Message, true -> rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]); false -> ok end, - BQS; + nopub; {true, false} -> {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), (dead_letter_fun(maxdepth))([{Msg, AckTag}]), -- cgit v1.2.1 From 73c50523e13404389fd4b45a326bb8df19c13b7b Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 17 Dec 2012 11:54:27 +0000 Subject: Whitespace --- src/rabbit_amqqueue_process.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index ffff464d..922cb6cb 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -588,8 +588,8 @@ deliver_or_enqueue(Delivery = #delivery{message = Message}, end end. -publish_max(#delivery{message = Message, - sender = SenderPid}, +publish_max(#delivery{message = Message, + sender = SenderPid}, Props, Delivered, State = #q{backing_queue = BQ, backing_queue_state = BQS, max_depth = undefined}) -> @@ -602,7 +602,7 @@ publish_max(#delivery{message = Message, State = #q{backing_queue = BQ, backing_queue_state = BQS, dlx = XName, - max_depth = MaxDepth }) -> + max_depth = MaxDepth}) -> {Depth, Len} = {BQ:depth(BQS), BQ:len(BQS)}, case {Depth >= MaxDepth, Len =:= 0} of {false, _} -> -- cgit v1.2.1 From 882907e0ec1cc8cbfb11f692a3d79e75133eabd0 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 17 Dec 2012 14:05:15 +0000 Subject: Dead-letter persistent messages differently --- src/rabbit_amqqueue_process.erl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 922cb6cb..86064141 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -608,10 +608,7 @@ publish_max(#delivery{message = Message, {false, _} -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); {true, true} -> - case rabbit_exchange:lookup(XName) of - {ok, X} -> dead_letter_publish(Message, maxdepth, X, State); - {error, _} -> ok - end, + (dead_letter_fun(maxdepth))([{Message, undefined}]), case Confirm of true -> rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]); false -> ok @@ -857,7 +854,7 @@ cleanup_after_confirm(AckTags, State = #q{delayed_stop = DS, unconfirmed = UC, backing_queue = BQ, backing_queue_state = BQS}) -> - {_Guids, BQS1} = BQ:ack(AckTags, BQS), + {_Guids, BQS1} = BQ:ack([Ack || Ack <- AckTags, Ack /= undefined], BQS), State1 = State#q{backing_queue_state = BQS1}, case dtree:is_empty(UC) andalso DS =/= undefined of true -> case DS of -- cgit v1.2.1 From 7008dd09a161e026db0a574436eebe4506ac6101 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 17 Dec 2012 14:08:15 +0000 Subject: Remove unused variables --- src/rabbit_amqqueue_process.erl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 86064141..f5e2b400 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -590,19 +590,18 @@ deliver_or_enqueue(Delivery = #delivery{message = Message}, publish_max(#delivery{message = Message, sender = SenderPid}, - Props, Delivered, State = #q{backing_queue = BQ, - backing_queue_state = BQS, - max_depth = undefined}) -> + Props, Delivered, #q{backing_queue = BQ, + backing_queue_state = BQS, + max_depth = undefined}) -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); publish_max(#delivery{message = Message, msg_seq_no = MsgSeqNo, sender = SenderPid}, Props = #message_properties{needs_confirming = Confirm}, Delivered, - State = #q{backing_queue = BQ, - backing_queue_state = BQS, - dlx = XName, - max_depth = MaxDepth}) -> + #q{backing_queue = BQ, + backing_queue_state = BQS, + max_depth = MaxDepth}) -> {Depth, Len} = {BQ:depth(BQS), BQ:len(BQS)}, case {Depth >= MaxDepth, Len =:= 0} of {false, _} -> -- cgit v1.2.1 From 0309aee655406ca54e4ed6ac4535677ef866717b Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 18 Dec 2012 13:46:27 +0000 Subject: Emit final stats event for connections, queues and channels just before the deleted event. --- src/rabbit_amqqueue_process.erl | 2 ++ src/rabbit_channel.erl | 2 ++ src/rabbit_reader.erl | 23 ++++++++++++++++------- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 03bcdf43..cb83d28c 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -180,6 +180,8 @@ terminate(Reason, State = #q{q = #amqqueue{name = QName}, fun (BQS) -> BQS1 = BQ:delete_and_terminate(Reason, BQS), %% don't care if the internal delete doesn't return 'ok'. + rabbit_event:if_enabled(State, #q.stats_timer, + fun() -> emit_stats(State) end), rabbit_amqqueue:internal_delete(QName), BQS1 end, State). diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a3c82865..91eab01b 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -363,6 +363,8 @@ terminate(Reason, State) -> _ -> ok end, pg_local:leave(rabbit_channels, self()), + rabbit_event:if_enabled(State, #ch.stats_timer, + fun() -> emit_stats(State) end), rabbit_event:notify(channel_closed, [{pid, self()}]). code_change(_OldVsn, State, _Extra) -> diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 928786e9..80886b46 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -280,11 +280,13 @@ mainloop(Deb, State = #v1{sock = Sock, buf = Buf, buf_len = BufLen}) -> {data, Data} -> recvloop(Deb, State#v1{buf = [Data | Buf], buf_len = BufLen + size(Data), pending_recv = false}); - closed -> case State#v1.connection_state of + closed -> maybe_emit_stats(State), + case State#v1.connection_state of closed -> State; _ -> throw(connection_closed_abruptly) end; - {error, Reason} -> throw({inet_error, Reason}); + {error, Reason} -> maybe_emit_stats(State), + throw({inet_error, Reason}); {other, Other} -> handle_other(Other, Deb, State) end. @@ -305,9 +307,11 @@ handle_other({'EXIT', Parent, Reason}, _Deb, State = #v1{parent = Parent}) -> %% ordinary error case. However, since this termination is %% initiated by our parent it is probably more important to exit %% quickly. + maybe_emit_stats(State), exit(Reason); handle_other({channel_exit, _Channel, E = {writer, send_failed, _Error}}, - _Deb, _State) -> + _Deb, State) -> + maybe_emit_stats(State), throw(E); handle_other({channel_exit, Channel, Reason}, Deb, State) -> mainloop(Deb, handle_exception(State, Channel, Reason)); @@ -324,7 +328,8 @@ handle_other(handshake_timeout, _Deb, State) -> throw({handshake_timeout, State#v1.callback}); handle_other(heartbeat_timeout, Deb, State = #v1{connection_state = closed}) -> mainloop(Deb, State); -handle_other(heartbeat_timeout, _Deb, #v1{connection_state = S}) -> +handle_other(heartbeat_timeout, _Deb, State = #v1{connection_state = S}) -> + maybe_emit_stats(State), throw({heartbeat_timeout, S}); handle_other({'$gen_call', From, {shutdown, Explanation}}, Deb, State) -> {ForceTermination, NewState} = terminate(Explanation, State), @@ -358,8 +363,9 @@ handle_other({system, From, Request}, Deb, State = #v1{parent = Parent}) -> handle_other({bump_credit, Msg}, Deb, State) -> credit_flow:handle_bump_msg(Msg), recvloop(Deb, control_throttle(State)); -handle_other(Other, _Deb, _State) -> +handle_other(Other, _Deb, State) -> %% internal error -> something worth dying for + maybe_emit_stats(State), exit({unexpected_message, Other}). switch_callback(State, Callback, Length) -> @@ -805,8 +811,7 @@ handle_method0(#'connection.open'{virtual_host = VHostPath}, rabbit_event:notify(connection_created, [{type, network} | infos(?CREATION_EVENT_KEYS, State1)]), - rabbit_event:if_enabled(State1, #v1.stats_timer, - fun() -> emit_stats(State1) end), + maybe_emit_stats(State1), State1; handle_method0(#'connection.close'{}, State) when ?IS_RUNNING(State) -> lists:foreach(fun rabbit_channel:shutdown/1, all_channels()), @@ -977,6 +982,10 @@ cert_info(F, #v1{sock = Sock}) -> {ok, Cert} -> list_to_binary(F(Cert)) end. +maybe_emit_stats(State) -> + rabbit_event:if_enabled(State, #v1.stats_timer, + fun() -> emit_stats(State) end). + emit_stats(State) -> rabbit_event:notify(connection_stats, infos(?STATISTICS_KEYS, State)), rabbit_event:reset_stats_timer(State, #v1.stats_timer). -- cgit v1.2.1 From 4b9cc57601b69ad1e3a6f7e9649382dbb1cd8ef3 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 18 Dec 2012 17:20:02 +0000 Subject: Confirms for HA --- src/dtree.erl | 24 +++++++++++++++++++++++- src/rabbit_amqqueue_process.erl | 21 +++++++-------------- src/rabbit_channel.erl | 12 ++++++++++-- src/rabbit_misc.erl | 5 ++++- 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/dtree.erl b/src/dtree.erl index ca2d30cf..c59243bb 100644 --- a/src/dtree.erl +++ b/src/dtree.erl @@ -32,7 +32,7 @@ -module(dtree). --export([empty/0, insert/4, take/3, take/2, take_all/2, +-export([empty/0, insert/4, take/3, take/2, take_all/2, take_prim/2, is_defined/2, is_empty/1, smallest/1, size/1]). %%---------------------------------------------------------------------------- @@ -53,6 +53,7 @@ -spec(take/3 :: ([pk()], sk(), ?MODULE()) -> {[kv()], ?MODULE()}). -spec(take/2 :: (sk(), ?MODULE()) -> {[kv()], ?MODULE()}). -spec(take_all/2 :: (sk(), ?MODULE()) -> {[kv()], ?MODULE()}). +-spec(take_prim/2 :: (pk(), ?MODULE()) -> {[kv()], ?MODULE()}). -spec(is_defined/2 :: (sk(), ?MODULE()) -> boolean()). -spec(is_empty/1 :: (?MODULE()) -> boolean()). -spec(smallest/1 :: (?MODULE()) -> kv()). @@ -120,6 +121,13 @@ take_all(SK, {P, S}) -> {KVs, {P1, prune(SKS, PKS, S)}} end. +%% Drop the entry with the given primary key +take_prim(PK, {P, S} = DTree) -> + case gb_trees:lookup(PK, P) of + none -> {[], DTree}; + {value, {SKS, V}} -> {[{PK, V}], take_prim2(PK, SKS, DTree)} + end. + is_defined(SK, {_P, S}) -> gb_trees:is_defined(SK, S). is_empty({P, _S}) -> gb_trees:is_empty(P). @@ -149,6 +157,20 @@ take_all2(PKS, P) -> gb_trees:delete(PK, P0)} end, {[], gb_sets:empty(), P}, PKS). +take_prim2(PK, SKS, {P, S}) -> + {gb_trees:delete(PK, P), + rabbit_misc:gb_trees_fold( + fun (SK0, PKS, S1) -> + case gb_sets:is_member(SK0, SKS) of + false -> S1; + true -> PKS1 = gb_sets:delete(PK, PKS), + case gb_sets:is_empty(PKS1) of + true -> gb_trees:delete(SK0, S1); + false -> gb_trees:update(SK0, PKS1, S1) + end + end + end, S, S)}. + prune(SKS, PKS, S) -> gb_sets:fold(fun (SK0, S0) -> PKS1 = gb_trees:get(SK0, S0), diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index f5e2b400..01125819 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -580,11 +580,9 @@ deliver_or_enqueue(Delivery = #delivery{message = Message}, discard(Delivery, State2); {false, State2} -> case publish_max(Delivery, Props, Delivered, State2) of - nopub -> - State2; - BQS1 -> - ensure_ttl_timer(Props#message_properties.expiry, - State2#q{backing_queue_state = BQS1}) + nopub -> State2; + BQS1 -> ensure_ttl_timer(Props#message_properties.expiry, + State2#q{backing_queue_state = BQS1}) end end. @@ -597,21 +595,16 @@ publish_max(#delivery{message = Message, publish_max(#delivery{message = Message, msg_seq_no = MsgSeqNo, sender = SenderPid}, - Props = #message_properties{needs_confirming = Confirm}, - Delivered, - #q{backing_queue = BQ, - backing_queue_state = BQS, - max_depth = MaxDepth}) -> + Props, Delivered, #q{backing_queue = BQ, + backing_queue_state = BQS, + max_depth = MaxDepth}) -> {Depth, Len} = {BQ:depth(BQS), BQ:len(BQS)}, case {Depth >= MaxDepth, Len =:= 0} of {false, _} -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); {true, true} -> (dead_letter_fun(maxdepth))([{Message, undefined}]), - case Confirm of - true -> rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]); - false -> ok - end, + rabbit_misc:confirm_all(SenderPid, MsgSeqNo), nopub; {true, false} -> {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a3c82865..ae29861e 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -224,8 +224,9 @@ prioritise_call(Msg, _From, _State) -> prioritise_cast(Msg, _State) -> case Msg of - {confirm, _MsgSeqNos, _QPid} -> 5; - _ -> 0 + {confirm, _MsgSeqNos, _QPid} -> 5; + {confirm_all, _MsgSeqNo, _QPid} -> 5; + _ -> 0 end. prioritise_info(Msg, _State) -> @@ -316,6 +317,9 @@ handle_cast(force_event_refresh, State) -> noreply(State); handle_cast({confirm, MsgSeqNos, From}, State) -> State1 = #ch{confirmed = C} = confirm(MsgSeqNos, From, State), + noreply([send_confirms], State1, case C of [] -> hibernate; _ -> 0 end); +handle_cast({confirm_all, MsgSeqNo, _From}, State) -> + State1 = #ch{confirmed = C} = confirm_all(MsgSeqNo, State), noreply([send_confirms], State1, case C of [] -> hibernate; _ -> 0 end). handle_info({bump_credit, Msg}, State) -> @@ -571,6 +575,10 @@ confirm(MsgSeqNos, QPid, State = #ch{unconfirmed = UC}) -> {MXs, UC1} = dtree:take(MsgSeqNos, QPid, UC), record_confirms(MXs, State#ch{unconfirmed = UC1}). +confirm_all(MsgSeqNo, State = #ch{unconfirmed = UC}) -> + {MXs, UC1} = dtree:take_prim(MsgSeqNo, UC), + record_confirms(MXs, State#ch{unconfirmed = UC1}). + handle_method(#'channel.open'{}, _, State = #ch{state = starting}) -> {reply, #'channel.open_ok'{}, State#ch{state = running}}; diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 4efde50e..05569599 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -28,7 +28,7 @@ -export([enable_cover/0, report_cover/0]). -export([enable_cover/1, report_cover/1]). -export([start_cover/1]). --export([confirm_to_sender/2]). +-export([confirm_to_sender/2, confirm_all/2]). -export([throw_on_error/2, with_exit_handler/2, is_abnormal_exit/1, filter_exit_map/2]). -export([with_user/2, with_user_and_vhost/3]). @@ -428,6 +428,9 @@ report_coverage_percentage(File, Cov, NotCov, Mod) -> confirm_to_sender(Pid, MsgSeqNos) -> gen_server2:cast(Pid, {confirm, MsgSeqNos, self()}). +confirm_all(Pid, MsgSeqNo) -> + gen_server2:cast(Pid, {confirm_all, MsgSeqNo, self()}). + %% @doc Halts the emulator returning the given status code to the os. %% On Windows this function will block indefinitely so as to give the io %% subsystem time to flush stdout completely. -- cgit v1.2.1 From 726580efafc8fdaf70a409a17f89e0247a595891 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 18 Dec 2012 17:27:44 +0000 Subject: Maxdepth argument checking --- src/rabbit_amqqueue.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 9c089973..51f5bfea 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -413,8 +413,9 @@ check_int_arg({Type, _}, _) -> check_maxdepth_arg({Type, Val}, Args) -> case check_int_arg({Type, Val}, Args) of - ok when Val =< 0 -> {error, {value_non_positive, Val}}; - X -> X + ok when Val > 0 -> ok; + ok -> {error, {value_not_positive, Val}}; + Error -> Error end. check_expires_arg({Type, Val}, Args) -> -- cgit v1.2.1 From 653d599979edb2f0a38c6916c2de49a793d4fb56 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 18 Dec 2012 17:34:11 +0000 Subject: Inline --- 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 01125819..933c4029 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -598,8 +598,7 @@ publish_max(#delivery{message = Message, Props, Delivered, #q{backing_queue = BQ, backing_queue_state = BQS, max_depth = MaxDepth}) -> - {Depth, Len} = {BQ:depth(BQS), BQ:len(BQS)}, - case {Depth >= MaxDepth, Len =:= 0} of + case {BQ:depth(BQS) >= MaxDepth, BQ:len(BQS) =:= 0} of {false, _} -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); {true, true} -> -- cgit v1.2.1 From 2956e6dda62c838ace81cc60b22a698927f561b8 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 27 Dec 2012 21:57:29 +0000 Subject: get rid of bump_reductions it hurts performance in some cases and credit_flow does a better job --- src/rabbit_reader.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 928786e9..0296537d 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -622,8 +622,7 @@ post_process_frame({method, MethodName, _}, _ChPid, State = #v1{connection = #connection{ protocol = Protocol}}) -> case Protocol:method_has_content(MethodName) of - true -> erlang:bump_reductions(2000), - maybe_block(control_throttle(State)); + true -> maybe_block(control_throttle(State)); false -> control_throttle(State) end; post_process_frame(_Frame, _ChPid, State) -> -- cgit v1.2.1 From 902e542160fd0c345dde7dee7848af783a15b556 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 27 Dec 2012 22:30:21 +0000 Subject: replace credit_flow:update/3 with a macro un order to eliminate closure creation and thus improve performance. Also... - get rid of a 'get/2' call by processing result of 'erase' instead - inline remaining get/2 calls for performance --- src/credit_flow.erl | 72 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/src/credit_flow.erl b/src/credit_flow.erl index ba99811f..ec9b2c36 100644 --- a/src/credit_flow.erl +++ b/src/credit_flow.erl @@ -52,6 +52,19 @@ %%---------------------------------------------------------------------------- +%% process dict update macro - eliminates the performance-hurting +%% closure creation a HOF would introduce +-define(UPDATE(Key, Default, Var, Expr), + begin + case get(Key) of + undefined -> Var = Default; + Var -> ok + end, + put(Key, Expr) + end). + +%%---------------------------------------------------------------------------- + %% There are two "flows" here; of messages and of credit, going in %% opposite directions. The variable names "From" and "To" refer to %% the flow of credit, but the function names refer to the flow of @@ -66,29 +79,33 @@ send(From) -> send(From, ?DEFAULT_CREDIT). send(From, {InitialCredit, _MoreCreditAfter}) -> - update({credit_from, From}, InitialCredit, - fun (1) -> block(From), - 0; - (C) -> C - 1 - end). + ?UPDATE({credit_from, From}, InitialCredit, C, + if C == 1 -> block(From), + 0; + true -> C - 1 + end). ack(To) -> ack(To, ?DEFAULT_CREDIT). ack(To, {_InitialCredit, MoreCreditAfter}) -> - update({credit_to, To}, MoreCreditAfter, - fun (1) -> grant(To, MoreCreditAfter), - MoreCreditAfter; - (C) -> C - 1 - end). + ?UPDATE({credit_to, To}, MoreCreditAfter, C, + if C == 1 -> grant(To, MoreCreditAfter), + MoreCreditAfter; + true -> C - 1 + end). handle_bump_msg({From, MoreCredit}) -> - update({credit_from, From}, 0, - fun (C) when C =< 0 andalso C + MoreCredit > 0 -> unblock(From), - C + MoreCredit; - (C) -> C + MoreCredit - end). - -blocked() -> get(credit_blocked, []) =/= []. + ?UPDATE({credit_from, From}, 0, C, + if C =< 0 andalso C + MoreCredit > 0 -> unblock(From), + C + MoreCredit; + true -> C + MoreCredit + end). + +blocked() -> case get(credit_blocked) of + undefined -> false; + [] -> false; + _ -> true + end. peer_down(Peer) -> %% In theory we could also remove it from credit_deferred here, but it @@ -105,24 +122,17 @@ grant(To, Quantity) -> Msg = {bump_credit, {self(), Quantity}}, case blocked() of false -> To ! Msg; - true -> update(credit_deferred, [], - fun (Deferred) -> [{To, Msg} | Deferred] end) + true -> ?UPDATE(credit_deferred, [], Deferred, [{To, Msg} | Deferred]) end. -block(From) -> update(credit_blocked, [], fun (Blocks) -> [From | Blocks] end). +block(From) -> ?UPDATE(credit_blocked, [], Blocks, [From | Blocks]). unblock(From) -> - update(credit_blocked, [], fun (Blocks) -> Blocks -- [From] end), + ?UPDATE(credit_blocked, [], Blocks, Blocks -- [From]), case blocked() of - false -> [To ! Msg || {To, Msg} <- get(credit_deferred, [])], - erase(credit_deferred); + false -> case erase(credit_deferred) of + undefined -> ok; + Credits -> [To ! Msg || {To, Msg} <- Credits] + end; true -> ok end. - -get(Key, Default) -> - case get(Key) of - undefined -> Default; - Value -> Value - end. - -update(Key, Default, Fun) -> put(Key, Fun(get(Key, Default))), ok. -- cgit v1.2.1 -- cgit v1.2.1 From 08bc06b2af10de919da2e8ea74b21392b2e4f03b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 27 Dec 2012 23:25:38 +0000 Subject: only invoke control_throttle when necesseary --- src/rabbit_reader.erl | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 0296537d..83622a9f 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -598,35 +598,34 @@ process_frame(Frame, Channel, State) -> undefined -> create_channel(Channel, State); Other -> Other end, - case process_channel_frame(Frame, ChPid, AState) of - {ok, NewAState} -> put({channel, Channel}, {ChPid, NewAState}), - post_process_frame(Frame, ChPid, State); - {error, Reason} -> handle_exception(State, Channel, Reason) - end. - -process_channel_frame(Frame, ChPid, AState) -> case rabbit_command_assembler:process(Frame, AState) of - {ok, NewAState} -> {ok, NewAState}; - {ok, Method, NewAState} -> rabbit_channel:do(ChPid, Method), - {ok, NewAState}; - {ok, Method, Content, NewAState} -> rabbit_channel:do_flow( - ChPid, Method, Content), - {ok, NewAState}; - {error, Reason} -> {error, Reason} + {ok, NewAState} -> + put({channel, Channel}, {ChPid, NewAState}), + post_process_frame(Frame, ChPid, State); + {ok, Method, NewAState} -> + rabbit_channel:do(ChPid, Method), + put({channel, Channel}, {ChPid, NewAState}), + post_process_frame(Frame, ChPid, State); + {ok, Method, Content, NewAState} -> + rabbit_channel:do_flow(ChPid, Method, Content), + put({channel, Channel}, {ChPid, NewAState}), + post_process_frame(Frame, ChPid, control_throttle(State)); + {error, Reason} -> + {error, Reason} end. post_process_frame({method, 'channel.close_ok', _}, ChPid, State) -> channel_cleanup(ChPid), - control_throttle(State); + State; post_process_frame({method, MethodName, _}, _ChPid, State = #v1{connection = #connection{ protocol = Protocol}}) -> case Protocol:method_has_content(MethodName) of - true -> maybe_block(control_throttle(State)); - false -> control_throttle(State) + true -> maybe_block(State); + false -> State end; post_process_frame(_Frame, _ChPid, State) -> - control_throttle(State). + State. %%-------------------------------------------------------------------------- -- cgit v1.2.1 From 5ddf261aa6e75fbaa758a2c6fc214a39c3c15ad5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 13:40:44 +0000 Subject: get rid of rabbit_channel MASKED_CALL macro which simplifies the code and eliminates a performance hotspot --- src/rabbit_channel.erl | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a3c82865..b6f3d750 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -314,9 +314,12 @@ handle_cast({deliver, ConsumerTag, AckRequired, handle_cast(force_event_refresh, State) -> rabbit_event:notify(channel_created, infos(?CREATION_EVENT_KEYS, State)), noreply(State); + handle_cast({confirm, MsgSeqNos, From}, State) -> State1 = #ch{confirmed = C} = confirm(MsgSeqNos, From, State), - noreply([send_confirms], State1, case C of [] -> hibernate; _ -> 0 end). + Timeout = case C of [] -> hibernate; _ -> 0 end, + %% NB: don't call noreply/1 since we don't want to send confirms. + {noreply, ensure_stats_timer(State1), Timeout}. handle_info({bump_credit, Msg}, State) -> credit_flow:handle_bump_msg(Msg), @@ -327,8 +330,10 @@ handle_info(timeout, State) -> handle_info(emit_stats, State) -> emit_stats(State), - noreply([ensure_stats_timer], - rabbit_event:reset_stats_timer(State, #ch.stats_timer)); + State1 = rabbit_event:reset_stats_timer(State, #ch.stats_timer), + %% NB: don't call noreply/1 since we don't want to kick off the + %% stats timer. + {noreply, send_confirms(State1), hibernate}; handle_info({'DOWN', _MRef, process, QPid, Reason}, State) -> State1 = handle_publishing_queue_down(QPid, Reason, State), @@ -372,30 +377,11 @@ format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ). %%--------------------------------------------------------------------------- -reply(Reply, NewState) -> reply(Reply, [], NewState). - -reply(Reply, Mask, NewState) -> reply(Reply, Mask, NewState, hibernate). - -reply(Reply, Mask, NewState, Timeout) -> - {reply, Reply, next_state(Mask, NewState), Timeout}. - -noreply(NewState) -> noreply([], NewState). - -noreply(Mask, NewState) -> noreply(Mask, NewState, hibernate). - -noreply(Mask, NewState, Timeout) -> - {noreply, next_state(Mask, NewState), Timeout}. +reply(Reply, NewState) -> {reply, Reply, next_state(NewState), hibernate}. --define(MASKED_CALL(Fun, Mask, State), - case lists:member(Fun, Mask) of - true -> State; - false -> Fun(State) - end). +noreply(NewState) -> {noreply, next_state(NewState), hibernate}. -next_state(Mask, State) -> - State1 = ?MASKED_CALL(ensure_stats_timer, Mask, State), - State2 = ?MASKED_CALL(send_confirms, Mask, State1), - State2. +next_state(State) -> ensure_stats_timer(send_confirms(State)). ensure_stats_timer(State) -> rabbit_event:ensure_stats_timer(State, #ch.stats_timer, emit_stats). -- cgit v1.2.1 From 30b9c29d9eafabcdcf0f6defdb49719b7de0b812 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 14:30:10 +0000 Subject: make reader react to flow control sooner ...by blocking when encountering content headers/body frames, rather than method frames of content-bearing commands. --- src/rabbit_reader.erl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 83622a9f..079de5e1 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -617,13 +617,10 @@ process_frame(Frame, Channel, State) -> post_process_frame({method, 'channel.close_ok', _}, ChPid, State) -> channel_cleanup(ChPid), State; -post_process_frame({method, MethodName, _}, _ChPid, - State = #v1{connection = #connection{ - protocol = Protocol}}) -> - case Protocol:method_has_content(MethodName) of - true -> maybe_block(State); - false -> State - end; +post_process_frame({content_header, _, _, _, _}, _ChPid, State) -> + maybe_block(State); +post_process_frame({content_body, _}, _ChPid, State) -> + maybe_block(State); post_process_frame(_Frame, _ChPid, State) -> State. -- cgit v1.2.1 From bfb47ab54fc39d1ef4554dc537bc2d83aaa95e2d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 17:59:38 +0000 Subject: move a bunch of reader state fields into 'connection' These fields are constant, seldom read, and associated with the connection. Also, move #connection definition from rabbit.hrl to rabbit_reader - nothing else is using this and it is really private to the reader. --- include/rabbit.hrl | 3 --- src/rabbit_reader.erl | 67 ++++++++++++++++++++++++--------------------------- 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/include/rabbit.hrl b/include/rabbit.hrl index 0ccb80bf..7385b4b3 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -27,9 +27,6 @@ -record(vhost, {virtual_host, dummy}). --record(connection, {protocol, user, timeout_sec, frame_max, vhost, - client_properties, capabilities}). - -record(content, {class_id, properties, %% either 'none', or a decoded record/tuple diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 928786e9..924e7c77 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -35,12 +35,15 @@ %%-------------------------------------------------------------------------- --record(v1, {parent, sock, name, connection, callback, recv_len, pending_recv, +-record(v1, {parent, sock, connection, callback, recv_len, pending_recv, connection_state, queue_collector, heartbeater, stats_timer, channel_sup_sup_pid, start_heartbeat_fun, buf, buf_len, auth_mechanism, auth_state, conserve_resources, - last_blocked_by, last_blocked_at, host, peer_host, - port, peer_port}). + last_blocked_by, last_blocked_at}). + +-record(connection, {name, host, peer_host, port, peer_port, + protocol, user, timeout_sec, frame_max, vhost, + client_properties, capabilities}). -define(STATISTICS_KEYS, [pid, recv_oct, recv_cnt, send_oct, send_cnt, send_pend, state, last_blocked_by, last_blocked_age, @@ -205,8 +208,12 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, {PeerHost, PeerPort, Host, Port} = socket_ends(Sock), State = #v1{parent = Parent, sock = ClientSock, - name = list_to_binary(Name), connection = #connection{ + name = list_to_binary(Name), + host = Host, + peer_host = PeerHost, + port = Port, + peer_port = PeerPort, protocol = none, user = none, timeout_sec = ?HANDSHAKE_TIMEOUT, @@ -228,11 +235,7 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, auth_state = none, conserve_resources = false, last_blocked_by = none, - last_blocked_at = never, - host = Host, - peer_host = PeerHost, - port = Port, - peer_port = PeerPort}, + last_blocked_at = never}, try ok = inet_op(fun () -> rabbit_net:tune_buffer_size(ClientSock) end), recvloop(Deb, switch_callback(rabbit_event:init_stats_timer( @@ -531,9 +534,10 @@ payload_snippet(<>) -> %%-------------------------------------------------------------------------- create_channel(Channel, State) -> - #v1{sock = Sock, name = Name, queue_collector = Collector, + #v1{sock = Sock, queue_collector = Collector, channel_sup_sup_pid = ChanSupSup, - connection = #connection{protocol = Protocol, + connection = #connection{name = Name, + protocol = Protocol, frame_max = FrameMax, user = User, vhost = VHost, @@ -901,11 +905,6 @@ auth_phase(Response, infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. i(pid, #v1{}) -> self(); -i(name, #v1{name = Name}) -> Name; -i(host, #v1{host = Host}) -> Host; -i(peer_host, #v1{peer_host = PeerHost}) -> PeerHost; -i(port, #v1{port = Port}) -> Port; -i(peer_port, #v1{peer_port = PeerPort}) -> PeerPort; i(SockStat, S) when SockStat =:= recv_oct; SockStat =:= recv_cnt; SockStat =:= send_oct; @@ -932,26 +931,22 @@ i(auth_mechanism, #v1{auth_mechanism = none}) -> none; i(auth_mechanism, #v1{auth_mechanism = Mechanism}) -> proplists:get_value(name, Mechanism:description()); -i(protocol, #v1{connection = #connection{protocol = none}}) -> - none; -i(protocol, #v1{connection = #connection{protocol = Protocol}}) -> - Protocol:version(); -i(user, #v1{connection = #connection{user = none}}) -> - ''; -i(user, #v1{connection = #connection{user = #user{ - username = Username}}}) -> - Username; -i(vhost, #v1{connection = #connection{vhost = VHost}}) -> - VHost; -i(timeout, #v1{connection = #connection{timeout_sec = Timeout}}) -> - Timeout; -i(frame_max, #v1{connection = #connection{frame_max = FrameMax}}) -> - FrameMax; -i(client_properties, #v1{connection = #connection{client_properties = - ClientProperties}}) -> - ClientProperties; -i(Item, #v1{}) -> - throw({bad_argument, Item}). +i(Item, #v1{connection = Conn}) -> ic(Item, Conn). + +ic(name, #connection{name = Name}) -> Name; +ic(host, #connection{host = Host}) -> Host; +ic(peer_host, #connection{peer_host = PeerHost}) -> PeerHost; +ic(port, #connection{port = Port}) -> Port; +ic(peer_port, #connection{peer_port = PeerPort}) -> PeerPort; +ic(protocol, #connection{protocol = none}) -> none; +ic(protocol, #connection{protocol = P}) -> P:version(); +ic(user, #connection{user = none}) -> ''; +ic(user, #connection{user = U}) -> U#user.username; +ic(vhost, #connection{vhost = VHost}) -> VHost; +ic(timeout, #connection{timeout_sec = Timeout}) -> Timeout; +ic(frame_max, #connection{frame_max = FrameMax}) -> FrameMax; +ic(client_properties, #connection{client_properties = CP}) -> CP; +ic(Item, #connection{}) -> throw({bad_argument, Item}). socket_info(Get, Select, #v1{sock = Sock}) -> case Get(Sock) of -- cgit v1.2.1 From a931bba3333622c0dc18d8d91156d7233194a738 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 18:13:05 +0000 Subject: move some more reader state into #connection and also clear auth_state once auth has been completed, so we don't hang on to some potentially large piece of state. --- src/rabbit_reader.erl | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 924e7c77..a21a061b 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -38,12 +38,12 @@ -record(v1, {parent, sock, connection, callback, recv_len, pending_recv, connection_state, queue_collector, heartbeater, stats_timer, channel_sup_sup_pid, start_heartbeat_fun, buf, buf_len, - auth_mechanism, auth_state, conserve_resources, - last_blocked_by, last_blocked_at}). + conserve_resources, last_blocked_by, last_blocked_at}). -record(connection, {name, host, peer_host, port, peer_port, protocol, user, timeout_sec, frame_max, vhost, - client_properties, capabilities}). + client_properties, capabilities, + auth_mechanism, auth_state}). -define(STATISTICS_KEYS, [pid, recv_oct, recv_cnt, send_oct, send_cnt, send_pend, state, last_blocked_by, last_blocked_age, @@ -220,7 +220,9 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, frame_max = ?FRAME_MIN_SIZE, vhost = none, client_properties = none, - capabilities = []}, + capabilities = [], + auth_mechanism = none, + auth_state = none}, callback = uninitialized_callback, recv_len = 0, pending_recv = false, @@ -231,8 +233,6 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, start_heartbeat_fun = StartHeartbeatFun, buf = [], buf_len = 0, - auth_mechanism = none, - auth_state = none, conserve_resources = false, last_blocked_by = none, last_blocked_at = never}, @@ -750,13 +750,13 @@ handle_method0(#'connection.start_ok'{mechanism = Mechanism, {table, Capabilities1} -> Capabilities1; _ -> [] end, - State = State0#v1{auth_mechanism = AuthMechanism, - auth_state = AuthMechanism:init(Sock), - connection_state = securing, + State = State0#v1{connection_state = securing, connection = Connection#connection{ client_properties = ClientProperties, - capabilities = Capabilities}}, + capabilities = Capabilities, + auth_mechanism = AuthMechanism, + auth_state = AuthMechanism:init(Sock)}}, auth_phase(Response, State); handle_method0(#'connection.secure_ok'{response = Response}, @@ -874,10 +874,10 @@ auth_mechanisms_binary(Sock) -> string:join([atom_to_list(A) || A <- auth_mechanisms(Sock)], " ")). auth_phase(Response, - State = #v1{auth_mechanism = AuthMechanism, - auth_state = AuthState, - connection = Connection = - #connection{protocol = Protocol}, + State = #v1{connection = Connection = + #connection{protocol = Protocol, + auth_mechanism = AuthMechanism, + auth_state = AuthState}, sock = Sock}) -> case AuthMechanism:handle_response(Response, AuthState) of {refused, Msg, Args} -> @@ -890,14 +890,16 @@ auth_phase(Response, {challenge, Challenge, AuthState1} -> Secure = #'connection.secure'{challenge = Challenge}, ok = send_on_channel0(Sock, Secure, Protocol), - State#v1{auth_state = AuthState1}; + State#v1{connection = Connection#connection{ + auth_state = AuthState1}}; {ok, User} -> Tune = #'connection.tune'{channel_max = 0, frame_max = server_frame_max(), heartbeat = server_heartbeat()}, ok = send_on_channel0(Sock, Tune, Protocol), State#v1{connection_state = tuning, - connection = Connection#connection{user = User}} + connection = Connection#connection{user = User, + auth_state = none}} end. %%-------------------------------------------------------------------------- @@ -927,10 +929,6 @@ i(last_blocked_age, #v1{last_blocked_at = never}) -> i(last_blocked_age, #v1{last_blocked_at = T}) -> timer:now_diff(erlang:now(), T) / 1000000; i(channels, #v1{}) -> length(all_channels()); -i(auth_mechanism, #v1{auth_mechanism = none}) -> - none; -i(auth_mechanism, #v1{auth_mechanism = Mechanism}) -> - proplists:get_value(name, Mechanism:description()); i(Item, #v1{connection = Conn}) -> ic(Item, Conn). ic(name, #connection{name = Name}) -> Name; @@ -946,6 +944,10 @@ ic(vhost, #connection{vhost = VHost}) -> VHost; ic(timeout, #connection{timeout_sec = Timeout}) -> Timeout; ic(frame_max, #connection{frame_max = FrameMax}) -> FrameMax; ic(client_properties, #connection{client_properties = CP}) -> CP; +ic(auth_mechanism, #connection{auth_mechanism = none}) -> + none; +ic(auth_mechanism, #connection{auth_mechanism = Mechanism}) -> + proplists:get_value(name, Mechanism:description()); ic(Item, #connection{}) -> throw({bad_argument, Item}). socket_info(Get, Select, #v1{sock = Sock}) -> -- cgit v1.2.1 From 6a99fcb1a67220e7e771178983a5e0b9ad485f83 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 18:46:06 +0000 Subject: move throttle related reader state into sub-state --- src/rabbit_reader.erl | 60 +++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index a21a061b..154fa394 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -37,14 +37,15 @@ -record(v1, {parent, sock, connection, callback, recv_len, pending_recv, connection_state, queue_collector, heartbeater, stats_timer, - channel_sup_sup_pid, start_heartbeat_fun, buf, buf_len, - conserve_resources, last_blocked_by, last_blocked_at}). + channel_sup_sup_pid, start_heartbeat_fun, buf, buf_len, throttle}). -record(connection, {name, host, peer_host, port, peer_port, protocol, user, timeout_sec, frame_max, vhost, client_properties, capabilities, auth_mechanism, auth_state}). +-record(throttle, {conserve_resources, last_blocked_by, last_blocked_at}). + -define(STATISTICS_KEYS, [pid, recv_oct, recv_cnt, send_oct, send_cnt, send_pend, state, last_blocked_by, last_blocked_age, channels]). @@ -233,9 +234,10 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, start_heartbeat_fun = StartHeartbeatFun, buf = [], buf_len = 0, - conserve_resources = false, - last_blocked_by = none, - last_blocked_at = never}, + throttle = #throttle{ + conserve_resources = false, + last_blocked_by = none, + last_blocked_at = never}}, try ok = inet_op(fun () -> rabbit_net:tune_buffer_size(ClientSock) end), recvloop(Deb, switch_callback(rabbit_event:init_stats_timer( @@ -291,8 +293,10 @@ mainloop(Deb, State = #v1{sock = Sock, buf = Buf, buf_len = BufLen}) -> {other, Other} -> handle_other(Other, Deb, State) end. -handle_other({conserve_resources, Conserve}, Deb, State) -> - recvloop(Deb, control_throttle(State#v1{conserve_resources = Conserve})); +handle_other({conserve_resources, Conserve}, Deb, + State = #v1{throttle = Throttle}) -> + Throttle1 = Throttle#throttle{conserve_resources = Conserve}, + recvloop(Deb, control_throttle(State#v1{throttle = Throttle1})); handle_other({channel_closing, ChPid}, Deb, State) -> ok = rabbit_channel:ready_for_close(ChPid), channel_cleanup(ChPid), @@ -375,29 +379,31 @@ terminate(Explanation, State) when ?IS_RUNNING(State) -> terminate(_Explanation, State) -> {force, State}. -control_throttle(State = #v1{connection_state = CS, - conserve_resources = Mem}) -> - case {CS, Mem orelse credit_flow:blocked()} of +control_throttle(State = #v1{connection_state = CS, throttle = Throttle}) -> + case {CS, (Throttle#throttle.conserve_resources orelse + credit_flow:blocked())} of {running, true} -> State#v1{connection_state = blocking}; {blocking, false} -> State#v1{connection_state = running}; {blocked, false} -> ok = rabbit_heartbeat:resume_monitor( State#v1.heartbeater), State#v1{connection_state = running}; - {blocked, true} -> update_last_blocked_by(State); + {blocked, true} -> State#v1{throttle = update_last_blocked_by( + Throttle)}; {_, _} -> State end. -maybe_block(State = #v1{connection_state = blocking}) -> +maybe_block(State = #v1{connection_state = blocking, throttle = Throttle}) -> ok = rabbit_heartbeat:pause_monitor(State#v1.heartbeater), - update_last_blocked_by(State#v1{connection_state = blocked, - last_blocked_at = erlang:now()}); + State#v1{connection_state = blocked, + throttle = update_last_blocked_by( + Throttle#throttle{last_blocked_at = erlang:now()})}; maybe_block(State) -> State. -update_last_blocked_by(State = #v1{conserve_resources = true}) -> - State#v1{last_blocked_by = resource}; -update_last_blocked_by(State = #v1{conserve_resources = false}) -> - State#v1{last_blocked_by = flow}. +update_last_blocked_by(Throttle = #throttle{conserve_resources = true}) -> + Throttle#throttle{last_blocked_by = resource}; +update_last_blocked_by(Throttle = #throttle{conserve_resources = false}) -> + Throttle#throttle{last_blocked_by = flow}. %%-------------------------------------------------------------------------- %% error handling / termination @@ -794,10 +800,11 @@ handle_method0(#'connection.tune_ok'{frame_max = FrameMax, handle_method0(#'connection.open'{virtual_host = VHostPath}, State = #v1{connection_state = opening, - connection = Connection = #connection{ - user = User, - protocol = Protocol}, - sock = Sock}) -> + connection = Connection = #connection{ + user = User, + protocol = Protocol}, + sock = Sock, + throttle = Throttle}) -> ok = rabbit_access_control:check_vhost_access(User, VHostPath), NewConnection = Connection#connection{vhost = VHostPath}, ok = send_on_channel0(Sock, #'connection.open_ok'{}, Protocol), @@ -805,7 +812,8 @@ handle_method0(#'connection.open'{virtual_host = VHostPath}, State1 = control_throttle( State#v1{connection_state = running, connection = NewConnection, - conserve_resources = Conserve}), + throttle = Throttle#throttle{ + conserve_resources = Conserve}}), rabbit_event:notify(connection_created, [{type, network} | infos(?CREATION_EVENT_KEYS, State1)]), @@ -923,10 +931,10 @@ i(peer_cert_issuer, S) -> cert_info(fun rabbit_ssl:peer_cert_issuer/1, S); i(peer_cert_subject, S) -> cert_info(fun rabbit_ssl:peer_cert_subject/1, S); i(peer_cert_validity, S) -> cert_info(fun rabbit_ssl:peer_cert_validity/1, S); i(state, #v1{connection_state = CS}) -> CS; -i(last_blocked_by, #v1{last_blocked_by = By}) -> By; -i(last_blocked_age, #v1{last_blocked_at = never}) -> +i(last_blocked_by, #v1{throttle = #throttle{last_blocked_by = By}}) -> By; +i(last_blocked_age, #v1{throttle = #throttle{last_blocked_at = never}}) -> infinity; -i(last_blocked_age, #v1{last_blocked_at = T}) -> +i(last_blocked_age, #v1{throttle = #throttle{last_blocked_at = T}}) -> timer:now_diff(erlang:now(), T) / 1000000; i(channels, #v1{}) -> length(all_channels()); i(Item, #v1{connection = Conn}) -> ic(Item, Conn). -- cgit v1.2.1 From 601f5b9fad8f64f9d4115765eec1e69602b058ed Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 21:15:46 +0000 Subject: optimise rabbit_channel:process_routing_result/6 - put most common clause first - inline record_confirm/3 --- src/rabbit_channel.erl | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a3c82865..4d3a6f2c 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -555,11 +555,6 @@ queue_blocked(QPid, State = #ch{blocking = Blocking}) -> State#ch{blocking = Blocking1} end. -record_confirm(undefined, _, State) -> - State; -record_confirm(MsgSeqNo, XName, State) -> - record_confirms([{MsgSeqNo, XName}], State). - record_confirms([], State) -> State; record_confirms(MXs, State = #ch{confirmed = C}) -> @@ -1382,17 +1377,20 @@ deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{ publish, State1), State1. -process_routing_result(unroutable, _, XName, MsgSeqNo, Msg, State) -> - ok = basic_return(Msg, State, no_route), - incr_stats([{exchange_stats, XName, 1}], return_unroutable, State), - record_confirm(MsgSeqNo, XName, State); -process_routing_result(routed, [], XName, MsgSeqNo, _, State) -> - record_confirm(MsgSeqNo, XName, State); process_routing_result(routed, _, _, undefined, _, State) -> State; +process_routing_result(routed, [], XName, MsgSeqNo, _, State) -> + record_confirms([{MsgSeqNo, XName}], State); process_routing_result(routed, QPids, XName, MsgSeqNo, _, State) -> State#ch{unconfirmed = dtree:insert(MsgSeqNo, QPids, XName, - State#ch.unconfirmed)}. + State#ch.unconfirmed)}; +process_routing_result(unroutable, _, XName, MsgSeqNo, Msg, State) -> + ok = basic_return(Msg, State, no_route), + incr_stats([{exchange_stats, XName, 1}], return_unroutable, State), + case MsgSeqNo of + undefined -> State; + _ -> record_confirms([{MsgSeqNo, XName}], State) + end. send_nacks([], State) -> State; -- cgit v1.2.1 From 25bc0fabb9538906bf9b6eb0b72ae34e18fcf5c0 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 21:29:12 +0000 Subject: optimise pmon:monitor_all common cases --- src/pmon.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pmon.erl b/src/pmon.erl index 1aeebb72..37898119 100644 --- a/src/pmon.erl +++ b/src/pmon.erl @@ -19,6 +19,8 @@ -export([new/0, monitor/2, monitor_all/2, demonitor/2, is_monitored/2, erase/2, monitored/1, is_empty/1]). +-compile({no_auto_import, [monitor/2]}). + -ifdef(use_specs). %%---------------------------------------------------------------------------- @@ -48,7 +50,9 @@ monitor(Item, M) -> false -> dict:store(Item, erlang:monitor(process, Item), M) end. -monitor_all(Items, M) -> lists:foldl(fun monitor/2, M, Items). +monitor_all([], M) -> M; %% optimisation +monitor_all([Item], M) -> monitor(Item, M); %% optimisation +monitor_all(Items, M) -> lists:foldl(fun monitor/2, M, Items). demonitor(Item, M) -> case dict:find(Item, M) of -- cgit v1.2.1 From aa6ed6e1b531a9a598039984d25d3219817280ef Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 21:37:55 +0000 Subject: optimise rabbit_amqqueue:lookup/1 common cases --- src/rabbit_amqqueue.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 1b6cc223..1a270364 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -302,6 +302,8 @@ add_default_binding(#amqqueue{name = QueueName}) -> key = RoutingKey, args = []}). +lookup([]) -> []; %% optimisation +lookup([Name]) -> ets:lookup(rabbit_queue, Name); %% optimisation lookup(Names) when is_list(Names) -> %% Normally we'd call mnesia:dirty_read/1 here, but that is quite %% expensive for reasons explained in rabbit_misc:dirty_read/1. -- cgit v1.2.1 From f0fe8744f80e98980594470eb03309d6611f50c5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 21:58:33 +0000 Subject: refactor: shorter/better names for rabbit_trace functions --- src/rabbit_channel.erl | 4 ++-- src/rabbit_trace.erl | 18 ++++++++---------- src/rabbit_vhost.erl | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a3c82865..617ea25f 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -635,7 +635,7 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, end, case rabbit_basic:message(ExchangeName, RoutingKey, DecodedContent) of {ok, Message} -> - rabbit_trace:tap_trace_in(Message, TraceState), + rabbit_trace:tap_in(Message, TraceState), Delivery = rabbit_basic:delivery(Mandatory, Message, MsgSeqNo), QNames = rabbit_exchange:route(Exchange, Delivery), {noreply, @@ -1253,7 +1253,7 @@ record_sent(ConsumerTag, AckRequired, true -> incr_stats([{queue_stats, QName, 1}], redeliver, State); false -> ok end, - rabbit_trace:tap_trace_out(Msg, TraceState), + rabbit_trace:tap_out(Msg, TraceState), UAMQ1 = case AckRequired of true -> queue:in({DeliveryTag, ConsumerTag, {QPid, MsgId}}, UAMQ); diff --git a/src/rabbit_trace.erl b/src/rabbit_trace.erl index 3a5b96de..b9a7cc15 100644 --- a/src/rabbit_trace.erl +++ b/src/rabbit_trace.erl @@ -16,7 +16,7 @@ -module(rabbit_trace). --export([init/1, tracing/1, tap_trace_in/2, tap_trace_out/2, start/1, stop/1]). +-export([init/1, enabled/1, tap_in/2, tap_out/2, start/1, stop/1]). -include("rabbit.hrl"). -include("rabbit_framing.hrl"). @@ -31,9 +31,9 @@ -type(state() :: rabbit_types:exchange() | 'none'). -spec(init/1 :: (rabbit_types:vhost()) -> state()). --spec(tracing/1 :: (rabbit_types:vhost()) -> boolean()). --spec(tap_trace_in/2 :: (rabbit_types:basic_message(), state()) -> 'ok'). --spec(tap_trace_out/2 :: (rabbit_amqqueue:qmsg(), state()) -> 'ok'). +-spec(enabled/1 :: (rabbit_types:vhost()) -> boolean()). +-spec(tap_in/2 :: (rabbit_types:basic_message(), state()) -> 'ok'). +-spec(tap_out/2 :: (rabbit_amqqueue:qmsg(), state()) -> 'ok'). -spec(start/1 :: (rabbit_types:vhost()) -> 'ok'). -spec(stop/1 :: (rabbit_types:vhost()) -> 'ok'). @@ -43,23 +43,21 @@ %%---------------------------------------------------------------------------- init(VHost) -> - case tracing(VHost) of + case enabled(VHost) of false -> none; true -> {ok, X} = rabbit_exchange:lookup( rabbit_misc:r(VHost, exchange, ?XNAME)), X end. -tracing(VHost) -> +enabled(VHost) -> {ok, VHosts} = application:get_env(rabbit, ?TRACE_VHOSTS), lists:member(VHost, VHosts). -tap_trace_in(Msg = #basic_message{exchange_name = #resource{name = XName}}, - TraceX) -> +tap_in(Msg = #basic_message{exchange_name = #resource{name = XName}}, TraceX) -> maybe_trace(TraceX, Msg, <<"publish">>, XName, []). -tap_trace_out({#resource{name = QName}, _QPid, _QMsgId, Redelivered, Msg}, - TraceX) -> +tap_out({#resource{name = QName}, _QPid, _QMsgId, Redelivered, Msg}, TraceX) -> RedeliveredNum = case Redelivered of true -> 1; false -> 0 end, maybe_trace(TraceX, Msg, <<"deliver">>, QName, [{<<"redelivered">>, signedint, RedeliveredNum}]). diff --git a/src/rabbit_vhost.erl b/src/rabbit_vhost.erl index 297fa56f..0bb18f4c 100644 --- a/src/rabbit_vhost.erl +++ b/src/rabbit_vhost.erl @@ -123,7 +123,7 @@ with(VHostPath, Thunk) -> infos(Items, X) -> [{Item, i(Item, X)} || Item <- Items]. i(name, VHost) -> VHost; -i(tracing, VHost) -> rabbit_trace:tracing(VHost); +i(tracing, VHost) -> rabbit_trace:enabled(VHost); i(Item, _) -> throw({bad_argument, Item}). info(VHost) -> infos(?INFO_KEYS, VHost). -- cgit v1.2.1 From 542ad2ef89fe9e5fae4746866acdec06cd907ab9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 22:03:28 +0000 Subject: optimise rabbit_trace:tap_{in,out} common cases --- src/rabbit_trace.erl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/rabbit_trace.erl b/src/rabbit_trace.erl index b9a7cc15..601656da 100644 --- a/src/rabbit_trace.erl +++ b/src/rabbit_trace.erl @@ -54,13 +54,15 @@ enabled(VHost) -> {ok, VHosts} = application:get_env(rabbit, ?TRACE_VHOSTS), lists:member(VHost, VHosts). +tap_in(_Msg, none) -> ok; tap_in(Msg = #basic_message{exchange_name = #resource{name = XName}}, TraceX) -> - maybe_trace(TraceX, Msg, <<"publish">>, XName, []). + trace(TraceX, Msg, <<"publish">>, XName, []). +tap_out(_Msg, none) -> ok; tap_out({#resource{name = QName}, _QPid, _QMsgId, Redelivered, Msg}, TraceX) -> RedeliveredNum = case Redelivered of true -> 1; false -> 0 end, - maybe_trace(TraceX, Msg, <<"deliver">>, QName, - [{<<"redelivered">>, signedint, RedeliveredNum}]). + trace(TraceX, Msg, <<"deliver">>, QName, + [{<<"redelivered">>, signedint, RedeliveredNum}]). %%---------------------------------------------------------------------------- @@ -81,14 +83,11 @@ update_config(Fun) -> %%---------------------------------------------------------------------------- -maybe_trace(none, _Msg, _RKPrefix, _RKSuffix, _Extra) -> +trace(#exchange{name = Name}, #basic_message{exchange_name = Name}, + _RKPrefix, _RKSuffix, _Extra) -> ok; -maybe_trace(#exchange{name = Name}, #basic_message{exchange_name = Name}, - _RKPrefix, _RKSuffix, _Extra) -> - ok; -maybe_trace(X, Msg = #basic_message{content = #content{ - payload_fragments_rev = PFR}}, - RKPrefix, RKSuffix, Extra) -> +trace(X, Msg = #basic_message{content = #content{payload_fragments_rev = PFR}}, + RKPrefix, RKSuffix, Extra) -> {ok, _, _} = rabbit_basic:publish( X, <>, #'P_basic'{headers = msg_to_table(Msg) ++ Extra}, PFR), -- cgit v1.2.1 From f9af8cabdfd67e437792f0ba30b5312fe121e604 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 22:19:47 +0000 Subject: optimisation: inline rabbit_guid:blocks_to_binary/1 --- src/rabbit_guid.erl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/rabbit_guid.erl b/src/rabbit_guid.erl index cedbbdb3..8ee9ad5b 100644 --- a/src/rabbit_guid.erl +++ b/src/rabbit_guid.erl @@ -104,8 +104,6 @@ advance_blocks({B1, B2, B3, B4}, I) -> B5 = erlang:phash2({B1, I}, 4294967296), {{(B2 bxor B5), (B3 bxor B5), (B4 bxor B5), B5}, I+1}. -blocks_to_binary({B1, B2, B3, B4}) -> <>. - %% generate a GUID. This function should be used when performance is a %% priority and predictability is not an issue. Otherwise use %% gen_secure/0. @@ -114,14 +112,15 @@ gen() -> %% time we need a new guid we rotate them producing a new hash %% with the aid of the counter. Look at the comments in %% advance_blocks/2 for details. - {BS, I} = case get(guid) of - undefined -> <> = - erlang:md5(term_to_binary(fresh())), - {{B1,B2,B3,B4}, 0}; - {BS0, I0} -> advance_blocks(BS0, I0) - end, - put(guid, {BS, I}), - blocks_to_binary(BS). + case get(guid) of + undefined -> <> = Res = + erlang:md5(term_to_binary(fresh())), + put(guid, {{B1, B2, B3, B4}, 0}), + Res; + {BS, I} -> {{B1, B2, B3, B4}, _} = S = advance_blocks(BS, I), + put(guid, S), + <> + end. %% generate a non-predictable GUID. %% -- cgit v1.2.1 From 664c619c91e994c3645980be4903a20fab632e43 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 28 Dec 2012 22:29:25 +0000 Subject: optimisation: ditch guards on rabbit_misc:r/{2,3} --- src/rabbit_misc.erl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 4efde50e..edaa7198 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -352,13 +352,12 @@ set_table_value(Table, Key, Type, Value) -> sort_field_table( lists:keystore(Key, 1, Table, {Key, Type, Value})). -r(#resource{virtual_host = VHostPath}, Kind, Name) - when is_binary(Name) -> +r(#resource{virtual_host = VHostPath}, Kind, Name) -> #resource{virtual_host = VHostPath, kind = Kind, name = Name}; -r(VHostPath, Kind, Name) when is_binary(Name) andalso is_binary(VHostPath) -> +r(VHostPath, Kind, Name) -> #resource{virtual_host = VHostPath, kind = Kind, name = Name}. -r(VHostPath, Kind) when is_binary(VHostPath) -> +r(VHostPath, Kind) -> #resource{virtual_host = VHostPath, kind = Kind, name = '_'}. r_arg(#resource{virtual_host = VHostPath}, Kind, Table, Key) -> -- cgit v1.2.1 From 46d572342e6b1af9944fc170bebf92ca7a86f9f4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 29 Dec 2012 05:00:27 +0000 Subject: cosmetic --- src/rabbit_amqqueue_process.erl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 03bcdf43..781546af 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -791,12 +791,9 @@ stop(State) -> stop(undefined, noreply, State). stop(From, Reply, State = #q{unconfirmed = UC}) -> case {dtree:is_empty(UC), Reply} of - {true, noreply} -> - {stop, normal, State}; - {true, _} -> - {stop, normal, Reply, State}; - {false, _} -> - noreply(State#q{delayed_stop = {From, Reply}}) + {true, noreply} -> {stop, normal, State}; + {true, _} -> {stop, normal, Reply, State}; + {false, _} -> noreply(State#q{delayed_stop = {From, Reply}}) end. cleanup_after_confirm(AckTags, State = #q{delayed_stop = DS, -- cgit v1.2.1 From 6da6dbf02b3791fa5340444d4589bf683487bbeb Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 29 Dec 2012 13:02:02 +0000 Subject: move tx related state fields into sub-record --- src/rabbit_channel.erl | 135 +++++++++++++++++++++++-------------------------- 1 file changed, 64 insertions(+), 71 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 617ea25f..dd858758 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -33,14 +33,15 @@ -export([list_local/0]). -record(ch, {state, protocol, channel, reader_pid, writer_pid, conn_pid, - conn_name, limiter, tx_status, next_tag, unacked_message_q, - uncommitted_message_q, uncommitted_acks, uncommitted_nacks, user, + conn_name, limiter, tx, next_tag, unacked_message_q, user, virtual_host, most_recently_declared_queue, queue_names, queue_monitors, consumer_mapping, blocking, queue_consumers, delivering_queues, queue_collector_pid, stats_timer, confirm_enabled, publish_seqno, unconfirmed, confirmed, capabilities, trace_state}). +-record(tx, {msgs, acks, nacks}). + -define(MAX_PERMISSION_CACHE_SIZE, 12). -define(STATISTICS_KEYS, @@ -186,12 +187,9 @@ init([Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, VHost, conn_pid = ConnPid, conn_name = ConnName, limiter = Limiter, - tx_status = none, + tx = none, next_tag = 1, unacked_message_q = queue:new(), - uncommitted_message_q = queue:new(), - uncommitted_acks = [], - uncommitted_nacks = [], user = User, virtual_host = VHost, most_recently_declared_queue = <<>>, @@ -599,8 +597,8 @@ handle_method(#'channel.close'{}, _, State = #ch{reader_pid = ReaderPid}) -> %% while waiting for the reply to a synchronous command, we generally %% do allow this...except in the case of a pending tx.commit, where %% it could wreak havoc. -handle_method(_Method, _, #ch{tx_status = TxStatus}) - when TxStatus =/= none andalso TxStatus =/= in_progress -> +handle_method(_Method, _, #ch{tx = Tx}) + when Tx =:= committing orelse Tx =:= failed -> rabbit_misc:protocol_error( channel_error, "unexpected command while processing 'tx.commit'", []); @@ -614,7 +612,7 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, routing_key = RoutingKey, mandatory = Mandatory}, Content, State = #ch{virtual_host = VHostPath, - tx_status = TxStatus, + tx = Tx, confirm_enabled = ConfirmEnabled, trace_state = TraceState}) -> ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), @@ -628,7 +626,7 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, check_user_id_header(Props, State), check_expiration_header(Props), {MsgSeqNo, State1} = - case {TxStatus, ConfirmEnabled} of + case {Tx, ConfirmEnabled} of {none, false} -> {undefined, State}; {_, _} -> SeqNo = State#ch.publish_seqno, {SeqNo, State#ch{publish_seqno = SeqNo + 1}} @@ -638,12 +636,12 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, rabbit_trace:tap_in(Message, TraceState), Delivery = rabbit_basic:delivery(Mandatory, Message, MsgSeqNo), QNames = rabbit_exchange:route(Exchange, Delivery), + DQ = {Delivery, QNames}, {noreply, - case TxStatus of - none -> deliver_to_queues({Delivery, QNames}, State1); - in_progress -> TMQ = State1#ch.uncommitted_message_q, - NewTMQ = queue:in({Delivery, QNames}, TMQ), - State1#ch{uncommitted_message_q = NewTMQ} + case Tx of + none -> deliver_to_queues(DQ, State1); + #tx{msgs = Msgs} -> Msgs1 = queue:in(DQ, Msgs), + State1#ch{tx = Tx#tx{msgs = Msgs1}} end}; {error, Reason} -> precondition_failed("invalid message: ~p", [Reason]) @@ -657,15 +655,14 @@ handle_method(#'basic.nack'{delivery_tag = DeliveryTag, handle_method(#'basic.ack'{delivery_tag = DeliveryTag, multiple = Multiple}, - _, State = #ch{unacked_message_q = UAMQ, tx_status = TxStatus}) -> + _, State = #ch{unacked_message_q = UAMQ, tx = Tx}) -> {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), State1 = State#ch{unacked_message_q = Remaining}, {noreply, - case TxStatus of - none -> ack(Acked, State1), - State1; - in_progress -> State1#ch{uncommitted_acks = - Acked ++ State1#ch.uncommitted_acks} + case Tx of + none -> ack(Acked, State1), + State1; + #tx{acks = Acks} -> State1#ch{tx = Tx#tx{acks = Acked ++ Acks}} end}; handle_method(#'basic.get'{queue = QueueNameBin, @@ -1043,34 +1040,37 @@ handle_method(#'queue.purge'{queue = QueueNameBin, handle_method(#'tx.select'{}, _, #ch{confirm_enabled = true}) -> precondition_failed("cannot switch from confirm to tx mode"); +handle_method(#'tx.select'{}, _, State = #ch{tx = none}) -> + {reply, #'tx.select_ok'{}, State#ch{tx = new_tx()}}; + handle_method(#'tx.select'{}, _, State) -> - {reply, #'tx.select_ok'{}, State#ch{tx_status = in_progress}}; + {reply, #'tx.select_ok'{}, State}; -handle_method(#'tx.commit'{}, _, #ch{tx_status = none}) -> +handle_method(#'tx.commit'{}, _, #ch{tx = none}) -> precondition_failed("channel is not transactional"); -handle_method(#'tx.commit'{}, _, - State = #ch{uncommitted_message_q = TMQ, - uncommitted_acks = TAL, - uncommitted_nacks = TNL, - limiter = Limiter}) -> - State1 = rabbit_misc:queue_fold(fun deliver_to_queues/2, State, TMQ), - ack(TAL, State1), +handle_method(#'tx.commit'{}, _, State = #ch{tx = #tx{msgs = Msgs, + acks = Acks, + nacks = Nacks}, + limiter = Limiter}) -> + State1 = rabbit_misc:queue_fold(fun deliver_to_queues/2, State, Msgs), + ack(Acks, State1), lists:foreach( - fun({Requeue, Acked}) -> reject(Requeue, Acked, Limiter) end, TNL), - {noreply, maybe_complete_tx(new_tx(State1#ch{tx_status = committing}))}; + fun({Requeue, Acked}) -> reject(Requeue, Acked, Limiter) end, Nacks), + {noreply, maybe_complete_tx(State1#ch{tx = committing})}; -handle_method(#'tx.rollback'{}, _, #ch{tx_status = none}) -> +handle_method(#'tx.rollback'{}, _, #ch{tx = none}) -> precondition_failed("channel is not transactional"); handle_method(#'tx.rollback'{}, _, State = #ch{unacked_message_q = UAMQ, - uncommitted_acks = TAL, - uncommitted_nacks = TNL}) -> - TNL1 = lists:append([L || {_, L} <- TNL]), - UAMQ1 = queue:from_list(lists:usort(TAL ++ TNL1 ++ queue:to_list(UAMQ))), - {reply, #'tx.rollback_ok'{}, new_tx(State#ch{unacked_message_q = UAMQ1})}; - -handle_method(#'confirm.select'{}, _, #ch{tx_status = in_progress}) -> + tx = #tx{acks = Acks, + nacks = Nacks}}) -> + NacksL = lists:append([L || {_, L} <- Nacks]), + UAMQ1 = queue:from_list(lists:usort(Acks ++ NacksL ++ queue:to_list(UAMQ))), + {reply, #'tx.rollback_ok'{}, State#ch{unacked_message_q = UAMQ1, + tx = new_tx()}}; + +handle_method(#'confirm.select'{}, _, #ch{tx = #tx{}}) -> precondition_failed("cannot switch from tx to confirm mode"); handle_method(#'confirm.select'{nowait = NoWait}, _, State) -> @@ -1218,17 +1218,15 @@ basic_return(#basic_message{exchange_name = ExchangeName, Content). reject(DeliveryTag, Requeue, Multiple, - State = #ch{unacked_message_q = UAMQ, tx_status = TxStatus}) -> + State = #ch{unacked_message_q = UAMQ, tx = Tx}) -> {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), State1 = State#ch{unacked_message_q = Remaining}, {noreply, - case TxStatus of - none -> - reject(Requeue, Acked, State1#ch.limiter), - State1; - in_progress -> - State1#ch{uncommitted_nacks = - [{Requeue, Acked} | State1#ch.uncommitted_nacks]} + case Tx of + none -> reject(Requeue, Acked, State1#ch.limiter), + State1; + #tx{nacks = Nacks} -> Nacks1 = [{Requeue, Acked} | Nacks], + State1#ch{tx = Tx#tx{nacks = Nacks1}} end}. reject(Requeue, Acked, Limiter) -> @@ -1296,9 +1294,7 @@ ack(Acked, State = #ch{queue_names = QNames}) -> ok = notify_limiter(State#ch.limiter, Acked), incr_stats(Incs, ack, State). -new_tx(State) -> State#ch{uncommitted_message_q = queue:new(), - uncommitted_acks = [], - uncommitted_nacks = []}. +new_tx() -> #tx{msgs = queue:new(), acks = [], nacks = []}. notify_queues(State = #ch{state = closing}) -> {ok, State}; @@ -1396,18 +1392,18 @@ process_routing_result(routed, QPids, XName, MsgSeqNo, _, State) -> send_nacks([], State) -> State; -send_nacks(MXs, State = #ch{tx_status = none}) -> +send_nacks(MXs, State = #ch{tx = none}) -> coalesce_and_send([MsgSeqNo || {MsgSeqNo, _} <- MXs], fun(MsgSeqNo, Multiple) -> #'basic.nack'{delivery_tag = MsgSeqNo, multiple = Multiple} end, State); send_nacks(_, State) -> - maybe_complete_tx(State#ch{tx_status = failed}). + maybe_complete_tx(State#ch{tx = failed}). -send_confirms(State = #ch{tx_status = none, confirmed = []}) -> +send_confirms(State = #ch{tx = none, confirmed = []}) -> State; -send_confirms(State = #ch{tx_status = none, confirmed = C}) -> +send_confirms(State = #ch{tx = none, confirmed = C}) -> MsgSeqNos = lists:foldl( fun ({MsgSeqNo, XName}, MSNs) -> @@ -1447,7 +1443,7 @@ coalesce_and_send(MsgSeqNos, MkMsgFun, WriterPid, MkMsgFun(SeqNo, false)) || SeqNo <- Ss], State. -maybe_complete_tx(State = #ch{tx_status = in_progress}) -> +maybe_complete_tx(State = #ch{tx = #tx{}}) -> State; maybe_complete_tx(State = #ch{unconfirmed = UC}) -> case dtree:is_empty(UC) of @@ -1455,16 +1451,16 @@ maybe_complete_tx(State = #ch{unconfirmed = UC}) -> true -> complete_tx(State#ch{confirmed = []}) end. -complete_tx(State = #ch{tx_status = committing}) -> +complete_tx(State = #ch{tx = committing}) -> ok = rabbit_writer:send_command(State#ch.writer_pid, #'tx.commit_ok'{}), - State#ch{tx_status = in_progress}; -complete_tx(State = #ch{tx_status = failed}) -> + State#ch{tx = new_tx()}; +complete_tx(State = #ch{tx = failed}) -> {noreply, State1} = handle_exception( rabbit_misc:amqp_error( precondition_failed, "partial tx completion", [], 'tx.commit'), State), - State1#ch{tx_status = in_progress}. + State1#ch{tx = new_tx()}. infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items]. @@ -1473,19 +1469,16 @@ i(connection, #ch{conn_pid = ConnPid}) -> ConnPid; i(number, #ch{channel = Channel}) -> Channel; i(user, #ch{user = User}) -> User#user.username; i(vhost, #ch{virtual_host = VHost}) -> VHost; -i(transactional, #ch{tx_status = TE}) -> TE =/= none; +i(transactional, #ch{tx = Tx}) -> Tx =/= none; i(confirm, #ch{confirm_enabled = CE}) -> CE; i(name, State) -> name(State); -i(consumer_count, #ch{consumer_mapping = ConsumerMapping}) -> - dict:size(ConsumerMapping); -i(messages_unconfirmed, #ch{unconfirmed = UC}) -> - dtree:size(UC); -i(messages_unacknowledged, #ch{unacked_message_q = UAMQ}) -> - queue:len(UAMQ); -i(messages_uncommitted, #ch{uncommitted_message_q = TMQ}) -> - queue:len(TMQ); -i(acks_uncommitted, #ch{uncommitted_acks = TAL}) -> - length(TAL); +i(consumer_count, #ch{consumer_mapping = CM}) -> dict:size(CM); +i(messages_unconfirmed, #ch{unconfirmed = UC}) -> dtree:size(UC); +i(messages_unacknowledged, #ch{unacked_message_q = UAMQ}) -> queue:len(UAMQ); +i(messages_uncommitted, #ch{tx = #tx{msgs = Msgs}}) -> queue:len(Msgs); +i(messages_uncommitted, #ch{}) -> 0; +i(acks_uncommitted, #ch{tx = #tx{acks = Acks}}) -> length(Acks); +i(acks_uncommitted, #ch{}) -> 0; i(prefetch_count, #ch{limiter = Limiter}) -> rabbit_limiter:get_limit(Limiter); i(client_flow_blocked, #ch{limiter = Limiter}) -> -- cgit v1.2.1 From 8b8767feb898435d9bdb22f0956cdc148c490800 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 29 Dec 2012 15:49:15 +0000 Subject: change rabbit_exchange:route/2's traversal order from breadth-first to depth-first, thus eliminating the queue data structure and hence simplifying the code and improving performance. Plus a little bit of refactoring in the area. --- src/rabbit_exchange.erl | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index e72cbafe..9339161f 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -310,22 +310,19 @@ route(#exchange{name = #resource{name = <<"">>, virtual_host = VHost}}, [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; route(X = #exchange{name = XName}, Delivery) -> - route1(Delivery, {queue:from_list([X]), XName, []}). - -route1(Delivery, {WorkList, SeenXs, QNames}) -> - case queue:out(WorkList) of - {empty, _WorkList} -> - lists:usort(QNames); - {{value, X = #exchange{type = Type}}, WorkList1} -> - DstNames = process_alternate( - X, ((type_to_module(Type)):route(X, Delivery))), - route1(Delivery, - lists:foldl(fun process_route/2, {WorkList1, SeenXs, QNames}, - DstNames)) - end. + route1(Delivery, {[X], XName, []}). + +route1(_, {[], _, QNames}) -> + lists:usort(QNames); +route1(Delivery, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> + DstNames = process_alternate( + X, ((type_to_module(Type)):route(X, Delivery))), + route1(Delivery, + lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, + DstNames)). process_alternate(#exchange{arguments = []}, Results) -> %% optimisation - Results; + Results; process_alternate(#exchange{name = XName, arguments = Args}, []) -> case rabbit_misc:r_arg(XName, exchange, Args, <<"alternate-exchange">>) of undefined -> []; @@ -339,23 +336,25 @@ process_route(#resource{kind = exchange} = XName, Acc; process_route(#resource{kind = exchange} = XName, {WorkList, #resource{kind = exchange} = SeenX, QNames}) -> - {case lookup(XName) of - {ok, X} -> queue:in(X, WorkList); - {error, not_found} -> WorkList - end, gb_sets:from_list([SeenX, XName]), QNames}; + {cons_if_present(XName, WorkList), + gb_sets:from_list([SeenX, XName]), QNames}; process_route(#resource{kind = exchange} = XName, {WorkList, SeenXs, QNames} = Acc) -> case gb_sets:is_element(XName, SeenXs) of true -> Acc; - false -> {case lookup(XName) of - {ok, X} -> queue:in(X, WorkList); - {error, not_found} -> WorkList - end, gb_sets:add_element(XName, SeenXs), QNames} + false -> {cons_if_present(XName, WorkList), + gb_sets:add_element(XName, SeenXs), QNames} end; process_route(#resource{kind = queue} = QName, {WorkList, SeenXs, QNames}) -> {WorkList, SeenXs, [QName | QNames]}. +cons_if_present(XName, L) -> + case lookup(XName) of + {ok, X} -> [X | L]; + {error, not_found} -> L + end. + call_with_exchange(XName, Fun) -> rabbit_misc:execute_mnesia_tx_with_tail( fun () -> case mnesia:read({rabbit_exchange, XName}) of -- cgit v1.2.1 From 764f6ad7f60ba0b07aa00559a83ee3ffbe38f4f7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 29 Dec 2012 20:40:49 +0000 Subject: small refactor --- src/rabbit_reader.erl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 83622a9f..840f430e 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -594,21 +594,22 @@ handle_frame(Type, Channel, Payload, State) -> unexpected_frame(Type, Channel, Payload, State). process_frame(Frame, Channel, State) -> - {ChPid, AState} = case get({channel, Channel}) of + ChKey = {channel, Channel}, + {ChPid, AState} = case get(ChKey) of undefined -> create_channel(Channel, State); Other -> Other end, case rabbit_command_assembler:process(Frame, AState) of {ok, NewAState} -> - put({channel, Channel}, {ChPid, NewAState}), + put(ChKey, {ChPid, NewAState}), post_process_frame(Frame, ChPid, State); {ok, Method, NewAState} -> rabbit_channel:do(ChPid, Method), - put({channel, Channel}, {ChPid, NewAState}), + put(ChKey, {ChPid, NewAState}), post_process_frame(Frame, ChPid, State); {ok, Method, Content, NewAState} -> rabbit_channel:do_flow(ChPid, Method, Content), - put({channel, Channel}, {ChPid, NewAState}), + put(ChKey, {ChPid, NewAState}), post_process_frame(Frame, ChPid, control_throttle(State)); {error, Reason} -> {error, Reason} -- cgit v1.2.1 From efba5a5ebb0f0141e2856228b77eaad0d1f6a603 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 29 Dec 2012 22:10:25 +0000 Subject: optimisation: macro-fy rabbit_channel:incr_stats/3 so we can avoid constructing complex terms for stats when stats emission is disabled --- src/rabbit_channel.erl | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 1b1bccf7..c19f9c3a 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -65,6 +65,12 @@ -define(INFO_KEYS, ?CREATION_EVENT_KEYS ++ ?STATISTICS_KEYS -- [pid]). +-define(INCR_STATS(Incs, Measure, State), + case rabbit_event:stats_level(State, #ch.stats_timer) of + fine -> incr_stats(Incs, Measure); + _ -> ok + end). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -1238,14 +1244,14 @@ record_sent(ConsumerTag, AckRequired, State = #ch{unacked_message_q = UAMQ, next_tag = DeliveryTag, trace_state = TraceState}) -> - incr_stats([{queue_stats, QName, 1}], case {ConsumerTag, AckRequired} of - {none, true} -> get; - {none, false} -> get_no_ack; - {_ , true} -> deliver; - {_ , false} -> deliver_no_ack - end, State), + ?INCR_STATS([{queue_stats, QName, 1}], case {ConsumerTag, AckRequired} of + {none, true} -> get; + {none, false} -> get_no_ack; + {_ , true} -> deliver; + {_ , false} -> deliver_no_ack + end, State), case Redelivered of - true -> incr_stats([{queue_stats, QName, 1}], redeliver, State); + true -> ?INCR_STATS([{queue_stats, QName, 1}], redeliver, State); false -> ok end, rabbit_trace:tap_out(Msg, TraceState), @@ -1289,7 +1295,7 @@ ack(Acked, State = #ch{queue_names = QNames}) -> end end, [], Acked), ok = notify_limiter(State#ch.limiter, Acked), - incr_stats(Incs, ack, State). + ?INCR_STATS(Incs, ack, State). new_tx(State) -> State#ch{uncommitted_message_q = queue:new(), uncommitted_acks = [], @@ -1370,11 +1376,11 @@ deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{ XName, MsgSeqNo, Message, State#ch{queue_names = QNames1, queue_monitors = QMons1}), - incr_stats([{exchange_stats, XName, 1} | - [{queue_exchange_stats, {QName, XName}, 1} || - QPid <- DeliveredQPids, - {ok, QName} <- [dict:find(QPid, QNames1)]]], - publish, State1), + ?INCR_STATS([{exchange_stats, XName, 1} | + [{queue_exchange_stats, {QName, XName}, 1} || + QPid <- DeliveredQPids, + {ok, QName} <- [dict:find(QPid, QNames1)]]], + publish, State1), State1. process_routing_result(routed, _, _, undefined, _, State) -> @@ -1386,7 +1392,7 @@ process_routing_result(routed, QPids, XName, MsgSeqNo, _, State) -> State#ch.unconfirmed)}; process_routing_result(unroutable, _, XName, MsgSeqNo, Msg, State) -> ok = basic_return(Msg, State, no_route), - incr_stats([{exchange_stats, XName, 1}], return_unroutable, State), + ?INCR_STATS([{exchange_stats, XName, 1}], return_unroutable, State), case MsgSeqNo of undefined -> State; _ -> record_confirms([{MsgSeqNo, XName}], State) @@ -1409,7 +1415,7 @@ send_confirms(State = #ch{tx_status = none, confirmed = C}) -> MsgSeqNos = lists:foldl( fun ({MsgSeqNo, XName}, MSNs) -> - incr_stats([{exchange_stats, XName, 1}], confirm, State), + ?INCR_STATS([{exchange_stats, XName, 1}], confirm, State), [MsgSeqNo | MSNs] end, [], lists:append(C)), send_confirms(MsgSeqNos, State#ch{confirmed = []}); @@ -1494,12 +1500,8 @@ i(Item, _) -> name(#ch{conn_name = ConnName, channel = Channel}) -> list_to_binary(rabbit_misc:format("~s (~p)", [ConnName, Channel])). -incr_stats(Incs, Measure, State) -> - case rabbit_event:stats_level(State, #ch.stats_timer) of - fine -> [update_measures(Type, Key, Inc, Measure) || - {Type, Key, Inc} <- Incs]; - _ -> ok - end. +incr_stats(Incs, Measure) -> + [update_measures(Type, Key, Inc, Measure) || {Type, Key, Inc} <- Incs]. update_measures(Type, Key, Inc, Measure) -> Measures = case get({Type, Key}) of -- cgit v1.2.1 From c17877b449ba4f6a39ae9dbb9c930a2b02c2c5ed Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 30 Dec 2012 15:22:22 +0000 Subject: consistency --- src/rabbit_control_main.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index b4272555..819435ee 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -283,7 +283,7 @@ action(forget_cluster_node, Node, [ClusterNodeS], Opts, Inform) -> action(sync_queue, Node, [Queue], Opts, Inform) -> VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Synchronising queue ~s in ~s", [Queue, VHost]), + Inform("Synchronising queue \"~s\" in vhost \"~s\"", [Queue, VHost]), rpc_call(Node, rabbit_amqqueue, sync, [list_to_binary(Queue), list_to_binary(VHost)]); -- cgit v1.2.1 From 261a7e89ff29e0d35957e3f97c833301bf34b1c6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 30 Dec 2012 15:52:33 +0000 Subject: correct docs to reflect idempotence --- docs/rabbitmqctl.1.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/rabbitmqctl.1.xml b/docs/rabbitmqctl.1.xml index 1583ab98..a95f7b3d 100644 --- a/docs/rabbitmqctl.1.xml +++ b/docs/rabbitmqctl.1.xml @@ -465,9 +465,8 @@ synchronise itself. The queue will block while synchronisation takes place (all publishers to and consumers from the queue will block). The queue must be - mirrored, must have unsynchronised slaves, and must not - have any pending unacknowledged messages for this - command to succeed. + mirrored, and must not have any pending unacknowledged + messages for this command to succeed. Note that unsynchronised queues from which messages are -- cgit v1.2.1 From 66033c0ae9b6c79ac34dcf6859dc31fd20b802e5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 30 Dec 2012 18:13:16 +0000 Subject: cosmetic(ish) refactors on mq_sync:master_send - more sensible arg order (a folding function should generally take the element first and the acc last) - somewhat neater handling of the acc --- src/rabbit_mirror_queue_sync.erl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index bb12cf49..f9502219 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -68,21 +68,20 @@ master_go(Syncer, Ref, Log, BQ, BQS) -> end. master_go0(Args, BQ, BQS) -> - case BQ:fold(fun (Msg, MsgProps, {I, Last}) -> - master_send(Args, I, Last, Msg, MsgProps) + case BQ:fold(fun (Msg, MsgProps, Acc) -> + master_send(Msg, MsgProps, Args, Acc) end, {0, erlang:now()}, BQS) of {{shutdown, Reason}, BQS1} -> {shutdown, Reason, BQS1}; {{sync_died, Reason}, BQS1} -> {sync_died, Reason, BQS1}; {_, BQS1} -> master_done(Args, BQS1) end. -master_send({Syncer, Ref, Log, Parent}, I, Last, Msg, MsgProps) -> - Acc = {I + 1, - case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of - true -> Log("~p messages", [I]), - erlang:now(); - false -> Last - end}, +master_send(Msg, MsgProps, {Syncer, Ref, Log, Parent}, {I, Last}) -> + T = case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of + true -> Log("~p messages", [I]), + erlang:now(); + false -> Last + end, receive {'$gen_cast', {set_maximum_since_use, Age}} -> ok = file_handle_cache:set_maximum_since_use(Age) @@ -91,7 +90,7 @@ master_send({Syncer, Ref, Log, Parent}, I, Last, Msg, MsgProps) -> end, receive {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, - {cont, Acc}; + {cont, {I + 1, T}}; {'EXIT', Parent, Reason} -> {stop, {shutdown, Reason}}; {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}} end. -- cgit v1.2.1 From 5c56a6702ca13de0bbc67daf5c9bcb098011dd42 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 12:37:23 +0000 Subject: better fun names --- src/rabbit_amqqueue_process.erl | 28 ++++++++++++++-------------- src/rabbit_mirror_queue_master.erl | 4 ++-- src/rabbit_mirror_queue_sync.erl | 12 ++++++------ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 26c0edbe..0cc686f4 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1163,22 +1163,22 @@ handle_call(sync_mirrors, _From, State = #q{backing_queue = rabbit_mirror_queue_master = BQ, backing_queue_state = BQS}) -> S = fun(BQSN) -> State#q{backing_queue_state = BQSN} end, - InfoPull = fun (Status) -> - receive {'$gen_call', From, {info, Items}} -> - Infos = infos(Items, State#q{status = Status}), - gen_server2:reply(From, {ok, Infos}) - after 0 -> - ok - end - end, - InfoPush = fun (Status) -> - rabbit_event:if_enabled( - State, #q.stats_timer, - fun() -> emit_stats(State#q{status = Status}) end) - end, + HandleInfo = fun (Status) -> + receive {'$gen_call', From, {info, Items}} -> + Infos = infos(Items, State#q{status = Status}), + gen_server2:reply(From, {ok, Infos}) + after 0 -> + ok + end + end, + EmitStats = fun (Status) -> + rabbit_event:if_enabled( + State, #q.stats_timer, + fun() -> emit_stats(State#q{status = Status}) end) + end, case BQ:depth(BQS) - BQ:len(BQS) of 0 -> case rabbit_mirror_queue_master:sync_mirrors( - InfoPull, InfoPush, BQS) of + HandleInfo, EmitStats, BQS) of {shutdown, Reason, BQS1} -> {stop, Reason, S(BQS1)}; {Result, BQS1} -> reply(Result, S(BQS1)) end; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index 601649ef..db0308b7 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -127,7 +127,7 @@ stop_mirroring(State = #state { coordinator = CPid, stop_all_slaves(shutdown, State), {BQ, BQS}. -sync_mirrors(InfoPull, InfoPush, +sync_mirrors(HandleInfo, EmitStats, State = #state { name = QName, gm = GM, backing_queue = BQ, @@ -143,7 +143,7 @@ sync_mirrors(InfoPull, InfoPush, gm:broadcast(GM, {sync_start, Ref, Syncer, SPids}), S = fun(BQSN) -> State#state{backing_queue_state = BQSN} end, case rabbit_mirror_queue_sync:master_go( - Syncer, Ref, Log, InfoPull, InfoPush, BQ, BQS) of + Syncer, Ref, Log, HandleInfo, EmitStats, BQ, BQS) of {shutdown, R, BQS1} -> {stop, R, S(BQS1)}; {sync_died, R, BQS1} -> Log("~p", [R]), {ok, S(BQS1)}; diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 5f0307fc..a1c54517 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -59,12 +59,12 @@ master_prepare(Ref, Log, SPids) -> MPid = self(), spawn_link(fun () -> syncer(Ref, Log, MPid, SPids) end). -master_go(Syncer, Ref, Log, InfoPull, InfoPush, BQ, BQS) -> - Args = {Syncer, Ref, Log, InfoPull, InfoPush, rabbit_misc:get_parent()}, +master_go(Syncer, Ref, Log, HandleInfo, EmitStats, BQ, BQS) -> + Args = {Syncer, Ref, Log, HandleInfo, EmitStats, rabbit_misc:get_parent()}, receive {'EXIT', Syncer, normal} -> {already_synced, BQS}; {'EXIT', Syncer, Reason} -> {sync_died, Reason, BQS}; - {ready, Syncer} -> InfoPush({syncing, 0}), + {ready, Syncer} -> EmitStats({syncing, 0}), master_go0(Args, BQ, BQS) end. @@ -77,15 +77,15 @@ master_go0(Args, BQ, BQS) -> {_, BQS1} -> master_done(Args, BQS1) end. -master_send(Msg, MsgProps, {Syncer, Ref, Log, InfoPull, InfoPush, Parent}, +master_send(Msg, MsgProps, {Syncer, Ref, Log, HandleInfo, EmitStats, Parent}, {I, Last}) -> T = case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of - true -> InfoPush({syncing, I}), + true -> EmitStats({syncing, I}), Log("~p messages", [I]), erlang:now(); false -> Last end, - InfoPull({syncing, I}), + HandleInfo({syncing, I}), receive {'$gen_cast', {set_maximum_since_use, Age}} -> ok = file_handle_cache:set_maximum_since_use(Age) -- cgit v1.2.1 From bd366efb9320c18f17ff2bfb229533c68233454a Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 12:40:17 +0000 Subject: oops --- src/rabbit_mirror_queue_sync.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index a1c54517..e3edecb5 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -107,7 +107,7 @@ master_send(Msg, MsgProps, {Syncer, Ref, Log, HandleInfo, EmitStats, Parent}, {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}} end. -master_done({Syncer, Ref, _Log, Parent}, BQS) -> +master_done({Syncer, Ref, _Log, _HandleInfo, _EmitStats, Parent}, BQS) -> receive {next, Ref} -> unlink(Syncer), Syncer ! {done, Ref}, -- cgit v1.2.1 From 4aa86fc039e0fdd7adf41b3b4e50b821c753a129 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 12:44:45 +0000 Subject: refactor: extract stop_syncer function --- src/rabbit_mirror_queue_sync.erl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index e3edecb5..38a18e4a 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -94,11 +94,7 @@ master_send(Msg, MsgProps, {Syncer, Ref, Log, HandleInfo, EmitStats, Parent}, end, receive {'$gen_call', From, - cancel_sync_mirrors} -> unlink(Syncer), - Syncer ! {cancel, Ref}, - receive {'EXIT', Syncer, _} -> ok - after 0 -> ok - end, + cancel_sync_mirrors} -> stop_syncer(Syncer, {cancel, Ref}), gen_server2:reply(From, ok), {stop, cancelled}; {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, @@ -109,16 +105,19 @@ master_send(Msg, MsgProps, {Syncer, Ref, Log, HandleInfo, EmitStats, Parent}, master_done({Syncer, Ref, _Log, _HandleInfo, _EmitStats, Parent}, BQS) -> receive - {next, Ref} -> unlink(Syncer), - Syncer ! {done, Ref}, - receive {'EXIT', Syncer, _} -> ok - after 0 -> ok - end, + {next, Ref} -> stop_syncer(Syncer, {done, Ref}), {ok, BQS}; {'EXIT', Parent, Reason} -> {shutdown, Reason, BQS}; {'EXIT', Syncer, Reason} -> {sync_died, Reason, BQS} end. +stop_syncer(Syncer, Msg) -> + unlink(Syncer), + Syncer ! Msg, + receive {'EXIT', Syncer, _} -> ok + after 0 -> ok + end. + %% Master %% --------------------------------------------------------------------------- %% Syncer -- cgit v1.2.1 From 3eeb54407bc7efe970e401d81fc1ab5f8a512c8e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 15:06:09 +0000 Subject: refactor: less passing around of State in dl publishing --- src/rabbit_amqqueue_process.erl | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 5feaf9bc..461b5a6d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -758,8 +758,8 @@ dead_letter_fun(Reason) -> gen_server2:cast(self(), {dead_letter, Msg, AckTag, Reason}) end. -dead_letter_publish(Msg, Reason, X, State = #q{publish_seqno = MsgSeqNo}) -> - DLMsg = make_dead_letter_msg(Reason, Msg, State), +dead_letter_publish(Msg, Reason, X, RK, MsgSeqNo, QName) -> + DLMsg = make_dead_letter_msg(Msg, Reason, X#exchange.name, RK, QName), Delivery = rabbit_basic:delivery(false, DLMsg, MsgSeqNo), {Queues, Cycles} = detect_dead_letter_cycles( DLMsg, rabbit_exchange:route(X, Delivery)), @@ -838,19 +838,16 @@ detect_dead_letter_cycles(#basic_message{content = Content}, Queues) -> end end. -make_dead_letter_msg(Reason, - Msg = #basic_message{content = Content, +make_dead_letter_msg(Msg = #basic_message{content = Content, exchange_name = Exchange, routing_keys = RoutingKeys}, - State = #q{dlx = DLX, dlx_routing_key = DlxRoutingKey}) -> + Reason, DLX, RK, #resource{name = QName}) -> {DeathRoutingKeys, HeadersFun1} = - case DlxRoutingKey of + case RK of undefined -> {RoutingKeys, fun (H) -> H end}; - _ -> {[DlxRoutingKey], - fun (H) -> lists:keydelete(<<"CC">>, 1, H) end} + _ -> {[RK], fun (H) -> lists:keydelete(<<"CC">>, 1, H) end} end, ReasonBin = list_to_binary(atom_to_list(Reason)), - #resource{name = QName} = qname(State), TimeSec = rabbit_misc:now_ms() div 1000, HeadersFun2 = fun (Headers) -> @@ -1251,13 +1248,14 @@ handle_cast({set_maximum_since_use, Age}, State) -> noreply(State); handle_cast({dead_letter, Msg, AckTag, Reason}, - State = #q{dlx = XName, - publish_seqno = SeqNo, - unconfirmed = UC, - queue_monitors = QMons}) -> + State = #q{dlx = XName, + dlx_routing_key = RK, + publish_seqno = SeqNo, + unconfirmed = UC, + queue_monitors = QMons}) -> case rabbit_exchange:lookup(XName) of {ok, X} -> - case dead_letter_publish(Msg, Reason, X, State) of + case dead_letter_publish(Msg, Reason, X, RK, SeqNo, qname(State)) of [] -> cleanup_after_confirm([AckTag], State); QPids -> UC1 = dtree:insert(SeqNo, QPids, AckTag, UC), QMons1 = pmon:monitor_all(QPids, QMons), -- cgit v1.2.1 From 37ef7fd3b3621fe7c91a67c325a4678cb579aeca Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 16:22:57 +0000 Subject: don't send expired messages to self() during bulk expiry since they all end up in the mailbox, consuming memory --- src/rabbit_amqqueue_process.erl | 47 ++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 461b5a6d..057d9391 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -716,23 +716,46 @@ drop_expired_messages(State = #q{dlx = DLX, backing_queue = BQ }) -> Now = now_micros(), ExpirePred = fun (#message_properties{expiry = Exp}) -> Now >= Exp end, - {Props, BQS1} = + {Props, State1} = case DLX of - undefined -> BQ:dropwhile(ExpirePred, BQS); - _ -> DLXFun = dead_letter_fun(expired), - {Next, ok, BQS2} = - BQ:fetchwhile( - ExpirePred, - fun (Msg, _IsDelivered, AckTag, Acc) -> - DLXFun(Msg, AckTag), - Acc - end, ok, BQS), - {Next, BQS2} + undefined -> {Next, BQS1} = BQ:dropwhile(ExpirePred, BQS), + {Next, State#q{backing_queue_state = BQS1}}; + _ -> case rabbit_exchange:lookup(DLX) of + {ok, X} -> + drop_expired_messages(ExpirePred, X, State); + {error, not_found} -> + {Next, BQS1} = BQ:dropwhile(ExpirePred, BQS), + {Next, State#q{backing_queue_state = BQS1}} + end end, ensure_ttl_timer(case Props of undefined -> undefined; #message_properties{expiry = Exp} -> Exp - end, State#q{backing_queue_state = BQS1}). + end, State1). + +drop_expired_messages(ExpirePred, X, State = #q{dlx_routing_key = RK, + publish_seqno = SeqNo0, + unconfirmed = UC0, + queue_monitors = QMons0, + backing_queue_state = BQS, + backing_queue = BQ}) -> + QName = qname(State), + {Next, {ConfirmImm1, SeqNo1, UC1, QMons1}, BQS1} = + BQ:fetchwhile( + ExpirePred, + fun (Msg, _IsDelivered, AckTag, {ConfirmImm, SeqNo, UC, QMons}) -> + case dead_letter_publish(Msg, expired, X, RK, SeqNo, QName) of + [] -> {[AckTag | ConfirmImm], SeqNo, UC, QMons}; + QPids -> {ConfirmImm, SeqNo + 1, + dtree:insert(SeqNo, QPids, AckTag, UC), + pmon:monitor_all(QPids, QMons)} + end + end, {[], SeqNo0, UC0, QMons0}, BQS), + {_Guids, BQS2} = BQ:ack(ConfirmImm1, BQS1), + {Next, State#q{publish_seqno = SeqNo1, + unconfirmed = UC1, + queue_monitors = QMons1, + backing_queue_state = BQS2}}. ensure_ttl_timer(undefined, State) -> State; -- cgit v1.2.1 From 75d9df8f53a5dfe92386b65ac21e7c1789532ed2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 18:56:06 +0000 Subject: replace bq:foreach_ack with a more versatile ackfold --- src/rabbit_amqqueue_process.erl | 6 ++++-- src/rabbit_backing_queue.erl | 20 +++++++++----------- src/rabbit_mirror_queue_master.erl | 15 ++++++++------- src/rabbit_tests.erl | 4 ++-- src/rabbit_variable_queue.erl | 24 ++++++++++++------------ 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 781546af..2ee5122c 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1202,8 +1202,10 @@ handle_cast({reject, AckTags, false, ChPid}, State) -> ChPid, AckTags, State, fun (State1 = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - BQS1 = BQ:foreach_ack(fun(M, A) -> DLXFun([{M, A}]) end, - BQS, AckTags), + {ok, BQS1} = BQ:ackfold( + fun (Msg, _IsDelivered, AckTag, ok) -> + DLXFun([{Msg, AckTag}]) + end, ok, BQS, AckTags), State1#q{backing_queue_state = BQS1} end)); diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 272df5c1..da7ff10d 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -35,8 +35,8 @@ fun ((atom(), fun ((atom(), state()) -> state())) -> 'ok')). -type(duration() :: ('undefined' | 'infinity' | number())). --type(msg_fun() :: fun((rabbit_types:basic_message(), ack()) -> 'ok') | - 'undefined'). +-type(msg_fun(A) :: fun ((rabbit_types:basic_message(), boolean(), ack(), A) + -> A)). -type(msg_pred() :: fun ((rabbit_types:message_properties()) -> boolean())). %% Called on startup with a list of durable queue names. The queues @@ -137,10 +137,7 @@ %% flag and ack tag, to the supplied function. The function is also %% fed an accumulator. The result of fetchwhile is as for dropwhile %% plus the accumulator. --callback fetchwhile(msg_pred(), - fun ((rabbit_types:basic_message(), boolean(), ack(), A) - -> A), - A, state()) +-callback fetchwhile(msg_pred(), msg_fun(A), A, state()) -> {rabbit_types:message_properties() | undefined, A, state()}. @@ -156,14 +153,15 @@ %% about. Must return 1 msg_id per Ack, in the same order as Acks. -callback ack([ack()], state()) -> {msg_ids(), state()}. -%% Acktags supplied are for messages which should be processed. The -%% provided callback function is called with each message. --callback foreach_ack(msg_fun(), state(), [ack()]) -> state(). - %% Reinsert messages into the queue which have already been delivered %% and were pending acknowledgement. -callback requeue([ack()], state()) -> {msg_ids(), state()}. +%% Fold over messages by ack tag. The supplied function is called with +%% each message, its IsDelivered flag, its ack tag, and an +%% accumulator. +-callback ackfold(msg_fun(A), A, state(), [ack()]) -> {A, state()}. + %% Fold over all the messages in a queue and return the accumulated %% results, leaving the queue undisturbed. -callback fold(fun((rabbit_types:basic_message(), @@ -233,7 +231,7 @@ behaviour_info(callbacks) -> {delete_and_terminate, 2}, {purge, 1}, {publish, 5}, {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, {dropwhile, 2}, {fetchwhile, 4}, - {fetch, 2}, {ack, 2}, {foreach_ack, 3}, {requeue, 2}, {fold, 3}, {len, 1}, + {fetch, 2}, {ack, 2}, {requeue, 2}, {ackfold, 4}, {fold, 3}, {len, 1}, {is_empty, 1}, {depth, 1}, {set_ram_duration_target, 2}, {ram_duration, 1}, {needs_timeout, 1}, {timeout, 1}, {handle_pre_hibernate, 1}, {status, 1}, {invoke, 3}, {is_duplicate, 2}] ; diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index e3d967bc..e857f395 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -18,11 +18,11 @@ -export([init/3, terminate/2, delete_and_terminate/2, purge/1, publish/5, publish_delivered/4, - discard/3, fetch/2, drop/2, ack/2, - requeue/2, fold/3, len/1, is_empty/1, depth/1, drain_confirmed/1, + discard/3, fetch/2, drop/2, ack/2, requeue/2, ackfold/4, fold/3, + len/1, is_empty/1, depth/1, drain_confirmed/1, dropwhile/2, fetchwhile/4, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, - status/1, invoke/3, is_duplicate/2, foreach_ack/3]). + status/1, invoke/3, is_duplicate/2]). -export([start/1, stop/0]). @@ -281,10 +281,6 @@ ack(AckTags, State = #state { gm = GM, end, {MsgIds, State #state { backing_queue_state = BQS1 }}. -foreach_ack(MsgFun, State = #state { backing_queue = BQ, - backing_queue_state = BQS }, AckTags) -> - State #state { backing_queue_state = BQ:foreach_ack(MsgFun, BQS, AckTags) }. - requeue(AckTags, State = #state { gm = GM, backing_queue = BQ, backing_queue_state = BQS }) -> @@ -292,6 +288,11 @@ requeue(AckTags, State = #state { gm = GM, ok = gm:broadcast(GM, {requeue, MsgIds}), {MsgIds, State #state { backing_queue_state = BQS1 }}. +ackfold(MsgFun, Acc, State = #state { backing_queue = BQ, + backing_queue_state = BQS }, AckTags) -> + {Acc1, BQS1} = BQ:ackfold(MsgFun, Acc, BQS, AckTags), + {Acc1, State #state { backing_queue_state = BQS1 }}. + fold(Fun, Acc, State = #state { backing_queue = BQ, backing_queue_state = BQS }) -> {Result, BQS1} = BQ:fold(Fun, Acc, BQS), diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index b499c59b..30606fdb 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2608,8 +2608,8 @@ test_variable_queue_all_the_bits_not_covered_elsewhere2(VQ0) -> test_variable_queue_fold_msg_on_disk(VQ0) -> VQ1 = variable_queue_publish(true, 1, VQ0), {VQ2, AckTags} = variable_queue_fetch(1, true, false, 1, VQ1), - VQ3 = rabbit_variable_queue:foreach_ack(fun (_M, _A) -> ok end, - VQ2, AckTags), + {ok, VQ3} = rabbit_variable_queue:ackfold(fun (_M, _D, _A, ok) -> ok end, + ok, VQ2, AckTags), VQ3. test_queue_recover() -> diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 3e4c7c86..ce43200d 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -19,10 +19,10 @@ -export([init/3, terminate/2, delete_and_terminate/2, purge/1, publish/5, publish_delivered/4, discard/3, drain_confirmed/1, dropwhile/2, fetchwhile/4, - fetch/2, drop/2, ack/2, requeue/2, fold/3, len/1, + fetch/2, drop/2, ack/2, requeue/2, ackfold/4, fold/3, len/1, is_empty/1, depth/1, set_ram_duration_target/2, ram_duration/1, needs_timeout/1, timeout/1, handle_pre_hibernate/1, status/1, invoke/3, - is_duplicate/2, multiple_routing_keys/0, foreach_ack/3]). + is_duplicate/2, multiple_routing_keys/0]). -export([start/1, stop/0]). @@ -650,16 +650,6 @@ ack(AckTags, State) -> persistent_count = PCount1, ack_out_counter = AckOutCount + length(AckTags) })}. -foreach_ack(undefined, State, _AckTags) -> - State; -foreach_ack(MsgFun, State = #vqstate{pending_ack = PA}, AckTags) -> - a(lists:foldl(fun(SeqId, State1) -> - {MsgStatus, State2} = - read_msg(gb_trees:get(SeqId, PA), false, State1), - MsgFun(MsgStatus#msg_status.msg, SeqId), - State2 - end, State, AckTags)). - requeue(AckTags, #vqstate { delta = Delta, q3 = Q3, q4 = Q4, @@ -681,6 +671,16 @@ requeue(AckTags, #vqstate { delta = Delta, in_counter = InCounter + MsgCount, len = Len + MsgCount }))}. +ackfold(MsgFun, Acc, State, AckTags) -> + {AccN, StateN} = + lists:foldl( + fun(SeqId, {Acc0, State0 = #vqstate{ pending_ack = PA }}) -> + {#msg_status { msg = Msg, is_delivered = IsDelivered }, + State1 } = read_msg(gb_trees:get(SeqId, PA), false, State0), + {MsgFun(Msg, IsDelivered, SeqId, Acc0), State1} + end, {Acc, State}, AckTags), + {AccN, a(StateN)}. + fold(Fun, Acc, #vqstate { q1 = Q1, q2 = Q2, delta = #delta { start_seq_id = DeltaSeqId, -- cgit v1.2.1 From 0c603bc18f3276b7e1a72712f63f32aeb6de35e0 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 19:55:38 +0000 Subject: drop IsDelivered from bq:{fetchwhile,ackfold} since we don't need it --- src/rabbit_amqqueue_process.erl | 9 ++++----- src/rabbit_backing_queue.erl | 14 ++++++-------- src/rabbit_tests.erl | 6 +++--- src/rabbit_variable_queue.erl | 11 +++++------ 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 2ee5122c..b5ad1ac0 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -720,7 +720,7 @@ drop_expired_messages(State = #q{dlx = DLX, undefined -> BQ:dropwhile(ExpirePred, BQS); _ -> {Next, Msgs, BQS2} = BQ:fetchwhile(ExpirePred, - fun accumulate_msgs/4, + fun accumulate_msgs/3, [], BQS), case Msgs of [] -> ok; @@ -734,7 +734,7 @@ drop_expired_messages(State = #q{dlx = DLX, #message_properties{expiry = Exp} -> Exp end, State#q{backing_queue_state = BQS1}). -accumulate_msgs(Msg, _IsDelivered, AckTag, Acc) -> [{Msg, AckTag} | Acc]. +accumulate_msgs(Msg, AckTag, Acc) -> [{Msg, AckTag} | Acc]. ensure_ttl_timer(undefined, State) -> State; @@ -1203,9 +1203,8 @@ handle_cast({reject, AckTags, false, ChPid}, State) -> fun (State1 = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {ok, BQS1} = BQ:ackfold( - fun (Msg, _IsDelivered, AckTag, ok) -> - DLXFun([{Msg, AckTag}]) - end, ok, BQS, AckTags), + fun (M, A, ok) -> DLXFun([{M, A}]) end, + ok, BQS, AckTags), State1#q{backing_queue_state = BQS1} end)); diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index da7ff10d..99b5946e 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -35,8 +35,7 @@ fun ((atom(), fun ((atom(), state()) -> state())) -> 'ok')). -type(duration() :: ('undefined' | 'infinity' | number())). --type(msg_fun(A) :: fun ((rabbit_types:basic_message(), boolean(), ack(), A) - -> A)). +-type(msg_fun(A) :: fun ((rabbit_types:basic_message(), ack(), A) -> A)). -type(msg_pred() :: fun ((rabbit_types:message_properties()) -> boolean())). %% Called on startup with a list of durable queue names. The queues @@ -133,10 +132,10 @@ -> {rabbit_types:message_properties() | undefined, state()}. %% Like dropwhile, except messages are fetched in "require -%% acknowledgement" mode and are passed, together with their Delivered -%% flag and ack tag, to the supplied function. The function is also -%% fed an accumulator. The result of fetchwhile is as for dropwhile -%% plus the accumulator. +%% acknowledgement" mode and are passed, together with their ack tag, +%% to the supplied function. The function is also fed an +%% accumulator. The result of fetchwhile is as for dropwhile plus the +%% accumulator. -callback fetchwhile(msg_pred(), msg_fun(A), A, state()) -> {rabbit_types:message_properties() | undefined, A, state()}. @@ -158,8 +157,7 @@ -callback requeue([ack()], state()) -> {msg_ids(), state()}. %% Fold over messages by ack tag. The supplied function is called with -%% each message, its IsDelivered flag, its ack tag, and an -%% accumulator. +%% each message, its ack tag, and an accumulator. -callback ackfold(msg_fun(A), A, state(), [ack()]) -> {A, state()}. %% Fold over all the messages in a queue and return the accumulated diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 30606fdb..09ed3d08 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2434,7 +2434,7 @@ test_dropfetchwhile(VQ0) -> {#message_properties{expiry = 6}, {Msgs, AckTags}, VQ2} = rabbit_variable_queue:fetchwhile( fun (#message_properties{expiry = Expiry}) -> Expiry =< 5 end, - fun (Msg, _Delivered, AckTag, {MsgAcc, AckAcc}) -> + fun (Msg, AckTag, {MsgAcc, AckAcc}) -> {[Msg | MsgAcc], [AckTag | AckAcc]} end, {[], []}, VQ1), true = lists:seq(1, 5) == [msg2int(M) || M <- lists:reverse(Msgs)], @@ -2473,7 +2473,7 @@ test_fetchwhile_varying_ram_duration(VQ0) -> fun (VQ1) -> {_, ok, VQ2} = rabbit_variable_queue:fetchwhile( fun (_) -> false end, - fun (_, _, _, A) -> A end, + fun (_, _, A) -> A end, ok, VQ1), VQ2 end, VQ0). @@ -2608,7 +2608,7 @@ test_variable_queue_all_the_bits_not_covered_elsewhere2(VQ0) -> test_variable_queue_fold_msg_on_disk(VQ0) -> VQ1 = variable_queue_publish(true, 1, VQ0), {VQ2, AckTags} = variable_queue_fetch(1, true, false, 1, VQ1), - {ok, VQ3} = rabbit_variable_queue:ackfold(fun (_M, _D, _A, ok) -> ok end, + {ok, VQ3} = rabbit_variable_queue:ackfold(fun (_M, _A, ok) -> ok end, ok, VQ2, AckTags), VQ3. diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index ce43200d..05468a6e 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -597,10 +597,9 @@ fetchwhile(Pred, Fun, Acc, State) -> {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> case Pred(MsgProps) of true -> {MsgStatus1, State2} = read_msg(MsgStatus, State1), - {{Msg, IsDelivered, AckTag}, State3} = + {{Msg, _IsDelivered, AckTag}, State3} = internal_fetch(true, MsgStatus1, State2), - Acc1 = Fun(Msg, IsDelivered, AckTag, Acc), - fetchwhile(Pred, Fun, Acc1, State3); + fetchwhile(Pred, Fun, Fun(Msg, AckTag, Acc), State3); false -> {MsgProps, Acc, a(in_r(MsgStatus, State1))} end end. @@ -675,9 +674,9 @@ ackfold(MsgFun, Acc, State, AckTags) -> {AccN, StateN} = lists:foldl( fun(SeqId, {Acc0, State0 = #vqstate{ pending_ack = PA }}) -> - {#msg_status { msg = Msg, is_delivered = IsDelivered }, - State1 } = read_msg(gb_trees:get(SeqId, PA), false, State0), - {MsgFun(Msg, IsDelivered, SeqId, Acc0), State1} + {#msg_status { msg = Msg }, State1} = + read_msg(gb_trees:get(SeqId, PA), false, State0), + {MsgFun(Msg, SeqId, Acc0), State1} end, {Acc, State}, AckTags), {AccN, a(StateN)}. -- cgit v1.2.1 From 36ab39f88197ec18898b4568736def82af6deced Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 20:24:36 +0000 Subject: refactor: rename drop_expired_messages to drop_expired_msgs --- src/rabbit_amqqueue_process.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 781546af..d2a2769b 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -262,7 +262,7 @@ process_args(State = #q{q = #amqqueue{arguments = Arguments}}) -> init_expires(Expires, State) -> ensure_expiry_timer(State#q{expires = Expires}). -init_ttl(TTL, State) -> drop_expired_messages(State#q{ttl = TTL}). +init_ttl(TTL, State) -> drop_expired_msgs(State#q{ttl = TTL}). init_dlx(DLX, State = #q{q = #amqqueue{name = QName}}) -> State#q{dlx = rabbit_misc:r(QName, exchange, DLX)}. @@ -479,7 +479,7 @@ deliver_msg_to_consumer(DeliverFun, deliver_from_queue_deliver(AckRequired, State) -> {Result, State1} = fetch(AckRequired, State), State2 = #q{backing_queue = BQ, backing_queue_state = BQS} = - drop_expired_messages(State1), + drop_expired_msgs(State1), {Result, BQ:is_empty(BQS), State2}. confirm_messages([], State) -> @@ -526,7 +526,7 @@ discard(#delivery{sender = SenderPid, message = #basic_message{id = MsgId}}, run_message_queue(State) -> State1 = #q{backing_queue = BQ, backing_queue_state = BQS} = - drop_expired_messages(State), + drop_expired_msgs(State), {_IsEmpty1, State2} = deliver_msgs_to_consumers( fun deliver_from_queue_deliver/2, BQ:is_empty(BQS), State1), @@ -711,7 +711,7 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) -> T -> now_micros() + T * 1000 end. -drop_expired_messages(State = #q{dlx = DLX, +drop_expired_msgs(State = #q{dlx = DLX, backing_queue_state = BQS, backing_queue = BQ }) -> Now = now_micros(), @@ -1050,7 +1050,7 @@ handle_call({basic_get, ChPid, NoAck}, _From, State = #q{q = #amqqueue{name = QName}}) -> AckRequired = not NoAck, State1 = ensure_expiry_timer(State), - case fetch(AckRequired, drop_expired_messages(State1)) of + case fetch(AckRequired, drop_expired_msgs(State1)) of {empty, State2} -> reply(empty, State2); {{Message, IsDelivered, AckTag}, State2} -> @@ -1123,7 +1123,7 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, handle_call(stat, _From, State) -> State1 = #q{backing_queue = BQ, backing_queue_state = BQS} = - drop_expired_messages(ensure_expiry_timer(State)), + drop_expired_msgs(ensure_expiry_timer(State)), reply({ok, BQ:len(BQS), active_consumer_count()}, State1); handle_call({delete, IfUnused, IfEmpty}, From, @@ -1312,7 +1312,7 @@ handle_info(maybe_expire, State) -> end; handle_info(drop_expired, State) -> - noreply(drop_expired_messages(State#q{ttl_timer_ref = undefined})); + noreply(drop_expired_msgs(State#q{ttl_timer_ref = undefined})); handle_info(emit_stats, State) -> emit_stats(State), -- cgit v1.2.1 From 90be903d63138c5c18ba7f95ed9458876e176259 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 20:25:53 +0000 Subject: cosmetic --- 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 d2a2769b..ce3b4ce8 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -712,8 +712,8 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) -> end. drop_expired_msgs(State = #q{dlx = DLX, - backing_queue_state = BQS, - backing_queue = BQ }) -> + backing_queue_state = BQS, + backing_queue = BQ }) -> Now = now_micros(), ExpirePred = fun (#message_properties{expiry = Exp}) -> Now >= Exp end, {Props, BQS1} = case DLX of -- cgit v1.2.1 From 2ae774b2f225b08ff9b1240d70f0d62c948e3362 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 20:46:17 +0000 Subject: rename --- src/rabbit_amqqueue_process.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 6b8f8c61..b908361c 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -722,7 +722,7 @@ drop_expired_msgs(State = #q{dlx = DLX, {Next, State#q{backing_queue_state = BQS1}}; _ -> case rabbit_exchange:lookup(DLX) of {ok, X} -> - drop_expired_messages(ExpirePred, X, State); + dead_letter_expired_msgs(ExpirePred, X, State); {error, not_found} -> {Next, BQS1} = BQ:dropwhile(ExpirePred, BQS), {Next, State#q{backing_queue_state = BQS1}} @@ -733,12 +733,12 @@ drop_expired_msgs(State = #q{dlx = DLX, #message_properties{expiry = Exp} -> Exp end, State1). -drop_expired_messages(ExpirePred, X, State = #q{dlx_routing_key = RK, - publish_seqno = SeqNo0, - unconfirmed = UC0, - queue_monitors = QMons0, - backing_queue_state = BQS, - backing_queue = BQ}) -> +dead_letter_expired_msgs(ExpirePred, X, State = #q{dlx_routing_key = RK, + publish_seqno = SeqNo0, + unconfirmed = UC0, + queue_monitors = QMons0, + backing_queue_state = BQS, + backing_queue = BQ}) -> QName = qname(State), {Next, {ConfirmImm1, SeqNo1, UC1, QMons1}, BQS1} = BQ:fetchwhile( -- cgit v1.2.1 From de7441ce679272f8f7200b768c00cf1e06996823 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 21:41:29 +0000 Subject: don't send dead-lettered messages to self() during 'reject' handling since they all end up in the mailbox, consuming memory --- src/rabbit_amqqueue_process.erl | 69 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b908361c..8e20f4e1 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -757,6 +757,29 @@ dead_letter_expired_msgs(ExpirePred, X, State = #q{dlx_routing_key = RK, queue_monitors = QMons1, backing_queue_state = BQS2}}. +dead_letter_rejected_msgs(AckTags, X, State = #q{dlx_routing_key = RK, + publish_seqno = SeqNo0, + unconfirmed = UC0, + queue_monitors = QMons0, + backing_queue_state = BQS, + backing_queue = BQ}) -> + QName = qname(State), + {{ConfirmImm1, SeqNo1, UC1, QMons1}, BQS1} = + BQ:ackfold( + fun (Msg, AckTag, {ConfirmImm, SeqNo, UC, QMons}) -> + case dead_letter_publish(Msg, rejected, X, RK, SeqNo, QName) of + [] -> {[AckTag | ConfirmImm], SeqNo, UC, QMons}; + QPids -> {ConfirmImm, SeqNo + 1, + dtree:insert(SeqNo, QPids, AckTag, UC), + pmon:monitor_all(QPids, QMons)} + end + end, {[], SeqNo0, UC0, QMons0}, BQS, AckTags), + {_Guids, BQS2} = BQ:ack(ConfirmImm1, BQS1), + State#q{publish_seqno = SeqNo1, + unconfirmed = UC1, + queue_monitors = QMons1, + backing_queue_state = BQS2}. + ensure_ttl_timer(undefined, State) -> State; ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = undefined}) -> @@ -776,11 +799,6 @@ ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = TRef, ensure_ttl_timer(_Expiry, State) -> State. -dead_letter_fun(Reason) -> - fun(Msg, AckTag) -> - gen_server2:cast(self(), {dead_letter, Msg, AckTag, Reason}) - end. - dead_letter_publish(Msg, Reason, X, RK, MsgSeqNo, QName) -> DLMsg = make_dead_letter_msg(Msg, Reason, X#exchange.name, RK, QName), Delivery = rabbit_basic:delivery(false, DLMsg, MsgSeqNo), @@ -1216,17 +1234,16 @@ handle_cast({reject, AckTags, true, ChPid}, State) -> handle_cast({reject, AckTags, false, ChPid}, State = #q{dlx = undefined}) -> noreply(ack(AckTags, ChPid, State)); -handle_cast({reject, AckTags, false, ChPid}, State) -> - DLXFun = dead_letter_fun(rejected), - noreply(subtract_acks( - ChPid, AckTags, State, - fun (State1 = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> - {ok, BQS1} = BQ:ackfold( - fun (M, A, ok) -> DLXFun([{M, A}]) end, - ok, BQS, AckTags), - State1#q{backing_queue_state = BQS1} - end)); +handle_cast({reject, AckTags, false, ChPid}, State = #q{dlx = DLX}) -> + noreply(case rabbit_exchange:lookup(DLX) of + {ok, X} -> subtract_acks( + ChPid, AckTags, State, + fun (State1) -> + dead_letter_rejected_msgs( + AckTags, X, State1) + end); + {error, not_found} -> ack(AckTags, ChPid, State) + end); handle_cast(delete_immediately, State) -> stop(State); @@ -1272,26 +1289,6 @@ handle_cast({set_maximum_since_use, Age}, State) -> ok = file_handle_cache:set_maximum_since_use(Age), noreply(State); -handle_cast({dead_letter, Msg, AckTag, Reason}, - State = #q{dlx = XName, - dlx_routing_key = RK, - publish_seqno = SeqNo, - unconfirmed = UC, - queue_monitors = QMons}) -> - case rabbit_exchange:lookup(XName) of - {ok, X} -> - case dead_letter_publish(Msg, Reason, X, RK, SeqNo, qname(State)) of - [] -> cleanup_after_confirm([AckTag], State); - QPids -> UC1 = dtree:insert(SeqNo, QPids, AckTag, UC), - QMons1 = pmon:monitor_all(QPids, QMons), - State#q{publish_seqno = SeqNo + 1, - unconfirmed = UC1, - queue_monitors = QMons1} - end; - {error, not_found} -> - cleanup_after_confirm([AckTag], State) - end; - handle_cast(start_mirroring, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> %% lookup again to get policy for init_with_existing_bq -- cgit v1.2.1 From 5cf5346c7cf124b13f18dec81ba57d5e12983a75 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 1 Jan 2013 22:48:49 +0000 Subject: refactor: extract dead lettering commonality --- src/rabbit_amqqueue_process.erl | 80 ++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 8e20f4e1..66e48024 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -733,52 +733,42 @@ drop_expired_msgs(State = #q{dlx = DLX, #message_properties{expiry = Exp} -> Exp end, State1). -dead_letter_expired_msgs(ExpirePred, X, State = #q{dlx_routing_key = RK, - publish_seqno = SeqNo0, - unconfirmed = UC0, - queue_monitors = QMons0, - backing_queue_state = BQS, - backing_queue = BQ}) -> +dead_letter_expired_msgs(ExpirePred, X, State = #q{backing_queue = BQ}) -> + dead_letter_msgs(fun (DLFun, Acc, BQS1) -> + BQ:fetchwhile(ExpirePred, DLFun, Acc, BQS1) + end, expired, X, State). + +dead_letter_rejected_msgs(AckTags, X, State = #q{backing_queue = BQ}) -> + {ok, State1} = + dead_letter_msgs( + fun (DLFun, Acc, BQS) -> + {Acc1, BQS1} = BQ:ackfold(DLFun, Acc, BQS, AckTags), + {ok, Acc1, BQS1} + end, rejected, X, State), + State1. + +dead_letter_msgs(Fun, Reason, X, State = #q{dlx_routing_key = RK, + publish_seqno = SeqNo0, + unconfirmed = UC0, + queue_monitors = QMons0, + backing_queue_state = BQS, + backing_queue = BQ}) -> QName = qname(State), - {Next, {ConfirmImm1, SeqNo1, UC1, QMons1}, BQS1} = - BQ:fetchwhile( - ExpirePred, - fun (Msg, AckTag, {ConfirmImm, SeqNo, UC, QMons}) -> - case dead_letter_publish(Msg, expired, X, RK, SeqNo, QName) of - [] -> {[AckTag | ConfirmImm], SeqNo, UC, QMons}; - QPids -> {ConfirmImm, SeqNo + 1, - dtree:insert(SeqNo, QPids, AckTag, UC), - pmon:monitor_all(QPids, QMons)} - end - end, {[], SeqNo0, UC0, QMons0}, BQS), - {_Guids, BQS2} = BQ:ack(ConfirmImm1, BQS1), - {Next, State#q{publish_seqno = SeqNo1, - unconfirmed = UC1, - queue_monitors = QMons1, - backing_queue_state = BQS2}}. - -dead_letter_rejected_msgs(AckTags, X, State = #q{dlx_routing_key = RK, - publish_seqno = SeqNo0, - unconfirmed = UC0, - queue_monitors = QMons0, - backing_queue_state = BQS, - backing_queue = BQ}) -> - QName = qname(State), - {{ConfirmImm1, SeqNo1, UC1, QMons1}, BQS1} = - BQ:ackfold( - fun (Msg, AckTag, {ConfirmImm, SeqNo, UC, QMons}) -> - case dead_letter_publish(Msg, rejected, X, RK, SeqNo, QName) of - [] -> {[AckTag | ConfirmImm], SeqNo, UC, QMons}; - QPids -> {ConfirmImm, SeqNo + 1, - dtree:insert(SeqNo, QPids, AckTag, UC), - pmon:monitor_all(QPids, QMons)} - end - end, {[], SeqNo0, UC0, QMons0}, BQS, AckTags), - {_Guids, BQS2} = BQ:ack(ConfirmImm1, BQS1), - State#q{publish_seqno = SeqNo1, - unconfirmed = UC1, - queue_monitors = QMons1, - backing_queue_state = BQS2}. + {Res, {AckImm1, SeqNo1, UC1, QMons1}, BQS1} = + Fun(fun (Msg, AckTag, {AckImm, SeqNo, UC, QMons}) -> + case dead_letter_publish(Msg, Reason, + X, RK, SeqNo, QName) of + [] -> {[AckTag | AckImm], SeqNo, UC, QMons}; + QPids -> {AckImm, SeqNo + 1, + dtree:insert(SeqNo, QPids, AckTag, UC), + pmon:monitor_all(QPids, QMons)} + end + end, {[], SeqNo0, UC0, QMons0}, BQS), + {_Guids, BQS2} = BQ:ack(AckImm1, BQS1), + {Res, State#q{publish_seqno = SeqNo1, + unconfirmed = UC1, + queue_monitors = QMons1, + backing_queue_state = BQS2}}. ensure_ttl_timer(undefined, State) -> State; -- cgit v1.2.1 From 5d494c7a596291cb4d598fd80808bdfa38285235 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Jan 2013 09:16:18 +0000 Subject: cosmetic --- src/rabbit_variable_queue.erl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 3e4c7c86..42592482 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1132,12 +1132,11 @@ internal_fetch(AckRequired, MsgStatus = #msg_status { ok = msg_store_remove(MSCState, IsPersistent, [MsgId]) end, Ack = fun () -> rabbit_queue_index:ack([SeqId], IndexState1) end, - IndexState2 = - case {AckRequired, MsgOnDisk, IndexOnDisk} of - {false, true, false} -> Rem(), IndexState1; - {false, true, true} -> Rem(), Ack(); - _ -> IndexState1 - end, + IndexState2 = case {AckRequired, MsgOnDisk, IndexOnDisk} of + {false, true, false} -> Rem(), IndexState1; + {false, true, true} -> Rem(), Ack(); + _ -> IndexState1 + end, %% 3. If an ack is required, add something sensible to PA {AckTag, State1} = case AckRequired of -- cgit v1.2.1 From 86e2fa5e69b026ebf3e7a018a593f479c93f10c3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Jan 2013 09:17:47 +0000 Subject: cosmetic --- src/rabbit_variable_queue.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 42592482..230aa612 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1147,7 +1147,7 @@ internal_fetch(AckRequired, MsgStatus = #msg_status { false -> {undefined, State} end, - PCount1 = PCount - one_if(IsPersistent andalso not AckRequired), + PCount1 = PCount - one_if(IsPersistent andalso not AckRequired), RamMsgCount1 = RamMsgCount - one_if(Msg =/= undefined), {{Msg, IsDelivered, AckTag}, -- cgit v1.2.1 From a545a3b732e9f20ab860d0b25d99c704d5770dd4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Jan 2013 09:33:15 +0000 Subject: refactor: rename vq:intern_fetch to 'remove' --- src/rabbit_variable_queue.erl | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 230aa612..8e23b591 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -584,7 +584,7 @@ dropwhile(Pred, State) -> {undefined, a(State1)}; {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> case Pred(MsgProps) of - true -> {_, State2} = internal_fetch(false, MsgStatus, State1), + true -> {_, State2} = remove(false, MsgStatus, State1), dropwhile(Pred, State2); false -> {MsgProps, a(in_r(MsgStatus, State1))} end @@ -598,7 +598,7 @@ fetchwhile(Pred, Fun, Acc, State) -> case Pred(MsgProps) of true -> {MsgStatus1, State2} = read_msg(MsgStatus, State1), {{Msg, IsDelivered, AckTag}, State3} = - internal_fetch(true, MsgStatus1, State2), + remove(true, MsgStatus1, State2), Acc1 = Fun(Msg, IsDelivered, AckTag, Acc), fetchwhile(Pred, Fun, Acc1, State3); false -> {MsgProps, Acc, a(in_r(MsgStatus, State1))} @@ -613,7 +613,7 @@ fetch(AckRequired, State) -> %% it is possible that the message wasn't read from disk %% at this point, so read it in. {MsgStatus1, State2} = read_msg(MsgStatus, State1), - {Res, State3} = internal_fetch(AckRequired, MsgStatus1, State2), + {Res, State3} = remove(AckRequired, MsgStatus1, State2), {Res, a(State3)} end. @@ -623,7 +623,7 @@ drop(AckRequired, State) -> {empty, a(State1)}; {{value, MsgStatus}, State1} -> {{_Msg, _IsDelivered, AckTag}, State2} = - internal_fetch(AckRequired, MsgStatus, State1), + remove(AckRequired, MsgStatus, State1), {{MsgStatus#msg_status.msg_id, AckTag}, a(State2)} end. @@ -1108,20 +1108,20 @@ read_msg(MsgStatus = #msg_status { msg = undefined, read_msg(MsgStatus, _CountDiskToRam, State) -> {MsgStatus, State}. -internal_fetch(AckRequired, MsgStatus = #msg_status { - seq_id = SeqId, - msg_id = MsgId, - msg = Msg, - is_persistent = IsPersistent, - is_delivered = IsDelivered, - msg_on_disk = MsgOnDisk, - index_on_disk = IndexOnDisk }, - State = #vqstate {ram_msg_count = RamMsgCount, - out_counter = OutCount, - index_state = IndexState, - msg_store_clients = MSCState, - len = Len, - persistent_count = PCount }) -> +remove(AckRequired, MsgStatus = #msg_status { + seq_id = SeqId, + msg_id = MsgId, + msg = Msg, + is_persistent = IsPersistent, + is_delivered = IsDelivered, + msg_on_disk = MsgOnDisk, + index_on_disk = IndexOnDisk }, + State = #vqstate {ram_msg_count = RamMsgCount, + out_counter = OutCount, + index_state = IndexState, + msg_store_clients = MSCState, + len = Len, + persistent_count = PCount }) -> %% 1. Mark it delivered if necessary IndexState1 = maybe_write_delivered( IndexOnDisk andalso not IsDelivered, -- cgit v1.2.1 From a7de37ccf7ec5b75fab1f63bc0dc8feb186a86ba Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Jan 2013 14:54:51 +0000 Subject: refactor: return less from vq:remove --- src/rabbit_variable_queue.erl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index eabfe136..9508b9c8 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -596,9 +596,9 @@ fetchwhile(Pred, Fun, Acc, State) -> {undefined, Acc, a(State1)}; {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> case Pred(MsgProps) of - true -> {MsgStatus1, State2} = read_msg(MsgStatus, State1), - {{Msg, _IsDelivered, AckTag}, State3} = - remove(true, MsgStatus1, State2), + true -> {MsgStatus1 = #msg_status { msg = Msg }, State2} = + read_msg(MsgStatus, State1), + {AckTag, State3} = remove(true, MsgStatus1, State2), fetchwhile(Pred, Fun, Fun(Msg, AckTag, Acc), State3); false -> {MsgProps, Acc, a(in_r(MsgStatus, State1))} end @@ -611,9 +611,11 @@ fetch(AckRequired, State) -> {{value, MsgStatus}, State1} -> %% it is possible that the message wasn't read from disk %% at this point, so read it in. - {MsgStatus1, State2} = read_msg(MsgStatus, State1), - {Res, State3} = remove(AckRequired, MsgStatus1, State2), - {Res, a(State3)} + {MsgStatus1 = #msg_status { msg = Msg, + is_delivered = IsDelivered }, State2} = + read_msg(MsgStatus, State1), + {AckTag, State3} = remove(AckRequired, MsgStatus1, State2), + {{Msg, IsDelivered, AckTag}, a(State3)} end. drop(AckRequired, State) -> @@ -621,8 +623,7 @@ drop(AckRequired, State) -> {empty, State1} -> {empty, a(State1)}; {{value, MsgStatus}, State1} -> - {{_Msg, _IsDelivered, AckTag}, State2} = - remove(AckRequired, MsgStatus, State1), + {AckTag, State2} = remove(AckRequired, MsgStatus, State1), {{MsgStatus#msg_status.msg_id, AckTag}, a(State2)} end. @@ -1149,12 +1150,11 @@ remove(AckRequired, MsgStatus = #msg_status { PCount1 = PCount - one_if(IsPersistent andalso not AckRequired), RamMsgCount1 = RamMsgCount - one_if(Msg =/= undefined), - {{Msg, IsDelivered, AckTag}, - State1 #vqstate { ram_msg_count = RamMsgCount1, - out_counter = OutCount + 1, - index_state = IndexState2, - len = Len - 1, - persistent_count = PCount1 }}. + {AckTag, State1 #vqstate { ram_msg_count = RamMsgCount1, + out_counter = OutCount + 1, + index_state = IndexState2, + len = Len - 1, + persistent_count = PCount1 }}. purge_betas_and_deltas(LensByStore, State = #vqstate { q3 = Q3, -- cgit v1.2.1 From 87cc957f8b05a985dd3ee09f11e4d56ca684d126 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 2 Jan 2013 16:58:55 +0000 Subject: only retain ram msgs when inserting into pending_ack which keeps memory use constant during fetch operations --- src/rabbit_variable_queue.erl | 51 ++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 9508b9c8..37ca6de0 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -596,9 +596,8 @@ fetchwhile(Pred, Fun, Acc, State) -> {undefined, Acc, a(State1)}; {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> case Pred(MsgProps) of - true -> {MsgStatus1 = #msg_status { msg = Msg }, State2} = - read_msg(MsgStatus, State1), - {AckTag, State3} = remove(true, MsgStatus1, State2), + true -> {Msg, State2} = read_msg(MsgStatus, false, State1), + {AckTag, State3} = remove(true, MsgStatus, State2), fetchwhile(Pred, Fun, Fun(Msg, AckTag, Acc), State3); false -> {MsgProps, Acc, a(in_r(MsgStatus, State1))} end @@ -611,11 +610,9 @@ fetch(AckRequired, State) -> {{value, MsgStatus}, State1} -> %% it is possible that the message wasn't read from disk %% at this point, so read it in. - {MsgStatus1 = #msg_status { msg = Msg, - is_delivered = IsDelivered }, State2} = - read_msg(MsgStatus, State1), - {AckTag, State3} = remove(AckRequired, MsgStatus1, State2), - {{Msg, IsDelivered, AckTag}, a(State3)} + {Msg, State2} = read_msg(MsgStatus, false, State1), + {AckTag, State3} = remove(AckRequired, MsgStatus, State2), + {{Msg, MsgStatus#msg_status.is_delivered, AckTag}, a(State3)} end. drop(AckRequired, State) -> @@ -675,8 +672,8 @@ ackfold(MsgFun, Acc, State, AckTags) -> {AccN, StateN} = lists:foldl( fun(SeqId, {Acc0, State0 = #vqstate{ pending_ack = PA }}) -> - {#msg_status { msg = Msg }, State1} = - read_msg(gb_trees:get(SeqId, PA), false, State0), + MsgStatus = gb_trees:get(SeqId, PA), + {Msg, State1} = read_msg(MsgStatus, false, State0), {MsgFun(Msg, SeqId, Acc0), State1} end, {Acc, State}, AckTags), {AccN, a(StateN)}. @@ -688,9 +685,9 @@ fold(Fun, Acc, #vqstate { q1 = Q1, q3 = Q3, q4 = Q4 } = State) -> QFun = fun(MsgStatus, {Acc0, State0}) -> - {#msg_status { msg = Msg, msg_props = MsgProps }, State1 } = - read_msg(MsgStatus, false, State0), - {StopGo, AccNext} = Fun(Msg, MsgProps, Acc0), + {Msg, State1} = read_msg(MsgStatus, false, State0), + {StopGo, AccNext} = + Fun(Msg, MsgStatus#msg_status.msg_props, Acc0), {StopGo, {AccNext, State1}} end, {Cont1, {Acc1, State1}} = qfoldl(QFun, {cont, {Acc, State }}, Q4), @@ -1075,9 +1072,10 @@ in_r(MsgStatus = #msg_status { msg = undefined }, State = #vqstate { q3 = Q3, q4 = Q4 }) -> case ?QUEUE:is_empty(Q4) of true -> State #vqstate { q3 = ?QUEUE:in_r(MsgStatus, Q3) }; - false -> {MsgStatus1, State1 = #vqstate { q4 = Q4a }} = - read_msg(MsgStatus, State), - State1 #vqstate { q4 = ?QUEUE:in_r(MsgStatus1, Q4a) } + false -> {Msg, State1 = #vqstate { q4 = Q4a }} = + read_msg(MsgStatus, true, State), + State1 #vqstate { q4 = ?QUEUE:in_r(MsgStatus#msg_status { + msg = Msg }, Q4a) } end; in_r(MsgStatus, State = #vqstate { q4 = Q4 }) -> State #vqstate { q4 = ?QUEUE:in_r(MsgStatus, Q4) }. @@ -1093,20 +1091,18 @@ queue_out(State = #vqstate { q4 = Q4 }) -> {{value, MsgStatus}, State #vqstate { q4 = Q4a }} end. -read_msg(MsgStatus, State) -> read_msg(MsgStatus, true, State). - -read_msg(MsgStatus = #msg_status { msg = undefined, - msg_id = MsgId, - is_persistent = IsPersistent }, +read_msg(#msg_status { msg = undefined, + msg_id = MsgId, + is_persistent = IsPersistent }, CountDiskToRam, State = #vqstate { ram_msg_count = RamMsgCount, msg_store_clients = MSCState}) -> {{ok, Msg = #basic_message {}}, MSCState1} = msg_store_read(MSCState, IsPersistent, MsgId), - {MsgStatus #msg_status { msg = Msg }, - State #vqstate { ram_msg_count = RamMsgCount + one_if(CountDiskToRam), - msg_store_clients = MSCState1 }}; -read_msg(MsgStatus, _CountDiskToRam, State) -> - {MsgStatus, State}. + RamMsgCount1 = RamMsgCount + one_if(CountDiskToRam), + {Msg, State #vqstate { ram_msg_count = RamMsgCount1, + msg_store_clients = MSCState1 }}; +read_msg(#msg_status { msg = Msg }, _CountDiskToRam, State) -> + {Msg, State}. remove(AckRequired, MsgStatus = #msg_status { seq_id = SeqId, @@ -1375,7 +1371,8 @@ msg_indices_written_to_disk(Callback, MsgIdSet) -> %%---------------------------------------------------------------------------- publish_alpha(#msg_status { msg = undefined } = MsgStatus, State) -> - read_msg(MsgStatus, State); + {Msg, State1} = read_msg(MsgStatus, true, State), + {MsgStatus#msg_status { msg = Msg }, State1}; publish_alpha(MsgStatus, #vqstate {ram_msg_count = RamMsgCount } = State) -> {MsgStatus, State #vqstate { ram_msg_count = RamMsgCount + 1 }}. -- cgit v1.2.1 From 2957576dddea3ca4a344c6003f1c559c3dfeb9a1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 3 Jan 2013 13:02:06 +0000 Subject: oops; put exception handling back in --- src/rabbit_reader.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 840f430e..86859687 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -612,7 +612,7 @@ process_frame(Frame, Channel, State) -> put(ChKey, {ChPid, NewAState}), post_process_frame(Frame, ChPid, control_throttle(State)); {error, Reason} -> - {error, Reason} + handle_exception(State, Channel, Reason) end. post_process_frame({method, 'channel.close_ok', _}, ChPid, State) -> -- cgit v1.2.1 From 4aeac66d004c6bfd7775424921d78697ad320410 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Jan 2013 13:32:01 +0000 Subject: Explain --- src/credit_flow.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/credit_flow.erl b/src/credit_flow.erl index ec9b2c36..8f1d4d00 100644 --- a/src/credit_flow.erl +++ b/src/credit_flow.erl @@ -56,6 +56,9 @@ %% closure creation a HOF would introduce -define(UPDATE(Key, Default, Var, Expr), begin + %% We delibarately allow Var to escape from the case here + %% to be used in Expr. Any temporary var we introduced + %% would also escape, and might conflict. case get(Key) of undefined -> Var = Default; Var -> ok -- cgit v1.2.1 From 67677c12a0e1d805bb326e127f2a19981b0a6ff5 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Jan 2013 13:36:06 +0000 Subject: Typo --- src/credit_flow.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/credit_flow.erl b/src/credit_flow.erl index 8f1d4d00..102c353f 100644 --- a/src/credit_flow.erl +++ b/src/credit_flow.erl @@ -56,7 +56,7 @@ %% closure creation a HOF would introduce -define(UPDATE(Key, Default, Var, Expr), begin - %% We delibarately allow Var to escape from the case here + %% We deliberately allow Var to escape from the case here %% to be used in Expr. Any temporary var we introduced %% would also escape, and might conflict. case get(Key) of -- cgit v1.2.1 From c6f09ee2749801d133350a8e048896b82a860083 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Jan 2013 16:49:46 +0000 Subject: Explain why --- src/rabbit_mirror_queue_sync.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index f9502219..c90a141f 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -219,6 +219,7 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, slave_sync_loop(Args, TRef, BQS1); {'EXIT', Parent, Reason} -> {stop, Reason, {TRef, BQS}}; + %% If the master throws an exception {'$gen_cast', {gm, {delete_and_terminate, Reason}}} -> BQS1 = BQ:delete_and_terminate(Reason, BQS), {stop, Reason, {TRef, BQS1}} -- cgit v1.2.1 From f713c839e78b451f060a1ea784d0f7b3b2e360d2 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Jan 2013 16:51:09 +0000 Subject: Consistency with the real slave. --- src/rabbit_mirror_queue_sync.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index c90a141f..040f3c9b 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -221,6 +221,6 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, {stop, Reason, {TRef, BQS}}; %% If the master throws an exception {'$gen_cast', {gm, {delete_and_terminate, Reason}}} -> - BQS1 = BQ:delete_and_terminate(Reason, BQS), - {stop, Reason, {TRef, BQS1}} + BQ:delete_and_terminate(Reason, BQS), + {stop, Reason, {TRef, undefined}} end. -- cgit v1.2.1 From 32b0db675b3b32ed643ca4d551d1bad318379deb Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Jan 2013 17:03:59 +0000 Subject: API consistency. --- src/rabbit_amqqueue.erl | 11 +++-------- src/rabbit_control_main.erl | 9 +++++---- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 3169948b..35b4fadf 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -31,7 +31,7 @@ -export([notify_down_all/2, limit_all/3]). -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). --export([start_mirroring/1, stop_mirroring/1, sync/2]). +-export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1]). %% internal -export([internal_declare/2, internal_delete/1, run_backing_queue/3, @@ -173,7 +173,7 @@ (rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'). -spec(start_mirroring/1 :: (pid()) -> 'ok'). -spec(stop_mirroring/1 :: (pid()) -> 'ok'). --spec(sync/2 :: (binary(), rabbit_types:vhost()) -> +-spec(sync_mirrors/1 :: (pid()) -> 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). -endif. @@ -592,12 +592,7 @@ set_maximum_since_use(QPid, Age) -> start_mirroring(QPid) -> ok = delegate_cast(QPid, start_mirroring). stop_mirroring(QPid) -> ok = delegate_cast(QPid, stop_mirroring). -sync(QNameBin, VHostBin) -> - QName = rabbit_misc:r(VHostBin, queue, QNameBin), - case lookup(QName) of - {ok, #amqqueue{pid = QPid}} -> delegate_call(QPid, sync_mirrors); - E -> E - end. +sync_mirrors(QPid) -> delegate_call(QPid, sync_mirrors). on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index 819435ee..24528e32 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -281,11 +281,12 @@ action(forget_cluster_node, Node, [ClusterNodeS], Opts, Inform) -> rpc_call(Node, rabbit_mnesia, forget_cluster_node, [ClusterNode, RemoveWhenOffline]); -action(sync_queue, Node, [Queue], Opts, Inform) -> +action(sync_queue, Node, [Q], Opts, Inform) -> VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Synchronising queue \"~s\" in vhost \"~s\"", [Queue, VHost]), - rpc_call(Node, rabbit_amqqueue, sync, - [list_to_binary(Queue), list_to_binary(VHost)]); + Inform("Synchronising queue \"~s\" in vhost \"~s\"", [Q, VHost]), + rpc_call(Node, rabbit_amqqueue, with, + [rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q)), + fun(#amqqueue{pid = P}) -> rabbit_amqqueue:sync_mirrors(P) end]); action(wait, Node, [PidFile], _Opts, Inform) -> Inform("Waiting for ~p", [Node]), -- cgit v1.2.1 From 514e84e5a865b03b3ddda30ea3a0f09c5ff9ae94 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Jan 2013 17:15:33 +0000 Subject: Don't use a closure for the usual cluster upgrade reasons. --- src/rabbit_control_main.erl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index 24528e32..70ca6177 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -17,7 +17,7 @@ -module(rabbit_control_main). -include("rabbit.hrl"). --export([start/0, stop/0, action/5]). +-export([start/0, stop/0, action/5, sync_queue/1]). -define(RPC_TIMEOUT, infinity). -define(EXTERNAL_CHECK_INTERVAL, 1000). @@ -284,9 +284,8 @@ action(forget_cluster_node, Node, [ClusterNodeS], Opts, Inform) -> action(sync_queue, Node, [Q], Opts, Inform) -> VHost = proplists:get_value(?VHOST_OPT, Opts), Inform("Synchronising queue \"~s\" in vhost \"~s\"", [Q, VHost]), - rpc_call(Node, rabbit_amqqueue, with, - [rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q)), - fun(#amqqueue{pid = P}) -> rabbit_amqqueue:sync_mirrors(P) end]); + rpc_call(Node, rabbit_control_main, sync_queue, + [rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q))]); action(wait, Node, [PidFile], _Opts, Inform) -> Inform("Waiting for ~p", [Node]), @@ -521,6 +520,10 @@ action(eval, Node, [Expr], _Opts, _Inform) -> format_parse_error({_Line, Mod, Err}) -> lists:flatten(Mod:format_error(Err)). +sync_queue(Q) -> + rabbit_amqqueue:with( + Q, fun(#amqqueue{pid = QPid}) -> rabbit_amqqueue:sync_mirrors(QPid) end). + %%---------------------------------------------------------------------------- wait_for_application(Node, PidFile, Application, Inform) -> -- cgit v1.2.1 From 123584aed5eb4fd71fe6090e525c339843081630 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 3 Jan 2013 17:38:51 +0000 Subject: Specs. --- src/credit_flow.erl | 2 +- src/rabbit_mirror_queue_master.erl | 2 ++ src/rabbit_mirror_queue_sync.erl | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/credit_flow.erl b/src/credit_flow.erl index ba99811f..c2bec7c7 100644 --- a/src/credit_flow.erl +++ b/src/credit_flow.erl @@ -37,7 +37,7 @@ -ifdef(use_specs). --opaque(bump_msg() :: {pid(), non_neg_integer()}). +-type(bump_msg() :: {pid(), non_neg_integer()}). -type(credit_spec() :: {non_neg_integer(), non_neg_integer()}). -spec(send/1 :: (pid()) -> 'ok'). diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index c9b6269b..70df62e2 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -70,6 +70,8 @@ -spec(init_with_existing_bq/3 :: (rabbit_types:amqqueue(), atom(), any()) -> master_state()). -spec(stop_mirroring/1 :: (master_state()) -> {atom(), any()}). +-spec(sync_mirrors/1 :: (master_state()) -> + {'ok', master_state()} | {stop, any(), master_state()}). -endif. diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 040f3c9b..ac03ca8d 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -52,6 +52,25 @@ %% || || -- sync_complete --> || %% || (Dies) || +-ifdef(use_specs). + +-type(log_fun() :: fun ((string(), [any()]) -> 'ok')). +-type(bq() :: atom()). +-type(bqs() :: any()). + +-spec(master_prepare/3 :: (reference(), log_fun(), [pid()]) -> pid()). +-spec(master_go/5 :: (pid(), reference(), log_fun(), bq(), bqs()) -> + {'already_synced', bqs()} | {'ok', bqs()} | + {'shutdown', any(), bqs()} | + {'sync_died', any(), bqs()}). +-spec(slave/7 :: (non_neg_integer(), reference(), timer:tref(), pid(), + bq(), bqs(), fun((bq(), bqs()) -> {timer:tref(), bqs()})) -> + 'denied' | + {'ok' | 'failed', {timer:tref(), bqs()}} | + {'stop', any(), {timer:tref(), bqs()}}). + +-endif. + %% --------------------------------------------------------------------------- %% Master -- cgit v1.2.1 From 673ee6e88793eb90c0c86d40ecf317ea0e4fd2e6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 3 Jan 2013 21:10:51 +0000 Subject: bring up to date with 'default' --- src/rabbit_amqqueue.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 71dc8257..fbe146e8 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -600,7 +600,7 @@ set_maximum_since_use(QPid, Age) -> start_mirroring(QPid) -> ok = delegate:cast(QPid, start_mirroring). stop_mirroring(QPid) -> ok = delegate:cast(QPid, stop_mirroring). -sync_mirrors(QPid) -> delegate_call(QPid, sync_mirrors). +sync_mirrors(QPid) -> delegate:call(QPid, sync_mirrors). on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( -- cgit v1.2.1 From 4d137f9a7b05d2a994e8e1a8d0da2f7ee9d12b65 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 3 Jan 2013 21:54:35 +0000 Subject: fix bug found by dialyzer --- 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 326065d1..6b065b96 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1151,8 +1151,8 @@ handle_call(sync_mirrors, _From, S = fun(BQSN) -> State#q{backing_queue_state = BQSN} end, case BQ:depth(BQS) - BQ:len(BQS) of 0 -> case rabbit_mirror_queue_master:sync_mirrors(BQS) of - {shutdown, Reason, BQS1} -> {stop, Reason, S(BQS1)}; - {Result, BQS1} -> reply(Result, S(BQS1)) + {ok, BQS1} -> reply(ok, S(BQS1)); + {stop, Reason, BQS1} -> {stop, Reason, S(BQS1)} end; _ -> reply({error, pending_acks}, State) end; -- cgit v1.2.1 From 192ff326d82e601c0ebc1ebcdcc0d4411fda08eb Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 3 Jan 2013 22:05:17 +0000 Subject: make credit waiting less brittle We were relying on running out of credit for all slaves *simultaneously*, which requires in-depth knowledge of the credit flow logic and that no other credit-requiring messages are sent to a slave prior to this. Fortunately, since we are running in a fresh separate process we can simply handle *any* credit bumping message and *any* DOWN message. As a bonus we can revert to making the type of the bump msg opaque. --- src/credit_flow.erl | 2 +- src/rabbit_mirror_queue_sync.erl | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/credit_flow.erl b/src/credit_flow.erl index dff339fc..102c353f 100644 --- a/src/credit_flow.erl +++ b/src/credit_flow.erl @@ -37,7 +37,7 @@ -ifdef(use_specs). --type(bump_msg() :: {pid(), non_neg_integer()}). +-opaque(bump_msg() :: {pid(), non_neg_integer()}). -type(credit_spec() :: {non_neg_integer(), non_neg_integer()}). -spec(send/1 :: (pid()) -> 'ok'). diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index ac03ca8d..8c561d1c 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -148,7 +148,7 @@ syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> MPid ! {next, Ref}, receive {msg, Ref, Msg, MsgProps} -> - SPidsMRefs1 = wait_for_credit(SPidsMRefs, Ref), + SPidsMRefs1 = wait_for_credit(SPidsMRefs), [begin credit_flow:send(SPid), SPid ! {sync_msg, Ref, Msg, MsgProps} @@ -158,10 +158,16 @@ syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> SPidsMRefs end. -wait_for_credit(SPidsMRefs, Ref) -> +wait_for_credit(SPidsMRefs) -> case credit_flow:blocked() of - true -> wait_for_credit(foreach_slave(SPidsMRefs, Ref, - fun sync_receive_credit/3), Ref); + true -> receive + {bump_credit, Msg} -> + credit_flow:handle_bump_msg(Msg), + wait_for_credit(SPidsMRefs); + {'DOWN', MRef, _, SPid, _} -> + credit_flow:peer_down(SPid), + wait_for_credit(lists:delete({SPid, MRef}, SPidsMRefs)) + end; false -> SPidsMRefs end. @@ -176,13 +182,6 @@ sync_receive_ready(SPid, MRef, Ref) -> {'DOWN', MRef, _, SPid, _} -> ignore end. -sync_receive_credit(SPid, MRef, _Ref) -> - receive - {bump_credit, {SPid, _} = Msg} -> credit_flow:handle_bump_msg(Msg), - SPid; - {'DOWN', MRef, _, SPid, _} -> credit_flow:peer_down(SPid), - ignore - end. sync_send_complete(SPid, _MRef, Ref) -> SPid ! {sync_complete, Ref}. -- cgit v1.2.1 From 08c59a9170c1858d009ccecd089b6f1b2d25cd20 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 3 Jan 2013 22:29:56 +0000 Subject: simplify syncer we don't need to track monitors --- src/rabbit_mirror_queue_sync.erl | 56 +++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 8c561d1c..88f4639f 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -131,61 +131,51 @@ master_done({Syncer, Ref, _Log, Parent}, BQS) -> %% Syncer syncer(Ref, Log, MPid, SPids) -> - SPidsMRefs = [{SPid, erlang:monitor(process, SPid)} || SPid <- SPids], + [erlang:monitor(process, SPid) || SPid <- SPids], %% We wait for a reply from the slaves so that we know they are in %% a receive block and will thus receive messages we send to them %% *without* those messages ending up in their gen_server2 pqueue. - case foreach_slave(SPidsMRefs, Ref, fun sync_receive_ready/3) of - [] -> Log("all slaves already synced", []); - SPidsMRefs1 -> MPid ! {ready, self()}, - Log("~p to sync", [[rabbit_misc:pid_to_string(S) || - {S, _} <- SPidsMRefs1]]), - SPidsMRefs2 = syncer_loop({Ref, MPid}, SPidsMRefs1), - foreach_slave(SPidsMRefs2, Ref, fun sync_send_complete/3) + case [SPid || SPid <- SPids, + receive + {sync_ready, Ref, SPid} -> true; + {sync_deny, Ref, SPid} -> false; + {'DOWN', _, process, SPid, _} -> false + end] of + [] -> Log("all slaves already synced", []); + SPids1 -> MPid ! {ready, self()}, + Log("~p to sync", [[rabbit_misc:pid_to_string(SPid) || + SPid <- SPids1]]), + SPids2 = syncer_loop(Ref, MPid, SPids1), + [SPid ! {sync_complete, Ref} || SPid <- SPids2] end. -syncer_loop({Ref, MPid} = Args, SPidsMRefs) -> +syncer_loop(Ref, MPid, SPids) -> MPid ! {next, Ref}, receive {msg, Ref, Msg, MsgProps} -> - SPidsMRefs1 = wait_for_credit(SPidsMRefs), + SPids1 = wait_for_credit(SPids), [begin credit_flow:send(SPid), SPid ! {sync_msg, Ref, Msg, MsgProps} - end || {SPid, _} <- SPidsMRefs1], - syncer_loop(Args, SPidsMRefs1); + end || SPid <- SPids1], + syncer_loop(Ref, MPid, SPids1); {done, Ref} -> - SPidsMRefs + SPids end. -wait_for_credit(SPidsMRefs) -> +wait_for_credit(SPids) -> case credit_flow:blocked() of true -> receive {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), - wait_for_credit(SPidsMRefs); - {'DOWN', MRef, _, SPid, _} -> + wait_for_credit(SPids); + {'DOWN', _, _, SPid, _} -> credit_flow:peer_down(SPid), - wait_for_credit(lists:delete({SPid, MRef}, SPidsMRefs)) + wait_for_credit(lists:delete(SPid, SPids)) end; - false -> SPidsMRefs + false -> SPids end. -foreach_slave(SPidsMRefs, Ref, Fun) -> - [{SPid, MRef} || {SPid, MRef} <- SPidsMRefs, - Fun(SPid, MRef, Ref) =/= ignore]. - -sync_receive_ready(SPid, MRef, Ref) -> - receive - {sync_ready, Ref, SPid} -> SPid; - {sync_deny, Ref, SPid} -> ignore; - {'DOWN', MRef, _, SPid, _} -> ignore - end. - - -sync_send_complete(SPid, _MRef, Ref) -> - SPid ! {sync_complete, Ref}. - %% Syncer %% --------------------------------------------------------------------------- %% Slave -- cgit v1.2.1 From ca53fb64918bbf0f32765644654ec7d0001a86cf Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 3 Jan 2013 22:37:00 +0000 Subject: cosmetic --- src/rabbit_mirror_queue_sync.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 88f4639f..bb69a664 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -145,8 +145,7 @@ syncer(Ref, Log, MPid, SPids) -> SPids1 -> MPid ! {ready, self()}, Log("~p to sync", [[rabbit_misc:pid_to_string(SPid) || SPid <- SPids1]]), - SPids2 = syncer_loop(Ref, MPid, SPids1), - [SPid ! {sync_complete, Ref} || SPid <- SPids2] + syncer_loop(Ref, MPid, SPids1) end. syncer_loop(Ref, MPid, SPids) -> @@ -160,7 +159,7 @@ syncer_loop(Ref, MPid, SPids) -> end || SPid <- SPids1], syncer_loop(Ref, MPid, SPids1); {done, Ref} -> - SPids + [SPid ! {sync_complete, Ref} || SPid <- SPids] end. wait_for_credit(SPids) -> -- cgit v1.2.1 From f6f0d2fe572ae7697d91139396d634864cecdf04 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 4 Jan 2013 02:21:40 +0000 Subject: cosmetic(ish) --- src/rabbit_mirror_queue_sync.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index bb69a664..10a74cc9 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -168,7 +168,7 @@ wait_for_credit(SPids) -> {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), wait_for_credit(SPids); - {'DOWN', _, _, SPid, _} -> + {'DOWN', _, process, SPid, _} -> credit_flow:peer_down(SPid), wait_for_credit(lists:delete(SPid, SPids)) end; -- cgit v1.2.1 From e1220e0133aa10d98e8af0e4318b4885e74a9757 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 4 Jan 2013 12:16:12 +0000 Subject: Remove confirm_all --- src/dtree.erl | 24 +----------------------- src/rabbit_amqqueue_process.erl | 1 - src/rabbit_channel.erl | 9 ++------- src/rabbit_misc.erl | 5 +---- 4 files changed, 4 insertions(+), 35 deletions(-) diff --git a/src/dtree.erl b/src/dtree.erl index c59243bb..ca2d30cf 100644 --- a/src/dtree.erl +++ b/src/dtree.erl @@ -32,7 +32,7 @@ -module(dtree). --export([empty/0, insert/4, take/3, take/2, take_all/2, take_prim/2, +-export([empty/0, insert/4, take/3, take/2, take_all/2, is_defined/2, is_empty/1, smallest/1, size/1]). %%---------------------------------------------------------------------------- @@ -53,7 +53,6 @@ -spec(take/3 :: ([pk()], sk(), ?MODULE()) -> {[kv()], ?MODULE()}). -spec(take/2 :: (sk(), ?MODULE()) -> {[kv()], ?MODULE()}). -spec(take_all/2 :: (sk(), ?MODULE()) -> {[kv()], ?MODULE()}). --spec(take_prim/2 :: (pk(), ?MODULE()) -> {[kv()], ?MODULE()}). -spec(is_defined/2 :: (sk(), ?MODULE()) -> boolean()). -spec(is_empty/1 :: (?MODULE()) -> boolean()). -spec(smallest/1 :: (?MODULE()) -> kv()). @@ -121,13 +120,6 @@ take_all(SK, {P, S}) -> {KVs, {P1, prune(SKS, PKS, S)}} end. -%% Drop the entry with the given primary key -take_prim(PK, {P, S} = DTree) -> - case gb_trees:lookup(PK, P) of - none -> {[], DTree}; - {value, {SKS, V}} -> {[{PK, V}], take_prim2(PK, SKS, DTree)} - end. - is_defined(SK, {_P, S}) -> gb_trees:is_defined(SK, S). is_empty({P, _S}) -> gb_trees:is_empty(P). @@ -157,20 +149,6 @@ take_all2(PKS, P) -> gb_trees:delete(PK, P0)} end, {[], gb_sets:empty(), P}, PKS). -take_prim2(PK, SKS, {P, S}) -> - {gb_trees:delete(PK, P), - rabbit_misc:gb_trees_fold( - fun (SK0, PKS, S1) -> - case gb_sets:is_member(SK0, SKS) of - false -> S1; - true -> PKS1 = gb_sets:delete(PK, PKS), - case gb_sets:is_empty(PKS1) of - true -> gb_trees:delete(SK0, S1); - false -> gb_trees:update(SK0, PKS1, S1) - end - end - end, S, S)}. - prune(SKS, PKS, S) -> gb_sets:fold(fun (SK0, S0) -> PKS1 = gb_trees:get(SK0, S0), diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index f588c024..09f01109 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -595,7 +595,6 @@ publish_max(#delivery{message = Message, BQ:publish(Message, Props, Delivered, SenderPid, BQS); {true, true} -> (dead_letter_fun(maxdepth))([{Message, undefined}]), - rabbit_misc:confirm_all(SenderPid, MsgSeqNo), nopub; {true, false} -> {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 885452ce..1af60de8 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -228,9 +228,8 @@ prioritise_call(Msg, _From, _State) -> prioritise_cast(Msg, _State) -> case Msg of - {confirm, _MsgSeqNos, _QPid} -> 5; - {confirm_all, _MsgSeqNo, _QPid} -> 5; - _ -> 0 + {confirm, _MsgSeqNos, _QPid} -> 5; + _ -> 0 end. prioritise_info(Msg, _State) -> @@ -557,10 +556,6 @@ confirm(MsgSeqNos, QPid, State = #ch{unconfirmed = UC}) -> {MXs, UC1} = dtree:take(MsgSeqNos, QPid, UC), record_confirms(MXs, State#ch{unconfirmed = UC1}). -confirm_all(MsgSeqNo, State = #ch{unconfirmed = UC}) -> - {MXs, UC1} = dtree:take_prim(MsgSeqNo, UC), - record_confirms(MXs, State#ch{unconfirmed = UC1}). - handle_method(#'channel.open'{}, _, State = #ch{state = starting}) -> {reply, #'channel.open_ok'{}, State#ch{state = running}}; diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index c6c8676f..edaa7198 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -28,7 +28,7 @@ -export([enable_cover/0, report_cover/0]). -export([enable_cover/1, report_cover/1]). -export([start_cover/1]). --export([confirm_to_sender/2, confirm_all/2]). +-export([confirm_to_sender/2]). -export([throw_on_error/2, with_exit_handler/2, is_abnormal_exit/1, filter_exit_map/2]). -export([with_user/2, with_user_and_vhost/3]). @@ -427,9 +427,6 @@ report_coverage_percentage(File, Cov, NotCov, Mod) -> confirm_to_sender(Pid, MsgSeqNos) -> gen_server2:cast(Pid, {confirm, MsgSeqNos, self()}). -confirm_all(Pid, MsgSeqNo) -> - gen_server2:cast(Pid, {confirm_all, MsgSeqNo, self()}). - %% @doc Halts the emulator returning the given status code to the os. %% On Windows this function will block indefinitely so as to give the io %% subsystem time to flush stdout completely. -- cgit v1.2.1 From d5b173e4611ca319ce8f0d65fdce0e84b879d919 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 4 Jan 2013 12:33:11 +0000 Subject: Rename depth to length --- src/rabbit_amqqueue.erl | 4 ++-- src/rabbit_amqqueue_process.erl | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index dacf4f0a..9d6dcd15 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -390,7 +390,7 @@ check_declare_arguments(QueueName, Args) -> {<<"x-message-ttl">>, fun check_message_ttl_arg/2}, {<<"x-dead-letter-exchange">>, fun check_string_arg/2}, {<<"x-dead-letter-routing-key">>, fun check_dlxrk_arg/2}, - {<<"x-maxdepth">>, fun check_maxdepth_arg/2}], + {<<"x-max-length">>, fun check_max_length_arg/2}], [case rabbit_misc:table_lookup(Args, Key) of undefined -> ok; TypeVal -> case Fun(TypeVal, Args) of @@ -413,7 +413,7 @@ check_int_arg({Type, _}, _) -> false -> {error, {unacceptable_type, Type}} end. -check_maxdepth_arg({Type, Val}, Args) -> +check_max_length_arg({Type, Val}, Args) -> case check_int_arg({Type, Val}, Args) of ok when Val > 0 -> ok; ok -> {error, {value_not_positive, Val}}; diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 09f01109..6eb40886 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -55,7 +55,7 @@ queue_monitors, dlx, dlx_routing_key, - max_depth + max_length }). -record(consumer, {tag, ack_required}). @@ -135,7 +135,7 @@ init(Q) -> senders = pmon:new(), dlx = undefined, dlx_routing_key = undefined, - max_depth = undefined, + max_length = undefined, publish_seqno = 1, unconfirmed = dtree:empty(), delayed_stop = undefined, @@ -161,7 +161,7 @@ init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS, rate_timer_ref = RateTRef, expiry_timer_ref = undefined, ttl = undefined, - max_depth = undefined, + max_length = undefined, senders = Senders, publish_seqno = 1, unconfirmed = dtree:empty(), @@ -262,7 +262,7 @@ process_args(State = #q{q = #amqqueue{arguments = Arguments}}) -> {<<"x-dead-letter-exchange">>, fun init_dlx/2}, {<<"x-dead-letter-routing-key">>, fun init_dlx_routing_key/2}, {<<"x-message-ttl">>, fun init_ttl/2}, - {<<"x-maxdepth">>, fun init_maxdepth/2}]). + {<<"x-max-length">>, fun init_max_length/2}]). init_expires(Expires, State) -> ensure_expiry_timer(State#q{expires = Expires}). @@ -274,8 +274,8 @@ init_dlx(DLX, State = #q{q = #amqqueue{name = QName}}) -> init_dlx_routing_key(RoutingKey, State) -> State#q{dlx_routing_key = RoutingKey}. -init_maxdepth(MaxDepth, State) -> - State#q{max_depth = MaxDepth}. +init_max_length(MaxLen, State) -> + State#q{max_length = MaxLen}. terminate_shutdown(Fun, State) -> State1 = #q{backing_queue_state = BQS} = @@ -582,23 +582,23 @@ publish_max(#delivery{message = Message, sender = SenderPid}, Props, Delivered, #q{backing_queue = BQ, backing_queue_state = BQS, - max_depth = undefined}) -> + max_length = undefined}) -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); publish_max(#delivery{message = Message, msg_seq_no = MsgSeqNo, sender = SenderPid}, Props, Delivered, #q{backing_queue = BQ, backing_queue_state = BQS, - max_depth = MaxDepth}) -> - case {BQ:depth(BQS) >= MaxDepth, BQ:len(BQS) =:= 0} of + max_length = MaxLen}) -> + case {BQ:depth(BQS) >= MaxLen, BQ:len(BQS) =:= 0} of {false, _} -> BQ:publish(Message, Props, Delivered, SenderPid, BQS); {true, true} -> - (dead_letter_fun(maxdepth))([{Message, undefined}]), + (dead_letter_fun(maxlen))([{Message, undefined}]), nopub; {true, false} -> {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), - (dead_letter_fun(maxdepth))([{Msg, AckTag}]), + (dead_letter_fun(maxlen))([{Msg, AckTag}]), BQ:publish(Message, Props, Delivered, SenderPid, BQS1) end. -- cgit v1.2.1 From 93cad02186b4c0ac27a02090a9e0c719ac6dfc8d Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 4 Jan 2013 15:42:55 +0000 Subject: Ignore queue depth for maxlen --- src/rabbit_amqqueue.erl | 6 +++--- src/rabbit_amqqueue_process.erl | 25 +++++++++---------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 9d6dcd15..92a9f4b3 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -415,9 +415,9 @@ check_int_arg({Type, _}, _) -> check_max_length_arg({Type, Val}, Args) -> case check_int_arg({Type, Val}, Args) of - ok when Val > 0 -> ok; - ok -> {error, {value_not_positive, Val}}; - Error -> Error + ok when Val >= 0 -> ok; + ok -> {error, {value_negative, Val}}; + Error -> Error end. check_expires_arg({Type, Val}, Args) -> diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 6eb40886..a4a30021 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -571,11 +571,9 @@ deliver_or_enqueue(Delivery = #delivery{message = Message}, {false, State2 = #q{ttl = 0, dlx = undefined}} -> discard(Delivery, State2); {false, State2} -> - case publish_max(Delivery, Props, Delivered, State2) of - nopub -> State2; - BQS1 -> ensure_ttl_timer(Props#message_properties.expiry, - State2#q{backing_queue_state = BQS1}) - end + BQS1 = publish_max(Delivery, Props, Delivered, State2), + ensure_ttl_timer(Props#message_properties.expiry, + State2#q{backing_queue_state = BQS1}) end. publish_max(#delivery{message = Message, @@ -590,16 +588,11 @@ publish_max(#delivery{message = Message, Props, Delivered, #q{backing_queue = BQ, backing_queue_state = BQS, max_length = MaxLen}) -> - case {BQ:depth(BQS) >= MaxLen, BQ:len(BQS) =:= 0} of - {false, _} -> - BQ:publish(Message, Props, Delivered, SenderPid, BQS); - {true, true} -> - (dead_letter_fun(maxlen))([{Message, undefined}]), - nopub; - {true, false} -> - {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), - (dead_letter_fun(maxlen))([{Msg, AckTag}]), - BQ:publish(Message, Props, Delivered, SenderPid, BQS1) + case BQ:len(BQS) >= MaxLen of + true -> {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), + (dead_letter_fun(maxlen))([{Msg, AckTag}]), + BQ:publish(Message, Props, Delivered, SenderPid, BQS1); + false -> BQ:publish(Message, Props, Delivered, SenderPid, BQS) end. requeue_and_run(AckTags, State = #q{backing_queue = BQ, @@ -833,7 +826,7 @@ cleanup_after_confirm(AckTags, State = #q{delayed_stop = DS, unconfirmed = UC, backing_queue = BQ, backing_queue_state = BQS}) -> - {_Guids, BQS1} = BQ:ack([Ack || Ack <- AckTags, Ack /= undefined], BQS), + {_Guids, BQS1} = BQ:ack(AckTags, BQS), State1 = State#q{backing_queue_state = BQS1}, case dtree:is_empty(UC) andalso DS =/= undefined of true -> case DS of -- cgit v1.2.1 From 0af18258f54c8d7a57d64e005ee2fda9bce97b8f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 4 Jan 2013 16:10:42 +0000 Subject: tweak logging --- src/rabbit_mirror_queue_sync.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 10a74cc9..508d46e9 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -143,8 +143,7 @@ syncer(Ref, Log, MPid, SPids) -> end] of [] -> Log("all slaves already synced", []); SPids1 -> MPid ! {ready, self()}, - Log("~p to sync", [[rabbit_misc:pid_to_string(SPid) || - SPid <- SPids1]]), + Log("mirrors ~p to sync", [[node(SPid) || SPid <- SPids1]]), syncer_loop(Ref, MPid, SPids1) end. -- cgit v1.2.1 From 6532a3ca4f8ec7b51e3810c4c2220aaeea681935 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 4 Jan 2013 18:38:12 +0000 Subject: some more control flow abstraction --- src/rabbit_amqqueue_process.erl | 47 ++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 66e48024..0bef1e4b 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -711,28 +711,27 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) -> T -> now_micros() + T * 1000 end. -drop_expired_msgs(State = #q{dlx = DLX, - backing_queue_state = BQS, +drop_expired_msgs(State = #q{backing_queue_state = BQS, backing_queue = BQ }) -> Now = now_micros(), ExpirePred = fun (#message_properties{expiry = Exp}) -> Now >= Exp end, {Props, State1} = - case DLX of - undefined -> {Next, BQS1} = BQ:dropwhile(ExpirePred, BQS), - {Next, State#q{backing_queue_state = BQS1}}; - _ -> case rabbit_exchange:lookup(DLX) of - {ok, X} -> - dead_letter_expired_msgs(ExpirePred, X, State); - {error, not_found} -> - {Next, BQS1} = BQ:dropwhile(ExpirePred, BQS), - {Next, State#q{backing_queue_state = BQS1}} - end - end, + with_dlx( + State#q.dlx, + fun (X) -> dead_letter_expired_msgs(ExpirePred, X, State) end, + fun () -> {Next, BQS1} = BQ:dropwhile(ExpirePred, BQS), + {Next, State#q{backing_queue_state = BQS1}} end), ensure_ttl_timer(case Props of undefined -> undefined; #message_properties{expiry = Exp} -> Exp end, State1). +with_dlx(undefined, _With, Without) -> Without(); +with_dlx(DLX, With, Without) -> case rabbit_exchange:lookup(DLX) of + {ok, X} -> With(X); + {error, not_found} -> Without() + end. + dead_letter_expired_msgs(ExpirePred, X, State = #q{backing_queue = BQ}) -> dead_letter_msgs(fun (DLFun, Acc, BQS1) -> BQ:fetchwhile(ExpirePred, DLFun, Acc, BQS1) @@ -1221,19 +1220,15 @@ handle_cast({ack, AckTags, ChPid}, State) -> handle_cast({reject, AckTags, true, ChPid}, State) -> noreply(requeue(AckTags, ChPid, State)); -handle_cast({reject, AckTags, false, ChPid}, State = #q{dlx = undefined}) -> - noreply(ack(AckTags, ChPid, State)); - -handle_cast({reject, AckTags, false, ChPid}, State = #q{dlx = DLX}) -> - noreply(case rabbit_exchange:lookup(DLX) of - {ok, X} -> subtract_acks( - ChPid, AckTags, State, - fun (State1) -> - dead_letter_rejected_msgs( - AckTags, X, State1) - end); - {error, not_found} -> ack(AckTags, ChPid, State) - end); +handle_cast({reject, AckTags, false, ChPid}, State) -> + noreply(with_dlx( + State#q.dlx, + fun (X) -> subtract_acks(ChPid, AckTags, State, + fun (State1) -> + dead_letter_rejected_msgs( + AckTags, X, State1) + end) end, + fun () -> ack(AckTags, ChPid, State) end)); handle_cast(delete_immediately, State) -> stop(State); -- cgit v1.2.1 From dc87918e5f1160d7f7c72b55ab484720072b26af Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 4 Jan 2013 19:44:45 +0000 Subject: refactor: disentangle head dropping and publishing --- src/rabbit_amqqueue_process.erl | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index a4a30021..77514383 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -560,7 +560,7 @@ attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, {false, State#q{backing_queue_state = BQS1}} end. -deliver_or_enqueue(Delivery = #delivery{message = Message}, +deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, Delivered, State) -> {Confirm, State1} = send_or_record_confirm(Delivery, State), Props = message_properties(Message, Confirm, State), @@ -571,28 +571,23 @@ deliver_or_enqueue(Delivery = #delivery{message = Message}, {false, State2 = #q{ttl = 0, dlx = undefined}} -> discard(Delivery, State2); {false, State2} -> - BQS1 = publish_max(Delivery, Props, Delivered, State2), + State3 = #q{backing_queue = BQ, backing_queue_state = BQS} = + maybe_drop_head(State2), + BQS1 = BQ:publish(Message, Props, Delivered, SenderPid, BQS), ensure_ttl_timer(Props#message_properties.expiry, - State2#q{backing_queue_state = BQS1}) + State3#q{backing_queue_state = BQS1}) end. -publish_max(#delivery{message = Message, - sender = SenderPid}, - Props, Delivered, #q{backing_queue = BQ, - backing_queue_state = BQS, - max_length = undefined}) -> - BQ:publish(Message, Props, Delivered, SenderPid, BQS); -publish_max(#delivery{message = Message, - msg_seq_no = MsgSeqNo, - sender = SenderPid}, - Props, Delivered, #q{backing_queue = BQ, - backing_queue_state = BQS, - max_length = MaxLen}) -> +maybe_drop_head(State = #q{max_length = undefined}) -> + State; +maybe_drop_head(State = #q{max_length = MaxLen, + backing_queue = BQ, + backing_queue_state = BQS}) -> case BQ:len(BQS) >= MaxLen of true -> {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), (dead_letter_fun(maxlen))([{Msg, AckTag}]), - BQ:publish(Message, Props, Delivered, SenderPid, BQS1); - false -> BQ:publish(Message, Props, Delivered, SenderPid, BQS) + State#q{backing_queue_state = BQS1}; + false -> State end. requeue_and_run(AckTags, State = #q{backing_queue = BQ, -- cgit v1.2.1 From feddbf3ec5fdb0a112cbd770bed20ea9cd3e02fc Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 4 Jan 2013 19:59:55 +0000 Subject: refactor queue initialisation - get rid of all the 'undefined' setting, since that's the default - extract commonality of state initialisation --- src/rabbit_amqqueue_process.erl | 50 +++++++++++++---------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 6b065b96..35f0b816 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -120,26 +120,7 @@ info_keys() -> ?INFO_KEYS. init(Q) -> process_flag(trap_exit, true), - State = #q{q = Q#amqqueue{pid = self()}, - exclusive_consumer = none, - has_had_consumers = false, - backing_queue = undefined, - backing_queue_state = undefined, - active_consumers = queue:new(), - expires = undefined, - sync_timer_ref = undefined, - rate_timer_ref = undefined, - expiry_timer_ref = undefined, - ttl = undefined, - senders = pmon:new(), - dlx = undefined, - dlx_routing_key = undefined, - publish_seqno = 1, - unconfirmed = dtree:empty(), - delayed_stop = undefined, - queue_monitors = pmon:new(), - msg_id_to_channel = gb_trees:empty()}, - {ok, rabbit_event:init_stats_timer(State, #q.stats_timer), hibernate, + {ok, init_state(Q#amqqueue{pid = self()}), hibernate, {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}. init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS, @@ -148,27 +129,28 @@ init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS, none -> ok; _ -> erlang:monitor(process, Owner) end, + State = init_state(Q), + State1 = State#q{backing_queue = BQ, + backing_queue_state = BQS, + rate_timer_ref = RateTRef, + senders = Senders, + msg_id_to_channel = MTC}, + State2 = process_args(State1), + lists:foldl(fun (Delivery, StateN) -> + deliver_or_enqueue(Delivery, true, StateN) + end, State2, Deliveries). + +init_state(Q) -> State = #q{q = Q, exclusive_consumer = none, has_had_consumers = false, - backing_queue = BQ, - backing_queue_state = BQS, active_consumers = queue:new(), - expires = undefined, - sync_timer_ref = undefined, - rate_timer_ref = RateTRef, - expiry_timer_ref = undefined, - ttl = undefined, - senders = Senders, + senders = pmon:new(), publish_seqno = 1, unconfirmed = dtree:empty(), - delayed_stop = undefined, queue_monitors = pmon:new(), - msg_id_to_channel = MTC}, - State1 = process_args(rabbit_event:init_stats_timer(State, #q.stats_timer)), - lists:foldl(fun (Delivery, StateN) -> - deliver_or_enqueue(Delivery, true, StateN) - end, State1, Deliveries). + msg_id_to_channel = gb_trees:empty()}, + rabbit_event:init_stats_timer(State, #q.stats_timer). terminate(shutdown = R, State = #q{backing_queue = BQ}) -> terminate_shutdown(fun (BQS) -> BQ:terminate(R, BQS) end, State); -- cgit v1.2.1 From 8f3985f1cd22af9bcf76db38e0a4b2c3a3505955 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 4 Jan 2013 20:05:51 +0000 Subject: cosmetic --- 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 004180db..f56df9d9 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -254,8 +254,7 @@ init_dlx(DLX, State = #q{q = #amqqueue{name = QName}}) -> init_dlx_routing_key(RoutingKey, State) -> State#q{dlx_routing_key = RoutingKey}. -init_max_length(MaxLen, State) -> - State#q{max_length = MaxLen}. +init_max_length(MaxLen, State) -> State#q{max_length = MaxLen}. terminate_shutdown(Fun, State) -> State1 = #q{backing_queue_state = BQS} = -- cgit v1.2.1 From d175a33ce7ab8a303f2fc1266c9479c23615c7ac Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 4 Jan 2013 21:15:57 +0000 Subject: assert queue dlx arg equivalence and restructure code to make future omissions less likely --- src/rabbit_amqqueue.erl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 173f7648..f1e46fa2 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -380,14 +380,10 @@ with_exclusive_access_or_die(Name, ReaderPid, F) -> assert_args_equivalence(#amqqueue{name = QueueName, arguments = Args}, RequiredArgs) -> - rabbit_misc:assert_args_equivalence( - Args, RequiredArgs, QueueName, [<<"x-expires">>, <<"x-message-ttl">>]). + rabbit_misc:assert_args_equivalence(Args, RequiredArgs, QueueName, + [Key || {Key, _Fun} <- args()]). check_declare_arguments(QueueName, Args) -> - Checks = [{<<"x-expires">>, fun check_expires_arg/2}, - {<<"x-message-ttl">>, fun check_message_ttl_arg/2}, - {<<"x-dead-letter-exchange">>, fun check_string_arg/2}, - {<<"x-dead-letter-routing-key">>, fun check_dlxrk_arg/2}], [case rabbit_misc:table_lookup(Args, Key) of undefined -> ok; TypeVal -> case Fun(TypeVal, Args) of @@ -398,9 +394,15 @@ check_declare_arguments(QueueName, Args) -> [Key, rabbit_misc:rs(QueueName), Error]) end - end || {Key, Fun} <- Checks], + end || {Key, Fun} <- args()], ok. +args() -> + [{<<"x-expires">>, fun check_expires_arg/2}, + {<<"x-message-ttl">>, fun check_message_ttl_arg/2}, + {<<"x-dead-letter-exchange">>, fun check_string_arg/2}, + {<<"x-dead-letter-routing-key">>, fun check_dlxrk_arg/2}]. + check_string_arg({longstr, _}, _Args) -> ok; check_string_arg({Type, _}, _Args) -> {error, {unacceptable_type, Type}}. -- cgit v1.2.1 From 7f1f617a1f058fc893f6ed593208532e60ef06bf Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 5 Jan 2013 15:24:51 +0000 Subject: optimisation: shrink gen_server2 state slightly for a (very) modest performance gain, and slightly neater code --- src/gen_server2.erl | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 78bbbe06..de43cf5c 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -196,8 +196,7 @@ %% State record -record(gs2_state, {parent, name, state, mod, time, - timeout_state, queue, debug, prioritise_call, - prioritise_cast, prioritise_info}). + timeout_state, queue, debug, prioritisers}). -ifdef(use_specs). @@ -638,17 +637,17 @@ adjust_timeout_state(SleptAt, AwokeAt, {backoff, CurrentTO, MinimumTO, {backoff, CurrentTO1, MinimumTO, DesiredHibPeriod, RandomState1}. in({'$gen_cast', Msg} = Input, - GS2State = #gs2_state { prioritise_cast = PC }) -> - in(Input, PC(Msg, GS2State), GS2State); + GS2State = #gs2_state { prioritisers = {_, F, _} }) -> + in(Input, F(Msg, GS2State), GS2State); in({'$gen_call', From, Msg} = Input, - GS2State = #gs2_state { prioritise_call = PC }) -> - in(Input, PC(Msg, From, GS2State), GS2State); + GS2State = #gs2_state { prioritisers = {F, _, _} }) -> + in(Input, F(Msg, From, GS2State), GS2State); in({'EXIT', Parent, _R} = Input, GS2State = #gs2_state { parent = Parent }) -> in(Input, infinity, GS2State); in({system, _From, _Req} = Input, GS2State) -> in(Input, infinity, GS2State); -in(Input, GS2State = #gs2_state { prioritise_info = PI }) -> - in(Input, PI(Input, GS2State), GS2State). +in(Input, GS2State = #gs2_state { prioritisers = {_, _, F} }) -> + in(Input, F(Input, GS2State), GS2State). in(Input, Priority, GS2State = #gs2_state { queue = Queue }) -> GS2State # gs2_state { queue = priority_queue:in(Input, Priority, Queue) }. @@ -1165,16 +1164,13 @@ whereis_name(Name) -> end. find_prioritisers(GS2State = #gs2_state { mod = Mod }) -> - PrioriCall = function_exported_or_default( - Mod, 'prioritise_call', 3, - fun (_Msg, _From, _State) -> 0 end), - PrioriCast = function_exported_or_default(Mod, 'prioritise_cast', 2, - fun (_Msg, _State) -> 0 end), - PrioriInfo = function_exported_or_default(Mod, 'prioritise_info', 2, - fun (_Msg, _State) -> 0 end), - GS2State #gs2_state { prioritise_call = PrioriCall, - prioritise_cast = PrioriCast, - prioritise_info = PrioriInfo }. + PCall = function_exported_or_default(Mod, 'prioritise_call', 3, + fun (_Msg, _From, _State) -> 0 end), + PCast = function_exported_or_default(Mod, 'prioritise_cast', 2, + fun (_Msg, _State) -> 0 end), + PInfo = function_exported_or_default(Mod, 'prioritise_info', 2, + fun (_Msg, _State) -> 0 end), + GS2State #gs2_state { prioritisers = {PCall, PCast, PInfo} }. function_exported_or_default(Mod, Fun, Arity, Default) -> case erlang:function_exported(Mod, Fun, Arity) of -- cgit v1.2.1 From c0c20029324ba398fe07e05ec74e4fcdb5894b1e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 5 Jan 2013 16:14:20 +0000 Subject: refactor gen_server2 debug handling - more uniformity - less code duplication - less closure creation -> performance increase --- src/gen_server2.erl | 60 ++++++++++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index de43cf5c..dc55948b 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -863,13 +863,19 @@ dispatch(Info, Mod, State) -> common_reply(_Name, From, Reply, _NState, [] = _Debug) -> reply(From, Reply), []; -common_reply(Name, From, Reply, NState, Debug) -> - reply(Name, From, Reply, NState, Debug). +common_reply(Name, {To, Tag} = From, Reply, NState, Debug) -> + reply(From, Reply), + sys:handle_debug(Debug, fun print_event/3, Name, {out, Reply, To, NState}). + +common_noreply(_Name, _NState, [] = _Debug) -> + []; +common_noreply(Name, NState, Debug) -> + sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}). -common_debug([] = _Debug, _Func, _Info, _Event) -> +common_become(_Name, _Mod, _NState, [] = _Debug) -> []; -common_debug(Debug, Func, Info, Event) -> - sys:handle_debug(Debug, Func, Info, Event). +common_become(Name, Mod, NState, Debug) -> + sys:handle_debug(Debug, fun print_event/3, Name, {become, Mod, NState}). handle_msg({'$gen_call', From, Msg}, GS2State = #gs2_state { mod = Mod, state = State, @@ -886,23 +892,11 @@ handle_msg({'$gen_call', From, Msg}, GS2State = #gs2_state { mod = Mod, loop(GS2State #gs2_state { state = NState, time = Time1, debug = Debug1}); - {noreply, NState} -> - Debug1 = common_debug(Debug, fun print_event/3, Name, - {noreply, NState}), - loop(GS2State #gs2_state {state = NState, - time = infinity, - debug = Debug1}); - {noreply, NState, Time1} -> - Debug1 = common_debug(Debug, fun print_event/3, Name, - {noreply, NState}), - loop(GS2State #gs2_state {state = NState, - time = Time1, - debug = Debug1}); {stop, Reason, Reply, NState} -> {'EXIT', R} = (catch terminate(Reason, Msg, GS2State #gs2_state { state = NState })), - reply(Name, From, Reply, NState, Debug), + common_reply(Name, From, Reply, NState, Debug), exit(R); Other -> handle_common_reply(Other, Msg, GS2State) @@ -915,28 +909,24 @@ handle_common_reply(Reply, Msg, GS2State = #gs2_state { name = Name, debug = Debug}) -> case Reply of {noreply, NState} -> - Debug1 = common_debug(Debug, fun print_event/3, Name, - {noreply, NState}), - loop(GS2State #gs2_state { state = NState, - time = infinity, - debug = Debug1 }); + Debug1 = common_noreply(Name, NState, Debug), + loop(GS2State #gs2_state {state = NState, + time = infinity, + debug = Debug1}); {noreply, NState, Time1} -> - Debug1 = common_debug(Debug, fun print_event/3, Name, - {noreply, NState}), - loop(GS2State #gs2_state { state = NState, - time = Time1, - debug = Debug1 }); + Debug1 = common_noreply(Name, NState, Debug), + loop(GS2State #gs2_state {state = NState, + time = Time1, + debug = Debug1}); {become, Mod, NState} -> - Debug1 = common_debug(Debug, fun print_event/3, Name, - {become, Mod, NState}), + Debug1 = common_become(Name, Mod, NState, Debug), loop(find_prioritisers( GS2State #gs2_state { mod = Mod, state = NState, time = infinity, debug = Debug1 })); {become, Mod, NState, Time1} -> - Debug1 = common_debug(Debug, fun print_event/3, Name, - {become, Mod, NState}), + Debug1 = common_become(Name, Mod, NState, Debug), loop(find_prioritisers( GS2State #gs2_state { mod = Mod, state = NState, @@ -956,12 +946,6 @@ handle_common_termination(Reply, Msg, GS2State) -> terminate({bad_return_value, Reply}, Msg, GS2State) end. -reply(Name, {To, Tag}, Reply, State, Debug) -> - reply({To, Tag}, Reply), - sys:handle_debug( - Debug, fun print_event/3, Name, {out, Reply, To, State}). - - %%----------------------------------------------------------------- %% Callback functions for system messages handling. %%----------------------------------------------------------------- -- cgit v1.2.1 From 24fd11646a0fdf8741b527254893a1e35a87a3bf Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 5 Jan 2013 17:04:20 +0000 Subject: optimisation: improve performance of permission cache Don't update the cache when the permission is already in it. This saves list munching and a 'put' at the expense of no longer being strictly LRU, but that only affects pathological cases. --- src/rabbit_channel.erl | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 1af60de8..11a117ee 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -432,15 +432,13 @@ check_resource_access(User, Resource, Perm) -> undefined -> []; Other -> Other end, - CacheTail = - case lists:member(V, Cache) of - true -> lists:delete(V, Cache); - false -> ok = rabbit_access_control:check_resource_access( - User, Resource, Perm), - lists:sublist(Cache, ?MAX_PERMISSION_CACHE_SIZE - 1) - end, - put(permission_cache, [V | CacheTail]), - ok. + case lists:member(V, Cache) of + true -> ok; + false -> ok = rabbit_access_control:check_resource_access( + User, Resource, Perm), + CacheTail = lists:sublist(Cache, ?MAX_PERMISSION_CACHE_SIZE-1), + put(permission_cache, [V | CacheTail]) + end. clear_permission_cache() -> erase(permission_cache), -- cgit v1.2.1 From 0949f2f599c5183c4beb979b6804936dd29525ce Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 5 Jan 2013 23:47:50 +0000 Subject: optimise rabbit_amqqueue:qpids/1 common case --- src/rabbit_amqqueue.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index fbe146e8..1ec89c63 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -678,6 +678,8 @@ deliver(Qs, Delivery, _Flow) -> R -> {routed, [QPid || {QPid, ok} <- R]} end. +qpids([]) -> {[], []}; %% optimisation +qpids([#amqqueue{pid = QPid, slave_pids = SPids}]) -> {[QPid], SPids}; %% opt qpids(Qs) -> {MPids, SPids} = lists:foldl(fun (#amqqueue{pid = QPid, slave_pids = SPids}, {MPidAcc, SPidAcc}) -> -- cgit v1.2.1 From 115369202c1a49b3030e091a545c775149b818da Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 6 Jan 2013 00:35:11 +0000 Subject: common-case optimisations for delegate:invoke[_no_result] --- src/delegate.erl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/delegate.erl b/src/delegate.erl index 9222c34c..96b8ba31 100644 --- a/src/delegate.erl +++ b/src/delegate.erl @@ -62,6 +62,13 @@ invoke(Pid, Fun) when is_pid(Pid) -> erlang:raise(Class, Reason, StackTrace) end; +invoke([], _Fun) -> %% optimisation + {[], []}; +invoke([Pid], Fun) when node(Pid) =:= node() -> %% optimisation + case safe_invoke(Pid, Fun) of + {ok, _, Result} -> {[{Pid, Result}], []}; + {error, _, Error} -> {[], [{Pid, Error}]} + end; invoke(Pids, Fun) when is_list(Pids) -> {LocalPids, Grouped} = group_pids_by_node(Pids), %% The use of multi_call is only safe because the timeout is @@ -90,6 +97,11 @@ invoke_no_result(Pid, Fun) when is_pid(Pid) andalso node(Pid) =:= node() -> invoke_no_result(Pid, Fun) when is_pid(Pid) -> invoke_no_result([Pid], Fun); +invoke_no_result([], _Fun) -> %% optimisation + ok; +invoke_no_result([Pid], Fun) when node(Pid) =:= node() -> %% optimisation + safe_invoke(Pid, Fun), %% must not die + ok; invoke_no_result(Pids, Fun) when is_list(Pids) -> {LocalPids, Grouped} = group_pids_by_node(Pids), case orddict:fetch_keys(Grouped) of -- cgit v1.2.1 From f2474ad8809cd83b71741eef718138d2479b745e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 6 Jan 2013 00:44:35 +0000 Subject: optimise "route to no queues" path --- src/rabbit_channel.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 11a117ee..b9f8d1bb 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1327,6 +1327,10 @@ notify_limiter(Limiter, Acked) -> end end. +deliver_to_queues({#delivery{message = #basic_message{exchange_name = XName}}, + []}, State) -> %% optimisation + ?INCR_STATS([{exchange_stats, XName, 1}], publish, State), + State; deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{ exchange_name = XName}, msg_seq_no = MsgSeqNo}, -- cgit v1.2.1 From 53d2a03f0f5dc5a442672292aa0e49f48ca5e560 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 6 Jan 2013 01:31:31 +0000 Subject: optimise "no confirms" case of rabbit_amqqueue_process:discard --- src/rabbit_amqqueue_process.erl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 35f0b816..d7cd9fb1 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -498,11 +498,14 @@ send_or_record_confirm(#delivery{sender = SenderPid, rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]), {immediately, State}. -discard(#delivery{sender = SenderPid, message = #basic_message{id = MsgId}}, - State) -> - %% fake an 'eventual' confirm from BQ; noop if not needed +discard(#delivery{sender = SenderPid, + msg_seq_no = MsgSeqNo, + message = #basic_message{id = MsgId}}, State) -> State1 = #q{backing_queue = BQ, backing_queue_state = BQS} = - confirm_messages([MsgId], State), + case MsgSeqNo of + undefined -> State; + _ -> confirm_messages([MsgId], State) + end, BQS1 = BQ:discard(MsgId, SenderPid, BQS), State1#q{backing_queue_state = BQS1}. -- cgit v1.2.1 From a045f82f44f70dddc74f025812300ed104688f5b Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 6 Jan 2013 03:55:13 +0000 Subject: cosmetic --- 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 b9f8d1bb..37354f93 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1320,7 +1320,7 @@ notify_limiter(Limiter, Acked) -> case rabbit_limiter:is_enabled(Limiter) of false -> ok; true -> case lists:foldl(fun ({_, none, _}, Acc) -> Acc; - ({_, _, _}, Acc) -> Acc + 1 + ({_, _, _}, Acc) -> Acc + 1 end, 0, Acked) of 0 -> ok; Count -> rabbit_limiter:ack(Limiter, Count) -- cgit v1.2.1 From 0b7c1a6c6ee008f836efb7d40a48a1df9d850fac Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 6 Jan 2013 04:43:14 +0000 Subject: restrict previous optimisation, for better workingness --- src/rabbit_channel.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 37354f93..aaa463f1 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1327,7 +1327,9 @@ notify_limiter(Limiter, Acked) -> end end. -deliver_to_queues({#delivery{message = #basic_message{exchange_name = XName}}, +deliver_to_queues({#delivery{message = #basic_message{exchange_name = XName}, + msg_seq_no = undefined, + mandatory = false}, []}, State) -> %% optimisation ?INCR_STATS([{exchange_stats, XName, 1}], publish, State), State; -- cgit v1.2.1 From 2683e219af2c91432a0b5453d978b175c02fea5c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 6 Jan 2013 04:48:18 +0000 Subject: optimise rabbit_channel:ack/2 - moving the ?INCR_STATS call inside the fold_per_queue fun reduces consing when stats are disabled - since this was the only call to fold_per_queue where we cared about the result, and we no longer do, we can switch the 'fold' to 'foreach' --- src/rabbit_channel.erl | 55 ++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index aaa463f1..68625dbf 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -800,14 +800,12 @@ handle_method(#'basic.recover_async'{requeue = true}, limiter = Limiter}) -> OkFun = fun () -> ok end, UAMQL = queue:to_list(UAMQ), - ok = fold_per_queue( - fun (QPid, MsgIds, ok) -> - rabbit_misc:with_exit_handler( - OkFun, fun () -> - rabbit_amqqueue:requeue( - QPid, MsgIds, self()) - end) - end, ok, UAMQL), + foreach_per_queue( + fun (QPid, MsgIds) -> + rabbit_misc:with_exit_handler( + OkFun, + fun () -> rabbit_amqqueue:requeue(QPid, MsgIds, self()) end) + end, UAMQL), ok = notify_limiter(Limiter, UAMQL), %% No answer required - basic.recover is the newer, synchronous %% variant of this method @@ -1215,10 +1213,10 @@ reject(DeliveryTag, Requeue, Multiple, end}. reject(Requeue, Acked, Limiter) -> - ok = fold_per_queue( - fun (QPid, MsgIds, ok) -> - rabbit_amqqueue:reject(QPid, MsgIds, Requeue, self()) - end, ok, Acked), + foreach_per_queue( + fun (QPid, MsgIds) -> + rabbit_amqqueue:reject(QPid, MsgIds, Requeue, self()) + end, Acked), ok = notify_limiter(Limiter, Acked). record_sent(ConsumerTag, AckRequired, @@ -1267,17 +1265,16 @@ collect_acks(ToAcc, PrefixAcc, Q, DeliveryTag, Multiple) -> end. ack(Acked, State = #ch{queue_names = QNames}) -> - Incs = fold_per_queue( - fun (QPid, MsgIds, L) -> - ok = rabbit_amqqueue:ack(QPid, MsgIds, self()), - case dict:find(QPid, QNames) of - {ok, QName} -> Count = length(MsgIds), - [{queue_stats, QName, Count} | L]; - error -> L - end - end, [], Acked), - ok = notify_limiter(State#ch.limiter, Acked), - ?INCR_STATS(Incs, ack, State). + foreach_per_queue( + fun (QPid, MsgIds) -> + ok = rabbit_amqqueue:ack(QPid, MsgIds, self()), + ?INCR_STATS(case dict:find(QPid, QNames) of + {ok, QName} -> Count = length(MsgIds), + [{queue_stats, QName, Count}]; + error -> [] + end, ack, State) + end, Acked), + ok = notify_limiter(State#ch.limiter, Acked). new_tx() -> #tx{msgs = queue:new(), acks = [], nacks = []}. @@ -1289,15 +1286,15 @@ notify_queues(State = #ch{consumer_mapping = Consumers, sets:union(sets:from_list(consumer_queues(Consumers)), DQ)), {rabbit_amqqueue:notify_down_all(QPids, self()), State#ch{state = closing}}. -fold_per_queue(_F, Acc, []) -> - Acc; -fold_per_queue(F, Acc, [{_DTag, _CTag, {QPid, MsgId}}]) -> %% common case - F(QPid, [MsgId], Acc); -fold_per_queue(F, Acc, UAL) -> +foreach_per_queue(_F, []) -> + ok; +foreach_per_queue(F, [{_DTag, _CTag, {QPid, MsgId}}]) -> %% common case + F(QPid, [MsgId]); +foreach_per_queue(F, UAL) -> T = lists:foldl(fun ({_DTag, _CTag, {QPid, MsgId}}, T) -> rabbit_misc:gb_trees_cons(QPid, MsgId, T) end, gb_trees:empty(), UAL), - rabbit_misc:gb_trees_fold(F, Acc, T). + rabbit_misc:gb_trees_foreach(F, T). enable_limiter(State = #ch{unacked_message_q = UAMQ, limiter = Limiter}) -> -- cgit v1.2.1 From 716d91ed4d1935c966126a7636195c2ab57770eb Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 6 Jan 2013 05:33:26 +0000 Subject: optimise ack collection for the common case of ack'ing/reject'ing the oldest tag --- src/rabbit_channel.erl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 68625dbf..2686d76d 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1245,19 +1245,25 @@ record_sent(ConsumerTag, AckRequired, collect_acks(Q, 0, true) -> {queue:to_list(Q), queue:new()}; collect_acks(Q, DeliveryTag, Multiple) -> - collect_acks([], queue:new(), Q, DeliveryTag, Multiple). + collect_acks([], [], Q, DeliveryTag, Multiple). collect_acks(ToAcc, PrefixAcc, Q, DeliveryTag, Multiple) -> case queue:out(Q) of {{value, UnackedMsg = {CurrentDeliveryTag, _ConsumerTag, _Msg}}, QTail} -> if CurrentDeliveryTag == DeliveryTag -> - {[UnackedMsg | ToAcc], queue:join(PrefixAcc, QTail)}; + {[UnackedMsg | ToAcc], + case PrefixAcc of + [] -> QTail; + _ -> queue:join( + queue:from_list(lists:reverse(PrefixAcc)), + QTail) + end}; Multiple -> collect_acks([UnackedMsg | ToAcc], PrefixAcc, QTail, DeliveryTag, Multiple); true -> - collect_acks(ToAcc, queue:in(UnackedMsg, PrefixAcc), + collect_acks(ToAcc, [UnackedMsg | PrefixAcc], QTail, DeliveryTag, Multiple) end; {empty, _} -> -- cgit v1.2.1 From 18aa3cb89836a29a91704a11b62a32ca0e43ef0d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 7 Jan 2013 10:52:55 +0000 Subject: record pending acks in a queue rather than set in queue process Just as we do in the channel; this is more efficient for the typical ack-in-order access pattern. We depend on tags being passed to the queue process in order when ack'ing/rejecting. This requires some slightly fiddly code in the channel. --- src/rabbit_amqqueue_process.erl | 30 ++++++++++++++++-------- src/rabbit_channel.erl | 51 ++++++++++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index d7cd9fb1..4341c3d6 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -352,7 +352,7 @@ ch_record(ChPid) -> undefined -> MonitorRef = erlang:monitor(process, ChPid), C = #cr{ch_pid = ChPid, monitor_ref = MonitorRef, - acktags = sets:new(), + acktags = queue:new(), consumer_count = 0, blocked_consumers = queue:new(), is_limit_active = false, @@ -366,9 +366,9 @@ ch_record(ChPid) -> update_ch_record(C = #cr{consumer_count = ConsumerCount, acktags = ChAckTags, unsent_message_count = UnsentMessageCount}) -> - case {sets:size(ChAckTags), ConsumerCount, UnsentMessageCount} of - {0, 0, 0} -> ok = erase_ch_record(C); - _ -> ok = store_ch_record(C) + case {queue:is_empty(ChAckTags), ConsumerCount, UnsentMessageCount} of + {true, 0, 0} -> ok = erase_ch_record(C); + _ -> ok = store_ch_record(C) end, C. @@ -451,7 +451,7 @@ deliver_msg_to_consumer(DeliverFun, rabbit_channel:deliver(ChPid, ConsumerTag, AckRequired, {QName, self(), AckTag, IsDelivered, Message}), ChAckTags1 = case AckRequired of - true -> sets:add_element(AckTag, ChAckTags); + true -> queue:in(AckTag, ChAckTags); false -> ChAckTags end, update_ch_record(C#cr{acktags = ChAckTags1, @@ -638,7 +638,7 @@ handle_ch_down(DownPid, State = #q{exclusive_consumer = Holder, senders = Senders1}, case should_auto_delete(State1) of true -> {stop, State1}; - false -> {ok, requeue_and_run(sets:to_list(ChAckTags), + false -> {ok, requeue_and_run(queue:to_list(ChAckTags), ensure_expiry_timer(State1))} end end. @@ -677,11 +677,21 @@ subtract_acks(ChPid, AckTags, State, Fun) -> not_found -> State; C = #cr{acktags = ChAckTags} -> - update_ch_record(C#cr{acktags = lists:foldl(fun sets:del_element/2, - ChAckTags, AckTags)}), + update_ch_record( + C#cr{acktags = subtract_acks(AckTags, [], ChAckTags)}), Fun(State) end. +subtract_acks([], [], AckQ) -> + AckQ; +subtract_acks([], Prefix, AckQ) -> + queue:join(queue:from_list(lists:reverse(Prefix)), AckQ); +subtract_acks([T | TL] = AckTags, Prefix, AckQ) -> + case queue:out(AckQ) of + {{value, T}, QTail} -> subtract_acks(TL, Prefix, QTail); + {{value, AT}, QTail} -> subtract_acks(AckTags, [AT | Prefix], QTail) + end. + message_properties(Message, Confirm, #q{ttl = TTL}) -> #message_properties{expiry = calculate_msg_expiry(Message, TTL), needs_confirming = Confirm == eventually}. @@ -886,7 +896,7 @@ i(exclusive_consumer_tag, #q{exclusive_consumer = {_ChPid, ConsumerTag}}) -> i(messages_ready, #q{backing_queue_state = BQS, backing_queue = BQ}) -> BQ:len(BQS); i(messages_unacknowledged, _) -> - lists:sum([sets:size(C#cr.acktags) || C <- all_ch_record()]); + lists:sum([queue:len(C#cr.acktags) || C <- all_ch_record()]); i(messages, State) -> lists:sum([i(Item, State) || Item <- [messages_ready, messages_unacknowledged]]); @@ -1042,7 +1052,7 @@ handle_call({basic_get, ChPid, NoAck}, _From, State3 = #q{backing_queue = BQ, backing_queue_state = BQS} = case AckRequired of true -> C = #cr{acktags = ChAckTags} = ch_record(ChPid), - ChAckTags1 = sets:add_element(AckTag, ChAckTags), + ChAckTags1 = queue:in(AckTag, ChAckTags), update_ch_record(C#cr{acktags = ChAckTags1}), State2; false -> State2 diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 2686d76d..831058db 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -40,7 +40,14 @@ queue_collector_pid, stats_timer, confirm_enabled, publish_seqno, unconfirmed, confirmed, capabilities, trace_state}). --record(tx, {msgs, acks, nacks}). + +-record(tx, {msgs, acks}). %% (1) +%% (1) acks looks s.t. like this: +%% [{true,[[6,7,8],[5]]},{ack,[[4],[1,2,3]]}, ...] +%% +%% Each element is a pair consisting of a tag and a list of lists of +%% ack'ed/reject'ed msg ids. The tag is one of 'ack' (to ack), 'true' +%% (reject w requeue), 'false' (reject w/o requeue). -define(MAX_PERMISSION_CACHE_SIZE, 12). @@ -647,7 +654,8 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, case Tx of none -> ack(Acked, State1), State1; - #tx{acks = Acks} -> State1#ch{tx = Tx#tx{acks = Acked ++ Acks}} + #tx{acks = Acks} -> Acks1 = ack_cons(ack, Acked, Acks), + State1#ch{tx = Tx#tx{acks = Acks1}} end}; handle_method(#'basic.get'{queue = QueueNameBin, @@ -1032,24 +1040,23 @@ handle_method(#'tx.select'{}, _, State) -> handle_method(#'tx.commit'{}, _, #ch{tx = none}) -> precondition_failed("channel is not transactional"); -handle_method(#'tx.commit'{}, _, State = #ch{tx = #tx{msgs = Msgs, - acks = Acks, - nacks = Nacks}, +handle_method(#'tx.commit'{}, _, State = #ch{tx = #tx{msgs = Msgs, + acks = Acks}, limiter = Limiter}) -> State1 = rabbit_misc:queue_fold(fun deliver_to_queues/2, State, Msgs), - ack(Acks, State1), lists:foreach( - fun({Requeue, Acked}) -> reject(Requeue, Acked, Limiter) end, Nacks), + fun ({ack, A}) -> ack(append_reverse(A), State1); + ({Requeue, A}) -> reject(Requeue, append_reverse(A), Limiter) + end, lists:reverse(Acks)), {noreply, maybe_complete_tx(State1#ch{tx = committing})}; handle_method(#'tx.rollback'{}, _, #ch{tx = none}) -> precondition_failed("channel is not transactional"); handle_method(#'tx.rollback'{}, _, State = #ch{unacked_message_q = UAMQ, - tx = #tx{acks = Acks, - nacks = Nacks}}) -> - NacksL = lists:append([L || {_, L} <- Nacks]), - UAMQ1 = queue:from_list(lists:usort(Acks ++ NacksL ++ queue:to_list(UAMQ))), + tx = #tx{acks = Acks}}) -> + AcksL = append_reverse([append_reverse(L) || {_, L} <- Acks]), + UAMQ1 = queue:from_list(lists:usort(AcksL ++ queue:to_list(UAMQ))), {reply, #'tx.rollback_ok'{}, State#ch{unacked_message_q = UAMQ1, tx = new_tx()}}; @@ -1206,10 +1213,10 @@ reject(DeliveryTag, Requeue, Multiple, State1 = State#ch{unacked_message_q = Remaining}, {noreply, case Tx of - none -> reject(Requeue, Acked, State1#ch.limiter), - State1; - #tx{nacks = Nacks} -> Nacks1 = [{Requeue, Acked} | Nacks], - State1#ch{tx = Tx#tx{nacks = Nacks1}} + none -> reject(Requeue, Acked, State1#ch.limiter), + State1; + #tx{acks = Acks} -> Acks1 = ack_cons(Requeue, Acked, Acks), + State1#ch{tx = Tx#tx{acks = Acks1}} end}. reject(Requeue, Acked, Limiter) -> @@ -1252,7 +1259,10 @@ collect_acks(ToAcc, PrefixAcc, Q, DeliveryTag, Multiple) -> {{value, UnackedMsg = {CurrentDeliveryTag, _ConsumerTag, _Msg}}, QTail} -> if CurrentDeliveryTag == DeliveryTag -> - {[UnackedMsg | ToAcc], + {case ToAcc of + [] -> [UnackedMsg]; + _ -> lists:reverse([UnackedMsg | ToAcc]) + end, case PrefixAcc of [] -> QTail; _ -> queue:join( @@ -1282,7 +1292,7 @@ ack(Acked, State = #ch{queue_names = QNames}) -> end, Acked), ok = notify_limiter(State#ch.limiter, Acked). -new_tx() -> #tx{msgs = queue:new(), acks = [], nacks = []}. +new_tx() -> #tx{msgs = queue:new(), acks = []}. notify_queues(State = #ch{state = closing}) -> {ok, State}; @@ -1299,7 +1309,7 @@ foreach_per_queue(F, [{_DTag, _CTag, {QPid, MsgId}}]) -> %% common case foreach_per_queue(F, UAL) -> T = lists:foldl(fun ({_DTag, _CTag, {QPid, MsgId}}, T) -> rabbit_misc:gb_trees_cons(QPid, MsgId, T) - end, gb_trees:empty(), UAL), + end, gb_trees:empty(), lists:reverse(UAL)), rabbit_misc:gb_trees_foreach(F, T). enable_limiter(State = #ch{unacked_message_q = UAMQ, @@ -1440,6 +1450,11 @@ coalesce_and_send(MsgSeqNos, MkMsgFun, WriterPid, MkMsgFun(SeqNo, false)) || SeqNo <- Ss], State. +append_reverse(L) -> lists:append(lists:reverse(L)). + +ack_cons(Tag, Acked, [{Tag, Acks} | L]) -> [{Tag, [Acked | Acks]} | L]; +ack_cons(Tag, Acked, Acks) -> [{Tag, [Acked]} | Acks]. + maybe_complete_tx(State = #ch{tx = #tx{}}) -> State; maybe_complete_tx(State = #ch{unconfirmed = UC}) -> -- cgit v1.2.1 From 0860b908377d89725fc366095e6c273eae2d691a Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 7 Jan 2013 11:35:28 +0000 Subject: simplify & document ordering --- src/rabbit_channel.erl | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 831058db..cccd09dd 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -43,11 +43,13 @@ -record(tx, {msgs, acks}). %% (1) %% (1) acks looks s.t. like this: -%% [{true,[[6,7,8],[5]]},{ack,[[4],[1,2,3]]}, ...] +%% [{false,[5,4]},{true,[3]},{ack,[2,1]}, ...] %% -%% Each element is a pair consisting of a tag and a list of lists of +%% Each element is a pair consisting of a tag and a list of %% ack'ed/reject'ed msg ids. The tag is one of 'ack' (to ack), 'true' -%% (reject w requeue), 'false' (reject w/o requeue). +%% (reject w requeue), 'false' (reject w/o requeue). The msg ids, as +%% well as the list overall, are in "most-recent (generally youngest) +%% ack first" order. -define(MAX_PERMISSION_CACHE_SIZE, 12). @@ -813,7 +815,7 @@ handle_method(#'basic.recover_async'{requeue = true}, rabbit_misc:with_exit_handler( OkFun, fun () -> rabbit_amqqueue:requeue(QPid, MsgIds, self()) end) - end, UAMQL), + end, lists:reverse(UAMQL)), ok = notify_limiter(Limiter, UAMQL), %% No answer required - basic.recover is the newer, synchronous %% variant of this method @@ -1045,8 +1047,8 @@ handle_method(#'tx.commit'{}, _, State = #ch{tx = #tx{msgs = Msgs, limiter = Limiter}) -> State1 = rabbit_misc:queue_fold(fun deliver_to_queues/2, State, Msgs), lists:foreach( - fun ({ack, A}) -> ack(append_reverse(A), State1); - ({Requeue, A}) -> reject(Requeue, append_reverse(A), Limiter) + fun ({ack, A}) -> ack(lists:reverse(A), State1); + ({Requeue, A}) -> reject(Requeue, lists:reverse(A), Limiter) end, lists:reverse(Acks)), {noreply, maybe_complete_tx(State1#ch{tx = committing})}; @@ -1055,7 +1057,7 @@ handle_method(#'tx.rollback'{}, _, #ch{tx = none}) -> handle_method(#'tx.rollback'{}, _, State = #ch{unacked_message_q = UAMQ, tx = #tx{acks = Acks}}) -> - AcksL = append_reverse([append_reverse(L) || {_, L} <- Acks]), + AcksL = lists:append(lists:reverse([lists:reverse(L) || {_, L} <- Acks])), UAMQ1 = queue:from_list(lists:usort(AcksL ++ queue:to_list(UAMQ))), {reply, #'tx.rollback_ok'{}, State#ch{unacked_message_q = UAMQ1, tx = new_tx()}}; @@ -1219,6 +1221,7 @@ reject(DeliveryTag, Requeue, Multiple, State1#ch{tx = Tx#tx{acks = Acks1}} end}. +%% NB: Acked is in youngest-first order reject(Requeue, Acked, Limiter) -> foreach_per_queue( fun (QPid, MsgIds) -> @@ -1249,8 +1252,9 @@ record_sent(ConsumerTag, AckRequired, end, State#ch{unacked_message_q = UAMQ1, next_tag = DeliveryTag + 1}. +%% NB: returns acks in youngest-first order collect_acks(Q, 0, true) -> - {queue:to_list(Q), queue:new()}; + {lists:reverse(queue:to_list(Q)), queue:new()}; collect_acks(Q, DeliveryTag, Multiple) -> collect_acks([], [], Q, DeliveryTag, Multiple). @@ -1259,10 +1263,7 @@ collect_acks(ToAcc, PrefixAcc, Q, DeliveryTag, Multiple) -> {{value, UnackedMsg = {CurrentDeliveryTag, _ConsumerTag, _Msg}}, QTail} -> if CurrentDeliveryTag == DeliveryTag -> - {case ToAcc of - [] -> [UnackedMsg]; - _ -> lists:reverse([UnackedMsg | ToAcc]) - end, + {[UnackedMsg | ToAcc], case PrefixAcc of [] -> QTail; _ -> queue:join( @@ -1280,6 +1281,7 @@ collect_acks(ToAcc, PrefixAcc, Q, DeliveryTag, Multiple) -> precondition_failed("unknown delivery tag ~w", [DeliveryTag]) end. +%% NB: Acked is in youngest-first order ack(Acked, State = #ch{queue_names = QNames}) -> foreach_per_queue( fun (QPid, MsgIds) -> @@ -1306,10 +1308,12 @@ foreach_per_queue(_F, []) -> ok; foreach_per_queue(F, [{_DTag, _CTag, {QPid, MsgId}}]) -> %% common case F(QPid, [MsgId]); +%% NB: UAL should be in youngest-first order; the tree values will +%% then be in oldest-first order foreach_per_queue(F, UAL) -> T = lists:foldl(fun ({_DTag, _CTag, {QPid, MsgId}}, T) -> rabbit_misc:gb_trees_cons(QPid, MsgId, T) - end, gb_trees:empty(), lists:reverse(UAL)), + end, gb_trees:empty(), UAL), rabbit_misc:gb_trees_foreach(F, T). enable_limiter(State = #ch{unacked_message_q = UAMQ, @@ -1450,10 +1454,8 @@ coalesce_and_send(MsgSeqNos, MkMsgFun, WriterPid, MkMsgFun(SeqNo, false)) || SeqNo <- Ss], State. -append_reverse(L) -> lists:append(lists:reverse(L)). - -ack_cons(Tag, Acked, [{Tag, Acks} | L]) -> [{Tag, [Acked | Acks]} | L]; -ack_cons(Tag, Acked, Acks) -> [{Tag, [Acked]} | Acks]. +ack_cons(Tag, Acked, [{Tag, Acks} | L]) -> [{Tag, Acked ++ Acks} | L]; +ack_cons(Tag, Acked, Acks) -> [{Tag, Acked} | Acks]. maybe_complete_tx(State = #ch{tx = #tx{}}) -> State; -- cgit v1.2.1 From cebfaf63a18858987bd19b0d2dbfa11642392c69 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 7 Jan 2013 11:53:54 +0000 Subject: get rid of #tx{} and fix uncommitted_acks info item --- src/rabbit_channel.erl | 77 +++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index cccd09dd..df056a6e 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -40,17 +40,6 @@ queue_collector_pid, stats_timer, confirm_enabled, publish_seqno, unconfirmed, confirmed, capabilities, trace_state}). - --record(tx, {msgs, acks}). %% (1) -%% (1) acks looks s.t. like this: -%% [{false,[5,4]},{true,[3]},{ack,[2,1]}, ...] -%% -%% Each element is a pair consisting of a tag and a list of -%% ack'ed/reject'ed msg ids. The tag is one of 'ack' (to ack), 'true' -%% (reject w requeue), 'false' (reject w/o requeue). The msg ids, as -%% well as the list overall, are in "most-recent (generally youngest) -%% ack first" order. - -define(MAX_PERMISSION_CACHE_SIZE, 12). -define(STATISTICS_KEYS, @@ -631,12 +620,11 @@ handle_method(#'basic.publish'{exchange = ExchangeNameBin, Delivery = rabbit_basic:delivery(Mandatory, Message, MsgSeqNo), QNames = rabbit_exchange:route(Exchange, Delivery), DQ = {Delivery, QNames}, - {noreply, - case Tx of - none -> deliver_to_queues(DQ, State1); - #tx{msgs = Msgs} -> Msgs1 = queue:in(DQ, Msgs), - State1#ch{tx = Tx#tx{msgs = Msgs1}} - end}; + {noreply, case Tx of + none -> deliver_to_queues(DQ, State1); + {Msgs, Acks} -> Msgs1 = queue:in(DQ, Msgs), + State1#ch{tx = {Msgs1, Acks}} + end}; {error, Reason} -> precondition_failed("invalid message: ~p", [Reason]) end; @@ -652,13 +640,12 @@ handle_method(#'basic.ack'{delivery_tag = DeliveryTag, _, State = #ch{unacked_message_q = UAMQ, tx = Tx}) -> {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), State1 = State#ch{unacked_message_q = Remaining}, - {noreply, - case Tx of - none -> ack(Acked, State1), - State1; - #tx{acks = Acks} -> Acks1 = ack_cons(ack, Acked, Acks), - State1#ch{tx = Tx#tx{acks = Acks1}} - end}; + {noreply, case Tx of + none -> ack(Acked, State1), + State1; + {Msgs, Acks} -> Acks1 = ack_cons(ack, Acked, Acks), + State1#ch{tx = {Msgs, Acks1}} + end}; handle_method(#'basic.get'{queue = QueueNameBin, no_ack = NoAck}, @@ -1042,8 +1029,7 @@ handle_method(#'tx.select'{}, _, State) -> handle_method(#'tx.commit'{}, _, #ch{tx = none}) -> precondition_failed("channel is not transactional"); -handle_method(#'tx.commit'{}, _, State = #ch{tx = #tx{msgs = Msgs, - acks = Acks}, +handle_method(#'tx.commit'{}, _, State = #ch{tx = {Msgs, Acks}, limiter = Limiter}) -> State1 = rabbit_misc:queue_fold(fun deliver_to_queues/2, State, Msgs), lists:foreach( @@ -1056,13 +1042,13 @@ handle_method(#'tx.rollback'{}, _, #ch{tx = none}) -> precondition_failed("channel is not transactional"); handle_method(#'tx.rollback'{}, _, State = #ch{unacked_message_q = UAMQ, - tx = #tx{acks = Acks}}) -> + tx = {_Msgs, Acks}}) -> AcksL = lists:append(lists:reverse([lists:reverse(L) || {_, L} <- Acks])), UAMQ1 = queue:from_list(lists:usort(AcksL ++ queue:to_list(UAMQ))), {reply, #'tx.rollback_ok'{}, State#ch{unacked_message_q = UAMQ1, tx = new_tx()}}; -handle_method(#'confirm.select'{}, _, #ch{tx = #tx{}}) -> +handle_method(#'confirm.select'{}, _, #ch{tx = {_, _}}) -> precondition_failed("cannot switch from tx to confirm mode"); handle_method(#'confirm.select'{nowait = NoWait}, _, State) -> @@ -1213,13 +1199,12 @@ reject(DeliveryTag, Requeue, Multiple, State = #ch{unacked_message_q = UAMQ, tx = Tx}) -> {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), State1 = State#ch{unacked_message_q = Remaining}, - {noreply, - case Tx of - none -> reject(Requeue, Acked, State1#ch.limiter), - State1; - #tx{acks = Acks} -> Acks1 = ack_cons(Requeue, Acked, Acks), - State1#ch{tx = Tx#tx{acks = Acks1}} - end}. + {noreply, case Tx of + none -> reject(Requeue, Acked, State1#ch.limiter), + State1; + {Msgs, Acks} -> Acks1 = ack_cons(Requeue, Acked, Acks), + State1#ch{tx = {Msgs, Acks1}} + end}. %% NB: Acked is in youngest-first order reject(Requeue, Acked, Limiter) -> @@ -1294,7 +1279,19 @@ ack(Acked, State = #ch{queue_names = QNames}) -> end, Acked), ok = notify_limiter(State#ch.limiter, Acked). -new_tx() -> #tx{msgs = queue:new(), acks = []}. +%% {Msgs, Acks} +%% +%% Msgs is a queue. +%% +%% Acks looks s.t. like this: +%% [{false,[5,4]},{true,[3]},{ack,[2,1]}, ...] +%% +%% Each element is a pair consisting of a tag and a list of +%% ack'ed/reject'ed msg ids. The tag is one of 'ack' (to ack), 'true' +%% (reject w requeue), 'false' (reject w/o requeue). The msg ids, as +%% well as the list overall, are in "most-recent (generally youngest) +%% ack first" order. +new_tx() -> {queue:new(), []}. notify_queues(State = #ch{state = closing}) -> {ok, State}; @@ -1457,7 +1454,9 @@ coalesce_and_send(MsgSeqNos, MkMsgFun, ack_cons(Tag, Acked, [{Tag, Acks} | L]) -> [{Tag, Acked ++ Acks} | L]; ack_cons(Tag, Acked, Acks) -> [{Tag, Acked} | Acks]. -maybe_complete_tx(State = #ch{tx = #tx{}}) -> +ack_len(Acks) -> lists:sum([length(L) || {ack, L} <- Acks]). + +maybe_complete_tx(State = #ch{tx = {_, _}}) -> State; maybe_complete_tx(State = #ch{unconfirmed = UC}) -> case dtree:is_empty(UC) of @@ -1489,9 +1488,9 @@ i(name, State) -> name(State); i(consumer_count, #ch{consumer_mapping = CM}) -> dict:size(CM); i(messages_unconfirmed, #ch{unconfirmed = UC}) -> dtree:size(UC); i(messages_unacknowledged, #ch{unacked_message_q = UAMQ}) -> queue:len(UAMQ); -i(messages_uncommitted, #ch{tx = #tx{msgs = Msgs}}) -> queue:len(Msgs); +i(messages_uncommitted, #ch{tx = {Msgs, _Acks}}) -> queue:len(Msgs); i(messages_uncommitted, #ch{}) -> 0; -i(acks_uncommitted, #ch{tx = #tx{acks = Acks}}) -> length(Acks); +i(acks_uncommitted, #ch{tx = {_Msgs, Acks}}) -> ack_len(Acks); i(acks_uncommitted, #ch{}) -> 0; i(prefetch_count, #ch{limiter = Limiter}) -> rabbit_limiter:get_limit(Limiter); -- cgit v1.2.1 From 259f60e0bbec5388c3834fe18ff49cf82d7cf575 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 7 Jan 2013 11:58:54 +0000 Subject: oops --- src/rabbit_channel.erl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index df056a6e..88e3dfc5 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1032,10 +1032,9 @@ handle_method(#'tx.commit'{}, _, #ch{tx = none}) -> handle_method(#'tx.commit'{}, _, State = #ch{tx = {Msgs, Acks}, limiter = Limiter}) -> State1 = rabbit_misc:queue_fold(fun deliver_to_queues/2, State, Msgs), - lists:foreach( - fun ({ack, A}) -> ack(lists:reverse(A), State1); - ({Requeue, A}) -> reject(Requeue, lists:reverse(A), Limiter) - end, lists:reverse(Acks)), + lists:foreach(fun ({ack, A}) -> ack(A, State1); + ({Requeue, A}) -> reject(Requeue, A, Limiter) + end, lists:reverse(Acks)), {noreply, maybe_complete_tx(State1#ch{tx = committing})}; handle_method(#'tx.rollback'{}, _, #ch{tx = none}) -> -- cgit v1.2.1 From 461310b1d1a7205b9fcaa50ff41a2dd32e51b869 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 7 Jan 2013 12:39:21 +0000 Subject: Idempotence --- src/rabbit_amqqueue_process.erl | 2 +- src/rabbit_control_main.erl | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 64d55684..bb1a5f86 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1166,7 +1166,7 @@ handle_call(sync_mirrors, _From, State) -> %% By definition if we get this message here we do not have to do anything. handle_call(cancel_sync_mirrors, _From, State) -> - reply({error, not_syncing}, State); + reply({ok, not_syncing}, State); handle_call(force_event_refresh, _From, State = #q{exclusive_consumer = Exclusive}) -> diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index 9f48877c..0f1620bf 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -161,6 +161,12 @@ start() -> false -> io:format("...done.~n") end, rabbit_misc:quit(0); + {ok, Info} -> + case Quiet of + true -> ok; + false -> io:format("...done (~p).~n", [Info]) + end, + rabbit_misc:quit(0); {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> %% < R15 PrintInvalidCommandError(), usage(); -- cgit v1.2.1 From 07de428feb957283231fc94eb13e9d722ccccf2d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 7 Jan 2013 15:13:44 +0000 Subject: tweak: make use of rabbit_misc:rs/1 --- src/rabbit_control_main.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index 5bde996e..6ccd5011 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -283,9 +283,9 @@ action(forget_cluster_node, Node, [ClusterNodeS], Opts, Inform) -> action(sync_queue, Node, [Q], Opts, Inform) -> VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Synchronising queue \"~s\" in vhost \"~s\"", [Q, VHost]), - rpc_call(Node, rabbit_control_main, sync_queue, - [rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q))]); + QName = rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q)), + Inform("Synchronising ~s", [rabbit_misc:rs(QName)]), + rpc_call(Node, rabbit_control_main, sync_queue, [QName]); action(wait, Node, [PidFile], _Opts, Inform) -> Inform("Waiting for ~p", [Node]), -- cgit v1.2.1 From f3a2cac4dad2c9e8cdbc3576e8b4e5efa1029471 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 7 Jan 2013 15:16:20 +0000 Subject: tweak: make use of rabbit_misc:rs/1 --- src/rabbit_control_main.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl index 8cd9f83b..fc9c41a4 100644 --- a/src/rabbit_control_main.erl +++ b/src/rabbit_control_main.erl @@ -296,9 +296,9 @@ action(sync_queue, Node, [Q], Opts, Inform) -> action(cancel_sync_queue, Node, [Q], Opts, Inform) -> VHost = proplists:get_value(?VHOST_OPT, Opts), - Inform("Stopping synchronising queue ~s in ~s", [Q, VHost]), - rpc_call(Node, rabbit_control_main, cancel_sync_queue, - [rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q))]); + QName = rabbit_misc:r(list_to_binary(VHost), queue, list_to_binary(Q)), + Inform("Stopping synchronising ~s", [rabbit_misc:rs(QName)]), + rpc_call(Node, rabbit_control_main, cancel_sync_queue, [QName]); action(wait, Node, [PidFile], _Opts, Inform) -> Inform("Waiting for ~p", [Node]), -- cgit v1.2.1 From 89a5c13172f05437e9f4fef0ee4e271b2e6dde28 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 7 Jan 2013 17:37:40 +0000 Subject: Resurrect --- src/rabbit_limiter.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index f102c3b9..62bcecf3 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -346,3 +346,4 @@ notify_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> ok end, State#lim{queues = NewQueues}. + -- cgit v1.2.1 From 7c0751538285d150f4ebbf2d6536f2b09b2ec26d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 7 Jan 2013 22:19:33 +0000 Subject: get rid of vq's ram_ack_index and instead track ram and disk acks in separate gb_trees, which is more efficient. --- src/rabbit_variable_queue.erl | 131 +++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 59 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 37ca6de0..59acd194 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -255,8 +255,8 @@ q3, q4, next_seq_id, - pending_ack, - ram_ack_index, + ram_pending_ack, + disk_pending_ack, index_state, msg_store_clients, durable, @@ -348,8 +348,8 @@ q3 :: ?QUEUE:?QUEUE(), q4 :: ?QUEUE:?QUEUE(), next_seq_id :: seq_id(), - pending_ack :: gb_tree(), - ram_ack_index :: gb_set(), + ram_pending_ack :: gb_tree(), + disk_pending_ack :: gb_tree(), index_state :: any(), msg_store_clients :: 'undefined' | {{any(), binary()}, {any(), binary()}}, @@ -670,12 +670,11 @@ requeue(AckTags, #vqstate { delta = Delta, ackfold(MsgFun, Acc, State, AckTags) -> {AccN, StateN} = - lists:foldl( - fun(SeqId, {Acc0, State0 = #vqstate{ pending_ack = PA }}) -> - MsgStatus = gb_trees:get(SeqId, PA), - {Msg, State1} = read_msg(MsgStatus, false, State0), - {MsgFun(Msg, SeqId, Acc0), State1} - end, {Acc, State}, AckTags), + lists:foldl(fun(SeqId, {Acc0, State0}) -> + MsgStatus = lookup_pending_ack(SeqId, State0), + {Msg, State1} = read_msg(MsgStatus, false, State0), + {MsgFun(Msg, SeqId, Acc0), State1} + end, {Acc, State}, AckTags), {AccN, a(StateN)}. fold(Fun, Acc, #vqstate { q1 = Q1, @@ -702,8 +701,8 @@ len(#vqstate { len = Len }) -> Len. is_empty(State) -> 0 == len(State). -depth(State = #vqstate { pending_ack = Ack }) -> - len(State) + gb_trees:size(Ack). +depth(State = #vqstate { ram_pending_ack = RPA, disk_pending_ack = DPA }) -> + len(State) + gb_trees:size(RPA) + gb_trees:size(DPA). set_ram_duration_target( DurationTarget, State = #vqstate { @@ -740,7 +739,7 @@ ram_duration(State = #vqstate { ack_out_counter = AckOutCount, ram_msg_count = RamMsgCount, ram_msg_count_prev = RamMsgCountPrev, - ram_ack_index = RamAckIndex, + ram_pending_ack = RPA, ram_ack_count_prev = RamAckCountPrev }) -> Now = now(), {AvgEgressRate, Egress1} = update_rate(Now, Timestamp, OutCount, Egress), @@ -751,7 +750,7 @@ ram_duration(State = #vqstate { {AvgAckIngressRate, AckIngress1} = update_rate(Now, AckTimestamp, AckInCount, AckIngress), - RamAckCount = gb_sets:size(RamAckIndex), + RamAckCount = gb_trees:size(RPA), Duration = %% msgs+acks / (msgs+acks/sec) == sec case (AvgEgressRate == 0 andalso AvgIngressRate == 0 andalso @@ -811,8 +810,8 @@ handle_pre_hibernate(State = #vqstate { index_state = IndexState }) -> status(#vqstate { q1 = Q1, q2 = Q2, delta = Delta, q3 = Q3, q4 = Q4, len = Len, - pending_ack = PA, - ram_ack_index = RAI, + ram_pending_ack = RPA, + disk_pending_ack = DPA, target_ram_count = TargetRamCount, ram_msg_count = RamMsgCount, next_seq_id = NextSeqId, @@ -827,10 +826,10 @@ status(#vqstate { {q3 , ?QUEUE:len(Q3)}, {q4 , ?QUEUE:len(Q4)}, {len , Len}, - {pending_acks , gb_trees:size(PA)}, + {pending_acks , gb_trees:size(RPA) + gb_trees:size(DPA)}, {target_ram_count , TargetRamCount}, {ram_msg_count , RamMsgCount}, - {ram_ack_count , gb_sets:size(RAI)}, + {ram_ack_count , gb_trees:size(RPA)}, {next_seq_id , NextSeqId}, {persistent_count , PersistentCount}, {avg_ingress_rate , AvgIngressRate}, @@ -962,7 +961,7 @@ maybe_write_delivered(false, _SeqId, IndexState) -> maybe_write_delivered(true, SeqId, IndexState) -> rabbit_queue_index:deliver([SeqId], IndexState). -betas_from_index_entries(List, TransientThreshold, PA, IndexState) -> +betas_from_index_entries(List, TransientThreshold, RPA, DPA, IndexState) -> {Filtered, Delivers, Acks} = lists:foldr( fun ({MsgId, SeqId, MsgProps, IsPersistent, IsDelivered}, @@ -971,7 +970,8 @@ betas_from_index_entries(List, TransientThreshold, PA, IndexState) -> true -> {Filtered1, cons_if(not IsDelivered, SeqId, Delivers1), [SeqId | Acks1]}; - false -> case gb_trees:is_defined(SeqId, PA) of + false -> case (gb_trees:is_defined(SeqId, RPA) orelse + gb_trees:is_defined(SeqId, DPA)) of false -> {?QUEUE:in_r( m(#msg_status { @@ -1033,8 +1033,8 @@ init(IsDurable, IndexState, DeltaCount, Terms, AsyncCallback, q3 = ?QUEUE:new(), q4 = ?QUEUE:new(), next_seq_id = NextSeqId, - pending_ack = gb_trees:empty(), - ram_ack_index = gb_sets:empty(), + ram_pending_ack = gb_trees:empty(), + disk_pending_ack = gb_trees:empty(), index_state = IndexState1, msg_store_clients = {PersistentClient, TransientClient}, durable = IsDurable, @@ -1248,33 +1248,47 @@ maybe_write_to_disk(ForceMsg, ForceIndex, MsgStatus, %%---------------------------------------------------------------------------- record_pending_ack(#msg_status { seq_id = SeqId, msg = Msg } = MsgStatus, - State = #vqstate { pending_ack = PA, - ram_ack_index = RAI, - ack_in_counter = AckInCount}) -> - RAI1 = case Msg of - undefined -> RAI; - _ -> gb_sets:insert(SeqId, RAI) - end, - State #vqstate { pending_ack = gb_trees:insert(SeqId, MsgStatus, PA), - ram_ack_index = RAI1, - ack_in_counter = AckInCount + 1}. + State = #vqstate { ram_pending_ack = RPA, + disk_pending_ack = DPA, + ack_in_counter = AckInCount}) -> + {RPA1, DPA1} = + case Msg of + undefined -> {RPA, gb_trees:insert(SeqId, MsgStatus, DPA)}; + _ -> {gb_trees:insert(SeqId, MsgStatus, RPA), DPA} + end, + State #vqstate { ram_pending_ack = RPA1, + disk_pending_ack = DPA1, + ack_in_counter = AckInCount + 1}. + +lookup_pending_ack(SeqId, #vqstate { ram_pending_ack = RPA, + disk_pending_ack = DPA }) -> + case gb_trees:lookup(SeqId, RPA) of + {value, V} -> V; + none -> gb_trees:get(SeqId, DPA) + end. -remove_pending_ack(SeqId, State = #vqstate { pending_ack = PA, - ram_ack_index = RAI }) -> - {gb_trees:get(SeqId, PA), - State #vqstate { pending_ack = gb_trees:delete(SeqId, PA), - ram_ack_index = gb_sets:delete_any(SeqId, RAI) }}. +remove_pending_ack(SeqId, State = #vqstate { ram_pending_ack = RPA, + disk_pending_ack = DPA }) -> + case gb_trees:lookup(SeqId, RPA) of + {value, V} -> RPA1 = gb_trees:delete(SeqId, RPA), + {V, State #vqstate { ram_pending_ack = RPA1 }}; + none -> DPA1 = gb_trees:delete(SeqId, DPA), + {gb_trees:get(SeqId, DPA), + State #vqstate { disk_pending_ack = DPA1 }} + end. purge_pending_ack(KeepPersistent, - State = #vqstate { pending_ack = PA, + State = #vqstate { ram_pending_ack = RPA, + disk_pending_ack = DPA, index_state = IndexState, msg_store_clients = MSCState }) -> + F = fun (_SeqId, MsgStatus, Acc) -> accumulate_ack(MsgStatus, Acc) end, {IndexOnDiskSeqIds, MsgIdsByStore, _AllMsgIds} = - rabbit_misc:gb_trees_fold(fun (_SeqId, MsgStatus, Acc) -> - accumulate_ack(MsgStatus, Acc) - end, accumulate_ack_init(), PA), - State1 = State #vqstate { pending_ack = gb_trees:empty(), - ram_ack_index = gb_sets:empty() }, + rabbit_misc:gb_trees_fold( + F, rabbit_misc:gb_trees_fold(F, accumulate_ack_init(), RPA), DPA), + State1 = State #vqstate { ram_pending_ack = gb_trees:empty(), + disk_pending_ack = gb_trees:empty() }, + case KeepPersistent of true -> case orddict:find(false, MsgIdsByStore) of error -> State1; @@ -1500,7 +1514,7 @@ reduce_memory_use(_AlphaBetaFun, _BetaDeltaFun, _AckFun, {false, State}; reduce_memory_use(AlphaBetaFun, BetaDeltaFun, AckFun, State = #vqstate { - ram_ack_index = RamAckIndex, + ram_pending_ack = RPA, ram_msg_count = RamMsgCount, target_ram_count = TargetRamCount, rates = #rates { avg_ingress = AvgIngress, @@ -1510,8 +1524,7 @@ reduce_memory_use(AlphaBetaFun, BetaDeltaFun, AckFun, }) -> {Reduce, State1 = #vqstate { q2 = Q2, q3 = Q3 }} = - case chunk_size(RamMsgCount + gb_sets:size(RamAckIndex), - TargetRamCount) of + case chunk_size(RamMsgCount + gb_trees:size(RPA), TargetRamCount) of 0 -> {false, State}; %% Reduce memory of pending acks and alphas. The order is %% determined based on which is growing faster. Whichever @@ -1536,20 +1549,19 @@ reduce_memory_use(AlphaBetaFun, BetaDeltaFun, AckFun, limit_ram_acks(0, State) -> {0, State}; -limit_ram_acks(Quota, State = #vqstate { pending_ack = PA, - ram_ack_index = RAI }) -> - case gb_sets:is_empty(RAI) of +limit_ram_acks(Quota, State = #vqstate { ram_pending_ack = RPA, + disk_pending_ack = DPA }) -> + case gb_trees:is_empty(RPA) of true -> {Quota, State}; false -> - {SeqId, RAI1} = gb_sets:take_largest(RAI), - MsgStatus = gb_trees:get(SeqId, PA), + {SeqId, MsgStatus, RPA1} = gb_trees:take_largest(RPA), {MsgStatus1, State1} = maybe_write_to_disk(true, false, MsgStatus, State), - PA1 = gb_trees:update(SeqId, m(trim_msg_status(MsgStatus1)), PA), + DPA1 = gb_trees:insert(SeqId, m(trim_msg_status(MsgStatus1)), DPA), limit_ram_acks(Quota - 1, - State1 #vqstate { pending_ack = PA1, - ram_ack_index = RAI1 }) + State1 #vqstate { ram_pending_ack = RPA1, + disk_pending_ack = DPA1 }) end. reduce_memory_use(State) -> @@ -1617,7 +1629,8 @@ maybe_deltas_to_betas(State = #vqstate { delta = Delta, q3 = Q3, index_state = IndexState, - pending_ack = PA, + ram_pending_ack = RPA, + disk_pending_ack = DPA, transient_threshold = TransientThreshold }) -> #delta { start_seq_id = DeltaSeqId, count = DeltaCount, @@ -1625,10 +1638,10 @@ maybe_deltas_to_betas(State = #vqstate { DeltaSeqId1 = lists:min([rabbit_queue_index:next_segment_boundary(DeltaSeqId), DeltaSeqIdEnd]), - {List, IndexState1} = - rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, IndexState), - {Q3a, IndexState2} = - betas_from_index_entries(List, TransientThreshold, PA, IndexState1), + {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, + IndexState), + {Q3a, IndexState2} = betas_from_index_entries(List, TransientThreshold, + RPA, DPA, IndexState1), State1 = State #vqstate { index_state = IndexState2 }, case ?QUEUE:len(Q3a) of 0 -> -- cgit v1.2.1 From 547d0e785e915ee18ca379a610a66097d8f10010 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 7 Jan 2013 23:05:17 +0000 Subject: optimise vq:reduce_memory_use/4 invocations by suppressing the call, and thus the closure creations, when target_ram_count == infinity --- src/rabbit_variable_queue.erl | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 37ca6de0..6ac7feaa 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -783,19 +783,24 @@ ram_duration(State = #vqstate { ram_msg_count_prev = RamMsgCount, ram_ack_count_prev = RamAckCount }}. -needs_timeout(State = #vqstate { index_state = IndexState }) -> +needs_timeout(State = #vqstate { index_state = IndexState, + target_ram_count = TargetRamCount }) -> case must_sync_index(State) of true -> timed; false -> case rabbit_queue_index:needs_sync(IndexState) of true -> idle; - false -> case reduce_memory_use( - fun (_Quota, State1) -> {0, State1} end, - fun (_Quota, State1) -> State1 end, - fun (_Quota, State1) -> {0, State1} end, - State) of - {true, _State} -> idle; - {false, _State} -> false + false -> case TargetRamCount of + infinity -> false; + _ -> case + reduce_memory_use( + fun (_Quota, State1) -> {0, State1} end, + fun (_Quota, State1) -> State1 end, + fun (_Quota, State1) -> {0, State1} end, + State) of + {true, _State} -> idle; + {false, _State} -> false + end end end end. @@ -1495,9 +1500,6 @@ delta_fold( Fun, {cont, Acc}, DeltaSeqId, DeltaSeqIdEnd, %% one segment's worth of messages in q3 - and thus would risk %% perpetually reporting the need for a conversion when no such %% conversion is needed. That in turn could cause an infinite loop. -reduce_memory_use(_AlphaBetaFun, _BetaDeltaFun, _AckFun, - State = #vqstate {target_ram_count = infinity}) -> - {false, State}; reduce_memory_use(AlphaBetaFun, BetaDeltaFun, AckFun, State = #vqstate { ram_ack_index = RamAckIndex, @@ -1552,6 +1554,8 @@ limit_ram_acks(Quota, State = #vqstate { pending_ack = PA, ram_ack_index = RAI1 }) end. +reduce_memory_use(State = #vqstate { target_ram_count = infinity }) -> + State; reduce_memory_use(State) -> {_, State1} = reduce_memory_use(fun push_alphas_to_betas/2, fun push_betas_to_deltas/2, -- cgit v1.2.1 From 47b8892946f50f66fe15f4d8db24bcd4acad074f Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 8 Jan 2013 14:39:48 +0000 Subject: Stub for 1.0 support. --- src/rabbit_connection_sup.erl | 9 ++++++++- src/rabbit_reader.erl | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/rabbit_connection_sup.erl b/src/rabbit_connection_sup.erl index 12a532b6..f4f3c72f 100644 --- a/src/rabbit_connection_sup.erl +++ b/src/rabbit_connection_sup.erl @@ -42,10 +42,17 @@ start_link() -> SupPid, {collector, {rabbit_queue_collector, start_link, []}, intrinsic, ?MAX_WAIT, worker, [rabbit_queue_collector]}), + %% Note that rabbit_amqp1_0_session_sup_sup despite the name can + %% mimic rabbit_channel_sup_sup when we handle a 0-9-1 connection + %% and the 1.0 plugin is loaded. + ChannelSupSupModule = case code:is_loaded(rabbit_amqp1_0_session_sup_sup) of + false -> rabbit_channel_sup_sup; + _ -> rabbit_amqp1_0_session_sup_sup + end, {ok, ChannelSupSupPid} = supervisor2:start_child( SupPid, - {channel_sup_sup, {rabbit_channel_sup_sup, start_link, []}, + {channel_sup_sup, {ChannelSupSupModule, start_link, []}, intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}), {ok, ReaderPid} = supervisor2:start_child( diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 13e8feff..f140bd23 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -689,6 +689,15 @@ handle_input(handshake, <<"AMQP", 1, 1, 8, 0>>, State) -> handle_input(handshake, <<"AMQP", 1, 1, 9, 1>>, State) -> start_connection({8, 0, 0}, rabbit_framing_amqp_0_8, State); +%% ... and finally, the 1.0 spec is crystal clear! Note that the +%% FIXME TLS uses a different protocol number, and would go here. +handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>, State) -> + become_1_0(amqp, [0, 1, 0, 0], State); + +%% 3 stands for "SASL" +handle_input(handshake, <<"AMQP", 3, 1, 0, 0>>, State) -> + become_1_0(sasl, [0, 3, 0, 0], State); + handle_input(handshake, <<"AMQP", A, B, C, D>>, #v1{sock = Sock}) -> refuse_connection(Sock, {bad_version, A, B, C, D}); @@ -981,3 +990,28 @@ cert_info(F, #v1{sock = Sock}) -> emit_stats(State) -> rabbit_event:notify(connection_stats, infos(?STATISTICS_KEYS, State)), rabbit_event:reset_stats_timer(State, #v1.stats_timer). + +%% 1.0 stub + +become_1_0(Mode, HandshakeBytes, State = #v1{sock = Sock}) -> + case code:is_loaded(rabbit_amqp1_0_reader) of + false -> refuse_connection( + Sock, list_to_tuple([bad_version | HandshakeBytes])); + _ -> apply0(rabbit_amqp1_0_reader, become, + [Mode, pack_for_1_0(State)]) + end. + +%% Fool xref. Simply using apply(M, F, A) with constants is not enough. +apply0(M, F, A) -> apply(M, F, A). + +pack_for_1_0(#v1{parent = Parent, + sock = Sock, + recv_len = RecvLen, + pending_recv = PendingRecv, + queue_collector = QueueCollector, + channel_sup_sup_pid = ChannelSupSupPid, + start_heartbeat_fun = SHF, + buf = Buf, + buf_len = BufLen}) -> + {Parent, Sock, RecvLen, PendingRecv, QueueCollector, + ChannelSupSupPid, SHF, Buf, BufLen}. -- cgit v1.2.1 From 01a0f6109f9e8ef9f29379755f7212d86f1508c1 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 8 Jan 2013 15:12:21 +0000 Subject: Fix docs and specs. --- docs/rabbitmqctl.1.xml | 2 +- src/rabbit_amqqueue.erl | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/rabbitmqctl.1.xml b/docs/rabbitmqctl.1.xml index 31921769..c7069aed 100644 --- a/docs/rabbitmqctl.1.xml +++ b/docs/rabbitmqctl.1.xml @@ -1162,7 +1162,7 @@ status The status of the queue. Normally - 'running', but may be different if the queue is + 'running', but may be "{syncing, MsgCount}" if the queue is synchronising. diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 33c2cd62..2477b891 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -176,8 +176,7 @@ -spec(stop_mirroring/1 :: (pid()) -> 'ok'). -spec(sync_mirrors/1 :: (pid()) -> 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). --spec(cancel_sync_mirrors/1 :: (pid()) -> - 'ok' | rabbit_types:error('not_mirrored')). +-spec(cancel_sync_mirrors/1 :: (pid()) -> 'ok' | {'ok', 'not_syncing'}). -endif. -- cgit v1.2.1 From 0e929121c541ca467d9a30cc48b981b5f61ca997 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 8 Jan 2013 18:04:47 +0000 Subject: rationalise timer maintenance - introduce a couple of helper functions and use them wherever we can - pay attention to the result of timer cancellation, to prevent duplicate timer creation - stop all timers when a queue terminates. More out of politeness than necessity. --- src/rabbit_amqqueue_process.erl | 86 +++++++++++++++++---------------------- src/rabbit_mirror_queue_slave.erl | 28 ++++--------- src/rabbit_misc.erl | 19 +++++++++ src/rabbit_msg_store.erl | 13 +++--- 4 files changed, 70 insertions(+), 76 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 536ed48a..2293d001 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -254,7 +254,11 @@ init_dlx_routing_key(RoutingKey, State) -> terminate_shutdown(Fun, State) -> State1 = #q{backing_queue_state = BQS} = - stop_sync_timer(stop_rate_timer(State)), + lists:foldl(fun (F, S) -> F(S) end, State, + [fun stop_sync_timer/1, + fun stop_rate_timer/1, + fun stop_expiry_timer/1, + fun stop_ttl_timer/1]), case BQS of undefined -> State1; _ -> ok = rabbit_memory_monitor:deregister(self()), @@ -289,36 +293,18 @@ backing_queue_module(Q) -> true -> rabbit_mirror_queue_master end. -ensure_sync_timer(State = #q{sync_timer_ref = undefined}) -> - TRef = erlang:send_after(?SYNC_INTERVAL, self(), sync_timeout), - State#q{sync_timer_ref = TRef}; ensure_sync_timer(State) -> - State. + rabbit_misc:ensure_timer(State, #q.sync_timer_ref, + ?SYNC_INTERVAL, sync_timeout). -stop_sync_timer(State = #q{sync_timer_ref = undefined}) -> - State; -stop_sync_timer(State = #q{sync_timer_ref = TRef}) -> - erlang:cancel_timer(TRef), - State#q{sync_timer_ref = undefined}. - -ensure_rate_timer(State = #q{rate_timer_ref = undefined}) -> - TRef = erlang:send_after( - ?RAM_DURATION_UPDATE_INTERVAL, self(), update_ram_duration), - State#q{rate_timer_ref = TRef}; -ensure_rate_timer(State) -> - State. +stop_sync_timer(State) -> rabbit_misc:stop_timer(State, #q.sync_timer_ref). -stop_rate_timer(State = #q{rate_timer_ref = undefined}) -> - State; -stop_rate_timer(State = #q{rate_timer_ref = TRef}) -> - erlang:cancel_timer(TRef), - State#q{rate_timer_ref = undefined}. +ensure_rate_timer(State) -> + rabbit_misc:ensure_timer(State, #q.rate_timer_ref, + ?RAM_DURATION_UPDATE_INTERVAL, + update_ram_duration). -stop_expiry_timer(State = #q{expiry_timer_ref = undefined}) -> - State; -stop_expiry_timer(State = #q{expiry_timer_ref = TRef}) -> - erlang:cancel_timer(TRef), - State#q{expiry_timer_ref = undefined}. +stop_rate_timer(State) -> rabbit_misc:stop_timer(State, #q.rate_timer_ref). %% We wish to expire only when there are no consumers *and* the expiry %% hasn't been refreshed (by queue.declare or basic.get) for the @@ -328,11 +314,34 @@ ensure_expiry_timer(State = #q{expires = undefined}) -> ensure_expiry_timer(State = #q{expires = Expires}) -> case is_unused(State) of true -> NewState = stop_expiry_timer(State), - TRef = erlang:send_after(Expires, self(), maybe_expire), - NewState#q{expiry_timer_ref = TRef}; + rabbit_misc:ensure_timer(NewState, #q.expiry_timer_ref, + Expires, maybe_expire); false -> State end. +stop_expiry_timer(State) -> rabbit_misc:stop_timer(State, #q.expiry_timer_ref). + +ensure_ttl_timer(undefined, State) -> + State; +ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = undefined}) -> + After = (case Expiry - now_micros() of + V when V > 0 -> V + 999; %% always fire later + _ -> 0 + end) div 1000, + TRef = erlang:send_after(After, self(), drop_expired), + State#q{ttl_timer_ref = TRef, ttl_timer_expiry = Expiry}; +ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = TRef, + ttl_timer_expiry = TExpiry}) + when Expiry + 1000 < TExpiry -> + case erlang:cancel_timer(TRef) of + false -> State; + _ -> ensure_ttl_timer(Expiry, State#q{ttl_timer_ref = undefined}) + end; +ensure_ttl_timer(_Expiry, State) -> + State. + +stop_ttl_timer(State) -> rabbit_misc:stop_timer(State, #q.ttl_timer_ref). + ensure_stats_timer(State) -> rabbit_event:ensure_stats_timer(State, #q.stats_timer, emit_stats). @@ -764,25 +773,6 @@ dead_letter_msgs(Fun, Reason, X, State = #q{dlx_routing_key = RK, queue_monitors = QMons1, backing_queue_state = BQS2}}. -ensure_ttl_timer(undefined, State) -> - State; -ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = undefined}) -> - After = (case Expiry - now_micros() of - V when V > 0 -> V + 999; %% always fire later - _ -> 0 - end) div 1000, - TRef = erlang:send_after(After, self(), drop_expired), - State#q{ttl_timer_ref = TRef, ttl_timer_expiry = Expiry}; -ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = TRef, - ttl_timer_expiry = TExpiry}) - when Expiry + 1000 < TExpiry -> - case erlang:cancel_timer(TRef) of - false -> State; - _ -> ensure_ttl_timer(Expiry, State#q{ttl_timer_ref = undefined}) - end; -ensure_ttl_timer(_Expiry, State) -> - State. - dead_letter_publish(Msg, Reason, X, RK, MsgSeqNo, QName) -> DLMsg = make_dead_letter_msg(Msg, Reason, X#exchange.name, RK, QName), Delivery = rabbit_basic:delivery(false, DLMsg, MsgSeqNo), diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index feddf45a..9f12b34e 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -589,30 +589,18 @@ next_state(State = #state{backing_queue = BQ, backing_queue_state = BQS}) -> backing_queue_timeout(State = #state { backing_queue = BQ }) -> run_backing_queue(BQ, fun (M, BQS) -> M:timeout(BQS) end, State). -ensure_sync_timer(State = #state { sync_timer_ref = undefined }) -> - TRef = erlang:send_after(?SYNC_INTERVAL, self(), sync_timeout), - State #state { sync_timer_ref = TRef }; ensure_sync_timer(State) -> - State. + rabbit_misc:ensure_timer(State, #state.sync_timer_ref, + ?SYNC_INTERVAL, sync_timeout). + +stop_sync_timer(State) -> rabbit_misc:stop_timer(State, #state.sync_timer_ref). -stop_sync_timer(State = #state { sync_timer_ref = undefined }) -> - State; -stop_sync_timer(State = #state { sync_timer_ref = TRef }) -> - erlang:cancel_timer(TRef), - State #state { sync_timer_ref = undefined }. - -ensure_rate_timer(State = #state { rate_timer_ref = undefined }) -> - TRef = erlang:send_after(?RAM_DURATION_UPDATE_INTERVAL, - self(), update_ram_duration), - State #state { rate_timer_ref = TRef }; ensure_rate_timer(State) -> - State. + rabbit_misc:ensure_timer(State, #state.rate_timer_ref, + ?RAM_DURATION_UPDATE_INTERVAL, + update_ram_duration). -stop_rate_timer(State = #state { rate_timer_ref = undefined }) -> - State; -stop_rate_timer(State = #state { rate_timer_ref = TRef }) -> - erlang:cancel_timer(TRef), - State #state { rate_timer_ref = undefined }. +stop_rate_timer(State) -> rabbit_misc:stop_timer(State, #state.rate_timer_ref). ensure_monitoring(ChPid, State = #state { known_senders = KS }) -> State #state { known_senders = pmon:monitor(ChPid, KS) }. diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index ce3e3802..73a4c922 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -67,6 +67,7 @@ -export([check_expiry/1]). -export([base64url/1]). -export([interval_operation/4]). +-export([ensure_timer/4, stop_timer/2]). -export([get_parent/0]). %% Horrible macro to use in guards @@ -242,6 +243,8 @@ -spec(interval_operation/4 :: ({atom(), atom(), any()}, float(), non_neg_integer(), non_neg_integer()) -> {any(), non_neg_integer()}). +-spec(ensure_timer/4 :: (A, non_neg_integer(), non_neg_integer(), any()) -> A). +-spec(stop_timer/2 :: (A, non_neg_integer()) -> A). -spec(get_parent/0 :: () -> pid()). -endif. @@ -1047,6 +1050,22 @@ interval_operation({M, F, A}, MaxRatio, IdealInterval, LastInterval) -> round(LastInterval / 1.5)]) end}. +ensure_timer(State, Idx, After, Msg) -> + case element(Idx, State) of + undefined -> TRef = erlang:send_after(After, self(), Msg), + setelement(Idx, State, TRef); + _ -> State + end. + +stop_timer(State, Idx) -> + case element(Idx, State) of + undefined -> State; + TRef -> case erlang:cancel_timer(TRef) of + false -> State; + _ -> setelement(Idx, State, undefined) + end + end. + %% ------------------------------------------------------------------------- %% Begin copypasta from gen_server2.erl diff --git a/src/rabbit_msg_store.erl b/src/rabbit_msg_store.erl index c2e55022..485a3256 100644 --- a/src/rabbit_msg_store.erl +++ b/src/rabbit_msg_store.erl @@ -943,15 +943,12 @@ next_state(State = #msstate { cref_to_msg_ids = CTM }) -> _ -> {State, 0} end. -start_sync_timer(State = #msstate { sync_timer_ref = undefined }) -> - TRef = erlang:send_after(?SYNC_INTERVAL, self(), sync), - State #msstate { sync_timer_ref = TRef }. +start_sync_timer(State) -> + rabbit_misc:ensure_timer(State, #msstate.sync_timer_ref, + ?SYNC_INTERVAL, sync). -stop_sync_timer(State = #msstate { sync_timer_ref = undefined }) -> - State; -stop_sync_timer(State = #msstate { sync_timer_ref = TRef }) -> - erlang:cancel_timer(TRef), - State #msstate { sync_timer_ref = undefined }. +stop_sync_timer(State) -> + rabbit_misc:stop_timer(State, #msstate.sync_timer_ref). internal_sync(State = #msstate { current_file_handle = CurHdl, cref_to_msg_ids = CTM }) -> -- cgit v1.2.1 From 534051bae26aa38e7770351546173034c9c2d7d6 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 9 Jan 2013 11:10:56 +0000 Subject: Update dead-lettering due to queue length limit --- src/rabbit_amqqueue_process.erl | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 2506ff91..81db5491 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -563,10 +563,18 @@ maybe_drop_head(State = #q{max_length = MaxLen, backing_queue = BQ, backing_queue_state = BQS}) -> case BQ:len(BQS) >= MaxLen of - true -> {{Msg, _IsDelivered, AckTag}, BQS1} = BQ:fetch(true, BQS), - (dead_letter_fun(maxlen))([{Msg, AckTag}]), - State#q{backing_queue_state = BQS1}; - false -> State + true -> + with_dlx(State#q.dlx, + fun (X) -> + {ok, State1} = dead_letter_maxlen_msgs(X, State), + State1 + end, + fun () -> + {_, BQS1} = BQ:drop(false, BQS), + State#q{backing_queue_state = BQS1} + end); + false -> + State end. requeue_and_run(AckTags, State = #q{backing_queue = BQ, @@ -746,6 +754,12 @@ dead_letter_rejected_msgs(AckTags, X, State = #q{backing_queue = BQ}) -> end, rejected, X, State), State1. +dead_letter_maxlen_msgs(X, State = #q{backing_queue = BQ}) -> + dead_letter_msgs(fun (DLFun, Acc, BQS1) -> + {{Msg, _, AckTag}, BQS2} = BQ:fetch(true, BQS1), + {ok, DLFun(Msg, AckTag, Acc), BQS2} + end, maxlen, X, State). + dead_letter_msgs(Fun, Reason, X, State = #q{dlx_routing_key = RK, publish_seqno = SeqNo0, unconfirmed = UC0, -- cgit v1.2.1 From 253bc8f774078ef98bfc55f2373c948c08d7124d Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 9 Jan 2013 11:15:16 +0000 Subject: Add queue max length equivalence condition --- src/rabbit_amqqueue.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index a9f2f390..205a61f5 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -385,7 +385,8 @@ with_exclusive_access_or_die(Name, ReaderPid, F) -> assert_args_equivalence(#amqqueue{name = QueueName, arguments = Args}, RequiredArgs) -> rabbit_misc:assert_args_equivalence( - Args, RequiredArgs, QueueName, [<<"x-expires">>, <<"x-message-ttl">>]). + Args, RequiredArgs, QueueName, + [<<"x-expires">>, <<"x-message-ttl">>, <<"x-max-length">>]). check_declare_arguments(QueueName, Args) -> Checks = [{<<"x-expires">>, fun check_expires_arg/2}, -- cgit v1.2.1 From 06b3e6229030c357da8b1fc2cf816e1cf3e7e050 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 9 Jan 2013 14:52:41 +0000 Subject: I added that clause early in the history of this bug and it's always been wrong. The semantics of that line are just "set is_limit_active to false if the limiter is disabled", is_blocked (which only deals with the channel.flow case anyway) has nothing to do with it. --- 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 08a1ca70..87b93d17 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1249,8 +1249,7 @@ handle_cast({limit, ChPid, Limiter}, State) -> true -> ok = rabbit_limiter:register(Limiter, self()); false -> ok end, - Limited = OldLimited andalso rabbit_limiter:is_enabled(Limiter) - andalso rabbit_limiter:is_blocked(Limiter), + Limited = OldLimited andalso rabbit_limiter:is_enabled(Limiter), C#cr{limiter = Limiter, is_limit_active = Limited} end)); -- cgit v1.2.1 From ecd7fb6e097f72060fcae8c49f5903fd8e569676 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 9 Jan 2013 16:09:26 +0000 Subject: tiny refactor --- src/rabbit_amqqueue_process.erl | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 904eb6d0..589e8289 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -569,18 +569,14 @@ maybe_drop_head(State = #q{max_length = MaxLen, backing_queue = BQ, backing_queue_state = BQS}) -> case BQ:len(BQS) >= MaxLen of - true -> - with_dlx(State#q.dlx, - fun (X) -> - {ok, State1} = dead_letter_maxlen_msgs(X, State), - State1 - end, - fun () -> - {_, BQS1} = BQ:drop(false, BQS), - State#q{backing_queue_state = BQS1} - end); - false -> - State + true -> with_dlx( + State#q.dlx, + fun (X) -> dead_letter_maxlen_msgs(X, State) end, + fun () -> + {_, BQS1} = BQ:drop(false, BQS), + State#q{backing_queue_state = BQS1} + end); + false -> State end. requeue_and_run(AckTags, State = #q{backing_queue = BQ, @@ -771,10 +767,13 @@ dead_letter_rejected_msgs(AckTags, X, State = #q{backing_queue = BQ}) -> State1. dead_letter_maxlen_msgs(X, State = #q{backing_queue = BQ}) -> - dead_letter_msgs(fun (DLFun, Acc, BQS1) -> - {{Msg, _, AckTag}, BQS2} = BQ:fetch(true, BQS1), - {ok, DLFun(Msg, AckTag, Acc), BQS2} - end, maxlen, X, State). + {ok State1} = + dead_letter_msgs( + fun (DLFun, Acc, BQS1) -> + {{Msg, _, AckTag}, BQS2} = BQ:fetch(true, BQS1), + {ok, DLFun(Msg, AckTag, Acc), BQS2} + end, maxlen, X, State), + State1. dead_letter_msgs(Fun, Reason, X, State = #q{dlx_routing_key = RK, publish_seqno = SeqNo0, -- cgit v1.2.1 From e2393df71b70a19895cd27cde9ead044930c83fa Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 10 Jan 2013 15:34:49 +0000 Subject: cosmetic rename --- src/rabbit_alarm.erl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/rabbit_alarm.erl b/src/rabbit_alarm.erl index d7d4d82a..f813eab8 100644 --- a/src/rabbit_alarm.erl +++ b/src/rabbit_alarm.erl @@ -67,9 +67,8 @@ start() -> stop() -> ok. -register(Pid, HighMemMFA) -> - gen_event:call(?SERVER, ?MODULE, {register, Pid, HighMemMFA}, - infinity). +register(Pid, AlertMFA) -> + gen_event:call(?SERVER, ?MODULE, {register, Pid, AlertMFA}, infinity). set_alarm(Alarm) -> gen_event:notify(?SERVER, {set_alarm, Alarm}). clear_alarm(Alarm) -> gen_event:notify(?SERVER, {clear_alarm, Alarm}). @@ -94,9 +93,9 @@ init([]) -> alarmed_nodes = dict:new(), alarms = []}}. -handle_call({register, Pid, HighMemMFA}, State) -> +handle_call({register, Pid, AlertMFA}, State) -> {ok, 0 < dict:size(State#alarms.alarmed_nodes), - internal_register(Pid, HighMemMFA, State)}; + internal_register(Pid, AlertMFA, State)}; handle_call(get_alarms, State = #alarms{alarms = Alarms}) -> {ok, Alarms, State}; @@ -121,8 +120,8 @@ handle_event({node_up, Node}, State) -> handle_event({node_down, Node}, State) -> {ok, maybe_alert(fun dict_unappend_all/3, Node, [], State)}; -handle_event({register, Pid, HighMemMFA}, State) -> - {ok, internal_register(Pid, HighMemMFA, State)}; +handle_event({register, Pid, AlertMFA}, State) -> + {ok, internal_register(Pid, AlertMFA, State)}; handle_event(_Event, State) -> {ok, State}. @@ -198,14 +197,14 @@ alert(Alertees, Source, Alert, NodeComparator) -> end end, ok, Alertees). -internal_register(Pid, {M, F, A} = HighMemMFA, +internal_register(Pid, {M, F, A} = AlertMFA, State = #alarms{alertees = Alertees}) -> _MRef = erlang:monitor(process, Pid), case dict:find(node(), State#alarms.alarmed_nodes) of {ok, Sources} -> [apply(M, F, A ++ [Pid, R, true]) || R <- Sources]; error -> ok end, - NewAlertees = dict:store(Pid, HighMemMFA, Alertees), + NewAlertees = dict:store(Pid, AlertMFA, Alertees), State#alarms{alertees = NewAlertees}. handle_set_alarm({{resource_limit, Source, Node}, []}, State) -> -- cgit v1.2.1 From a09fb4cb5e720029bd1be47bae67a6103d885929 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 10 Jan 2013 15:54:35 +0000 Subject: consumer_mapping changed since this branch was last worked on. --- src/rabbit_channel.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 6c2c37f9..a8df19ff 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1090,11 +1090,11 @@ handle_method(#'basic.credit'{consumer_tag = CTag, %% want that? Because at least then it's consistent with the credit value %% we return. And Available is always going to be racy. Available = case dict:find(CTag, Consumers) of - {ok, {Q, _}} -> case rabbit_amqqueue:stat(Q) of - {ok, Len, _} -> Len; - _ -> -1 - end; - error -> -1 %% TODO these -1s smell very iffy! + {ok, Q} -> case rabbit_amqqueue:stat(Q) of + {ok, Len, _} -> Len; + _ -> -1 + end; + error -> -1 %% TODO these -1s smell very iffy! end, Limiter1 = case rabbit_limiter:is_enabled(Limiter) of true -> Limiter; -- cgit v1.2.1 From d65b067aa9759edd4faab55aad4aaca53690b075 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 10 Jan 2013 18:01:16 +0000 Subject: Second attempt at moving credit into the queue. This time we pretend the limiter is still doing the work. While testing this I note that the credit calculation is crazy when testing with Proton. But it was just as bad before, so let's commit. --- src/rabbit_amqqueue.erl | 5 + src/rabbit_amqqueue_process.erl | 30 ++++-- src/rabbit_channel.erl | 42 +++----- src/rabbit_limiter.erl | 227 ++++++++++++++++++++-------------------- 4 files changed, 155 insertions(+), 149 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 94150f1c..a337c722 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -32,6 +32,7 @@ -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). -export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1]). +-export([inform_limiter/3]). %% internal -export([internal_declare/2, internal_delete/1, run_backing_queue/3, @@ -175,6 +176,7 @@ -spec(stop_mirroring/1 :: (pid()) -> 'ok'). -spec(sync_mirrors/1 :: (pid()) -> 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). +-spec(inform_limiter/3 :: (pid(), pid(), any()) -> 'ok'). -endif. @@ -604,6 +606,9 @@ stop_mirroring(QPid) -> ok = delegate:cast(QPid, stop_mirroring). sync_mirrors(QPid) -> delegate:call(QPid, sync_mirrors). +inform_limiter(ChPid, QPid, Msg) -> + delegate:cast(QPid, {inform_limiter, ChPid, Msg}). + on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( fun () -> QsDels = diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 87b93d17..2ec54c7b 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -430,19 +430,25 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, case is_ch_blocked(C) of true -> block_consumer(C, E), {false, State}; - false -> case rabbit_limiter:can_send(C#cr.limiter, self(), - Consumer#consumer.ack_required, - Consumer#consumer.tag, - BQ:len(BQS)) of - false -> block_consumer(C#cr{is_limit_active = true}, E), + false -> #cr{limiter = Limiter, ch_pid = ChPid} = C, + {CanSend, Lim2} = + rabbit_limiter:can_send( + Limiter, ChPid, self(), Consumer#consumer.ack_required, + Consumer#consumer.tag, BQ:len(BQS)), + case CanSend of + false -> block_consumer(C#cr{is_limit_active = true, + limiter = Lim2}, E), {false, State}; - true -> AC1 = queue:in(E, State#q.active_consumers), + true -> update_ch_record(C#cr{limiter = Lim2}), %%[0] + AC1 = queue:in(E, State#q.active_consumers), deliver_msg_to_consumer( DeliverFun, Consumer, C, State#q{active_consumers = AC1}) end end. +%% [0] TODO is this a hotspot in the case where the limiter has not changed? + deliver_msg_to_consumer(DeliverFun, #consumer{tag = ConsumerTag, ack_required = AckRequired}, @@ -1250,7 +1256,9 @@ handle_cast({limit, ChPid, Limiter}, State) -> false -> ok end, Limited = OldLimited andalso rabbit_limiter:is_enabled(Limiter), - C#cr{limiter = Limiter, is_limit_active = Limited} + C#cr{limiter = rabbit_limiter:copy_queue_state( + OldLimiter, Limiter), + is_limit_active = Limited} end)); handle_cast({flush, ChPid}, State) -> @@ -1308,6 +1316,14 @@ handle_cast(stop_mirroring, State = #q{backing_queue = BQ, noreply(State#q{backing_queue = BQ1, backing_queue_state = BQS1}); +handle_cast({inform_limiter, ChPid, Msg}, + State = #q{backing_queue = BQ, + backing_queue_state = BQS}) -> + C = #cr{limiter = Limiter} = ch_record(ChPid), + Limiter2 = rabbit_limiter:inform(Limiter, ChPid, BQ:len(BQS), Msg), + update_ch_record(C#cr{limiter = Limiter2}), + noreply(State); + handle_cast(wake_up, State) -> noreply(State). diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index a8df19ff..6d00fdb2 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1080,35 +1080,19 @@ handle_method(#'channel.flow'{active = false}, _, end; handle_method(#'basic.credit'{consumer_tag = CTag, - credit = Credit, - count = Count, - drain = Drain}, _, - State = #ch{limiter = Limiter, - consumer_mapping = Consumers}) -> - %% We get Available first because it's likely that as soon as we set - %% the credit msgs will get consumed and it'll be out of date. Why do we - %% want that? Because at least then it's consistent with the credit value - %% we return. And Available is always going to be racy. - Available = case dict:find(CTag, Consumers) of - {ok, Q} -> case rabbit_amqqueue:stat(Q) of - {ok, Len, _} -> Len; - _ -> -1 - end; - error -> -1 %% TODO these -1s smell very iffy! - end, - Limiter1 = case rabbit_limiter:is_enabled(Limiter) of - true -> Limiter; - false -> enable_limiter(State) - end, - Limiter3 = - case rabbit_limiter:set_credit( - Limiter1, CTag, Credit, Count, Drain) of - ok -> Limiter1; - {disabled, Limiter2} -> ok = limit_queues(Limiter2, State), - Limiter2 - end, - State1 = State#ch{limiter = Limiter3}, - return_ok(State1, false, #'basic.credit_ok'{available = Available}); + credit = Credit, + count = Count, + drain = Drain} = M, _, + State = #ch{consumer_mapping = Consumers}) -> + %%io:format(" ~p~n", [M]), + case dict:find(CTag, Consumers) of + {ok, Q} -> ok = rabbit_amqqueue:inform_limiter( + self(), Q#amqqueue.pid, + {basic_credit, CTag, Credit, Count, Drain}), + {noreply, State}; + error -> rabbit_misc:protocol_error( + not_allowed, "unknown consumer tag '~s'", [CTag]) + end; handle_method(_MethodRecord, _Content, _State) -> rabbit_misc:protocol_error( diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 5f1bc07c..6e3a228b 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -24,15 +24,15 @@ -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). --export([limit/2, can_send/5, ack/2, register/2, unregister/2]). +-export([limit/2, can_send/6, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). --export([set_credit/5]). +-export([inform/4]). -import(rabbit_misc, [serial_add/2, serial_diff/2]). %%---------------------------------------------------------------------------- --record(token, {pid, enabled}). +-record(token, {pid, enabled, q_state}). -ifdef(use_specs). @@ -47,8 +47,9 @@ -spec(enable/2 :: (token(), non_neg_integer()) -> token()). -spec(disable/1 :: (token()) -> token()). -spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). --spec(can_send/5 :: (token(), pid(), boolean(), - rabbit_types:ctag(), non_neg_integer()) -> boolean()). +%% TODO +%% -spec(can_send/5 :: (token(), pid(), boolean(), +%% rabbit_types:ctag(), non_neg_integer()) -> boolean()). -spec(ack/2 :: (token(), non_neg_integer()) -> 'ok'). -spec(register/2 :: (token(), pid()) -> 'ok'). -spec(unregister/2 :: (token(), pid()) -> 'ok'). @@ -56,10 +57,10 @@ -spec(block/1 :: (token()) -> 'ok'). -spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). -spec(is_blocked/1 :: (token()) -> boolean()). --spec(set_credit/5 :: (token(), rabbit_types:ctag(), - non_neg_integer(), - non_neg_integer(), boolean()) -> 'ok'). - +%% -spec(set_credit/5 :: (token(), rabbit_types:ctag(), +%% non_neg_integer(), +%% non_neg_integer(), boolean()) -> 'ok'). +-spec(inform/4 :: (token(), pid(), non_neg_integer(), any()) -> token()). -endif. %%---------------------------------------------------------------------------- @@ -67,7 +68,6 @@ -record(lim, {prefetch_count = 0, ch_pid, blocked = false, - credits = dict:new(), queues = orddict:new(), % QPid -> {MonitorRef, Notify} volume = 0}). @@ -80,7 +80,8 @@ start_link() -> gen_server2:start_link(?MODULE, [], []). make_token() -> make_token(undefined). -make_token(Pid) -> #token{pid = Pid, enabled = false}. +make_token(Pid) -> #token{pid = Pid, enabled = false, + q_state = dict:new()}. is_enabled(#token{enabled = Enabled}) -> Enabled. @@ -97,19 +98,22 @@ limit(Limiter, PrefetchCount) -> %% breaching a limit. Note that we don't use maybe_call here in order %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. -can_send(#token{pid = Pid, enabled = true}, QPid, AckRequired, CTag, Len) -> +can_send(#token{pid = Pid, enabled = true, q_state = QState} = Token, + ChPid, QPid, AckRequired, CTag, Len) -> rabbit_misc:with_exit_handler( - fun () -> true end, + fun () -> {true, Token} end, fun () -> - gen_server2:call(Pid, {can_send, QPid, AckRequired, CTag, Len}, - infinity) + CanLim = gen_server2:call(Pid, {can_send, QPid, AckRequired}, + infinity), + {CanQ, NewQState} = can_send_q(CTag, Len, ChPid, QState), + {CanLim andalso CanQ, Token#token{q_state = NewQState}} end); -can_send(_, _, _, _, _) -> - true. +can_send(Token, _, _, _, _, _) -> + {true, Token}. %% Let the limiter know that the channel has received some acks from a %% consumer -ack(Limiter, CTag) -> maybe_cast(Limiter, {ack, CTag}). +ack(Limiter, Count) -> maybe_cast(Limiter, {ack, Count}). register(Limiter, QPid) -> maybe_cast(Limiter, {register, QPid}). @@ -126,12 +130,82 @@ block(Limiter) -> unblock(Limiter) -> maybe_call(Limiter, {unblock, Limiter}, ok). -set_credit(Limiter, CTag, Credit, Count, Drain) -> - maybe_call(Limiter, {set_credit, CTag, Credit, Count, Drain, Limiter}, ok). - is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). +inform(Limiter = #token{q_state = Credits}, + ChPid, Len, {basic_credit, CTag, Credit, Count, Drain}) -> + Credits2 = reset_credit(CTag, Len, ChPid, Credit, Count, Drain, Credits), + Limiter#token{q_state = Credits2}. + +%%---------------------------------------------------------------------------- +%% Queue-local code +%%---------------------------------------------------------------------------- + +%% We want to do all the AMQP 1.0-ish link level credit calculations in the +%% queue (to do them elsewhere introduces a ton of races). However, it's a big +%% chunk of code that is conceptually very linked to the limiter concept. So +%% we get the queue to hold a bit of state for us (#token.q_state), and +%% maintain a fiction that the limiter is making the decisions... + +can_send_q(CTag, Len, ChPid, Credits) -> + case dict:find(CTag, Credits) of + {ok, #credit{credit = 0}} -> exit(bang), {false, Credits}; + {ok, Cred} -> Credits2 = + decr_credit( + CTag, Len, ChPid, Cred, Credits), + {true, Credits2}; + _ -> {true, Credits} + end. + +decr_credit(CTag, Len, ChPid, Cred, Credits) -> + #credit{credit = Credit, count = Count, drain = Drain} = Cred, + {NewCredit, NewCount} = + case {Credit, Len, Drain} of + {1, _, _} -> {0, serial_add(Count, 1)}; + {_, 1, true} -> %% Drain, so advance til credit = 0 + NewCount0 = serial_add(Count, (Credit - 1)), + send_drained(ChPid, CTag, NewCount0), + {0, NewCount0}; %% Magic reduction to 0 + {_, _, _} -> {Credit - 1, serial_add(Count, 1)} + end, + update_credit(CTag, NewCredit, NewCount, Drain, Credits). + +send_drained(ChPid, CTag, Count) -> + rabbit_channel:send_command(ChPid, + #'basic.credit_state'{consumer_tag = CTag, + credit = 0, + count = Count, + available = 0, + drain = true}). + +%% Assert the credit state. The count may not match ours, in which +%% case we must rebase the credit. +%% TODO Edge case: if the queue has nothing in it, and drain is set, +%% we want to send a basic.credit back. +reset_credit(CTag, Len, ChPid, Credit0, Count0, Drain, Credits) -> + Count = + case dict:find(CTag, Credits) of + {ok, #credit{ count = LocalCount }} -> + LocalCount; + _ -> Count0 + end, + %% Our credit may have been reduced while messages are in flight, + %% so we bottom out at 0. + Credit = erlang:max(0, serial_diff(serial_add(Count0, Credit0), Count)), + rabbit_channel:send_command(ChPid, + #'basic.credit_ok'{available = Len}), + update_credit(CTag, Credit, Count, Drain, Credits). + +%% Store the credit +update_credit(CTag, -1, _Count, _Drain, Credits) -> + dict:erase(CTag, Credits); + +update_credit(CTag, Credit, Count, Drain, Credits) -> + dict:store(CTag, #credit{credit = Credit, + count = Count, + drain = Drain}, Credits). + %%---------------------------------------------------------------------------- %% gen_server callbacks %%---------------------------------------------------------------------------- @@ -142,26 +216,23 @@ init([]) -> prioritise_call(get_limit, _From, _State) -> 9; prioritise_call(_Msg, _From, _State) -> 0. -handle_call({can_send, QPid, _AckRequired, _CTag, _Len}, _From, +handle_call({can_send, QPid, _AckRequired}, _From, State = #lim{blocked = true}) -> {reply, false, limit_queue(QPid, State)}; -handle_call({can_send, QPid, AckRequired, CTag, Len}, _From, +handle_call({can_send, QPid, AckRequired}, _From, State = #lim{volume = Volume}) -> - case limit_reached(CTag, State) of + case limit_reached(State) of true -> {reply, false, limit_queue(QPid, State)}; - false -> {reply, true, - decr_credit(CTag, Len, - State#lim{volume = if AckRequired -> Volume + 1; - true -> Volume - end})} + false -> {reply, true, State#lim{volume = if AckRequired -> Volume + 1; + true -> Volume + end}} end; handle_call(get_limit, _From, State = #lim{prefetch_count = PrefetchCount}) -> {reply, PrefetchCount, State}; handle_call({limit, PrefetchCount, Token}, _From, State) -> - case maybe_notify(irrelevant, - State, State#lim{prefetch_count = PrefetchCount}) of + case maybe_notify(State, State#lim{prefetch_count = PrefetchCount}) of {cont, State1} -> {reply, ok, State1}; {stop, State1} -> @@ -171,17 +242,8 @@ handle_call({limit, PrefetchCount, Token}, _From, State) -> handle_call(block, _From, State) -> {reply, ok, State#lim{blocked = true}}; -handle_call({set_credit, CTag, Credit, Count, Drain, Token}, _From, State) -> - case maybe_notify(CTag, State, - reset_credit(CTag, Credit, Count, Drain, State)) of - {cont, State1} -> - {reply, ok, State1}; - {stop, State1} -> - {reply, {disabled, Token#token{enabled = false}}, State1} - end; - handle_call({unblock, Token}, _From, State) -> - case maybe_notify(irrelevant, State, State#lim{blocked = false}) of + case maybe_notify(State, State#lim{blocked = false}) of {cont, State1} -> {reply, ok, State1}; {stop, State1} -> @@ -197,11 +259,11 @@ handle_call({enable, Token, Channel, Volume}, _From, State) -> handle_call({disable, Token}, _From, State) -> {reply, Token#token{enabled = false}, State}. -handle_cast({ack, CTag}, State = #lim{volume = Volume}) -> +handle_cast({ack, Count}, State = #lim{volume = Volume}) -> NewVolume = if Volume == 0 -> 0; - true -> Volume - 1 + true -> Volume - Count end, - {cont, State1} = maybe_notify(CTag, State, State#lim{volume = NewVolume}), + {cont, State1} = maybe_notify(State, State#lim{volume = NewVolume}), {noreply, State1}; handle_cast({register, QPid}, State) -> @@ -223,14 +285,13 @@ code_change(_, State, _) -> %% Internal plumbing %%---------------------------------------------------------------------------- -maybe_notify(CTag, OldState, NewState) -> - case (limit_reached(CTag, OldState) orelse blocked(OldState)) andalso - not (limit_reached(CTag, NewState) orelse blocked(NewState)) of +maybe_notify(OldState, NewState) -> + case (limit_reached(OldState) orelse blocked(OldState)) andalso + not (limit_reached(NewState) orelse blocked(NewState)) of true -> NewState1 = notify_queues(NewState), - {case {NewState1#lim.prefetch_count, - dict:size(NewState1#lim.credits)} of - {0, 0} -> stop; - _ -> cont + {case NewState1#lim.prefetch_count of + 0 -> stop; + _ -> cont end, NewState1}; false -> {cont, NewState} end. @@ -245,67 +306,8 @@ maybe_cast(#token{pid = Pid, enabled = true}, Cast) -> maybe_cast(_, _Call) -> ok. -limit_reached(irrelevant, _) -> - false; -limit_reached(CTag, #lim{prefetch_count = Limit, volume = Volume, - credits = Credits}) -> - case dict:find(CTag, Credits) of - {ok, #credit{ credit = 0 }} -> true; - _ -> false - end orelse (Limit =/= 0 andalso Volume >= Limit). - -decr_credit(CTag, Len, State = #lim{ credits = Credits, - ch_pid = ChPid } ) -> - case dict:find(CTag, Credits) of - {ok, #credit{ credit = Credit, count = Count, drain = Drain }} -> - {NewCredit, NewCount} = - case {Credit, Len, Drain} of - {1, _, _} -> {0, serial_add(Count, 1)}; - {_, 1, true} -> - %% Drain, so advance til credit = 0 - NewCount0 = serial_add(Count, (Credit - 1)), - send_drained(ChPid, CTag, NewCount0), - {0, NewCount0}; %% Magic reduction to 0 - {_, _, _} -> {Credit - 1, serial_add(Count, 1)} - end, - update_credit(CTag, NewCredit, NewCount, Drain, State); - error -> - State - end. - -send_drained(ChPid, CTag, Count) -> - rabbit_channel:send_command(ChPid, - #'basic.credit_state'{consumer_tag = CTag, - credit = 0, - count = Count, - available = 0, - drain = true}). - -%% Assert the credit state. The count may not match ours, in which -%% case we must rebase the credit. -%% TODO Edge case: if the queue has nothing in it, and drain is set, -%% we want to send a basic.credit back. -reset_credit(CTag, Credit0, Count0, Drain, State = #lim{credits = Credits}) -> - Count = - case dict:find(CTag, Credits) of - {ok, #credit{ count = LocalCount }} -> - LocalCount; - _ -> Count0 - end, - %% Our credit may have been reduced while messages are in flight, - %% so we bottom out at 0. - Credit = erlang:max(0, serial_diff(serial_add(Count0, Credit0), Count)), - update_credit(CTag, Credit, Count, Drain, State). - -%% Store the credit -update_credit(CTag, -1, _Count, _Drain, State = #lim{credits = Credits}) -> - State#lim{credits = dict:erase(CTag, Credits)}; - -update_credit(CTag, Credit, Count, Drain, State = #lim{credits = Credits}) -> - State#lim{credits = dict:store(CTag, - #credit{credit = Credit, - count = Count, - drain = Drain}, Credits)}. +limit_reached(#lim{prefetch_count = Limit, volume = Volume}) -> + Limit =/= 0 andalso Volume >= Limit. blocked(#lim{blocked = Blocked}) -> Blocked. @@ -347,4 +349,3 @@ notify_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> ok end, State#lim{queues = NewQueues}. - -- cgit v1.2.1 From 4b286c55a5275b75c6e43cebe71ccc78e70ba9f5 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 11 Jan 2013 18:01:16 +0000 Subject: Right. There were a lot of bugs. This fixes most of them. One major confusion was that both the adapter and the credit impl were trying to normalise against remote count, now it's just the adapter. Also when we get credit we need to unblock. Also there were various things that assumed our local credit could not go negative - well it can and we just need to wait for it to be positive again. --- src/rabbit_amqqueue_process.erl | 31 +++++++++++++------ src/rabbit_channel.erl | 3 +- src/rabbit_limiter.erl | 67 ++++++++++++++++++++--------------------- 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 2ec54c7b..f48005ef 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -439,16 +439,13 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, false -> block_consumer(C#cr{is_limit_active = true, limiter = Lim2}, E), {false, State}; - true -> update_ch_record(C#cr{limiter = Lim2}), %%[0] - AC1 = queue:in(E, State#q.active_consumers), + true -> AC1 = queue:in(E, State#q.active_consumers), deliver_msg_to_consumer( - DeliverFun, Consumer, C, + DeliverFun, Consumer, C#cr{limiter = Lim2}, State#q{active_consumers = AC1}) end end. -%% [0] TODO is this a hotspot in the case where the limiter has not changed? - deliver_msg_to_consumer(DeliverFun, #consumer{tag = ConsumerTag, ack_required = AckRequired}, @@ -1317,12 +1314,26 @@ handle_cast(stop_mirroring, State = #q{backing_queue = BQ, backing_queue_state = BQS1}); handle_cast({inform_limiter, ChPid, Msg}, - State = #q{backing_queue = BQ, + State = #q{active_consumers = AC, + backing_queue = BQ, backing_queue_state = BQS}) -> - C = #cr{limiter = Limiter} = ch_record(ChPid), - Limiter2 = rabbit_limiter:inform(Limiter, ChPid, BQ:len(BQS), Msg), - update_ch_record(C#cr{limiter = Limiter2}), - noreply(State); + C = #cr{limiter = Limiter, + blocked_consumers = Blocked} = ch_record(ChPid), + {Unblock, Limiter2} = + rabbit_limiter:inform(Limiter, ChPid, BQ:len(BQS), Msg), + NewBlocked = queue:filter(fun({_ChPid, #consumer{tag = CTag}}) -> + not lists:member(CTag, Unblock) + end, Blocked), + NewUnblocked = queue:filter(fun({_ChPid, #consumer{tag = CTag}}) -> + lists:member(CTag, Unblock) + end, Blocked), + %% TODO can this whole thing be replaced by possibly_unblock? + %% TODO that is_limit_active = false thing is wrong - but we do + %% not allow for per-consumer blocking! + update_ch_record(C#cr{limiter = Limiter2, blocked_consumers = NewBlocked, + is_limit_active = false}), + AC1 = queue:join(NewUnblocked, AC), + noreply(run_message_queue(State#q{active_consumers = AC1})); handle_cast(wake_up, State) -> noreply(State). diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 6d00fdb2..c3a5b16d 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1082,9 +1082,8 @@ handle_method(#'channel.flow'{active = false}, _, handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, count = Count, - drain = Drain} = M, _, + drain = Drain}, _, State = #ch{consumer_mapping = Consumers}) -> - %%io:format(" ~p~n", [M]), case dict:find(CTag, Consumers) of {ok, Q} -> ok = rabbit_amqqueue:inform_limiter( self(), Q#amqqueue.pid, diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 6e3a228b..f031db51 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -60,7 +60,7 @@ %% -spec(set_credit/5 :: (token(), rabbit_types:ctag(), %% non_neg_integer(), %% non_neg_integer(), boolean()) -> 'ok'). --spec(inform/4 :: (token(), pid(), non_neg_integer(), any()) -> token()). +%%-spec(inform/4 :: (token(), pid(), non_neg_integer(), any()) -> token()). -endif. %%---------------------------------------------------------------------------- @@ -135,8 +135,9 @@ is_blocked(Limiter) -> inform(Limiter = #token{q_state = Credits}, ChPid, Len, {basic_credit, CTag, Credit, Count, Drain}) -> - Credits2 = reset_credit(CTag, Len, ChPid, Credit, Count, Drain, Credits), - Limiter#token{q_state = Credits2}. + {Unblock, Credits2} = + update_credit(CTag, Len, ChPid, Credit, Count, Drain, Credits), + {Unblock, Limiter#token{q_state = Credits2}}. %%---------------------------------------------------------------------------- %% Queue-local code @@ -150,26 +151,26 @@ inform(Limiter = #token{q_state = Credits}, can_send_q(CTag, Len, ChPid, Credits) -> case dict:find(CTag, Credits) of - {ok, #credit{credit = 0}} -> exit(bang), {false, Credits}; - {ok, Cred} -> Credits2 = - decr_credit( - CTag, Len, ChPid, Cred, Credits), - {true, Credits2}; - _ -> {true, Credits} + {ok, #credit{credit = C} = Cred} -> + if C > 0 -> Credits2 = decr_credit(CTag, Len, ChPid, Cred, Credits), + {true, Credits2}; + true -> {false, Credits} + end; + _ -> + {true, Credits} end. decr_credit(CTag, Len, ChPid, Cred, Credits) -> #credit{credit = Credit, count = Count, drain = Drain} = Cred, {NewCredit, NewCount} = - case {Credit, Len, Drain} of - {1, _, _} -> {0, serial_add(Count, 1)}; - {_, 1, true} -> %% Drain, so advance til credit = 0 - NewCount0 = serial_add(Count, (Credit - 1)), - send_drained(ChPid, CTag, NewCount0), - {0, NewCount0}; %% Magic reduction to 0 - {_, _, _} -> {Credit - 1, serial_add(Count, 1)} + case {Len, Drain} of + {1, true} -> %% Drain, so advance til credit = 0 + NewCount0 = serial_add(Count, (Credit - 1)), + send_drained(ChPid, CTag, NewCount0), + {0, NewCount0}; %% Magic reduction to 0 + {_, _} -> {Credit - 1, serial_add(Count, 1)} end, - update_credit(CTag, NewCredit, NewCount, Drain, Credits). + write_credit(CTag, NewCredit, NewCount, Drain, Credits). send_drained(ChPid, CTag, Count) -> rabbit_channel:send_command(ChPid, @@ -179,29 +180,27 @@ send_drained(ChPid, CTag, Count) -> available = 0, drain = true}). -%% Assert the credit state. The count may not match ours, in which -%% case we must rebase the credit. +%% Update the credit state. %% TODO Edge case: if the queue has nothing in it, and drain is set, %% we want to send a basic.credit back. -reset_credit(CTag, Len, ChPid, Credit0, Count0, Drain, Credits) -> +update_credit(CTag, Len, ChPid, Credit, Count0, Drain, Credits) -> Count = case dict:find(CTag, Credits) of - {ok, #credit{ count = LocalCount }} -> - LocalCount; - _ -> Count0 + %% Use our count if we can, more accurate + {ok, #credit{ count = LocalCount }} -> LocalCount; + %% But if this is new, take it from the adapter + _ -> Count0 end, - %% Our credit may have been reduced while messages are in flight, - %% so we bottom out at 0. - Credit = erlang:max(0, serial_diff(serial_add(Count0, Credit0), Count)), - rabbit_channel:send_command(ChPid, - #'basic.credit_ok'{available = Len}), - update_credit(CTag, Credit, Count, Drain, Credits). - -%% Store the credit -update_credit(CTag, -1, _Count, _Drain, Credits) -> - dict:erase(CTag, Credits); + rabbit_channel:send_command(ChPid, #'basic.credit_ok'{available = Len}), + NewCredits = write_credit(CTag, Credit, Count, Drain, Credits), + case Credit > 0 of + true -> {[CTag], NewCredits}; + false -> {[], NewCredits} + end. -update_credit(CTag, Credit, Count, Drain, Credits) -> +%% TODO currently we leak when a single session creates and destroys +%% lot of links. +write_credit(CTag, Credit, Count, Drain, Credits) -> dict:store(CTag, #credit{credit = Credit, count = Count, drain = Drain}, Credits). -- cgit v1.2.1 From 05d71de3832a386a0b843570a787e7b53e719088 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 11 Jan 2013 19:39:34 +0000 Subject: implement vq:fold in terms of an iterator --- src/rabbit_variable_queue.erl | 100 ++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 90ee3439..427bd03c 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -677,25 +677,7 @@ ackfold(MsgFun, Acc, State, AckTags) -> end, {Acc, State}, AckTags), {AccN, a(StateN)}. -fold(Fun, Acc, #vqstate { q1 = Q1, - q2 = Q2, - delta = #delta { start_seq_id = DeltaSeqId, - end_seq_id = DeltaSeqIdEnd }, - q3 = Q3, - q4 = Q4 } = State) -> - QFun = fun(MsgStatus, {Acc0, State0}) -> - {Msg, State1} = read_msg(MsgStatus, false, State0), - {StopGo, AccNext} = - Fun(Msg, MsgStatus#msg_status.msg_props, Acc0), - {StopGo, {AccNext, State1}} - end, - {Cont1, {Acc1, State1}} = qfoldl(QFun, {cont, {Acc, State }}, Q4), - {Cont2, {Acc2, State2}} = qfoldl(QFun, {Cont1, {Acc1, State1}}, Q3), - {Cont3, {Acc3, State3}} = delta_fold(Fun, {Cont2, Acc2}, - DeltaSeqId, DeltaSeqIdEnd, State2), - {Cont4, {Acc4, State4}} = qfoldl(QFun, {Cont3, {Acc3, State3}}, Q2), - {_, {Acc5, State5}} = qfoldl(QFun, {Cont4, {Acc4, State4}}, Q1), - {Acc5, State5}. +fold(Fun, Acc, State) -> ifold(Fun, Acc, iterator(State)). len(#vqstate { len = Len }) -> Len. @@ -1386,7 +1368,7 @@ msg_indices_written_to_disk(Callback, MsgIdSet) -> end). %%---------------------------------------------------------------------------- -%% Internal plumbing for requeue and fold +%% Internal plumbing for requeue %%---------------------------------------------------------------------------- publish_alpha(#msg_status { msg = undefined } = MsgStatus, State) -> @@ -1456,40 +1438,62 @@ beta_limit(Q) -> delta_limit(?BLANK_DELTA_PATTERN(_X)) -> undefined; delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. -qfoldl(_Fun, {stop, _Acc} = A, _Q) -> A; -qfoldl( Fun, {cont, Acc} = A, Q) -> - case ?QUEUE:out(Q) of - {empty, _Q} -> A; - {{value, V}, Q1} -> qfoldl(Fun, Fun(V, Acc), Q1) - end. +%%---------------------------------------------------------------------------- +%% Iterator +%%---------------------------------------------------------------------------- -lfoldl(_Fun, {stop, _Acc} = A, _L) -> A; -lfoldl(_Fun, {cont, _Acc} = A, []) -> A; -lfoldl( Fun, {cont, Acc}, [H | T]) -> lfoldl(Fun, Fun(H, Acc), T). - -delta_fold(_Fun, {stop, Acc}, _DeltaSeqId, _DeltaSeqIdEnd, State) -> - {stop, {Acc, State}}; -delta_fold(_Fun, {cont, Acc}, DeltaSeqIdEnd, DeltaSeqIdEnd, State) -> - {cont, {Acc, State}}; -delta_fold( Fun, {cont, Acc}, DeltaSeqId, DeltaSeqIdEnd, - #vqstate { index_state = IndexState, - msg_store_clients = MSCState } = State) -> +iterator(State = #vqstate{q4 = Q4}) -> {q4, Q4, State}. + +next({q4, _, State} = It) -> next(It, q3, State#vqstate.q3); +next({q3, _, State} = It) -> next(It, delta, State#vqstate.delta); +next({delta, _, State} = It) -> next(It, q2, State#vqstate.q2); +next({q2, _, State} = It) -> next(It, q1, State#vqstate.q1); +next({q1, _, State} = It) -> next(It, done, State); +next({done, _, State}) -> {empty, State}. + +next({delta, #delta{start_seq_id = DeltaSeqId, end_seq_id = DeltaSeqId}, State}, + NextKey, Next) -> + next({NextKey, Next, State}); +next({delta, Delta = #delta{start_seq_id = DeltaSeqId, + end_seq_id = DeltaSeqIdEnd}, + State = #vqstate{index_state = IndexState}}, NextKey, Next) -> DeltaSeqId1 = lists:min( [rabbit_queue_index:next_segment_boundary(DeltaSeqId), DeltaSeqIdEnd]), {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, IndexState), - {StopCont, {Acc1, MSCState1}} = - lfoldl(fun ({MsgId, _SeqId, MsgProps, IsPersistent, _IsDelivered}, - {Acc0, MSCState0}) -> - {{ok, Msg = #basic_message {}}, MSCState1} = - msg_store_read(MSCState0, IsPersistent, MsgId), - {StopCont, AccNext} = Fun(Msg, MsgProps, Acc0), - {StopCont, {AccNext, MSCState1}} - end, {cont, {Acc, MSCState}}, List), - delta_fold(Fun, {StopCont, Acc1}, DeltaSeqId1, DeltaSeqIdEnd, - State #vqstate { index_state = IndexState1, - msg_store_clients = MSCState1 }). + next({delta, {Delta#delta{start_seq_id = DeltaSeqId1}, List}, + State#vqstate{index_state = IndexState1}}, NextKey, Next); +next({delta, {Delta, []}, State}, NextKey, Next) -> + next({delta, Delta, State}, NextKey, Next); +next({delta, {Delta, [M | Rest]}, + State = #vqstate{msg_store_clients = MSCState}}, _NextKey, _Next) -> + {MsgId, _SeqId, MsgProps, IsPersistent, _IsDelivered} = M, + {{ok, Msg = #basic_message {}}, MSCState1} = + msg_store_read(MSCState, IsPersistent, MsgId), + State1 = State#vqstate{msg_store_clients = MSCState1}, + {value, Msg, MsgProps, {delta, {Delta, Rest}, State1}}; +next({Key, Q, State}, NextKey, Next) -> + case ?QUEUE:out(Q) of + {empty, _Q} -> + next({NextKey, Next, State}); + {{value, MsgStatus}, QN} -> + {Msg, State1} = read_msg(MsgStatus, false, State), + {value, Msg, MsgStatus#msg_status.msg_props, {Key, QN, State1}} + end. + +done({_, _, State}) -> State. + +ifold(Fun, Acc, It) -> + case next(It) of + {value, Msg, MsgProps, Next} -> + case Fun(Msg, MsgProps, Acc) of + {stop, Acc1} -> {Acc1, done(Next)}; + {cont, Acc1} -> ifold(Fun, Acc1, Next) + end; + {empty, Done} -> + {Acc, Done} + end. %%---------------------------------------------------------------------------- %% Phase changes -- cgit v1.2.1 From 0c84e4f85fa92a384646af16ec9087696c65c139 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 12 Jan 2013 10:18:28 +0000 Subject: unmodalise vq:read_msg --- src/rabbit_variable_queue.erl | 54 +++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 90ee3439..285dfcd7 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -527,7 +527,6 @@ publish(Msg = #basic_message { is_persistent = IsPersistent, id = MsgId }, in_counter = InCount, persistent_count = PCount, durable = IsDurable, - ram_msg_count = RamMsgCount, unconfirmed = UC }) -> IsPersistent1 = IsDurable andalso IsPersistent, MsgStatus = msg_status(IsPersistent1, IsDelivered, SeqId, Msg, MsgProps), @@ -538,12 +537,12 @@ publish(Msg = #basic_message { is_persistent = IsPersistent, id = MsgId }, end, PCount1 = PCount + one_if(IsPersistent1), UC1 = gb_sets_maybe_insert(NeedsConfirming, MsgId, UC), - a(reduce_memory_use(State2 #vqstate { next_seq_id = SeqId + 1, - len = Len + 1, - in_counter = InCount + 1, - persistent_count = PCount1, - ram_msg_count = RamMsgCount + 1, - unconfirmed = UC1 })). + a(reduce_memory_use( + inc_ram_msg_count(State2 #vqstate { next_seq_id = SeqId + 1, + len = Len + 1, + in_counter = InCount + 1, + persistent_count = PCount1, + unconfirmed = UC1 }))). publish_delivered(Msg = #basic_message { is_persistent = IsPersistent, id = MsgId }, @@ -596,7 +595,7 @@ fetchwhile(Pred, Fun, Acc, State) -> {undefined, Acc, a(State1)}; {{value, MsgStatus = #msg_status { msg_props = MsgProps }}, State1} -> case Pred(MsgProps) of - true -> {Msg, State2} = read_msg(MsgStatus, false, State1), + true -> {Msg, State2} = read_msg(MsgStatus, State1), {AckTag, State3} = remove(true, MsgStatus, State2), fetchwhile(Pred, Fun, Fun(Msg, AckTag, Acc), State3); false -> {MsgProps, Acc, a(in_r(MsgStatus, State1))} @@ -610,7 +609,7 @@ fetch(AckRequired, State) -> {{value, MsgStatus}, State1} -> %% it is possible that the message wasn't read from disk %% at this point, so read it in. - {Msg, State2} = read_msg(MsgStatus, false, State1), + {Msg, State2} = read_msg(MsgStatus, State1), {AckTag, State3} = remove(AckRequired, MsgStatus, State2), {{Msg, MsgStatus#msg_status.is_delivered, AckTag}, a(State3)} end. @@ -672,7 +671,7 @@ ackfold(MsgFun, Acc, State, AckTags) -> {AccN, StateN} = lists:foldl(fun(SeqId, {Acc0, State0}) -> MsgStatus = lookup_pending_ack(SeqId, State0), - {Msg, State1} = read_msg(MsgStatus, false, State0), + {Msg, State1} = read_msg(MsgStatus, State0), {MsgFun(Msg, SeqId, Acc0), State1} end, {Acc, State}, AckTags), {AccN, a(StateN)}. @@ -684,7 +683,7 @@ fold(Fun, Acc, #vqstate { q1 = Q1, q3 = Q3, q4 = Q4 } = State) -> QFun = fun(MsgStatus, {Acc0, State0}) -> - {Msg, State1} = read_msg(MsgStatus, false, State0), + {Msg, State1} = read_msg(MsgStatus, State0), {StopGo, AccNext} = Fun(Msg, MsgStatus#msg_status.msg_props, Acc0), {StopGo, {AccNext, State1}} @@ -1078,9 +1077,10 @@ in_r(MsgStatus = #msg_status { msg = undefined }, case ?QUEUE:is_empty(Q4) of true -> State #vqstate { q3 = ?QUEUE:in_r(MsgStatus, Q3) }; false -> {Msg, State1 = #vqstate { q4 = Q4a }} = - read_msg(MsgStatus, true, State), - State1 #vqstate { q4 = ?QUEUE:in_r(MsgStatus#msg_status { - msg = Msg }, Q4a) } + read_msg(MsgStatus, State), + inc_ram_msg_count( + State1 #vqstate { q4 = ?QUEUE:in_r(MsgStatus#msg_status { + msg = Msg }, Q4a) }) end; in_r(MsgStatus, State = #vqstate { q4 = Q4 }) -> State #vqstate { q4 = ?QUEUE:in_r(MsgStatus, Q4) }. @@ -1096,19 +1096,19 @@ queue_out(State = #vqstate { q4 = Q4 }) -> {{value, MsgStatus}, State #vqstate { q4 = Q4a }} end. -read_msg(#msg_status { msg = undefined, - msg_id = MsgId, - is_persistent = IsPersistent }, - CountDiskToRam, State = #vqstate { ram_msg_count = RamMsgCount, - msg_store_clients = MSCState}) -> +read_msg(#msg_status{msg = undefined, + msg_id = MsgId, + is_persistent = IsPersistent}, + State = #vqstate{msg_store_clients = MSCState}) -> {{ok, Msg = #basic_message {}}, MSCState1} = msg_store_read(MSCState, IsPersistent, MsgId), - RamMsgCount1 = RamMsgCount + one_if(CountDiskToRam), - {Msg, State #vqstate { ram_msg_count = RamMsgCount1, - msg_store_clients = MSCState1 }}; -read_msg(#msg_status { msg = Msg }, _CountDiskToRam, State) -> + {Msg, State #vqstate {msg_store_clients = MSCState1}}; +read_msg(#msg_status{msg = Msg}, State) -> {Msg, State}. +inc_ram_msg_count(State = #vqstate{ram_msg_count = RamMsgCount}) -> + State#vqstate{ram_msg_count = RamMsgCount + 1}. + remove(AckRequired, MsgStatus = #msg_status { seq_id = SeqId, msg_id = MsgId, @@ -1390,10 +1390,10 @@ msg_indices_written_to_disk(Callback, MsgIdSet) -> %%---------------------------------------------------------------------------- publish_alpha(#msg_status { msg = undefined } = MsgStatus, State) -> - {Msg, State1} = read_msg(MsgStatus, true, State), - {MsgStatus#msg_status { msg = Msg }, State1}; -publish_alpha(MsgStatus, #vqstate {ram_msg_count = RamMsgCount } = State) -> - {MsgStatus, State #vqstate { ram_msg_count = RamMsgCount + 1 }}. + {Msg, State1} = read_msg(MsgStatus, State), + {MsgStatus#msg_status { msg = Msg }, inc_ram_msg_count(State1)}; +publish_alpha(MsgStatus, State) -> + {MsgStatus, inc_ram_msg_count(State)}. publish_beta(MsgStatus, State) -> {#msg_status { msg = Msg} = MsgStatus1, -- cgit v1.2.1 From 6a5738c6025ee65865d1fc358758fb01b6be82e4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 12 Jan 2013 10:18:45 +0000 Subject: cosmetic --- src/rabbit_variable_queue.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 285dfcd7..5dc46f1b 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1122,7 +1122,7 @@ remove(AckRequired, MsgStatus = #msg_status { index_state = IndexState, msg_store_clients = MSCState, len = Len, - persistent_count = PCount }) -> + persistent_count = PCount}) -> %% 1. Mark it delivered if necessary IndexState1 = maybe_write_delivered( IndexOnDisk andalso not IsDelivered, @@ -1151,11 +1151,11 @@ remove(AckRequired, MsgStatus = #msg_status { PCount1 = PCount - one_if(IsPersistent andalso not AckRequired), RamMsgCount1 = RamMsgCount - one_if(Msg =/= undefined), - {AckTag, State1 #vqstate { ram_msg_count = RamMsgCount1, - out_counter = OutCount + 1, - index_state = IndexState2, - len = Len - 1, - persistent_count = PCount1 }}. + {AckTag, State1 #vqstate {ram_msg_count = RamMsgCount1, + out_counter = OutCount + 1, + index_state = IndexState2, + len = Len - 1, + persistent_count = PCount1}}. purge_betas_and_deltas(LensByStore, State = #vqstate { q3 = Q3, -- cgit v1.2.1 From 02f75e388797b9efb9b1535667e60904f72ab9ed Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 12 Jan 2013 11:35:24 +0000 Subject: pass State to iterator We want to be able to zip this iterator with other iterators that also manipulate the vqstate. Hence we must pass the State explicitly rather than keeping it opaque inside the iterator state. Also, some refactoring on read_msg. --- src/rabbit_variable_queue.erl | 94 ++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 51 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index ac6a50af..8cb5da0b 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -676,7 +676,7 @@ ackfold(MsgFun, Acc, State, AckTags) -> end, {Acc, State}, AckTags), {AccN, a(StateN)}. -fold(Fun, Acc, State) -> ifold(Fun, Acc, iterator(State)). +fold(Fun, Acc, State) -> ifold(Fun, Acc, iterator(State), State). len(#vqstate { len = Len }) -> Len. @@ -1080,14 +1080,16 @@ queue_out(State = #vqstate { q4 = Q4 }) -> read_msg(#msg_status{msg = undefined, msg_id = MsgId, - is_persistent = IsPersistent}, - State = #vqstate{msg_store_clients = MSCState}) -> - {{ok, Msg = #basic_message {}}, MSCState1} = - msg_store_read(MSCState, IsPersistent, MsgId), - {Msg, State #vqstate {msg_store_clients = MSCState1}}; + is_persistent = IsPersistent}, State) -> + read_msg(MsgId, IsPersistent, State); read_msg(#msg_status{msg = Msg}, State) -> {Msg, State}. +read_msg(MsgId, IsPersistent, State = #vqstate{msg_store_clients = MSCState}) -> + {{ok, Msg = #basic_message {}}, MSCState1} = + msg_store_read(MSCState, IsPersistent, MsgId), + {Msg, State #vqstate {msg_store_clients = MSCState1}}. + inc_ram_msg_count(State = #vqstate{ram_msg_count = RamMsgCount}) -> State#vqstate{ram_msg_count = RamMsgCount + 1}. @@ -1442,57 +1444,47 @@ delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. %% Iterator %%---------------------------------------------------------------------------- -iterator(State = #vqstate{q4 = Q4}) -> {q4, Q4, State}. - -next({q4, _, State} = It) -> next(It, q3, State#vqstate.q3); -next({q3, _, State} = It) -> next(It, delta, State#vqstate.delta); -next({delta, _, State} = It) -> next(It, q2, State#vqstate.q2); -next({q2, _, State} = It) -> next(It, q1, State#vqstate.q1); -next({q1, _, State} = It) -> next(It, done, State); -next({done, _, State}) -> {empty, State}. - -next({delta, #delta{start_seq_id = DeltaSeqId, end_seq_id = DeltaSeqId}, State}, - NextKey, Next) -> - next({NextKey, Next, State}); -next({delta, Delta = #delta{start_seq_id = DeltaSeqId, - end_seq_id = DeltaSeqIdEnd}, - State = #vqstate{index_state = IndexState}}, NextKey, Next) -> - DeltaSeqId1 = lists:min( - [rabbit_queue_index:next_segment_boundary(DeltaSeqId), - DeltaSeqIdEnd]), - {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, - IndexState), - next({delta, {Delta#delta{start_seq_id = DeltaSeqId1}, List}, - State#vqstate{index_state = IndexState1}}, NextKey, Next); -next({delta, {Delta, []}, State}, NextKey, Next) -> - next({delta, Delta, State}, NextKey, Next); -next({delta, {Delta, [M | Rest]}, - State = #vqstate{msg_store_clients = MSCState}}, _NextKey, _Next) -> +iterator(State) -> istate(start, State). + +istate(start, State) -> {q4, State#vqstate.q4}; +istate(q4, State) -> {q3, State#vqstate.q3}; +istate(q3, State) -> {delta, State#vqstate.delta}; +istate(delta, State) -> {q2, State#vqstate.q2}; +istate(q2, State) -> {q1, State#vqstate.q1}; +istate(q1, _State) -> done. + +next(done, State) -> {empty, State}; +next({delta, #delta{start_seq_id = SeqId, end_seq_id = SeqId}}, State) -> + next(istate(delta, State), State); +next({delta, Delta = #delta{start_seq_id = SeqId, end_seq_id = SeqIdEnd}}, + State = #vqstate{index_state = IndexState}) -> + SeqIdB = rabbit_queue_index:next_segment_boundary(SeqId), + SeqId1 = lists:min([SeqIdB, SeqIdEnd]), + {List, IndexState1} = rabbit_queue_index:read(SeqId, SeqId1, IndexState), + next({delta, Delta#delta{start_seq_id = SeqId1}, List}, + State#vqstate{index_state = IndexState1}); +next({delta, Delta, []}, State) -> next({delta, Delta}, State); +next({delta, Delta, [M | Rest]}, State) -> {MsgId, _SeqId, MsgProps, IsPersistent, _IsDelivered} = M, - {{ok, Msg = #basic_message {}}, MSCState1} = - msg_store_read(MSCState, IsPersistent, MsgId), - State1 = State#vqstate{msg_store_clients = MSCState1}, - {value, Msg, MsgProps, {delta, {Delta, Rest}, State1}}; -next({Key, Q, State}, NextKey, Next) -> + {Msg, State1} = read_msg(MsgId, IsPersistent, State), + {value, Msg, MsgProps, {delta, Delta, Rest}, State1}; +next({Key, Q}, State) -> case ?QUEUE:out(Q) of - {empty, _Q} -> - next({NextKey, Next, State}); - {{value, MsgStatus}, QN} -> - {Msg, State1} = read_msg(MsgStatus, State), - {value, Msg, MsgStatus#msg_status.msg_props, {Key, QN, State1}} + {empty, _Q} -> next(istate(Key, State), State); + {{value, MsgStatus}, QN} -> {Msg, State1} = read_msg(MsgStatus, State), + MsgProps = MsgStatus#msg_status.msg_props, + {value, Msg, MsgProps, {Key, QN}, State1} end. -done({_, _, State}) -> State. - -ifold(Fun, Acc, It) -> - case next(It) of - {value, Msg, MsgProps, Next} -> +ifold(Fun, Acc, It, State) -> + case next(It, State) of + {value, Msg, MsgProps, Next, State1} -> case Fun(Msg, MsgProps, Acc) of - {stop, Acc1} -> {Acc1, done(Next)}; - {cont, Acc1} -> ifold(Fun, Acc1, Next) + {stop, Acc1} -> {Acc1, State1}; + {cont, Acc1} -> ifold(Fun, Acc1, Next, State1) end; - {empty, Done} -> - {Acc, Done} + {empty, State1} -> + {Acc, State1} end. %%---------------------------------------------------------------------------- -- cgit v1.2.1 From 58872acdc0cc36fa2c47a9559fd087edd581ce15 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 12 Jan 2013 16:15:06 +0000 Subject: extract a vq helper fun for constructing a msg_status --- src/rabbit_variable_queue.erl | 45 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 5dc46f1b..dc32902f 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -897,11 +897,25 @@ gb_sets_maybe_insert(false, _Val, Set) -> Set; gb_sets_maybe_insert(true, Val, Set) -> gb_sets:add(Val, Set). msg_status(IsPersistent, IsDelivered, SeqId, - Msg = #basic_message { id = MsgId }, MsgProps) -> - #msg_status { seq_id = SeqId, msg_id = MsgId, msg = Msg, - is_persistent = IsPersistent, is_delivered = IsDelivered, - msg_on_disk = false, index_on_disk = false, - msg_props = MsgProps }. + Msg = #basic_message {id = MsgId}, MsgProps) -> + #msg_status{seq_id = SeqId, + msg_id = MsgId, + msg = Msg, + is_persistent = IsPersistent, + is_delivered = IsDelivered, + msg_on_disk = false, + index_on_disk = false, + msg_props = MsgProps}. + +beta_msg_status({MsgId, SeqId, MsgProps, IsPersistent, IsDelivered}) -> + #msg_status{seq_id = SeqId, + msg_id = MsgId, + msg = undefined, + is_persistent = IsPersistent, + is_delivered = IsDelivered, + msg_on_disk = true, + index_on_disk = true, + msg_props = MsgProps}. trim_msg_status(MsgStatus) -> MsgStatus #msg_status { msg = undefined }. @@ -968,7 +982,7 @@ maybe_write_delivered(true, SeqId, IndexState) -> betas_from_index_entries(List, TransientThreshold, RPA, DPA, IndexState) -> {Filtered, Delivers, Acks} = lists:foldr( - fun ({MsgId, SeqId, MsgProps, IsPersistent, IsDelivered}, + fun ({_MsgId, SeqId, _MsgProps, IsPersistent, IsDelivered} = M, {Filtered1, Delivers1, Acks1} = Acc) -> case SeqId < TransientThreshold andalso not IsPersistent of true -> {Filtered1, @@ -976,21 +990,10 @@ betas_from_index_entries(List, TransientThreshold, RPA, DPA, IndexState) -> [SeqId | Acks1]}; false -> case (gb_trees:is_defined(SeqId, RPA) orelse gb_trees:is_defined(SeqId, DPA)) of - false -> - {?QUEUE:in_r( - m(#msg_status { - seq_id = SeqId, - msg_id = MsgId, - msg = undefined, - is_persistent = IsPersistent, - is_delivered = IsDelivered, - msg_on_disk = true, - index_on_disk = true, - msg_props = MsgProps - }), Filtered1), - Delivers1, Acks1}; - true -> - Acc + false -> {?QUEUE:in_r(m(beta_msg_status(M)), + Filtered1), + Delivers1, Acks1}; + true -> Acc end end end, {?QUEUE:new(), [], []}, List), -- cgit v1.2.1 From 3f887c3ac7bb0c4f94bae9860b157906ed40a950 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 12 Jan 2013 16:18:48 +0000 Subject: return MsgStatus only from iterator leaving the message reading to the fold --- src/rabbit_variable_queue.erl | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index baeb4721..347964f4 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1468,23 +1468,20 @@ next({delta, Delta = #delta{start_seq_id = SeqId, end_seq_id = SeqIdEnd}}, State#vqstate{index_state = IndexState1}); next({delta, Delta, []}, State) -> next({delta, Delta}, State); next({delta, Delta, [M | Rest]}, State) -> - {MsgId, _SeqId, MsgProps, IsPersistent, _IsDelivered} = M, - {Msg, State1} = read_msg(MsgId, IsPersistent, State), - {value, Msg, MsgProps, {delta, Delta, Rest}, State1}; + {value, beta_msg_status(M), {delta, Delta, Rest}, State}; next({Key, Q}, State) -> case ?QUEUE:out(Q) of {empty, _Q} -> next(istate(Key, State), State); - {{value, MsgStatus}, QN} -> {Msg, State1} = read_msg(MsgStatus, State), - MsgProps = MsgStatus#msg_status.msg_props, - {value, Msg, MsgProps, {Key, QN}, State1} + {{value, MsgStatus}, QN} -> {value, MsgStatus, {Key, QN}, State} end. ifold(Fun, Acc, It, State) -> case next(It, State) of - {value, Msg, MsgProps, Next, State1} -> - case Fun(Msg, MsgProps, Acc) of - {stop, Acc1} -> {Acc1, State1}; - {cont, Acc1} -> ifold(Fun, Acc1, Next, State1) + {value, MsgStatus, Next, State1} -> + {Msg, State2} = read_msg(MsgStatus, State1), + case Fun(Msg, MsgStatus#msg_status.msg_props, Acc) of + {stop, Acc1} -> {Acc1, State2}; + {cont, Acc1} -> ifold(Fun, Acc1, Next, State2) end; {empty, State1} -> {Acc, State1} -- cgit v1.2.1 From 304ee315b59815ec5e20bcb8b25d31f2e6b18b84 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 12 Jan 2013 23:41:19 +0000 Subject: only pass the minimum necessary state to 'next' i.e. the IndexState. The remainder of the State is encapsulated inside the iterator state. Technically we only need q{1-4} and delta, but it's simpler and more obvious to just pass a read-only State around. --- src/rabbit_variable_queue.erl | 49 +++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 347964f4..dcaaa5ed 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1449,42 +1449,45 @@ delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. iterator(State) -> istate(start, State). -istate(start, State) -> {q4, State#vqstate.q4}; -istate(q4, State) -> {q3, State#vqstate.q3}; -istate(q3, State) -> {delta, State#vqstate.delta}; -istate(delta, State) -> {q2, State#vqstate.q2}; -istate(q2, State) -> {q1, State#vqstate.q1}; +istate(start, State) -> {q4, State#vqstate.q4, State}; +istate(q4, State) -> {q3, State#vqstate.q3, State}; +istate(q3, State) -> {delta, State#vqstate.delta, State}; +istate(delta, State) -> {q2, State#vqstate.q2, State}; +istate(q2, State) -> {q1, State#vqstate.q1, State}; istate(q1, _State) -> done. -next(done, State) -> {empty, State}; -next({delta, #delta{start_seq_id = SeqId, end_seq_id = SeqId}}, State) -> - next(istate(delta, State), State); -next({delta, Delta = #delta{start_seq_id = SeqId, end_seq_id = SeqIdEnd}}, - State = #vqstate{index_state = IndexState}) -> +next(done, IndexState) -> {empty, IndexState}; +next({delta, #delta{start_seq_id = SeqId, + end_seq_id = SeqId}, State}, IndexState) -> + next(istate(delta, State), IndexState); +next({delta, #delta{start_seq_id = SeqId, + end_seq_id = SeqIdEnd} = Delta, State}, IndexState) -> SeqIdB = rabbit_queue_index:next_segment_boundary(SeqId), SeqId1 = lists:min([SeqIdB, SeqIdEnd]), {List, IndexState1} = rabbit_queue_index:read(SeqId, SeqId1, IndexState), - next({delta, Delta#delta{start_seq_id = SeqId1}, List}, - State#vqstate{index_state = IndexState1}); -next({delta, Delta, []}, State) -> next({delta, Delta}, State); -next({delta, Delta, [M | Rest]}, State) -> - {value, beta_msg_status(M), {delta, Delta, Rest}, State}; -next({Key, Q}, State) -> + next({delta, Delta#delta{start_seq_id = SeqId1}, List, State}, IndexState1); +next({delta, Delta, [], State}, IndexState) -> + next({delta, Delta, State}, IndexState); +next({delta, Delta, [M | Rest], State}, IndexState) -> + {value, beta_msg_status(M), {delta, Delta, Rest, State}, IndexState}; +next({Key, Q, State}, IndexState) -> case ?QUEUE:out(Q) of - {empty, _Q} -> next(istate(Key, State), State); - {{value, MsgStatus}, QN} -> {value, MsgStatus, {Key, QN}, State} + {empty, _Q} -> next(istate(Key, State), IndexState); + {{value, MsgStatus}, QN} -> {value, MsgStatus, {Key, QN, State}, + IndexState} end. -ifold(Fun, Acc, It, State) -> - case next(It, State) of - {value, MsgStatus, Next, State1} -> +ifold(Fun, Acc, It, State = #vqstate{index_state = IndexState}) -> + case next(It, IndexState) of + {value, MsgStatus, Next, IndexState1} -> + State1 = State#vqstate{index_state = IndexState1}, {Msg, State2} = read_msg(MsgStatus, State1), case Fun(Msg, MsgStatus#msg_status.msg_props, Acc) of {stop, Acc1} -> {Acc1, State2}; {cont, Acc1} -> ifold(Fun, Acc1, Next, State2) end; - {empty, State1} -> - {Acc, State1} + {empty, IndexState1} -> + {Acc, State#vqstate{index_state = IndexState1}} end. %%---------------------------------------------------------------------------- -- cgit v1.2.1 From b9616756ea1fdd4dbb8a51376dd3aa531d7b4b70 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 12 Jan 2013 23:46:19 +0000 Subject: oops; nuke unused var --- src/gen_server2.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index dc55948b..20de79c2 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -863,7 +863,7 @@ dispatch(Info, Mod, State) -> common_reply(_Name, From, Reply, _NState, [] = _Debug) -> reply(From, Reply), []; -common_reply(Name, {To, Tag} = From, Reply, NState, Debug) -> +common_reply(Name, {To, _Tag} = From, Reply, NState, Debug) -> reply(From, Reply), sys:handle_debug(Debug, fun print_event/3, Name, {out, Reply, To, NState}). -- cgit v1.2.1 From f55da0d65a5851e4249ac0afee70ff200bff8aeb Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 13 Jan 2013 10:21:13 +0000 Subject: cosmetic --- src/rabbit_variable_queue.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index dcaaa5ed..d3147999 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1473,8 +1473,8 @@ next({delta, Delta, [M | Rest], State}, IndexState) -> next({Key, Q, State}, IndexState) -> case ?QUEUE:out(Q) of {empty, _Q} -> next(istate(Key, State), IndexState); - {{value, MsgStatus}, QN} -> {value, MsgStatus, {Key, QN, State}, - IndexState} + {{value, MsgStatus}, QN} -> Next = {Key, QN, State}, + {value, MsgStatus, Next, IndexState} end. ifold(Fun, Acc, It, State = #vqstate{index_state = IndexState}) -> -- cgit v1.2.1 From 7141d5bfc377e29d6c6b8f69762b6bb865a6b414 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 13 Jan 2013 10:49:08 +0000 Subject: include pending_acks in 'fold' we implement this as a zipper over three iterators --- src/rabbit_variable_queue.erl | 59 +++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index d3147999..185da19c 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -676,7 +676,19 @@ ackfold(MsgFun, Acc, State, AckTags) -> end, {Acc, State}, AckTags), {AccN, a(StateN)}. -fold(Fun, Acc, State) -> ifold(Fun, Acc, iterator(State), State). +fold(Fun, Acc, State = #vqstate{index_state = IndexState}) -> + {Its, IndexState1} = + lists:foldl(fun (It, {Its, IndexState2}) -> + case next(It, IndexState2) of + {empty, IndexState3} -> + {Its, IndexState3}; + {value, MsgStatus, It1, IndexState3} -> + {[{MsgStatus, It1} | Its], IndexState3} + end + end, {[], IndexState}, [msg_iterator(State), + disk_ack_iterator(State), + ram_ack_iterator(State)]), + ifold(Fun, Acc, Its, State#vqstate{index_state = IndexState1}). len(#vqstate { len = Len }) -> Len. @@ -1447,7 +1459,13 @@ delta_limit(#delta { start_seq_id = StartSeqId }) -> StartSeqId. %% Iterator %%---------------------------------------------------------------------------- -iterator(State) -> istate(start, State). +ram_ack_iterator(State) -> + {ack, gb_trees:iterator(State#vqstate.ram_pending_ack)}. + +disk_ack_iterator(State) -> + {ack, gb_trees:iterator(State#vqstate.disk_pending_ack)}. + +msg_iterator(State) -> istate(start, State). istate(start, State) -> {q4, State#vqstate.q4, State}; istate(q4, State) -> {q3, State#vqstate.q3, State}; @@ -1456,6 +1474,11 @@ istate(delta, State) -> {q2, State#vqstate.q2, State}; istate(q2, State) -> {q1, State#vqstate.q1, State}; istate(q1, _State) -> done. +next({ack, It}, IndexState) -> + case gb_trees:next(It) of + none -> {empty, IndexState}; + {_SeqId, MsgStatus, It1} -> {value, MsgStatus, {ack, It1}, IndexState} + end; next(done, IndexState) -> {empty, IndexState}; next({delta, #delta{start_seq_id = SeqId, end_seq_id = SeqId}, State}, IndexState) -> @@ -1477,17 +1500,27 @@ next({Key, Q, State}, IndexState) -> {value, MsgStatus, Next, IndexState} end. -ifold(Fun, Acc, It, State = #vqstate{index_state = IndexState}) -> - case next(It, IndexState) of - {value, MsgStatus, Next, IndexState1} -> - State1 = State#vqstate{index_state = IndexState1}, - {Msg, State2} = read_msg(MsgStatus, State1), - case Fun(Msg, MsgStatus#msg_status.msg_props, Acc) of - {stop, Acc1} -> {Acc1, State2}; - {cont, Acc1} -> ifold(Fun, Acc1, Next, State2) - end; - {empty, IndexState1} -> - {Acc, State#vqstate{index_state = IndexState1}} +ifold(_Fun, Acc, [], State) -> + {Acc, State}; +ifold(Fun, Acc, Its, State) -> + [{MsgStatus, It} | Rest] = lists:sort( + fun ({MsgStatus1, _}, {MsgStatus2, _}) -> + MsgStatus1#msg_status.seq_id < + MsgStatus2#msg_status.seq_id + end, Its), + {Msg, State1} = read_msg(MsgStatus, State), + case Fun(Msg, MsgStatus#msg_status.msg_props, Acc) of + {stop, Acc1} -> + {Acc1, State}; + {cont, Acc1} -> + case next(It, State1#vqstate.index_state) of + {empty, IndexState1} -> + ifold(Fun, Acc1, Rest, + State1#vqstate{index_state = IndexState1}); + {value, MsgStatus1, It1, IndexState1} -> + ifold(Fun, Acc1, [{MsgStatus1, It1} | Rest], + State1#vqstate{index_state = IndexState1}) + end end. %%---------------------------------------------------------------------------- -- cgit v1.2.1 From 63db60fd705c15fe66530fe68fedfb80c9eaaaa0 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 13 Jan 2013 10:59:59 +0000 Subject: refactor --- src/rabbit_variable_queue.erl | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 185da19c..6f77b867 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -677,17 +677,10 @@ ackfold(MsgFun, Acc, State, AckTags) -> {AccN, a(StateN)}. fold(Fun, Acc, State = #vqstate{index_state = IndexState}) -> - {Its, IndexState1} = - lists:foldl(fun (It, {Its, IndexState2}) -> - case next(It, IndexState2) of - {empty, IndexState3} -> - {Its, IndexState3}; - {value, MsgStatus, It1, IndexState3} -> - {[{MsgStatus, It1} | Its], IndexState3} - end - end, {[], IndexState}, [msg_iterator(State), - disk_ack_iterator(State), - ram_ack_iterator(State)]), + {Its, IndexState1} = lists:foldl(fun inext/2, {[], IndexState}, + [msg_iterator(State), + disk_ack_iterator(State), + ram_ack_iterator(State)]), ifold(Fun, Acc, Its, State#vqstate{index_state = IndexState1}). len(#vqstate { len = Len }) -> Len. @@ -1500,6 +1493,14 @@ next({Key, Q, State}, IndexState) -> {value, MsgStatus, Next, IndexState} end. +inext(It, {Its, IndexState}) -> + case next(It, IndexState) of + {empty, IndexState1} -> + {Its, IndexState1}; + {value, MsgStatus1, It1, IndexState1} -> + {[{MsgStatus1, It1} | Its], IndexState1} + end. + ifold(_Fun, Acc, [], State) -> {Acc, State}; ifold(Fun, Acc, Its, State) -> @@ -1513,14 +1514,8 @@ ifold(Fun, Acc, Its, State) -> {stop, Acc1} -> {Acc1, State}; {cont, Acc1} -> - case next(It, State1#vqstate.index_state) of - {empty, IndexState1} -> - ifold(Fun, Acc1, Rest, - State1#vqstate{index_state = IndexState1}); - {value, MsgStatus1, It1, IndexState1} -> - ifold(Fun, Acc1, [{MsgStatus1, It1} | Rest], - State1#vqstate{index_state = IndexState1}) - end + {Its1, IndexState1} = inext(It, {Rest, State1#vqstate.index_state}), + ifold(Fun, Acc1, Its1, State1#vqstate{index_state = IndexState1}) end. %%---------------------------------------------------------------------------- -- cgit v1.2.1 From bd61fc79a05b5d1facf4ccecfabdcbce04ed1b23 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 14 Jan 2013 11:25:16 +0000 Subject: improve vq:fold test by placing some messages in q1 --- src/rabbit_tests.erl | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 09ed3d08..45d5a09e 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2227,10 +2227,10 @@ variable_queue_publish(IsPersistent, Count, VQ) -> variable_queue_publish(IsPersistent, Count, fun (_N, P) -> P end, VQ). variable_queue_publish(IsPersistent, Count, PropFun, VQ) -> - variable_queue_publish(IsPersistent, Count, PropFun, + variable_queue_publish(IsPersistent, 1, Count, PropFun, fun (_N) -> <<>> end, VQ). -variable_queue_publish(IsPersistent, Count, PropFun, PayloadFun, VQ) -> +variable_queue_publish(IsPersistent, Start, Count, PropFun, PayloadFun, VQ) -> lists:foldl( fun (N, VQN) -> rabbit_variable_queue:publish( @@ -2242,7 +2242,7 @@ variable_queue_publish(IsPersistent, Count, PropFun, PayloadFun, VQ) -> end}, PayloadFun(N)), PropFun(N, #message_properties{}), false, self(), VQN) - end, VQ, lists:seq(1, Count)). + end, VQ, lists:seq(Start, Start + Count - 1)). variable_queue_fetch(Count, IsPersistent, IsDelivered, Len, VQ) -> lists:foldl(fun (N, {VQN, AckTagsAcc}) -> @@ -2327,13 +2327,21 @@ test_variable_queue() -> passed. test_variable_queue_fold(VQ0) -> - Count = rabbit_queue_index:next_segment_boundary(0) * 2 + 64, + JustOverTwoSegs = rabbit_queue_index:next_segment_boundary(0) * 2 + 64, VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), VQ2 = variable_queue_publish( - true, Count, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), + true, 1, JustOverTwoSegs, + fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), + VQ3 = rabbit_variable_queue:set_ram_duration_target(infinity, VQ2), + VQ4 = variable_queue_publish( + true, JustOverTwoSegs + 1, 64, + fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ3), + [false = V == 0 || {K, V} <- rabbit_variable_queue:status(VQ4), + lists:member(K, [q1, delta, q3])], %% precondition + Count = JustOverTwoSegs + 64, lists:foldl( - fun (Cut, VQ3) -> test_variable_queue_fold(Cut, Count, VQ3) end, - VQ2, [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]). + fun (Cut, VQ5) -> test_variable_queue_fold(Cut, Count, VQ5) end, + VQ4, [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]). test_variable_queue_fold(Cut, Count, VQ0) -> {Acc, VQ1} = rabbit_variable_queue:fold( @@ -2426,7 +2434,7 @@ test_dropfetchwhile(VQ0) -> %% add messages with sequential expiry VQ1 = variable_queue_publish( - false, Count, + false, 1, Count, fun (N, Props) -> Props#message_properties{expiry = N} end, fun erlang:term_to_binary/1, VQ0), -- cgit v1.2.1 From d3a12bdc757e87e3205d60e1bb4a58a94f3454ec Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 14 Jan 2013 13:35:05 +0000 Subject: improve assertion in vq:fold test --- src/rabbit_tests.erl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 45d5a09e..af8e2f9b 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2336,8 +2336,12 @@ test_variable_queue_fold(VQ0) -> VQ4 = variable_queue_publish( true, JustOverTwoSegs + 1, 64, fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ3), - [false = V == 0 || {K, V} <- rabbit_variable_queue:status(VQ4), - lists:member(K, [q1, delta, q3])], %% precondition + [false = case V of + {delta, _, 0, _} -> true; + 0 -> true; + _ -> false + end || {K, V} <- rabbit_variable_queue:status(VQ4), + lists:member(K, [q1, delta, q3])], %% precondition Count = JustOverTwoSegs + 64, lists:foldl( fun (Cut, VQ5) -> test_variable_queue_fold(Cut, Count, VQ5) end, -- cgit v1.2.1 From 1781208aff667a53475afc805882210dd3013dc4 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 14 Jan 2013 15:42:00 +0000 Subject: Improve workingness further. We now should be handling multiple blocks for different reasons correctly. --- src/rabbit_amqqueue_process.erl | 121 +++++++++++++++++++++------------------- src/rabbit_limiter.erl | 22 ++++---- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index f48005ef..8878de9c 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -64,9 +64,18 @@ monitor_ref, acktags, consumer_count, + %% Queue of {ChPid, #consumer{}} for consumers which have + %% been blocked for any reason blocked_consumers, + %% List of consumer tags which have individually been + %% blocked by the limiter. + blocked_ctags, + %% The limiter itself limiter, + %% Has the limiter imposed a channel-wide block, either + %% because of qos or channel flow? is_limit_active, + %% Internal flow control for queue -> writer unsent_message_count}). %%---------------------------------------------------------------------------- @@ -355,6 +364,7 @@ ch_record(ChPid) -> acktags = queue:new(), consumer_count = 0, blocked_consumers = queue:new(), + blocked_ctags = [], is_limit_active = false, limiter = rabbit_limiter:make_token(), unsent_message_count = 0}, @@ -402,13 +412,6 @@ block_consumer(C = #cr{blocked_consumers = Blocked}, QEntry) -> is_ch_blocked(#cr{unsent_message_count = Count, is_limit_active = Limited}) -> Limited orelse Count >= ?UNSENT_MESSAGE_LIMIT. -ch_record_state_transition(OldCR, NewCR) -> - case {is_ch_blocked(OldCR), is_ch_blocked(NewCR)} of - {true, false} -> unblock; - {false, true} -> block; - {_, _} -> ok - end. - deliver_msgs_to_consumers(_DeliverFun, true, State) -> {true, State}; deliver_msgs_to_consumers(DeliverFun, false, @@ -423,27 +426,38 @@ deliver_msgs_to_consumers(DeliverFun, false, deliver_msgs_to_consumers(DeliverFun, Stop, State1) end. -deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, +deliver_msg_to_consumer(DeliverFun, + E = {ChPid, + Consumer = #consumer{tag = CTag, + ack_required = AckReq}}, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> C = ch_record(ChPid), case is_ch_blocked(C) of - true -> block_consumer(C, E), - {false, State}; - false -> #cr{limiter = Limiter, ch_pid = ChPid} = C, - {CanSend, Lim2} = - rabbit_limiter:can_send( - Limiter, ChPid, self(), Consumer#consumer.ack_required, - Consumer#consumer.tag, BQ:len(BQS)), - case CanSend of - false -> block_consumer(C#cr{is_limit_active = true, - limiter = Lim2}, E), - {false, State}; - true -> AC1 = queue:in(E, State#q.active_consumers), - deliver_msg_to_consumer( - DeliverFun, Consumer, C#cr{limiter = Lim2}, - State#q{active_consumers = AC1}) - end + true -> + block_consumer(C, E), + {false, State}; + false -> + #cr{limiter = Limiter, ch_pid = ChPid, blocked_ctags = BCTags} = C, + case rabbit_limiter:can_cons_send( + Limiter, ChPid, CTag, BQ:len(BQS)) of + {false, Lim2} -> + %% TODO unify with first case? + block_consumer(C#cr{limiter = Lim2, + blocked_ctags = [CTag | BCTags]}, E), + {false, State}; + {true, Lim2} -> + case rabbit_limiter:can_ch_send(Limiter, self(), AckReq) of + false -> + block_consumer(C#cr{is_limit_active = true}, E), + {false, State}; + true -> + AC1 = queue:in(E, State#q.active_consumers), + deliver_msg_to_consumer( + DeliverFun, Consumer, C#cr{limiter = Lim2}, + State#q{active_consumers = AC1}) + end + end end. deliver_msg_to_consumer(DeliverFun, @@ -601,16 +615,20 @@ possibly_unblock(State, ChPid, Update) -> not_found -> State; C -> - C1 = Update(C), - case ch_record_state_transition(C, C1) of - ok -> update_ch_record(C1), - State; - unblock -> #cr{blocked_consumers = Consumers} = C1, - update_ch_record( - C1#cr{blocked_consumers = queue:new()}), - AC1 = queue:join(State#q.active_consumers, - Consumers), - run_message_queue(State#q{active_consumers = AC1}) + C1 = #cr{blocked_ctags = BCTags1} = Update(C), + {Blocked, Unblocked} = + lists:partition( + fun({_ChPid, #consumer{tag = CTag}}) -> + is_ch_blocked(C1) orelse lists:member(CTag, BCTags1) + end, queue:to_list(C1#cr.blocked_consumers)), + case Unblocked of + [] -> update_ch_record(C1), + State; + _ -> update_ch_record( + C1#cr{blocked_consumers = queue:from_list(Blocked)}), + AC1 = queue:join(State#q.active_consumers, + queue:from_list(Unblocked)), + run_message_queue(State#q{active_consumers = AC1}) end end. @@ -662,8 +680,6 @@ check_exclusive_access(none, true, State) -> consumer_count() -> consumer_count(fun (_) -> false end). -active_consumer_count() -> consumer_count(fun is_ch_blocked/1). - consumer_count(Exclude) -> lists:sum([Count || C = #cr{consumer_count = Count} <- all_ch_record(), not Exclude(C)]). @@ -909,8 +925,8 @@ i(messages, State) -> messages_unacknowledged]]); i(consumers, _) -> consumer_count(); -i(active_consumers, _) -> - active_consumer_count(); +i(active_consumers, #q{active_consumers = ActiveConsumers}) -> + queue:len(ActiveConsumers); i(memory, _) -> {memory, M} = process_info(self(), memory), M; @@ -1124,9 +1140,10 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, end; handle_call(stat, _From, State) -> - State1 = #q{backing_queue = BQ, backing_queue_state = BQS} = + State1 = #q{active_consumers = AC, + backing_queue = BQ, backing_queue_state = BQS} = drop_expired_msgs(ensure_expiry_timer(State)), - reply({ok, BQ:len(BQS), active_consumer_count()}, State1); + reply({ok, BQ:len(BQS), queue:len(AC)}, State1); handle_call({delete, IfUnused, IfEmpty}, From, State = #q{backing_queue_state = BQS, backing_queue = BQ}) -> @@ -1314,26 +1331,16 @@ handle_cast(stop_mirroring, State = #q{backing_queue = BQ, backing_queue_state = BQS1}); handle_cast({inform_limiter, ChPid, Msg}, - State = #q{active_consumers = AC, - backing_queue = BQ, + State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - C = #cr{limiter = Limiter, - blocked_consumers = Blocked} = ch_record(ChPid), + #cr{limiter = Limiter, + blocked_ctags = BCTags} = ch_record(ChPid), {Unblock, Limiter2} = rabbit_limiter:inform(Limiter, ChPid, BQ:len(BQS), Msg), - NewBlocked = queue:filter(fun({_ChPid, #consumer{tag = CTag}}) -> - not lists:member(CTag, Unblock) - end, Blocked), - NewUnblocked = queue:filter(fun({_ChPid, #consumer{tag = CTag}}) -> - lists:member(CTag, Unblock) - end, Blocked), - %% TODO can this whole thing be replaced by possibly_unblock? - %% TODO that is_limit_active = false thing is wrong - but we do - %% not allow for per-consumer blocking! - update_ch_record(C#cr{limiter = Limiter2, blocked_consumers = NewBlocked, - is_limit_active = false}), - AC1 = queue:join(NewUnblocked, AC), - noreply(run_message_queue(State#q{active_consumers = AC1})); + noreply(possibly_unblock( + State, ChPid, + fun(C) -> C#cr{blocked_ctags = BCTags -- Unblock, + limiter = Limiter2} end)); handle_cast(wake_up, State) -> noreply(State). diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index f031db51..9da1bc6f 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -24,7 +24,8 @@ -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). --export([limit/2, can_send/6, ack/2, register/2, unregister/2]). +-export([limit/2, can_ch_send/3, can_cons_send/4, + ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). -export([inform/4]). @@ -47,6 +48,7 @@ -spec(enable/2 :: (token(), non_neg_integer()) -> token()). -spec(disable/1 :: (token()) -> token()). -spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). +-spec(can_ch_send/3 :: (token(), pid(), boolean()) -> boolean()). %% TODO %% -spec(can_send/5 :: (token(), pid(), boolean(), %% rabbit_types:ctag(), non_neg_integer()) -> boolean()). @@ -98,18 +100,18 @@ limit(Limiter, PrefetchCount) -> %% breaching a limit. Note that we don't use maybe_call here in order %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. -can_send(#token{pid = Pid, enabled = true, q_state = QState} = Token, - ChPid, QPid, AckRequired, CTag, Len) -> +can_ch_send(#token{pid = Pid, enabled = true}, QPid, AckRequired) -> rabbit_misc:with_exit_handler( - fun () -> {true, Token} end, + fun () -> true end, fun () -> - CanLim = gen_server2:call(Pid, {can_send, QPid, AckRequired}, - infinity), - {CanQ, NewQState} = can_send_q(CTag, Len, ChPid, QState), - {CanLim andalso CanQ, Token#token{q_state = NewQState}} + gen_server2:call(Pid, {can_send, QPid, AckRequired}, infinity) end); -can_send(Token, _, _, _, _, _) -> - {true, Token}. +can_ch_send(_, _, _) -> + true. + +can_cons_send(#token{q_state = QState} = Token, ChPid, CTag, Len) -> + {CanQ, NewQState} = can_send_q(CTag, Len, ChPid, QState), + {CanQ, Token#token{q_state = NewQState}}. %% Let the limiter know that the channel has received some acks from a %% consumer -- cgit v1.2.1 From e4502d77d2aec095f9983a1cb03d0ec7472612f1 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 14 Jan 2013 15:50:45 +0000 Subject: No. --- src/rabbit_amqqueue_process.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 13292091..f33ce920 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -445,7 +445,6 @@ deliver_msg_to_consumer(DeliverFun, case rabbit_limiter:can_cons_send( Limiter, ChPid, CTag, BQ:len(BQS)) of {false, Lim2} -> - %% TODO unify with first case? block_consumer(C#cr{limiter = Lim2, blocked_ctags = [CTag | BCTags]}, E), {false, State}; -- cgit v1.2.1 From 903efe324ad46fa0978c0ed759eb03d83bb26542 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 14 Jan 2013 16:00:38 +0000 Subject: TODO-- --- src/rabbit_amqqueue_process.erl | 6 ++++-- src/rabbit_limiter.erl | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index f33ce920..47bc1641 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1152,10 +1152,12 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, case lookup_ch(ChPid) of not_found -> reply(ok, State); - C = #cr{blocked_consumers = Blocked} -> + C = #cr{limiter = Limiter, blocked_consumers = Blocked} -> emit_consumer_deleted(ChPid, ConsumerTag, qname(State)), + Limiter1 = rabbit_limiter:forget_consumer(Limiter, ConsumerTag), Blocked1 = remove_consumer(ChPid, ConsumerTag, Blocked), - update_consumer_count(C#cr{blocked_consumers = Blocked1}, -1), + update_consumer_count(C#cr{limiter = Limiter1, + blocked_consumers = Blocked1}, -1), State1 = State#q{ exclusive_consumer = case Holder of {ChPid, ConsumerTag} -> none; diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 9da1bc6f..39879063 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -27,7 +27,7 @@ -export([limit/2, can_ch_send/3, can_cons_send/4, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). --export([inform/4]). +-export([inform/4, forget_consumer/2]). -import(rabbit_misc, [serial_add/2, serial_diff/2]). @@ -141,6 +141,9 @@ inform(Limiter = #token{q_state = Credits}, update_credit(CTag, Len, ChPid, Credit, Count, Drain, Credits), {Unblock, Limiter#token{q_state = Credits2}}. +forget_consumer(Limiter = #token{q_state = Credits}, CTag) -> + Limiter#token{q_state = dict:erase(CTag, Credits)}. + %%---------------------------------------------------------------------------- %% Queue-local code %%---------------------------------------------------------------------------- @@ -200,8 +203,6 @@ update_credit(CTag, Len, ChPid, Credit, Count0, Drain, Credits) -> false -> {[], NewCredits} end. -%% TODO currently we leak when a single session creates and destroys -%% lot of links. write_credit(CTag, Credit, Count, Drain, Credits) -> dict:store(CTag, #credit{credit = Credit, count = Count, -- cgit v1.2.1 From 57663f28556439ab5ef9a15a35685732d92ea63a Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 14 Jan 2013 17:02:50 +0000 Subject: Allow setting credit for a consumer tag that does not yet exist, since the adapter wants to do that. We leak if you set credit for a ctag and then don't consume... but the adapter never does that. --- src/rabbit_channel.erl | 31 +++++++++++++++++++++++-------- src/rabbit_limiter.erl | 13 ++++++++----- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index c3a5b16d..2bc2db83 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -38,7 +38,7 @@ queue_names, queue_monitors, consumer_mapping, blocking, queue_consumers, delivering_queues, queue_collector_pid, stats_timer, confirm_enabled, publish_seqno, - unconfirmed, confirmed, capabilities, trace_state}). + unconfirmed, confirmed, capabilities, trace_state, credit_map}). -define(MAX_PERMISSION_CACHE_SIZE, 12). @@ -209,7 +209,8 @@ init([Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, VHost, unconfirmed = dtree:empty(), confirmed = [], capabilities = Capabilities, - trace_state = rabbit_trace:init(VHost)}, + trace_state = rabbit_trace:init(VHost), + credit_map = dict:new()}, State1 = rabbit_event:init_stats_timer(State, #ch.stats_timer), rabbit_event:notify(channel_created, infos(?CREATION_EVENT_KEYS, State1)), rabbit_event:if_enabled(State1, #ch.stats_timer, @@ -684,7 +685,8 @@ handle_method(#'basic.consume'{queue = QueueNameBin, nowait = NoWait}, _, State = #ch{conn_pid = ConnPid, limiter = Limiter, - consumer_mapping = ConsumerMapping}) -> + consumer_mapping = ConsumerMapping, + credit_map = CreditMap}) -> case dict:find(ConsumerTag, ConsumerMapping) of error -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), @@ -702,6 +704,15 @@ handle_method(#'basic.consume'{queue = QueueNameBin, case rabbit_amqqueue:with_exclusive_access_or_die( QueueName, ConnPid, fun (Q) -> + case dict:find(ActualConsumerTag, CreditMap) of + {ok, {Credit, Count, Drain}} -> + ok = rabbit_amqqueue:inform_limiter( + self(), Q#amqqueue.pid, + {basic_credit, ActualConsumerTag, + Credit, Count, Drain, false}); + error -> + ok + end, {rabbit_amqqueue:basic_consume( Q, NoAck, self(), Limiter, ActualConsumerTag, ExclusiveConsume, @@ -711,9 +722,11 @@ handle_method(#'basic.consume'{queue = QueueNameBin, end) of {ok, Q = #amqqueue{pid = QPid, name = QName}} -> CM1 = dict:store(ActualConsumerTag, Q, ConsumerMapping), + CrM1 = dict:erase(ActualConsumerTag, CreditMap), State1 = monitor_delivering_queue( NoAck, QPid, QName, - State#ch{consumer_mapping = CM1}), + State#ch{consumer_mapping = CM1, + credit_map = CrM1}), {noreply, case NoWait of true -> consumer_monitor(ActualConsumerTag, State1); @@ -1083,14 +1096,16 @@ handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, count = Count, drain = Drain}, _, - State = #ch{consumer_mapping = Consumers}) -> + State = #ch{consumer_mapping = Consumers, + credit_map = CMap}) -> case dict:find(CTag, Consumers) of {ok, Q} -> ok = rabbit_amqqueue:inform_limiter( self(), Q#amqqueue.pid, - {basic_credit, CTag, Credit, Count, Drain}), + {basic_credit, CTag, Credit, Count, Drain, true}), {noreply, State}; - error -> rabbit_misc:protocol_error( - not_allowed, "unknown consumer tag '~s'", [CTag]) + error -> CMap2 = dict:store(CTag, {Credit, Count, Drain}, CMap), + {reply, #'basic.credit_ok'{available = 0}, + State#ch{credit_map = CMap2}} end; handle_method(_MethodRecord, _Content, _State) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 39879063..900fbec3 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -136,9 +136,13 @@ is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). inform(Limiter = #token{q_state = Credits}, - ChPid, Len, {basic_credit, CTag, Credit, Count, Drain}) -> - {Unblock, Credits2} = - update_credit(CTag, Len, ChPid, Credit, Count, Drain, Credits), + ChPid, Len, {basic_credit, CTag, Credit, Count, Drain, Reply}) -> + {Unblock, Credits2} = update_credit(CTag, Credit, Count, Drain, Credits), + case Reply of + true -> rabbit_channel:send_command( + ChPid, #'basic.credit_ok'{available = Len}); + false -> ok + end, {Unblock, Limiter#token{q_state = Credits2}}. forget_consumer(Limiter = #token{q_state = Credits}, CTag) -> @@ -188,7 +192,7 @@ send_drained(ChPid, CTag, Count) -> %% Update the credit state. %% TODO Edge case: if the queue has nothing in it, and drain is set, %% we want to send a basic.credit back. -update_credit(CTag, Len, ChPid, Credit, Count0, Drain, Credits) -> +update_credit(CTag, Credit, Count0, Drain, Credits) -> Count = case dict:find(CTag, Credits) of %% Use our count if we can, more accurate @@ -196,7 +200,6 @@ update_credit(CTag, Len, ChPid, Credit, Count0, Drain, Credits) -> %% But if this is new, take it from the adapter _ -> Count0 end, - rabbit_channel:send_command(ChPid, #'basic.credit_ok'{available = Len}), NewCredits = write_credit(CTag, Credit, Count, Drain, Credits), case Credit > 0 of true -> {[CTag], NewCredits}; -- cgit v1.2.1 From 1a6d9d04133365dfb25313f63266d7afef87b5f3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 13:05:54 +0000 Subject: much more thorough testing of vq:requeue improving code coverage --- src/rabbit_tests.erl | 83 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index af8e2f9b..b6969d06 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2362,31 +2362,72 @@ test_variable_queue_fold(Cut, Count, VQ0) -> msg2int(#basic_message{content = #content{ payload_fragments_rev = P}}) -> binary_to_term(list_to_binary(lists:reverse(P))). -test_variable_queue_requeue(VQ0) -> - Interval = 50, - Count = rabbit_queue_index:next_segment_boundary(0) + 2 * Interval, +ack_subset(AckSeqs, Interval, Rem) -> + lists:filter(fun ({_Ack, N}) -> (N + Rem) rem Interval == 0 end, AckSeqs). + +requeue_one_by_one(Acks, VQ) -> + lists:foldl(fun (AckTag, VQN) -> + {_MsgId, VQM} = rabbit_variable_queue:requeue( + [AckTag], VQN), + VQM + end, VQ, Acks). + +%% Create a vq with messages in q1, delta, and q3, and holes (in the +%% form of pending acks) in the latter two. +variable_queue_with_holes(VQ0) -> + Interval = 64, + Count = rabbit_queue_index:next_segment_boundary(0)*2 + 2 * Interval, Seq = lists:seq(1, Count), VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), - VQ2 = variable_queue_publish(false, Count, VQ1), - {VQ3, Acks} = variable_queue_fetch(Count, false, false, Count, VQ2), - Subset = lists:foldl(fun ({Ack, N}, Acc) when N rem Interval == 0 -> - [Ack | Acc]; - (_, Acc) -> - Acc - end, [], lists:zip(Acks, Seq)), - {_MsgIds, VQ4} = rabbit_variable_queue:requeue(Acks -- Subset, VQ3), - VQ5 = lists:foldl(fun (AckTag, VQN) -> - {_MsgId, VQM} = rabbit_variable_queue:requeue( - [AckTag], VQN), - VQM - end, VQ4, Subset), - VQ6 = lists:foldl(fun (AckTag, VQa) -> - {{#basic_message{}, true, AckTag}, VQb} = + VQ2 = variable_queue_publish( + false, 1, Count, + fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), + {VQ3, AcksR} = variable_queue_fetch(Count, false, false, Count, VQ2), + Acks = lists:reverse(AcksR), + AckSeqs = lists:zip(Acks, Seq), + [{Subset1, _Seq1}, {Subset2, _Seq2}, {Subset3, Seq3}] = + [lists:unzip(ack_subset(AckSeqs, Interval, I)) || I <- [0, 1, 2]], + %% we requeue in three phases in order to exercise requeuing logic + %% in various vq states + {_MsgIds, VQ4} = rabbit_variable_queue:requeue( + Acks -- (Subset1 ++ Subset2 ++ Subset3), VQ3), + VQ5 = requeue_one_by_one(Subset1, VQ4), + %% by now we have some messages (and holes) in delt + VQ6 = requeue_one_by_one(Subset2, VQ5), + VQ7 = rabbit_variable_queue:set_ram_duration_target(infinity, VQ6), + %% add the q1 tail + VQ8 = variable_queue_publish( + true, Count + 1, 64, + fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ7), + %% assertions + [false = case V of + {delta, _, 0, _} -> true; + 0 -> true; + _ -> false + end || {K, V} <- rabbit_variable_queue:status(VQ8), + lists:member(K, [q1, delta, q3])], + Depth = Count + 64, + Depth = rabbit_variable_queue:depth(VQ8), + Len = Depth - length(Subset3), + Len = rabbit_variable_queue:len(VQ8), + {Len, (Seq -- Seq3), lists:seq(Count + 1, Count + 64), VQ8}. + +test_variable_queue_requeue(VQ0) -> + {_, RequeuedMsgs, FreshMsgs, VQ1} = variable_queue_with_holes(VQ0), + Msgs = + lists:zip(RequeuedMsgs, + lists:duplicate(length(RequeuedMsgs), true)) ++ + lists:zip(FreshMsgs, + lists:duplicate(length(FreshMsgs), false)), + VQ2 = lists:foldl(fun ({I, Requeued}, VQa) -> + {{M, MRequeued, _}, VQb} = rabbit_variable_queue:fetch(true, VQa), + Requeued = MRequeued, %% assertion + I = msg2int(M), %% assertion VQb - end, VQ5, lists:reverse(Acks)), - {empty, VQ7} = rabbit_variable_queue:fetch(true, VQ6), - VQ7. + end, VQ1, Msgs), + {empty, VQ3} = rabbit_variable_queue:fetch(true, VQ2), + VQ3. test_variable_queue_ack_limiting(VQ0) -> %% start by sending in a bunch of messages -- cgit v1.2.1 From 58ba91b595d7a65c345b94ec38b0b3538e909f35 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 13:17:38 +0000 Subject: test --- src/rabbit_tests.erl | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index b6969d06..7257827a 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2327,36 +2327,23 @@ test_variable_queue() -> passed. test_variable_queue_fold(VQ0) -> - JustOverTwoSegs = rabbit_queue_index:next_segment_boundary(0) * 2 + 64, - VQ1 = rabbit_variable_queue:set_ram_duration_target(0, VQ0), - VQ2 = variable_queue_publish( - true, 1, JustOverTwoSegs, - fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ1), - VQ3 = rabbit_variable_queue:set_ram_duration_target(infinity, VQ2), - VQ4 = variable_queue_publish( - true, JustOverTwoSegs + 1, 64, - fun (_, P) -> P end, fun erlang:term_to_binary/1, VQ3), - [false = case V of - {delta, _, 0, _} -> true; - 0 -> true; - _ -> false - end || {K, V} <- rabbit_variable_queue:status(VQ4), - lists:member(K, [q1, delta, q3])], %% precondition - Count = JustOverTwoSegs + 64, + {Count, RequeuedMsgs, FreshMsgs, VQ1} = variable_queue_with_holes(VQ0), + Msgs = RequeuedMsgs ++ FreshMsgs, lists:foldl( - fun (Cut, VQ5) -> test_variable_queue_fold(Cut, Count, VQ5) end, - VQ4, [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]). + fun (Cut, VQ2) -> test_variable_queue_fold(Cut, Msgs, VQ2) end, + VQ1, [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]). -test_variable_queue_fold(Cut, Count, VQ0) -> +test_variable_queue_fold(Cut, Msgs, VQ0) -> {Acc, VQ1} = rabbit_variable_queue:fold( fun (M, _, A) -> - case msg2int(M) =< Cut of - true -> {cont, [M | A]}; + MInt = msg2int(M), + case MInt =< Cut of + true -> {cont, [MInt | A]}; false -> {stop, A} end end, [], VQ0), - true = [N || N <- lists:seq(lists:min([Cut, Count]), 1, -1)] == - [msg2int(M) || M <- Acc], + Expected = lists:takewhile(fun (I) -> I =< Cut end, Msgs), + Expected = lists:reverse(Acc), %% assertion VQ1. msg2int(#basic_message{content = #content{ payload_fragments_rev = P}}) -> -- cgit v1.2.1 From b255ad4ce497b0926c776db2a3bcf09e795a30d8 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 13:23:37 +0000 Subject: filter out pending acks when folding over delta --- src/rabbit_variable_queue.erl | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index dc32902f..8a7045ea 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1475,7 +1475,9 @@ delta_fold(_Fun, {stop, Acc}, _DeltaSeqId, _DeltaSeqIdEnd, State) -> delta_fold(_Fun, {cont, Acc}, DeltaSeqIdEnd, DeltaSeqIdEnd, State) -> {cont, {Acc, State}}; delta_fold( Fun, {cont, Acc}, DeltaSeqId, DeltaSeqIdEnd, - #vqstate { index_state = IndexState, + #vqstate { ram_pending_ack = RPA, + disk_pending_ack = DPA, + index_state = IndexState, msg_store_clients = MSCState } = State) -> DeltaSeqId1 = lists:min( [rabbit_queue_index:next_segment_boundary(DeltaSeqId), @@ -1483,12 +1485,18 @@ delta_fold( Fun, {cont, Acc}, DeltaSeqId, DeltaSeqIdEnd, {List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1, IndexState), {StopCont, {Acc1, MSCState1}} = - lfoldl(fun ({MsgId, _SeqId, MsgProps, IsPersistent, _IsDelivered}, + lfoldl(fun ({MsgId, SeqId, MsgProps, IsPersistent, _IsDelivered}, {Acc0, MSCState0}) -> - {{ok, Msg = #basic_message {}}, MSCState1} = - msg_store_read(MSCState0, IsPersistent, MsgId), - {StopCont, AccNext} = Fun(Msg, MsgProps, Acc0), - {StopCont, {AccNext, MSCState1}} + case (gb_trees:is_defined(SeqId, RPA) orelse + gb_trees:is_defined(SeqId, DPA)) of + false -> {{ok, Msg = #basic_message{}}, MSCState1} = + msg_store_read(MSCState0, IsPersistent, + MsgId), + {StopCont, AccNext} = + Fun(Msg, MsgProps, Acc0), + {StopCont, {AccNext, MSCState1}}; + true -> {cont, {Acc0, MSCState0}} + end end, {cont, {Acc, MSCState}}, List), delta_fold(Fun, {StopCont, Acc1}, DeltaSeqId1, DeltaSeqIdEnd, State #vqstate { index_state = IndexState1, -- cgit v1.2.1 From f100430c6ccdabd5aea2352526901a13a8fb3150 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 15 Jan 2013 13:28:26 +0000 Subject: Cosmetic --- src/rabbit_amqqueue_process.erl | 12 +++++------- src/rabbit_limiter.erl | 3 +++ src/rabbit_reader.erl | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 47bc1641..66b63386 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1354,14 +1354,12 @@ handle_cast(stop_mirroring, State = #q{backing_queue = BQ, handle_cast({inform_limiter, ChPid, Msg}, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - #cr{limiter = Limiter, - blocked_ctags = BCTags} = ch_record(ChPid), - {Unblock, Limiter2} = - rabbit_limiter:inform(Limiter, ChPid, BQ:len(BQS), Msg), + #cr{limiter = Lim, + blocked_ctags = BCTags} = ch_record(ChPid), + {Unblock, Lim2} = rabbit_limiter:inform(Lim, ChPid, BQ:len(BQS), Msg), noreply(possibly_unblock( - State, ChPid, - fun(C) -> C#cr{blocked_ctags = BCTags -- Unblock, - limiter = Limiter2} end)); + State, ChPid, fun(C) -> C#cr{blocked_ctags = BCTags -- Unblock, + limiter = Lim2} end)); handle_cast(wake_up, State) -> noreply(State). diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 900fbec3..3cdb6849 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -72,6 +72,9 @@ blocked = false, queues = orddict: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. -record(credit, {count = 0, credit = 0, drain = false}). diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index f140bd23..fefff6b2 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -690,7 +690,7 @@ handle_input(handshake, <<"AMQP", 1, 1, 9, 1>>, State) -> start_connection({8, 0, 0}, rabbit_framing_amqp_0_8, State); %% ... and finally, the 1.0 spec is crystal clear! Note that the -%% FIXME TLS uses a different protocol number, and would go here. +%% TLS uses a different protocol number, and would go here. handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>, State) -> become_1_0(amqp, [0, 1, 0, 0], State); -- cgit v1.2.1 From f230802d1366c0fce8207f20c45b9b5aae1c4e6b Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 15 Jan 2013 13:40:02 +0000 Subject: Specs. And a function I forgot to write. --- src/rabbit_limiter.erl | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 3cdb6849..b5ec9f17 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -27,7 +27,7 @@ -export([limit/2, can_ch_send/3, can_cons_send/4, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). --export([inform/4, forget_consumer/2]). +-export([inform/4, forget_consumer/2, copy_queue_state/2]). -import(rabbit_misc, [serial_add/2, serial_diff/2]). @@ -49,9 +49,8 @@ -spec(disable/1 :: (token()) -> token()). -spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). -spec(can_ch_send/3 :: (token(), pid(), boolean()) -> boolean()). -%% TODO -%% -spec(can_send/5 :: (token(), pid(), boolean(), -%% rabbit_types:ctag(), non_neg_integer()) -> boolean()). +-spec(can_cons_send/4 :: (token(), pid(), rabbit_types:ctag(), + non_neg_integer()) -> {boolean(), token()}). -spec(ack/2 :: (token(), non_neg_integer()) -> 'ok'). -spec(register/2 :: (token(), pid()) -> 'ok'). -spec(unregister/2 :: (token(), pid()) -> 'ok'). @@ -59,10 +58,11 @@ -spec(block/1 :: (token()) -> 'ok'). -spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). -spec(is_blocked/1 :: (token()) -> boolean()). -%% -spec(set_credit/5 :: (token(), rabbit_types:ctag(), -%% non_neg_integer(), -%% non_neg_integer(), boolean()) -> 'ok'). -%%-spec(inform/4 :: (token(), pid(), non_neg_integer(), any()) -> token()). +-spec(inform/4 :: (token(), pid(), non_neg_integer(), any()) -> + {[rabbit_types:ctag()], token()}). +-spec(forget_consumer/2 :: (token(), rabbit_types:ctag()) -> token()). +-spec(copy_queue_state/2 :: (token(), token()) -> token()). + -endif. %%---------------------------------------------------------------------------- @@ -151,6 +151,9 @@ inform(Limiter = #token{q_state = Credits}, forget_consumer(Limiter = #token{q_state = Credits}, CTag) -> Limiter#token{q_state = dict:erase(CTag, Credits)}. +copy_queue_state(#token{q_state = Credits}, Token) -> + Token#token{q_state = Credits}. + %%---------------------------------------------------------------------------- %% Queue-local code %%---------------------------------------------------------------------------- -- cgit v1.2.1 From 8a816635bd03a5b9b3c98f771332612c15ab81a3 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 15 Jan 2013 14:00:28 +0000 Subject: Separate out can_cons_send. --- src/rabbit_amqqueue_process.erl | 29 ++++++++++++++--------------- src/rabbit_limiter.erl | 30 ++++++++++++++++-------------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 66b63386..fa70765d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -429,12 +429,7 @@ deliver_msgs_to_consumers(DeliverFun, false, deliver_msgs_to_consumers(DeliverFun, Stop, State1) end. -deliver_msg_to_consumer(DeliverFun, - E = {ChPid, - Consumer = #consumer{tag = CTag, - ack_required = AckReq}}, - State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> +deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> C = ch_record(ChPid), case is_ch_blocked(C) of true -> @@ -442,21 +437,21 @@ deliver_msg_to_consumer(DeliverFun, {false, State}; false -> #cr{limiter = Limiter, ch_pid = ChPid, blocked_ctags = BCTags} = C, - case rabbit_limiter:can_cons_send( - Limiter, ChPid, CTag, BQ:len(BQS)) of - {false, Lim2} -> - block_consumer(C#cr{limiter = Lim2, - blocked_ctags = [CTag | BCTags]}, E), + #consumer{tag = CTag} = Consumer, + case rabbit_limiter:can_cons_send(Limiter, CTag) of + false -> + block_consumer(C#cr{blocked_ctags = [CTag | BCTags]}, E), {false, State}; - {true, Lim2} -> - case rabbit_limiter:can_ch_send(Limiter, self(), AckReq) of + true -> + case rabbit_limiter:can_ch_send( + Limiter, self(), Consumer#consumer.ack_required) of false -> block_consumer(C#cr{is_limit_active = true}, E), {false, State}; true -> AC1 = queue:in(E, State#q.active_consumers), deliver_msg_to_consumer( - DeliverFun, Consumer, C#cr{limiter = Lim2}, + DeliverFun, Consumer, C, State#q{active_consumers = AC1}) end end @@ -467,8 +462,12 @@ deliver_msg_to_consumer(DeliverFun, ack_required = AckRequired}, C = #cr{ch_pid = ChPid, acktags = ChAckTags, + limiter = Limiter, unsent_message_count = Count}, - State = #q{q = #amqqueue{name = QName}}) -> + State = #q{q = #amqqueue{name = QName}, + backing_queue = BQ, + backing_queue_state = BQS}) -> + rabbit_limiter:record_cons_send(Limiter, ChPid, ConsumerTag, BQ:len(BQS)), {{Message, IsDelivered, AckTag}, Stop, State1} = DeliverFun(AckRequired, State), rabbit_channel:deliver(ChPid, ConsumerTag, AckRequired, diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index b5ec9f17..e32f072e 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -24,7 +24,7 @@ -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). --export([limit/2, can_ch_send/3, can_cons_send/4, +-export([limit/2, can_ch_send/3, can_cons_send/2, record_cons_send/4, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). -export([inform/4, forget_consumer/2, copy_queue_state/2]). @@ -49,8 +49,7 @@ -spec(disable/1 :: (token()) -> token()). -spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). -spec(can_ch_send/3 :: (token(), pid(), boolean()) -> boolean()). --spec(can_cons_send/4 :: (token(), pid(), rabbit_types:ctag(), - non_neg_integer()) -> {boolean(), token()}). +-spec(can_cons_send/2 :: (token(), rabbit_types:ctag()) -> boolean()). -spec(ack/2 :: (token(), non_neg_integer()) -> 'ok'). -spec(register/2 :: (token(), pid()) -> 'ok'). -spec(unregister/2 :: (token(), pid()) -> 'ok'). @@ -112,9 +111,15 @@ can_ch_send(#token{pid = Pid, enabled = true}, QPid, AckRequired) -> can_ch_send(_, _, _) -> true. -can_cons_send(#token{q_state = QState} = Token, ChPid, CTag, Len) -> - {CanQ, NewQState} = can_send_q(CTag, Len, ChPid, QState), - {CanQ, Token#token{q_state = NewQState}}. +can_cons_send(#token{q_state = Credits}, CTag) -> + case dict:find(CTag, Credits) of + {ok, #credit{credit = C}} when C > 0 -> true; + {ok, #credit{}} -> false; + error -> true + end. + +record_cons_send(#token{q_state = QState} = Token, ChPid, CTag, Len) -> + Token#token{q_state = record_send_q(CTag, Len, ChPid, QState)}. %% Let the limiter know that the channel has received some acks from a %% consumer @@ -164,15 +169,12 @@ copy_queue_state(#token{q_state = Credits}, Token) -> %% we get the queue to hold a bit of state for us (#token.q_state), and %% maintain a fiction that the limiter is making the decisions... -can_send_q(CTag, Len, ChPid, Credits) -> +record_send_q(CTag, Len, ChPid, Credits) -> case dict:find(CTag, Credits) of - {ok, #credit{credit = C} = Cred} -> - if C > 0 -> Credits2 = decr_credit(CTag, Len, ChPid, Cred, Credits), - {true, Credits2}; - true -> {false, Credits} - end; - _ -> - {true, Credits} + {ok, #credit{credit = C} = Cred} when C > 0 -> + decr_credit(CTag, Len, ChPid, Cred, Credits); + error -> + Credits end. decr_credit(CTag, Len, ChPid, Cred, Credits) -> -- cgit v1.2.1 From d7fbde0bc3c31f065a0f3c5a9d487b912f878c25 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 15 Jan 2013 14:58:43 +0000 Subject: Fix the case of sending credit state back when changing credit when there are no messages in the queue. --- src/rabbit_limiter.erl | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index e32f072e..e2c4fdbe 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -145,7 +145,8 @@ is_blocked(Limiter) -> inform(Limiter = #token{q_state = Credits}, ChPid, Len, {basic_credit, CTag, Credit, Count, Drain, Reply}) -> - {Unblock, Credits2} = update_credit(CTag, Credit, Count, Drain, Credits), + {Unblock, Credits2} = update_credit( + CTag, Len, ChPid, Credit, Count, Drain, Credits), case Reply of true -> rabbit_channel:send_command( ChPid, #'basic.credit_ok'{available = Len}); @@ -179,16 +180,19 @@ record_send_q(CTag, Len, ChPid, Credits) -> decr_credit(CTag, Len, ChPid, Cred, Credits) -> #credit{credit = Credit, count = Count, drain = Drain} = Cred, - {NewCredit, NewCount} = - case {Len, Drain} of - {1, true} -> %% Drain, so advance til credit = 0 - NewCount0 = serial_add(Count, (Credit - 1)), - send_drained(ChPid, CTag, NewCount0), - {0, NewCount0}; %% Magic reduction to 0 - {_, _} -> {Credit - 1, serial_add(Count, 1)} - end, + {NewCredit, NewCount} = maybe_drain(Len - 1, Drain, CTag, ChPid, + Credit - 1, serial_add(Count, 1)), write_credit(CTag, NewCredit, NewCount, Drain, Credits). +maybe_drain(0, true, CTag, ChPid, Credit, Count) -> + %% Drain, so advance til credit = 0 + NewCount = serial_add(Count, Credit - 2), + send_drained(ChPid, CTag, NewCount), + {0, NewCount}; %% Magic reduction to 0 + +maybe_drain(_, _, _, _, Credit, Count) -> + {Credit, Count}. + send_drained(ChPid, CTag, Count) -> rabbit_channel:send_command(ChPid, #'basic.credit_state'{consumer_tag = CTag, @@ -197,19 +201,16 @@ send_drained(ChPid, CTag, Count) -> available = 0, drain = true}). -%% Update the credit state. -%% TODO Edge case: if the queue has nothing in it, and drain is set, -%% we want to send a basic.credit back. -update_credit(CTag, Credit, Count0, Drain, Credits) -> - Count = - case dict:find(CTag, Credits) of - %% Use our count if we can, more accurate - {ok, #credit{ count = LocalCount }} -> LocalCount; - %% But if this is new, take it from the adapter - _ -> Count0 - end, - NewCredits = write_credit(CTag, Credit, Count, Drain, Credits), - case Credit > 0 of +update_credit(CTag, Len, ChPid, Credit, Count0, Drain, Credits) -> + Count = case dict:find(CTag, Credits) of + %% Use our count if we can, more accurate + {ok, #credit{ count = LocalCount }} -> LocalCount; + %% But if this is new, take it from the adapter + _ -> Count0 + end, + {NewCredit, NewCount} = maybe_drain(Len, Drain, CTag, ChPid, Credit, Count), + NewCredits = write_credit(CTag, NewCredit, NewCount, Drain, Credits), + case NewCredit > 0 of true -> {[CTag], NewCredits}; false -> {[], NewCredits} end. -- cgit v1.2.1 From 5c0dab72486ff0f64d8ccc161a2951b20ab96449 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 16:29:36 +0000 Subject: pass 'unacked' flag to BQ:fold fun so it can distinguish between 'ready' messages and those pending ack --- src/rabbit_backing_queue.erl | 2 +- src/rabbit_mirror_queue_sync.erl | 2 +- src/rabbit_tests.erl | 12 +++++++----- src/rabbit_variable_queue.erl | 23 ++++++++++++----------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 99b5946e..eb5c7043 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -164,7 +164,7 @@ %% results, leaving the queue undisturbed. -callback fold(fun((rabbit_types:basic_message(), rabbit_types:message_properties(), - A) -> {('stop' | 'cont'), A}), + boolean(), A) -> {('stop' | 'cont'), A}), A, state()) -> {A, state()}. %% How long is my queue? diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index f2ab67cd..4d6b1fc9 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -91,7 +91,7 @@ master_go(Syncer, Ref, Log, HandleInfo, EmitStats, BQ, BQS) -> end. master_go0(Args, BQ, BQS) -> - case BQ:fold(fun (Msg, MsgProps, Acc) -> + case BQ:fold(fun (Msg, MsgProps, false, Acc) -> master_send(Msg, MsgProps, Args, Acc) end, {0, erlang:now()}, BQS) of {{shutdown, Reason}, BQS1} -> {shutdown, Reason, BQS1}; diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 3f7aa9e2..8defedbd 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2330,14 +2330,16 @@ test_variable_queue_fold(VQ0) -> {Count, PendingMsgs, RequeuedMsgs, FreshMsgs, VQ1} = variable_queue_with_holes(VQ0), Msgs = lists:sort(PendingMsgs ++ RequeuedMsgs ++ FreshMsgs), - lists:foldl( - fun (Cut, VQ2) -> test_variable_queue_fold(Cut, Msgs, VQ2) end, - VQ1, [0, 1, 2, Count div 2, Count - 1, Count, Count + 1, Count * 2]). + lists:foldl(fun (Cut, VQ2) -> + test_variable_queue_fold(Cut, Msgs, PendingMsgs, VQ2) + end, VQ1, [0, 1, 2, Count div 2, + Count - 1, Count, Count + 1, Count * 2]). -test_variable_queue_fold(Cut, Msgs, VQ0) -> +test_variable_queue_fold(Cut, Msgs, PendingMsgs, VQ0) -> {Acc, VQ1} = rabbit_variable_queue:fold( - fun (M, _, A) -> + fun (M, _, Pending, A) -> MInt = msg2int(M), + Pending = lists:member(MInt, PendingMsgs), %% assert case MInt =< Cut of true -> {cont, [MInt | A]}; false -> {stop, A} diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index bbec70b2..c1db522b 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -1470,7 +1470,8 @@ istate(q1, _State) -> done. next({ack, It}, IndexState) -> case gb_trees:next(It) of none -> {empty, IndexState}; - {_SeqId, MsgStatus, It1} -> {value, MsgStatus, {ack, It1}, IndexState} + {_SeqId, MsgStatus, It1} -> Next = {ack, It1}, + {value, MsgStatus, true, Next, IndexState} end; next(done, IndexState) -> {empty, IndexState}; next({delta, #delta{start_seq_id = SeqId, @@ -1488,34 +1489,34 @@ next({delta, Delta, [{_, SeqId, _, _, _} = M | Rest], State}, IndexState) -> case (gb_trees:is_defined(SeqId, State#vqstate.ram_pending_ack) orelse gb_trees:is_defined(SeqId, State#vqstate.disk_pending_ack)) of false -> Next = {delta, Delta, Rest, State}, - {value, beta_msg_status(M), Next, IndexState}; + {value, beta_msg_status(M), false, Next, IndexState}; true -> next({delta, Delta, Rest, State}, IndexState) end; next({Key, Q, State}, IndexState) -> case ?QUEUE:out(Q) of {empty, _Q} -> next(istate(Key, State), IndexState); {{value, MsgStatus}, QN} -> Next = {Key, QN, State}, - {value, MsgStatus, Next, IndexState} + {value, MsgStatus, false, Next, IndexState} end. inext(It, {Its, IndexState}) -> case next(It, IndexState) of {empty, IndexState1} -> {Its, IndexState1}; - {value, MsgStatus1, It1, IndexState1} -> - {[{MsgStatus1, It1} | Its], IndexState1} + {value, MsgStatus1, Unacked, It1, IndexState1} -> + {[{MsgStatus1, Unacked, It1} | Its], IndexState1} end. ifold(_Fun, Acc, [], State) -> {Acc, State}; ifold(Fun, Acc, Its, State) -> - [{MsgStatus, It} | Rest] = lists:sort( - fun ({MsgStatus1, _}, {MsgStatus2, _}) -> - MsgStatus1#msg_status.seq_id < - MsgStatus2#msg_status.seq_id - end, Its), + [{MsgStatus, Unacked, It} | Rest] = + lists:sort(fun ({#msg_status{seq_id = SeqId1}, _, _}, + {#msg_status{seq_id = SeqId2}, _, _}) -> + SeqId1 =< SeqId2 + end, Its), {Msg, State1} = read_msg(MsgStatus, State), - case Fun(Msg, MsgStatus#msg_status.msg_props, Acc) of + case Fun(Msg, MsgStatus#msg_status.msg_props, Unacked, Acc) of {stop, Acc1} -> {Acc1, State}; {cont, Acc1} -> -- cgit v1.2.1 From 4e1cffb31dbedd1ea297d4794766fc67c046cd5d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 17:40:06 +0000 Subject: fix typo --- src/rabbit_reader.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index fefff6b2..c14951c3 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -696,7 +696,7 @@ handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>, State) -> %% 3 stands for "SASL" handle_input(handshake, <<"AMQP", 3, 1, 0, 0>>, State) -> - become_1_0(sasl, [0, 3, 0, 0], State); + become_1_0(sasl, [3, 1, 0, 0], State); handle_input(handshake, <<"AMQP", A, B, C, D>>, #v1{sock = Sock}) -> refuse_connection(Sock, {bad_version, A, B, C, D}); -- cgit v1.2.1 From ae444cc23f94d00b97f2854d935a60ba66176c05 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 17:43:30 +0000 Subject: simplify bad_version error handling --- src/rabbit_reader.erl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index c14951c3..0d2ba5d2 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -692,14 +692,14 @@ handle_input(handshake, <<"AMQP", 1, 1, 9, 1>>, State) -> %% ... and finally, the 1.0 spec is crystal clear! Note that the %% TLS uses a different protocol number, and would go here. handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>, State) -> - become_1_0(amqp, [0, 1, 0, 0], State); + become_1_0(amqp, {0, 1, 0, 0}, State); %% 3 stands for "SASL" handle_input(handshake, <<"AMQP", 3, 1, 0, 0>>, State) -> - become_1_0(sasl, [3, 1, 0, 0], State); + become_1_0(sasl, {3, 1, 0, 0}, State); handle_input(handshake, <<"AMQP", A, B, C, D>>, #v1{sock = Sock}) -> - refuse_connection(Sock, {bad_version, A, B, C, D}); + refuse_connection(Sock, {bad_version, {A, B, C, D}}); handle_input(handshake, Other, #v1{sock = Sock}) -> refuse_connection(Sock, {bad_header, Other}); @@ -993,10 +993,9 @@ emit_stats(State) -> %% 1.0 stub -become_1_0(Mode, HandshakeBytes, State = #v1{sock = Sock}) -> +become_1_0(Mode, Version, State = #v1{sock = Sock}) -> case code:is_loaded(rabbit_amqp1_0_reader) of - false -> refuse_connection( - Sock, list_to_tuple([bad_version | HandshakeBytes])); + false -> refuse_connection(Sock, {bad_version, Version}); _ -> apply0(rabbit_amqp1_0_reader, become, [Mode, pack_for_1_0(State)]) end. -- cgit v1.2.1 From 04e567b3ebf3f75c2830d6a147c792326ae2a822 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 18:39:00 +0000 Subject: make check_xref work when plugins dir is empty --- check_xref | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/check_xref b/check_xref index 8f65f3b1..0debd34a 100755 --- a/check_xref +++ b/check_xref @@ -50,6 +50,7 @@ shutdown(Rc, LibDir) -> check(Cwd, PluginsDir, LibDir, Checks) -> {ok, Plugins} = file:list_dir(PluginsDir), ok = file:make_dir(LibDir), + put({?MODULE, third_party}, []), [begin Source = filename:join(PluginsDir, Plugin), Target = filename:join(LibDir, Plugin), @@ -267,14 +268,8 @@ source_file(M) -> store_third_party(App) -> {ok, AppConfig} = application:get_all_key(App), - case get({?MODULE, third_party}) of - undefined -> - put({?MODULE, third_party}, - proplists:get_value(modules, AppConfig)); - Modules -> - put({?MODULE, third_party}, - proplists:get_value(modules, AppConfig) ++ Modules) - end. + AppModules = proplists:get_value(modules, AppConfig), + put({?MODULE, third_party}, AppModules ++ get({?MODULE, third_party})). %% TODO: this ought not to be maintained in such a fashion external_dependency(Path) -> -- cgit v1.2.1 From 0dc5006c8aada96821af5229215c3cb8e4698f2c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 18:41:10 +0000 Subject: optimising refactor of check_xref --- check_xref | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/check_xref b/check_xref index 0debd34a..ea0102ee 100755 --- a/check_xref +++ b/check_xref @@ -163,7 +163,8 @@ filters() -> filter_chain(FnChain) -> fun(AnalysisResult) -> - lists:foldl(fun(F, false) -> F(cleanup(AnalysisResult)); + Result = cleanup(AnalysisResult), + lists:foldl(fun(F, false) -> F(Result); (_F, true) -> true end, false, FnChain) end. -- cgit v1.2.1 From 444889b386a129691701232631b8e4efb084ee57 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 18:46:52 +0000 Subject: neater xref fooling --- src/rabbit_reader.erl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 0d2ba5d2..6d4becc0 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -996,13 +996,10 @@ emit_stats(State) -> become_1_0(Mode, Version, State = #v1{sock = Sock}) -> case code:is_loaded(rabbit_amqp1_0_reader) of false -> refuse_connection(Sock, {bad_version, Version}); - _ -> apply0(rabbit_amqp1_0_reader, become, - [Mode, pack_for_1_0(State)]) + _ -> M = rabbit_amqp1_0_reader, %% fool xref + M:become(Mode, pack_for_1_0(State)) end. -%% Fool xref. Simply using apply(M, F, A) with constants is not enough. -apply0(M, F, A) -> apply(M, F, A). - pack_for_1_0(#v1{parent = Parent, sock = Sock, recv_len = RecvLen, -- cgit v1.2.1 From 2ad15707cb8ccf7c192fb619b4bdb5032fd40b9c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 18:49:13 +0000 Subject: cosmetic --- src/rabbit_amqqueue.erl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 1715e848..1f2d653e 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -28,12 +28,11 @@ -export([consumers/1, consumers_all/1, consumer_info_keys/0]). -export([basic_get/3, basic_consume/7, basic_cancel/4]). -export([notify_sent/2, notify_sent_queue_down/1, unblock/2, flush_all/2]). --export([notify_down_all/2, limit_all/3]). +-export([notify_down_all/2, limit_all/3, inform_limiter/3]). -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). -export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1, cancel_sync_mirrors/1]). --export([inform_limiter/3]). %% internal -export([internal_declare/2, internal_delete/1, run_backing_queue/3, @@ -146,6 +145,7 @@ -spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). -spec(limit_all/3 :: (qpids(), pid(), rabbit_limiter:token()) -> ok_or_errors()). +-spec(inform_limiter/3 :: (pid(), pid(), any()) -> 'ok'). -spec(basic_get/3 :: (rabbit_types:amqqueue(), pid(), boolean()) -> {'ok', non_neg_integer(), qmsg()} | 'empty'). -spec(basic_consume/7 :: @@ -178,7 +178,6 @@ -spec(sync_mirrors/1 :: (pid()) -> 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). -spec(cancel_sync_mirrors/1 :: (pid()) -> 'ok' | {'ok', 'not_syncing'}). --spec(inform_limiter/3 :: (pid(), pid(), any()) -> 'ok'). -endif. @@ -535,6 +534,9 @@ notify_down_all(QPids, ChPid) -> limit_all(QPids, ChPid, Limiter) -> delegate:cast(QPids, {limit, ChPid, Limiter}). +inform_limiter(ChPid, QPid, Msg) -> + delegate:cast(QPid, {inform_limiter, ChPid, Msg}). + basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> delegate:call(QPid, {basic_get, ChPid, NoAck}). @@ -609,9 +611,6 @@ stop_mirroring(QPid) -> ok = delegate:cast(QPid, stop_mirroring). sync_mirrors(QPid) -> delegate:call(QPid, sync_mirrors). cancel_sync_mirrors(QPid) -> delegate:call(QPid, cancel_sync_mirrors). -inform_limiter(ChPid, QPid, Msg) -> - delegate:cast(QPid, {inform_limiter, ChPid, Msg}). - on_node_down(Node) -> rabbit_misc:execute_mnesia_tx_with_tail( fun () -> QsDels = -- cgit v1.2.1 From 097182274da2cd2d5a37ac35f6add7d06963ba76 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 18:50:56 +0000 Subject: API consistency --- src/rabbit_amqqueue.erl | 4 ++-- src/rabbit_channel.erl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 1f2d653e..788ec558 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -145,7 +145,7 @@ -spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). -spec(limit_all/3 :: (qpids(), pid(), rabbit_limiter:token()) -> ok_or_errors()). --spec(inform_limiter/3 :: (pid(), pid(), any()) -> 'ok'). +-spec(inform_limiter/3 :: (rabbit_types:amqqueue(), pid(), any()) -> 'ok'). -spec(basic_get/3 :: (rabbit_types:amqqueue(), pid(), boolean()) -> {'ok', non_neg_integer(), qmsg()} | 'empty'). -spec(basic_consume/7 :: @@ -534,7 +534,7 @@ notify_down_all(QPids, ChPid) -> limit_all(QPids, ChPid, Limiter) -> delegate:cast(QPids, {limit, ChPid, Limiter}). -inform_limiter(ChPid, QPid, Msg) -> +inform_limiter(#amqqueue{pid = QPid}, ChPid, Msg) -> delegate:cast(QPid, {inform_limiter, ChPid, Msg}). basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 2bc2db83..8d916703 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1100,7 +1100,7 @@ handle_method(#'basic.credit'{consumer_tag = CTag, credit_map = CMap}) -> case dict:find(CTag, Consumers) of {ok, Q} -> ok = rabbit_amqqueue:inform_limiter( - self(), Q#amqqueue.pid, + Q, self(), {basic_credit, CTag, Credit, Count, Drain, true}), {noreply, State}; error -> CMap2 = dict:store(CTag, {Credit, Count, Drain}, CMap), -- cgit v1.2.1 From 86f37f1c3cd7102c79880186c7fa5f866f265181 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 15 Jan 2013 20:53:07 +0000 Subject: some more reader connection state abstraction and a slightly more logical (and efficient) handle_frame clause order --- src/rabbit_reader.erl | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 13e8feff..7a28c8a3 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -64,6 +64,10 @@ State#v1.connection_state =:= blocking orelse State#v1.connection_state =:= blocked)). +-define(IS_STOPPING(State), + (State#v1.connection_state =:= closing orelse + State#v1.connection_state =:= closed)). + %%-------------------------------------------------------------------------- -ifdef(use_specs). @@ -323,9 +327,7 @@ handle_other({'DOWN', _MRef, process, ChPid, Reason}, Deb, State) -> handle_other(terminate_connection, _Deb, State) -> State; handle_other(handshake_timeout, Deb, State) - when ?IS_RUNNING(State) orelse - State#v1.connection_state =:= closing orelse - State#v1.connection_state =:= closed -> + when ?IS_RUNNING(State) orelse ?IS_STOPPING(State) -> mainloop(Deb, State); handle_other(handshake_timeout, _Deb, State) -> throw({handshake_timeout, State#v1.callback}); @@ -572,17 +574,13 @@ all_channels() -> [ChPid || {{ch_pid, ChPid}, _ChannelMRef} <- get()]. %%-------------------------------------------------------------------------- handle_frame(Type, 0, Payload, - State = #v1{connection_state = CS, - connection = #connection{protocol = Protocol}}) - when CS =:= closing; CS =:= closed -> + State = #v1{connection = #connection{protocol = Protocol}}) + when ?IS_STOPPING(State) -> case rabbit_command_assembler:analyze_frame(Type, Payload, Protocol) of {method, MethodName, FieldsBin} -> handle_method0(MethodName, FieldsBin, State); _Other -> State end; -handle_frame(_Type, _Channel, _Payload, State = #v1{connection_state = CS}) - when CS =:= closing; CS =:= closed -> - State; handle_frame(Type, 0, Payload, State = #v1{connection = #connection{protocol = Protocol}}) -> case rabbit_command_assembler:analyze_frame(Type, Payload, Protocol) of @@ -600,6 +598,8 @@ handle_frame(Type, Channel, Payload, heartbeat -> unexpected_frame(Type, Channel, Payload, State); Frame -> process_frame(Frame, Channel, State) end; +handle_frame(_Type, _Channel, _Payload, State) when ?IS_STOPPING(State) -> + State; handle_frame(Type, Channel, Payload, State) -> unexpected_frame(Type, Channel, Payload, State). @@ -820,10 +820,9 @@ handle_method0(#'connection.close'{}, State) when ?IS_RUNNING(State) -> lists:foreach(fun rabbit_channel:shutdown/1, all_channels()), maybe_close(State#v1{connection_state = closing}); handle_method0(#'connection.close'{}, - State = #v1{connection_state = CS, - connection = #connection{protocol = Protocol}, + State = #v1{connection = #connection{protocol = Protocol}, sock = Sock}) - when CS =:= closing; CS =:= closed -> + when ?IS_STOPPING(State) -> %% We're already closed or closing, so we don't need to cleanup %% anything. ok = send_on_channel0(Sock, #'connection.close_ok'{}, Protocol), @@ -832,8 +831,7 @@ handle_method0(#'connection.close_ok'{}, State = #v1{connection_state = closed}) -> self() ! terminate_connection, State; -handle_method0(_Method, State = #v1{connection_state = CS}) - when CS =:= closing; CS =:= closed -> +handle_method0(_Method, State) when ?IS_STOPPING(State) -> State; handle_method0(_Method, #v1{connection_state = S}) -> rabbit_misc:protocol_error( -- cgit v1.2.1 From 88c37f00b352a8720190ad33efde29d5db165be3 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 16 Jan 2013 10:55:59 +0000 Subject: ...and do the same thing here. --- src/rabbit_event.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rabbit_event.erl b/src/rabbit_event.erl index 7d91b6fa..2d626ad4 100644 --- a/src/rabbit_event.erl +++ b/src/rabbit_event.erl @@ -112,8 +112,10 @@ stop_stats_timer(C, P) -> case element(P, C) of #state{level = Level, timer = TRef} = State when Level =/= none andalso TRef =/= undefined -> - erlang:cancel_timer(TRef), - setelement(P, C, State#state{timer = undefined}); + case erlang:cancel_timer(TRef) of + false -> C; + _ -> setelement(P, C, State#state{timer = undefined}) + end; #state{} -> C end. -- cgit v1.2.1 From d932b7c913f24436a7c4e7d2b2e5109915cc19cd Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 16 Jan 2013 13:38:01 +0000 Subject: Unbreak --- 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 8d916703..a5a59314 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -707,7 +707,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, case dict:find(ActualConsumerTag, CreditMap) of {ok, {Credit, Count, Drain}} -> ok = rabbit_amqqueue:inform_limiter( - self(), Q#amqqueue.pid, + Q, self(), {basic_credit, ActualConsumerTag, Credit, Count, Drain, false}); error -> -- cgit v1.2.1 From ffb441fa1f41c4ff6edb98acbe57ecc1753a21fd Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 16 Jan 2013 14:13:03 +0000 Subject: This guard is not needed. --- 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 e2c4fdbe..57fd0c26 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -172,7 +172,7 @@ copy_queue_state(#token{q_state = Credits}, Token) -> record_send_q(CTag, Len, ChPid, Credits) -> case dict:find(CTag, Credits) of - {ok, #credit{credit = C} = Cred} when C > 0 -> + {ok, Cred} -> decr_credit(CTag, Len, ChPid, Cred, Credits); error -> Credits -- cgit v1.2.1 From c8d5866d31826de791a46d7aee5f9fcdccd929f5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 16 Jan 2013 17:11:50 +0000 Subject: remove superfluous condition --- src/rabbit_event.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_event.erl b/src/rabbit_event.erl index 2d626ad4..6a0d1892 100644 --- a/src/rabbit_event.erl +++ b/src/rabbit_event.erl @@ -110,8 +110,8 @@ ensure_stats_timer(C, P, Msg) -> stop_stats_timer(C, P) -> case element(P, C) of - #state{level = Level, timer = TRef} = State - when Level =/= none andalso TRef =/= undefined -> + #state{timer = TRef} = State + when TRef =/= undefined -> case erlang:cancel_timer(TRef) of false -> C; _ -> setelement(P, C, State#state{timer = undefined}) -- cgit v1.2.1 From 5db7d0090b706704f92bb1d5c41f99d139a5ca50 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 16 Jan 2013 17:17:06 +0000 Subject: cosmetic --- src/rabbit_event.erl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/rabbit_event.erl b/src/rabbit_event.erl index 6a0d1892..38d8cd54 100644 --- a/src/rabbit_event.erl +++ b/src/rabbit_event.erl @@ -110,8 +110,7 @@ ensure_stats_timer(C, P, Msg) -> stop_stats_timer(C, P) -> case element(P, C) of - #state{timer = TRef} = State - when TRef =/= undefined -> + #state{timer = TRef} = State when TRef =/= undefined -> case erlang:cancel_timer(TRef) of false -> C; _ -> setelement(P, C, State#state{timer = undefined}) @@ -122,8 +121,7 @@ stop_stats_timer(C, P) -> reset_stats_timer(C, P) -> case element(P, C) of - #state{timer = TRef} = State - when TRef =/= undefined -> + #state{timer = TRef} = State when TRef =/= undefined -> setelement(P, C, State#state{timer = undefined}); #state{} -> C -- cgit v1.2.1 From f90640eda347e7dc1756f5dbe747628816614fc8 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 17 Jan 2013 16:21:19 +0000 Subject: Unwind the stack before taking that one-way trip. --- src/rabbit_reader.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 6d4becc0..a1dfeeff 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -245,6 +245,8 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, handshake, 8)), log(info, "closing AMQP connection ~p (~s)~n", [self(), Name]) catch + throw:{become, M, F, A} -> + apply(M, F, A); Ex -> log(case Ex of connection_closed_abruptly -> warning; _ -> error @@ -996,8 +998,8 @@ emit_stats(State) -> become_1_0(Mode, Version, State = #v1{sock = Sock}) -> case code:is_loaded(rabbit_amqp1_0_reader) of false -> refuse_connection(Sock, {bad_version, Version}); - _ -> M = rabbit_amqp1_0_reader, %% fool xref - M:become(Mode, pack_for_1_0(State)) + _ -> throw({become, rabbit_amqp1_0_reader, become, + [Mode, pack_for_1_0(State)]}) end. pack_for_1_0(#v1{parent = Parent, -- cgit v1.2.1 From efdbf0a822e141a26d1306011a397c2638584ff9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 17 Jan 2013 16:43:07 +0000 Subject: allow 'become' to return, and become something else --- src/rabbit_reader.erl | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index a1dfeeff..7b867c8e 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -23,7 +23,7 @@ -export([system_continue/3, system_terminate/4, system_code_change/4]). --export([init/4, mainloop/2]). +-export([init/4, mainloop/2, recvloop/2]). -export([conserve_resources/3, server_properties/1]). @@ -240,13 +240,12 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, last_blocked_at = never}}, try ok = inet_op(fun () -> rabbit_net:tune_buffer_size(ClientSock) end), - recvloop(Deb, switch_callback(rabbit_event:init_stats_timer( - State, #v1.stats_timer), - handshake, 8)), + run({?MODULE, recvloop, + [Deb, switch_callback(rabbit_event:init_stats_timer( + State, #v1.stats_timer), + handshake, 8)]}), log(info, "closing AMQP connection ~p (~s)~n", [self(), Name]) catch - throw:{become, M, F, A} -> - apply(M, F, A); Ex -> log(case Ex of connection_closed_abruptly -> warning; _ -> error @@ -265,6 +264,11 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, end, done. +run({M, F, A}) -> + try apply(M, F, A) + catch {become, MFA} -> run(MFA) + end. + recvloop(Deb, State = #v1{pending_recv = true}) -> mainloop(Deb, State); recvloop(Deb, State = #v1{connection_state = blocked}) -> @@ -998,8 +1002,8 @@ emit_stats(State) -> become_1_0(Mode, Version, State = #v1{sock = Sock}) -> case code:is_loaded(rabbit_amqp1_0_reader) of false -> refuse_connection(Sock, {bad_version, Version}); - _ -> throw({become, rabbit_amqp1_0_reader, become, - [Mode, pack_for_1_0(State)]}) + _ -> throw({become, {rabbit_amqp1_0_reader, become, + [Mode, pack_for_1_0(State)]}}) end. pack_for_1_0(#v1{parent = Parent, -- cgit v1.2.1 From 31ab9068467652182ed6de357f9e480f6e87e7fd Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 17 Jan 2013 16:46:35 +0000 Subject: Start the chan_sup_sup later, once we have committed to 0-9-1 - one less hack. --- src/rabbit_connection_sup.erl | 14 +------------- src/rabbit_reader.erl | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/rabbit_connection_sup.erl b/src/rabbit_connection_sup.erl index f4f3c72f..d9a4735c 100644 --- a/src/rabbit_connection_sup.erl +++ b/src/rabbit_connection_sup.erl @@ -42,23 +42,11 @@ start_link() -> SupPid, {collector, {rabbit_queue_collector, start_link, []}, intrinsic, ?MAX_WAIT, worker, [rabbit_queue_collector]}), - %% Note that rabbit_amqp1_0_session_sup_sup despite the name can - %% mimic rabbit_channel_sup_sup when we handle a 0-9-1 connection - %% and the 1.0 plugin is loaded. - ChannelSupSupModule = case code:is_loaded(rabbit_amqp1_0_session_sup_sup) of - false -> rabbit_channel_sup_sup; - _ -> rabbit_amqp1_0_session_sup_sup - end, - {ok, ChannelSupSupPid} = - supervisor2:start_child( - SupPid, - {channel_sup_sup, {ChannelSupSupModule, start_link, []}, - intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}), {ok, ReaderPid} = supervisor2:start_child( SupPid, {reader, {rabbit_reader, start_link, - [ChannelSupSupPid, Collector, + [SupPid, Collector, rabbit_heartbeat:start_heartbeat_fun(SupPid)]}, intrinsic, ?MAX_WAIT, worker, [rabbit_reader]}), {ok, SupPid, ReaderPid}. diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index a1dfeeff..27ea4d4b 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -37,7 +37,8 @@ -record(v1, {parent, sock, connection, callback, recv_len, pending_recv, connection_state, queue_collector, heartbeater, stats_timer, - channel_sup_sup_pid, start_heartbeat_fun, buf, buf_len, throttle}). + channel_sup_sup_pid, conn_sup_pid, start_heartbeat_fun, + buf, buf_len, throttle}). -record(connection, {name, host, peer_host, port, peer_port, protocol, user, timeout_sec, frame_max, vhost, @@ -105,12 +106,12 @@ start_link(ChannelSupSupPid, Collector, StartHeartbeatFun) -> shutdown(Pid, Explanation) -> gen_server:call(Pid, {shutdown, Explanation}, infinity). -init(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun) -> +init(Parent, ConnSupPid, Collector, StartHeartbeatFun) -> Deb = sys:debug_options([]), receive {go, Sock, SockTransform} -> start_connection( - Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, Sock, + Parent, ConnSupPid, Collector, StartHeartbeatFun, Deb, Sock, SockTransform) end. @@ -199,7 +200,7 @@ name(Sock) -> socket_ends(Sock) -> socket_op(Sock, fun (S) -> rabbit_net:socket_ends(S, inbound) end). -start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, +start_connection(Parent, ConnSupPid, Collector, StartHeartbeatFun, Deb, Sock, SockTransform) -> process_flag(trap_exit, true), Name = name(Sock), @@ -230,7 +231,8 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, connection_state = pre_init, queue_collector = Collector, heartbeater = none, - channel_sup_sup_pid = ChannelSupSupPid, + conn_sup_pid = ConnSupPid, + channel_sup_sup_pid = none, start_heartbeat_fun = StartHeartbeatFun, buf = [], buf_len = 0, @@ -714,7 +716,13 @@ handle_input(Callback, Data, _State) -> %% are similar enough that clients will be happy with either. start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, Protocol, - State = #v1{sock = Sock, connection = Connection}) -> + State = #v1{sock = Sock, connection = Connection, + conn_sup_pid = ConnSupPid}) -> + {ok, ChannelSupSupPid} = + supervisor2:start_child( + ConnSupPid, + {channel_sup_sup, {rabbit_channel_sup_sup, start_link, []}, + intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}), Start = #'connection.start'{ version_major = ProtocolMajor, version_minor = ProtocolMinor, @@ -725,6 +733,7 @@ start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, switch_callback(State#v1{connection = Connection#connection{ timeout_sec = ?NORMAL_TIMEOUT, protocol = Protocol}, + channel_sup_sup_pid = ChannelSupSupPid, connection_state = starting}, frame_header, 7). @@ -1007,9 +1016,9 @@ pack_for_1_0(#v1{parent = Parent, recv_len = RecvLen, pending_recv = PendingRecv, queue_collector = QueueCollector, - channel_sup_sup_pid = ChannelSupSupPid, + conn_sup_pid = ConnSupPid, start_heartbeat_fun = SHF, buf = Buf, buf_len = BufLen}) -> - {Parent, Sock, RecvLen, PendingRecv, QueueCollector, - ChannelSupSupPid, SHF, Buf, BufLen}. + {Parent, Sock, RecvLen, PendingRecv, QueueCollector, ConnSupPid, SHF, + Buf, BufLen}. -- cgit v1.2.1 From b235064ef4642a8e3c69a7a04b41cba609355f7c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 17 Jan 2013 17:04:09 +0000 Subject: delay starting of channel_sup_sup as much as possible which makes abandoned connection attempts less costly --- src/rabbit_reader.erl | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index b411f927..e1462163 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -37,7 +37,7 @@ -record(v1, {parent, sock, connection, callback, recv_len, pending_recv, connection_state, queue_collector, heartbeater, stats_timer, - channel_sup_sup_pid, conn_sup_pid, start_heartbeat_fun, + conn_sup_pid, channel_sup_sup_pid, start_heartbeat_fun, buf, buf_len, throttle}). -record(connection, {name, host, peer_host, port, peer_port, @@ -720,13 +720,7 @@ handle_input(Callback, Data, _State) -> %% are similar enough that clients will be happy with either. start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, Protocol, - State = #v1{sock = Sock, connection = Connection, - conn_sup_pid = ConnSupPid}) -> - {ok, ChannelSupSupPid} = - supervisor2:start_child( - ConnSupPid, - {channel_sup_sup, {rabbit_channel_sup_sup, start_link, []}, - intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}), + State = #v1{sock = Sock, connection = Connection}) -> Start = #'connection.start'{ version_major = ProtocolMajor, version_minor = ProtocolMinor, @@ -737,7 +731,6 @@ start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, switch_callback(State#v1{connection = Connection#connection{ timeout_sec = ?NORMAL_TIMEOUT, protocol = Protocol}, - channel_sup_sup_pid = ChannelSupSupPid, connection_state = starting}, frame_header, 7). @@ -823,17 +816,24 @@ handle_method0(#'connection.open'{virtual_host = VHostPath}, connection = Connection = #connection{ user = User, protocol = Protocol}, + conn_sup_pid = ConnSupPid, sock = Sock, throttle = Throttle}) -> ok = rabbit_access_control:check_vhost_access(User, VHostPath), NewConnection = Connection#connection{vhost = VHostPath}, ok = send_on_channel0(Sock, #'connection.open_ok'{}, Protocol), Conserve = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}), + Throttle1 = Throttle#throttle{conserve_resources = Conserve}, + {ok, ChannelSupSupPid} = + supervisor2:start_child( + ConnSupPid, + {channel_sup_sup, {rabbit_channel_sup_sup, start_link, []}, + intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}), State1 = control_throttle( - State#v1{connection_state = running, - connection = NewConnection, - throttle = Throttle#throttle{ - conserve_resources = Conserve}}), + State#v1{connection_state = running, + connection = NewConnection, + channel_sup_sup_pid = ChannelSupSupPid, + throttle = Throttle1}), rabbit_event:notify(connection_created, [{type, network} | infos(?CREATION_EVENT_KEYS, State1)]), -- cgit v1.2.1 From a7fb2ee05bc227af8de4a85ef2f217ce32e6bacb Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 17 Jan 2013 17:16:46 +0000 Subject: Stick "real" 0-9-1 connections in an ETS table. --- include/rabbit.hrl | 2 ++ src/rabbit_networking.erl | 13 ++++--------- src/rabbit_reader.erl | 2 ++ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/include/rabbit.hrl b/include/rabbit.hrl index 7385b4b3..397a3df6 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -109,3 +109,5 @@ -define(INVALID_HEADERS_KEY, <<"x-invalid-headers">>). -define(ROUTING_HEADERS, [<<"CC">>, <<"BCC">>]). -define(DELETED_HEADER, <<"BCC">>). + +-define(CONNECTION_TABLE, rabbit_connection). diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index 31eeef73..52163354 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -117,6 +117,9 @@ %%---------------------------------------------------------------------------- boot() -> + %% This exists to disambiguate 0-x connections from 1.0 ones + %% (since they are both children of the same supervisor). + ets:new(?CONNECTION_TABLE, [public, named_table]), ok = start(), ok = boot_tcp(), ok = boot_ssl(). @@ -299,15 +302,7 @@ connections() -> rabbit_networking, connections_local, []). connections_local() -> - [Reader || - {_, ConnSup, supervisor, _} - <- supervisor:which_children(rabbit_tcp_client_sup), - Reader <- [try - rabbit_connection_sup:reader(ConnSup) - catch exit:{noproc, _} -> - noproc - end], - Reader =/= noproc]. + [P || {P} <- ets:tab2list(?CONNECTION_TABLE)]. connection_info_keys() -> rabbit_reader:info_keys(). diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index e1462163..fae65b1d 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -265,6 +265,7 @@ start_connection(Parent, ConnSupPid, Collector, StartHeartbeatFun, Deb, %% the socket. However, to keep the file_handle_cache %% accounting as accurate as possible we ought to close the %% socket w/o delay before termination. + ets:delete(?CONNECTION_TABLE, self()), rabbit_net:fast_close(ClientSock), rabbit_event:notify(connection_closed, [{pid, self()}]) end, @@ -721,6 +722,7 @@ handle_input(Callback, Data, _State) -> start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, Protocol, State = #v1{sock = Sock, connection = Connection}) -> + ets:insert(?CONNECTION_TABLE, {self()}), Start = #'connection.start'{ version_major = ProtocolMajor, version_minor = ProtocolMinor, -- cgit v1.2.1 From b3d4e8a9b7c99ef3d5d64cf8b71fe7e91b3e19eb Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 17 Jan 2013 17:32:28 +0000 Subject: cosmetic --- src/rabbit_reader.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index fae65b1d..f5ddf1b0 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -265,8 +265,8 @@ start_connection(Parent, ConnSupPid, Collector, StartHeartbeatFun, Deb, %% the socket. However, to keep the file_handle_cache %% accounting as accurate as possible we ought to close the %% socket w/o delay before termination. - ets:delete(?CONNECTION_TABLE, self()), rabbit_net:fast_close(ClientSock), + ets:delete(?CONNECTION_TABLE, self()), rabbit_event:notify(connection_closed, [{pid, self()}]) end, done. -- cgit v1.2.1 From 8d6369705ad1ff872fdb1896d05fb28c67e43d8a Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 17 Jan 2013 17:35:25 +0000 Subject: cosmetic --- src/rabbit_networking.erl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index 52163354..e864140d 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -117,8 +117,6 @@ %%---------------------------------------------------------------------------- boot() -> - %% This exists to disambiguate 0-x connections from 1.0 ones - %% (since they are both children of the same supervisor). ets:new(?CONNECTION_TABLE, [public, named_table]), ok = start(), ok = boot_tcp(), @@ -301,8 +299,7 @@ connections() -> rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running), rabbit_networking, connections_local, []). -connections_local() -> - [P || {P} <- ets:tab2list(?CONNECTION_TABLE)]. +connections_local() -> [P || {P} <- ets:tab2list(?CONNECTION_TABLE)]. connection_info_keys() -> rabbit_reader:info_keys(). -- cgit v1.2.1 From 672289cefb5532d74bb24924aebd14f4ac7afad8 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 17 Jan 2013 17:45:03 +0000 Subject: refactor: encapsulate rabbit_connection table --- include/rabbit.hrl | 2 -- src/rabbit_networking.erl | 11 ++++++++++- src/rabbit_reader.erl | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/rabbit.hrl b/include/rabbit.hrl index 397a3df6..7385b4b3 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -109,5 +109,3 @@ -define(INVALID_HEADERS_KEY, <<"x-invalid-headers">>). -define(ROUTING_HEADERS, [<<"CC">>, <<"BCC">>]). -define(DELETED_HEADER, <<"BCC">>). - --define(CONNECTION_TABLE, rabbit_connection). diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index e864140d..ee430fb4 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -18,7 +18,8 @@ -export([boot/0, start/0, start_tcp_listener/1, start_ssl_listener/2, stop_tcp_listener/1, on_node_down/1, active_listeners/0, - node_listeners/1, connections/0, connection_info_keys/0, + node_listeners/1, register_connection/1, unregister_connection/1, + connections/0, connection_info_keys/0, connection_info/1, connection_info/2, connection_info_all/0, connection_info_all/1, close_connection/2, force_connection_event_refresh/0, tcp_host/1]). @@ -40,6 +41,8 @@ -define(FIRST_TEST_BIND_PORT, 10000). +-define(CONNECTION_TABLE, rabbit_connection). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -65,6 +68,8 @@ -spec(stop_tcp_listener/1 :: (listener_config()) -> 'ok'). -spec(active_listeners/0 :: () -> [rabbit_types:listener()]). -spec(node_listeners/1 :: (node()) -> [rabbit_types:listener()]). +-spec(register_connection/1 :: (pid()) -> ok). +-spec(unregister_connection/1 :: (pid()) -> ok). -spec(connections/0 :: () -> [rabbit_types:connection()]). -spec(connections_local/0 :: () -> [rabbit_types:connection()]). -spec(connection_info_keys/0 :: () -> rabbit_types:info_keys()). @@ -295,6 +300,10 @@ start_client(Sock) -> start_ssl_client(SslOpts, Sock) -> start_client(Sock, ssl_transform_fun(SslOpts)). +register_connection(Pid) -> ets:insert(?CONNECTION_TABLE, {Pid}), ok. + +unregister_connection(Pid) -> ets:delete(?CONNECTION_TABLE, Pid), ok. + connections() -> rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running), rabbit_networking, connections_local, []). diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index f5ddf1b0..13459350 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -266,7 +266,7 @@ start_connection(Parent, ConnSupPid, Collector, StartHeartbeatFun, Deb, %% accounting as accurate as possible we ought to close the %% socket w/o delay before termination. rabbit_net:fast_close(ClientSock), - ets:delete(?CONNECTION_TABLE, self()), + rabbit_networking:unregister_connection(self()), rabbit_event:notify(connection_closed, [{pid, self()}]) end, done. @@ -722,7 +722,7 @@ handle_input(Callback, Data, _State) -> start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, Protocol, State = #v1{sock = Sock, connection = Connection}) -> - ets:insert(?CONNECTION_TABLE, {self()}), + rabbit_networking:register_connection(self()), Start = #'connection.start'{ version_major = ProtocolMajor, version_minor = ProtocolMinor, -- cgit v1.2.1 From 8ea1d6208a55a5e93e70515388a66f34f8948337 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 17 Jan 2013 18:47:34 +0000 Subject: simplifying refactor on rabbit_mnesia:discover_cluster --- src/rabbit_mnesia.erl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 6a442fec..d5efffa5 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -601,19 +601,16 @@ discover_cluster(Nodes) when is_list(Nodes) -> lists:foldl(fun (_, {ok, Res}) -> {ok, Res}; (Node, {error, _}) -> discover_cluster(Node) end, {error, no_nodes_provided}, Nodes); +discover_cluster(Node) when Node == node() -> + {error, {cannot_discover_cluster, "Cannot cluster node with itself"}}; discover_cluster(Node) -> OfflineError = {error, {cannot_discover_cluster, "The nodes provided are either offline or not running"}}, - case node() of - Node -> {error, {cannot_discover_cluster, - "Cannot cluster node with itself"}}; - _ -> case rpc:call(Node, - rabbit_mnesia, cluster_status_from_mnesia, []) of - {badrpc, _Reason} -> OfflineError; - {error, mnesia_not_running} -> OfflineError; - {ok, Res} -> {ok, Res} - end + case rpc:call(Node, rabbit_mnesia, cluster_status_from_mnesia, []) of + {badrpc, _Reason} -> OfflineError; + {error, mnesia_not_running} -> OfflineError; + {ok, Res} -> {ok, Res} end. schema_ok_or_move() -> -- cgit v1.2.1 From ab97f127fb502a5c64d289397720cf620da0e766 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 18 Jan 2013 11:53:05 +0000 Subject: Don't io:format the HiPE compilation warning. --- src/rabbit.erl | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index ef01bd88..7ba8a686 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -265,12 +265,21 @@ maybe_hipe_compile() -> {ok, Want} = application:get_env(rabbit, hipe_compile), Can = code:which(hipe) =/= non_existing, case {Want, Can} of - {true, true} -> hipe_compile(); - {true, false} -> io:format("Not HiPE compiling: HiPE not found in " - "this Erlang installation.~n"); - {false, _} -> ok + {true, true} -> hipe_compile(), + true; + {true, false} -> false; + {false, _} -> true end. +warn_if_hipe_compilation_failed(true) -> + ok; +warn_if_hipe_compilation_failed(false) -> + error_logger:warning_msg( + "Not HiPE compiling: HiPE not found in this Erlang installation.~n"). + +%% HiPE compilation happens before we have log handlers and can take a +%% long time, so make an exception to our no-stdout policy and display +%% progress via stdout. hipe_compile() -> Count = length(?HIPE_WORTHY), io:format("~nHiPE compiling: |~s|~n |", @@ -320,8 +329,9 @@ start() -> boot() -> start_it(fun() -> ok = ensure_application_loaded(), - maybe_hipe_compile(), + Success = maybe_hipe_compile(), ok = ensure_working_log_handlers(), + warn_if_hipe_compilation_failed(Success), rabbit_node_monitor:prepare_cluster_status_files(), ok = rabbit_upgrade:maybe_upgrade_mnesia(), %% It's important that the consistency check happens after -- cgit v1.2.1 From 70639db909ee3f0b514d8296ad934387a25eba9c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 18 Jan 2013 12:18:05 +0000 Subject: Remove that. --- include/rabbit.hrl | 1 - src/rabbit.erl | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/rabbit.hrl b/include/rabbit.hrl index 7385b4b3..78763045 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -88,7 +88,6 @@ -define(COPYRIGHT_MESSAGE, "Copyright (C) 2007-2012 VMware, Inc."). -define(INFORMATION_MESSAGE, "Licensed under the MPL. See http://www.rabbitmq.com/"). --define(PROTOCOL_VERSION, "AMQP 0-9-1 / 0-9 / 0-8"). -define(ERTS_MINIMUM, "5.6.3"). %% EMPTY_FRAME_SIZE, 8 = 1 + 2 + 4 + 1 diff --git a/src/rabbit.erl b/src/rabbit.erl index 7ba8a686..d9601ad1 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -740,8 +740,8 @@ log_banner() -> end, Banner = iolist_to_binary( rabbit_misc:format( - "~s ~s~n~s~n~s~n~s~n", - [Product, Version, ?PROTOCOL_VERSION, ?COPYRIGHT_MESSAGE, + "~s ~s~n~s~n~s~n", + [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]) ++ [case S of {"config file(s)" = K, []} -> -- cgit v1.2.1 From 44782048888c7818ec06c07311578775a42a4aa2 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 18 Jan 2013 12:21:59 +0000 Subject: These should go to the log, they have no excuse. --- src/rabbit_plugins.erl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rabbit_plugins.erl b/src/rabbit_plugins.erl index 9f94af7d..d2f36590 100644 --- a/src/rabbit_plugins.erl +++ b/src/rabbit_plugins.erl @@ -64,8 +64,8 @@ list(PluginsDir) -> [plugin_info(PluginsDir, Plug) || Plug <- EZs ++ FreeApps]), case Problems of [] -> ok; - _ -> io:format("Warning: Problem reading some plugins: ~p~n", - [Problems]) + _ -> error_logger:warning_msg( + "Problem reading some plugins: ~p~n", [Problems]) end, Plugins. @@ -112,8 +112,9 @@ prepare_plugins(EnabledFile, PluginsDistDir, ExpandDir) -> case Enabled -- plugin_names(ToUnpackPlugins) of [] -> ok; - Missing -> io:format("Warning: the following enabled plugins were " - "not found: ~p~n", [Missing]) + Missing -> error_logger:warning_msg( + "The following enabled plugins were not found: ~p~n", + [Missing]) end, %% Eliminate the contents of the destination directory -- cgit v1.2.1 From 54dde58d3128423a73eecf77117e417cb2ba663e Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 18 Jan 2013 12:32:50 +0000 Subject: Not sure that's worth special-casing --- src/rabbit.erl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index d9601ad1..dac0c4dd 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -691,12 +691,6 @@ force_event_refresh() -> %%--------------------------------------------------------------------------- %% misc -log_broker_started([]) -> - rabbit_misc:with_local_io( - fun() -> - error_logger:info_msg("Server startup complete~n", []), - io:format("~nBroker running~n") - end); log_broker_started(Plugins) -> rabbit_misc:with_local_io( fun() -> -- cgit v1.2.1 From ceb5f7fd0f030b5c92e6642f9ff0caeb18cbcc68 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 18 Jan 2013 12:34:27 +0000 Subject: Formatting tweaks --- src/rabbit.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index dac0c4dd..1900f794 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -695,7 +695,7 @@ log_broker_started(Plugins) -> rabbit_misc:with_local_io( fun() -> error_logger:info_msg( - "Server startup complete; plugins are:~n~n~p~n", [Plugins]), + "Server startup complete; plugins are: ~p~n", [Plugins]), io:format("~nBroker running with ~p plugins.~n", [length(Plugins)]) end). @@ -745,7 +745,7 @@ log_banner() -> {K, V} -> Format(K, V) end || S <- Settings]), - error_logger:info_msg("~s~n", [Banner]). + error_logger:info_msg("~s", [Banner]). home_dir() -> case init:get_argument(home) of -- cgit v1.2.1 From ad61bbf463646ff7f74ec80dc0feebff3fd24dad Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 18 Jan 2013 13:59:14 +0000 Subject: various reader related changes for AMQP 1.0 - mechanism for the reader to 'become' a different reader. - become the 1.0 reader if an AMQP 1.0 header is presented by a client and the reader is present. That way we can support 1.0 on the same port as 0-{8,9,9-1}. - defer starting of the channel_sup_sup and do that in the reader. This allows the AMQP 1.0 reader to start its own versio of the sup. It also makes aborted connections less costly. - track connections in an ets table rather than implicitly via the supervisor. That way AMQP 1.0 connections can exclude themselves, since they are already tracked via their direct connections. --- src/rabbit_connection_sup.erl | 7 +--- src/rabbit_networking.erl | 23 ++++++------- src/rabbit_reader.erl | 75 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/rabbit_connection_sup.erl b/src/rabbit_connection_sup.erl index 12a532b6..d9a4735c 100644 --- a/src/rabbit_connection_sup.erl +++ b/src/rabbit_connection_sup.erl @@ -42,16 +42,11 @@ start_link() -> SupPid, {collector, {rabbit_queue_collector, start_link, []}, intrinsic, ?MAX_WAIT, worker, [rabbit_queue_collector]}), - {ok, ChannelSupSupPid} = - supervisor2:start_child( - SupPid, - {channel_sup_sup, {rabbit_channel_sup_sup, start_link, []}, - intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}), {ok, ReaderPid} = supervisor2:start_child( SupPid, {reader, {rabbit_reader, start_link, - [ChannelSupSupPid, Collector, + [SupPid, Collector, rabbit_heartbeat:start_heartbeat_fun(SupPid)]}, intrinsic, ?MAX_WAIT, worker, [rabbit_reader]}), {ok, SupPid, ReaderPid}. diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index 31eeef73..ee430fb4 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -18,7 +18,8 @@ -export([boot/0, start/0, start_tcp_listener/1, start_ssl_listener/2, stop_tcp_listener/1, on_node_down/1, active_listeners/0, - node_listeners/1, connections/0, connection_info_keys/0, + node_listeners/1, register_connection/1, unregister_connection/1, + connections/0, connection_info_keys/0, connection_info/1, connection_info/2, connection_info_all/0, connection_info_all/1, close_connection/2, force_connection_event_refresh/0, tcp_host/1]). @@ -40,6 +41,8 @@ -define(FIRST_TEST_BIND_PORT, 10000). +-define(CONNECTION_TABLE, rabbit_connection). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -65,6 +68,8 @@ -spec(stop_tcp_listener/1 :: (listener_config()) -> 'ok'). -spec(active_listeners/0 :: () -> [rabbit_types:listener()]). -spec(node_listeners/1 :: (node()) -> [rabbit_types:listener()]). +-spec(register_connection/1 :: (pid()) -> ok). +-spec(unregister_connection/1 :: (pid()) -> ok). -spec(connections/0 :: () -> [rabbit_types:connection()]). -spec(connections_local/0 :: () -> [rabbit_types:connection()]). -spec(connection_info_keys/0 :: () -> rabbit_types:info_keys()). @@ -117,6 +122,7 @@ %%---------------------------------------------------------------------------- boot() -> + ets:new(?CONNECTION_TABLE, [public, named_table]), ok = start(), ok = boot_tcp(), ok = boot_ssl(). @@ -294,20 +300,15 @@ start_client(Sock) -> start_ssl_client(SslOpts, Sock) -> start_client(Sock, ssl_transform_fun(SslOpts)). +register_connection(Pid) -> ets:insert(?CONNECTION_TABLE, {Pid}), ok. + +unregister_connection(Pid) -> ets:delete(?CONNECTION_TABLE, Pid), ok. + connections() -> rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running), rabbit_networking, connections_local, []). -connections_local() -> - [Reader || - {_, ConnSup, supervisor, _} - <- supervisor:which_children(rabbit_tcp_client_sup), - Reader <- [try - rabbit_connection_sup:reader(ConnSup) - catch exit:{noproc, _} -> - noproc - end], - Reader =/= noproc]. +connections_local() -> [P || {P} <- ets:tab2list(?CONNECTION_TABLE)]. connection_info_keys() -> rabbit_reader:info_keys(). diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 7a28c8a3..13459350 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -23,7 +23,7 @@ -export([system_continue/3, system_terminate/4, system_code_change/4]). --export([init/4, mainloop/2]). +-export([init/4, mainloop/2, recvloop/2]). -export([conserve_resources/3, server_properties/1]). @@ -37,7 +37,8 @@ -record(v1, {parent, sock, connection, callback, recv_len, pending_recv, connection_state, queue_collector, heartbeater, stats_timer, - channel_sup_sup_pid, start_heartbeat_fun, buf, buf_len, throttle}). + conn_sup_pid, channel_sup_sup_pid, start_heartbeat_fun, + buf, buf_len, throttle}). -record(connection, {name, host, peer_host, port, peer_port, protocol, user, timeout_sec, frame_max, vhost, @@ -109,12 +110,12 @@ start_link(ChannelSupSupPid, Collector, StartHeartbeatFun) -> shutdown(Pid, Explanation) -> gen_server:call(Pid, {shutdown, Explanation}, infinity). -init(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun) -> +init(Parent, ConnSupPid, Collector, StartHeartbeatFun) -> Deb = sys:debug_options([]), receive {go, Sock, SockTransform} -> start_connection( - Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, Sock, + Parent, ConnSupPid, Collector, StartHeartbeatFun, Deb, Sock, SockTransform) end. @@ -203,7 +204,7 @@ name(Sock) -> socket_ends(Sock) -> socket_op(Sock, fun (S) -> rabbit_net:socket_ends(S, inbound) end). -start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, +start_connection(Parent, ConnSupPid, Collector, StartHeartbeatFun, Deb, Sock, SockTransform) -> process_flag(trap_exit, true), Name = name(Sock), @@ -234,7 +235,8 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, connection_state = pre_init, queue_collector = Collector, heartbeater = none, - channel_sup_sup_pid = ChannelSupSupPid, + conn_sup_pid = ConnSupPid, + channel_sup_sup_pid = none, start_heartbeat_fun = StartHeartbeatFun, buf = [], buf_len = 0, @@ -244,9 +246,10 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, last_blocked_at = never}}, try ok = inet_op(fun () -> rabbit_net:tune_buffer_size(ClientSock) end), - recvloop(Deb, switch_callback(rabbit_event:init_stats_timer( - State, #v1.stats_timer), - handshake, 8)), + run({?MODULE, recvloop, + [Deb, switch_callback(rabbit_event:init_stats_timer( + State, #v1.stats_timer), + handshake, 8)]}), log(info, "closing AMQP connection ~p (~s)~n", [self(), Name]) catch Ex -> log(case Ex of @@ -263,10 +266,16 @@ start_connection(Parent, ChannelSupSupPid, Collector, StartHeartbeatFun, Deb, %% accounting as accurate as possible we ought to close the %% socket w/o delay before termination. rabbit_net:fast_close(ClientSock), + rabbit_networking:unregister_connection(self()), rabbit_event:notify(connection_closed, [{pid, self()}]) end, done. +run({M, F, A}) -> + try apply(M, F, A) + catch {become, MFA} -> run(MFA) + end. + recvloop(Deb, State = #v1{pending_recv = true}) -> mainloop(Deb, State); recvloop(Deb, State = #v1{connection_state = blocked}) -> @@ -689,8 +698,17 @@ handle_input(handshake, <<"AMQP", 1, 1, 8, 0>>, State) -> handle_input(handshake, <<"AMQP", 1, 1, 9, 1>>, State) -> start_connection({8, 0, 0}, rabbit_framing_amqp_0_8, State); +%% ... and finally, the 1.0 spec is crystal clear! Note that the +%% TLS uses a different protocol number, and would go here. +handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>, State) -> + become_1_0(amqp, {0, 1, 0, 0}, State); + +%% 3 stands for "SASL" +handle_input(handshake, <<"AMQP", 3, 1, 0, 0>>, State) -> + become_1_0(sasl, {3, 1, 0, 0}, State); + handle_input(handshake, <<"AMQP", A, B, C, D>>, #v1{sock = Sock}) -> - refuse_connection(Sock, {bad_version, A, B, C, D}); + refuse_connection(Sock, {bad_version, {A, B, C, D}}); handle_input(handshake, Other, #v1{sock = Sock}) -> refuse_connection(Sock, {bad_header, Other}); @@ -704,6 +722,7 @@ handle_input(Callback, Data, _State) -> start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, Protocol, State = #v1{sock = Sock, connection = Connection}) -> + rabbit_networking:register_connection(self()), Start = #'connection.start'{ version_major = ProtocolMajor, version_minor = ProtocolMinor, @@ -799,17 +818,24 @@ handle_method0(#'connection.open'{virtual_host = VHostPath}, connection = Connection = #connection{ user = User, protocol = Protocol}, + conn_sup_pid = ConnSupPid, sock = Sock, throttle = Throttle}) -> ok = rabbit_access_control:check_vhost_access(User, VHostPath), NewConnection = Connection#connection{vhost = VHostPath}, ok = send_on_channel0(Sock, #'connection.open_ok'{}, Protocol), Conserve = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}), + Throttle1 = Throttle#throttle{conserve_resources = Conserve}, + {ok, ChannelSupSupPid} = + supervisor2:start_child( + ConnSupPid, + {channel_sup_sup, {rabbit_channel_sup_sup, start_link, []}, + intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}), State1 = control_throttle( - State#v1{connection_state = running, - connection = NewConnection, - throttle = Throttle#throttle{ - conserve_resources = Conserve}}), + State#v1{connection_state = running, + connection = NewConnection, + channel_sup_sup_pid = ChannelSupSupPid, + throttle = Throttle1}), rabbit_event:notify(connection_created, [{type, network} | infos(?CREATION_EVENT_KEYS, State1)]), @@ -979,3 +1005,24 @@ cert_info(F, #v1{sock = Sock}) -> emit_stats(State) -> rabbit_event:notify(connection_stats, infos(?STATISTICS_KEYS, State)), rabbit_event:reset_stats_timer(State, #v1.stats_timer). + +%% 1.0 stub + +become_1_0(Mode, Version, State = #v1{sock = Sock}) -> + case code:is_loaded(rabbit_amqp1_0_reader) of + false -> refuse_connection(Sock, {bad_version, Version}); + _ -> throw({become, {rabbit_amqp1_0_reader, become, + [Mode, pack_for_1_0(State)]}}) + end. + +pack_for_1_0(#v1{parent = Parent, + sock = Sock, + recv_len = RecvLen, + pending_recv = PendingRecv, + queue_collector = QueueCollector, + conn_sup_pid = ConnSupPid, + start_heartbeat_fun = SHF, + buf = Buf, + buf_len = BufLen}) -> + {Parent, Sock, RecvLen, PendingRecv, QueueCollector, ConnSupPid, SHF, + Buf, BufLen}. -- cgit v1.2.1 From d5679358424983a04b70e7e605f4c311fcc0395e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 18 Jan 2013 14:04:08 +0000 Subject: fix test connections only show up in 'list_connections' after the protocol header has been sent --- src/rabbit_tests.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 7257827a..b845360e 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1123,7 +1123,8 @@ test_server_status() -> [L || L = #listener{node = N} <- rabbit_networking:active_listeners(), N =:= node()], - {ok, _C} = gen_tcp:connect(H, P, []), + {ok, C} = gen_tcp:connect(H, P, []), + gen_tcp:send(C, <<"AMQP", 0, 0, 9, 1>>), timer:sleep(100), ok = info_action(list_connections, rabbit_networking:connection_info_keys(), false), -- cgit v1.2.1 From 96e34b42f50beff14f918f0f570155e2b10fc56a Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 14:17:31 +0000 Subject: cosmetic --- src/rabbit_backing_queue.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 99b5946e..9a3c67f9 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -71,8 +71,8 @@ %% content. -callback delete_and_terminate(any(), state()) -> state(). -%% Remove all messages in the queue, but not messages which have been -%% fetched and are pending acks. +%% Remove all 'fetchable' messages from the queue, i.e. all messages +%% except those that have been fetched already and are pending acks. -callback purge(state()) -> {purged_msg_count(), state()}. %% Publish a message. -- cgit v1.2.1 From 3bfb99cd754170f93d64851a0dfc05c7b6decc51 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 14:19:43 +0000 Subject: tiny refactor on variable_queue_with_holes --- src/rabbit_tests.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index b845360e..13454d31 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2328,7 +2328,8 @@ test_variable_queue() -> passed. test_variable_queue_fold(VQ0) -> - {Count, RequeuedMsgs, FreshMsgs, VQ1} = variable_queue_with_holes(VQ0), + {RequeuedMsgs, FreshMsgs, VQ1} = variable_queue_with_holes(VQ0), + Count = rabbit_variable_queue:len(VQ1), Msgs = RequeuedMsgs ++ FreshMsgs, lists:foldl( fun (Cut, VQ2) -> test_variable_queue_fold(Cut, Msgs, VQ2) end, @@ -2398,10 +2399,10 @@ variable_queue_with_holes(VQ0) -> Depth = rabbit_variable_queue:depth(VQ8), Len = Depth - length(Subset3), Len = rabbit_variable_queue:len(VQ8), - {Len, (Seq -- Seq3), lists:seq(Count + 1, Count + 64), VQ8}. + {(Seq -- Seq3), lists:seq(Count + 1, Count + 64), VQ8}. test_variable_queue_requeue(VQ0) -> - {_, RequeuedMsgs, FreshMsgs, VQ1} = variable_queue_with_holes(VQ0), + {RequeuedMsgs, FreshMsgs, VQ1} = variable_queue_with_holes(VQ0), Msgs = lists:zip(RequeuedMsgs, lists:duplicate(length(RequeuedMsgs), true)) ++ -- cgit v1.2.1 From ee6e1d0f90be6b2515471d7ee9e0e3989897e8c2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 14:20:51 +0000 Subject: add BQ:purge_acks/1 --- src/rabbit_backing_queue.erl | 6 +++++- src/rabbit_mirror_queue_master.erl | 4 +++- src/rabbit_variable_queue.erl | 4 +++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index 9a3c67f9..2b43c8ba 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -75,6 +75,10 @@ %% except those that have been fetched already and are pending acks. -callback purge(state()) -> {purged_msg_count(), state()}. +%% Remove all messages in the queue which have been fetched and are +%% pending acks. +-callback purge_acks(state()) -> state(). + %% Publish a message. -callback publish(rabbit_types:basic_message(), rabbit_types:message_properties(), boolean(), pid(), @@ -226,7 +230,7 @@ behaviour_info(callbacks) -> [{start, 1}, {stop, 0}, {init, 3}, {terminate, 2}, - {delete_and_terminate, 2}, {purge, 1}, {publish, 5}, + {delete_and_terminate, 2}, {purge, 1}, {purge_acks, 1}, {publish, 5}, {publish_delivered, 4}, {discard, 3}, {drain_confirmed, 1}, {dropwhile, 2}, {fetchwhile, 4}, {fetch, 2}, {ack, 2}, {requeue, 2}, {ackfold, 4}, {fold, 3}, {len, 1}, diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl index b5f72cad..c704804e 100644 --- a/src/rabbit_mirror_queue_master.erl +++ b/src/rabbit_mirror_queue_master.erl @@ -17,7 +17,7 @@ -module(rabbit_mirror_queue_master). -export([init/3, terminate/2, delete_and_terminate/2, - purge/1, publish/5, publish_delivered/4, + purge/1, purge_acks/1, publish/5, publish_delivered/4, discard/3, fetch/2, drop/2, ack/2, requeue/2, ackfold/4, fold/3, len/1, is_empty/1, depth/1, drain_confirmed/1, dropwhile/2, fetchwhile/4, set_ram_duration_target/2, ram_duration/1, @@ -198,6 +198,8 @@ purge(State = #state { gm = GM, {Count, BQS1} = BQ:purge(BQS), {Count, State #state { backing_queue_state = BQS1 }}. +purge_acks(_State) -> exit({not_implemented, {?MODULE, purge_acks}}). + publish(Msg = #basic_message { id = MsgId }, MsgProps, IsDelivered, ChPid, State = #state { gm = GM, seen_status = SS, diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 8a7045ea..7e09e5e3 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -16,7 +16,7 @@ -module(rabbit_variable_queue). --export([init/3, terminate/2, delete_and_terminate/2, purge/1, +-export([init/3, terminate/2, delete_and_terminate/2, purge/1, purge_acks/1, publish/5, publish_delivered/4, discard/3, drain_confirmed/1, dropwhile/2, fetchwhile/4, fetch/2, drop/2, ack/2, requeue/2, ackfold/4, fold/3, len/1, @@ -519,6 +519,8 @@ purge(State = #vqstate { q4 = Q4, ram_msg_count = 0, persistent_count = PCount1 })}. +purge_acks(State) -> a(purge_pending_ack(false, State)). + publish(Msg = #basic_message { is_persistent = IsPersistent, id = MsgId }, MsgProps = #message_properties { needs_confirming = NeedsConfirming }, IsDelivered, _ChPid, State = #vqstate { q1 = Q1, q3 = Q3, q4 = Q4, -- cgit v1.2.1 From a4891ce1d6006c6f36c8408d96028b7b3ee35be9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 14:43:56 +0000 Subject: add a test --- src/rabbit_tests.erl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 13454d31..7bd8d541 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2323,6 +2323,7 @@ test_variable_queue() -> fun test_dropwhile_varying_ram_duration/1, fun test_fetchwhile_varying_ram_duration/1, fun test_variable_queue_ack_limiting/1, + fun test_variable_queue_purge/1, fun test_variable_queue_requeue/1, fun test_variable_queue_fold/1]], passed. @@ -2418,6 +2419,21 @@ test_variable_queue_requeue(VQ0) -> {empty, VQ3} = rabbit_variable_queue:fetch(true, VQ2), VQ3. +test_variable_queue_purge(VQ0) -> + LenDepth = fun (VQ) -> + {rabbit_variable_queue:len(VQ), + rabbit_variable_queue:depth(VQ)} + end, + VQ1 = variable_queue_publish(false, 10, VQ0), + {VQ2, Acks} = variable_queue_fetch(6, false, false, 10, VQ1), + {4, VQ3} = rabbit_variable_queue:purge(VQ2), + {0, 6} = LenDepth(VQ3), + {_, VQ4} = rabbit_variable_queue:requeue(lists:sublist(Acks, 2), VQ3), + {2, 6} = LenDepth(VQ4), + VQ5 = rabbit_variable_queue:purge_acks(VQ4), + {2, 2} = LenDepth(VQ5), + VQ5. + test_variable_queue_ack_limiting(VQ0) -> %% start by sending in a bunch of messages Len = 1024, -- cgit v1.2.1 From 788524d54c276e721258814d74b51325556248b7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 14:56:58 +0000 Subject: cosmetic --- src/rabbit.erl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 7b8348fc..0f3c52ca 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -552,13 +552,11 @@ boot_error(Reason, Stacktrace) -> Args = [Reason, log_location(kernel), log_location(sasl)], boot_error(Reason, Fmt, Args, Stacktrace). +boot_error(Reason, Fmt, Args, not_available) -> + basic_boot_error(Reason, Fmt, Args); boot_error(Reason, Fmt, Args, Stacktrace) -> - case Stacktrace of - not_available -> basic_boot_error(Reason, Fmt, Args); - _ -> basic_boot_error(Reason, Fmt ++ - "Stack trace:~n ~p~n~n", - Args ++ [Stacktrace]) - end. + basic_boot_error(Reason, Fmt ++ "Stack trace:~n ~p~n~n", + Args ++ [Stacktrace]). basic_boot_error(Reason, Format, Args) -> io:format("~n~nBOOT FAILED~n===========~n~n" ++ Format, Args), -- cgit v1.2.1 From 374d44968ec18d1cc4ad51568d999ca59c839b80 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 15:46:43 +0000 Subject: eliminate "Function X has no local return" dialyzer errors --- src/rabbit.erl | 7 +++++++ src/rabbit_channel.erl | 6 ++++++ src/rabbit_exchange_type_invalid.erl | 4 ++++ src/rabbit_reader.erl | 7 ++++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 0f3c52ca..16694105 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -533,6 +533,9 @@ sort_boot_steps(UnsortedSteps) -> end]) end. +-ifdef(use_specs). +-spec(boot_error/2 :: (term(), not_available | [tuple()]) -> no_return()). +-endif. boot_error(Term={error, {timeout_waiting_for_tables, _}}, _Stacktrace) -> AllNodes = rabbit_mnesia:cluster_nodes(all), {Err, Nodes} = @@ -552,6 +555,10 @@ boot_error(Reason, Stacktrace) -> Args = [Reason, log_location(kernel), log_location(sasl)], boot_error(Reason, Fmt, Args, Stacktrace). +-ifdef(use_specs). +-spec(boot_error/4 :: (term(), string(), [any()], not_available | [tuple()]) + -> no_return()). +-endif. boot_error(Reason, Fmt, Args, not_available) -> basic_boot_error(Reason, Fmt, Args); boot_error(Reason, Fmt, Args, Stacktrace) -> diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 88e3dfc5..2b89be8f 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -412,8 +412,14 @@ handle_exception(Reason, State = #ch{protocol = Protocol, {stop, normal, State1} end. +-ifdef(use_specs). +-spec(precondition_failed/1 :: (string()) -> no_return()). +-endif. precondition_failed(Format) -> precondition_failed(Format, []). +-ifdef(use_specs). +-spec(precondition_failed/2 :: (string(), [any()]) -> no_return()). +-endif. precondition_failed(Format, Params) -> rabbit_misc:protocol_error(precondition_failed, Format, Params). diff --git a/src/rabbit_exchange_type_invalid.erl b/src/rabbit_exchange_type_invalid.erl index 101fe434..c5d781c2 100644 --- a/src/rabbit_exchange_type_invalid.erl +++ b/src/rabbit_exchange_type_invalid.erl @@ -31,6 +31,10 @@ description() -> serialise_events() -> false. +-ifdef(use_specs). +-spec(route/2 :: (rabbit_types:exchange(), rabbit_types:delivery()) + -> no_return()). +-endif. route(#exchange{name = Name, type = Type}, _) -> rabbit_misc:protocol_error( precondition_failed, diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 13459350..ae832749 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -1007,7 +1007,12 @@ emit_stats(State) -> rabbit_event:reset_stats_timer(State, #v1.stats_timer). %% 1.0 stub - +-ifdef(use_specs). +-spec(become_1_0/3 :: ('amqp' | 'sasl', + {non_neg_integer(), non_neg_integer(), + non_neg_integer(), non_neg_integer()}, + #v1{}) -> no_return()). +-endif. become_1_0(Mode, Version, State = #v1{sock = Sock}) -> case code:is_loaded(rabbit_amqp1_0_reader) of false -> refuse_connection(Sock, {bad_version, Version}); -- cgit v1.2.1 From 1e9492f35c5d13f8a49be16c4649cddd95bd32b7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 17:03:56 +0000 Subject: add xmerl to plt so we get fewer 'Unknown functions' in dialyzer mochijson2 depends on it --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c63e3dfd..bf33b931 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ $(BASIC_PLT): $(BEAM_TARGETS) else \ dialyzer --output_plt $@ --build_plt \ --apps erts kernel stdlib compiler sasl os_mon mnesia tools \ - public_key crypto ssl; \ + public_key crypto ssl xmerl; \ fi clean: -- cgit v1.2.1 From 005788d47882dade23b7c3b605bcafde4107222d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 20:16:22 +0000 Subject: eager sync of messages pending ack --- docs/rabbitmqctl.1.xml | 3 +-- src/rabbit_amqqueue.erl | 3 +-- src/rabbit_amqqueue_process.erl | 12 ++++-------- src/rabbit_mirror_queue_slave.erl | 3 +-- src/rabbit_mirror_queue_sync.erl | 26 ++++++++++++++++---------- 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/docs/rabbitmqctl.1.xml b/docs/rabbitmqctl.1.xml index c7069aed..bbd2fe5b 100644 --- a/docs/rabbitmqctl.1.xml +++ b/docs/rabbitmqctl.1.xml @@ -465,8 +465,7 @@ synchronise itself. The queue will block while synchronisation takes place (all publishers to and consumers from the queue will block). The queue must be - mirrored, and must not have any pending unacknowledged - messages for this command to succeed. + mirrored for this command to succeed. Note that unsynchronised queues from which messages are diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 2477b891..21b6bb92 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -174,8 +174,7 @@ (rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'). -spec(start_mirroring/1 :: (pid()) -> 'ok'). -spec(stop_mirroring/1 :: (pid()) -> 'ok'). --spec(sync_mirrors/1 :: (pid()) -> - 'ok' | rabbit_types:error('pending_acks' | 'not_mirrored')). +-spec(sync_mirrors/1 :: (pid()) -> 'ok' | rabbit_types:error('not_mirrored')). -spec(cancel_sync_mirrors/1 :: (pid()) -> 'ok' | {'ok', 'not_syncing'}). -endif. diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 0a07a005..2795e317 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1163,7 +1163,7 @@ handle_call({requeue, AckTags, ChPid}, From, State) -> noreply(requeue(AckTags, ChPid, State)); handle_call(sync_mirrors, _From, - State = #q{backing_queue = rabbit_mirror_queue_master = BQ, + State = #q{backing_queue = rabbit_mirror_queue_master, backing_queue_state = BQS}) -> S = fun(BQSN) -> State#q{backing_queue_state = BQSN} end, HandleInfo = fun (Status) -> @@ -1179,13 +1179,9 @@ handle_call(sync_mirrors, _From, State, #q.stats_timer, fun() -> emit_stats(State#q{status = Status}) end) end, - case BQ:depth(BQS) - BQ:len(BQS) of - 0 -> case rabbit_mirror_queue_master:sync_mirrors( - HandleInfo, EmitStats, BQS) of - {ok, BQS1} -> reply(ok, S(BQS1)); - {stop, Reason, BQS1} -> {stop, Reason, S(BQS1)} - end; - _ -> reply({error, pending_acks}, State) + case rabbit_mirror_queue_master:sync_mirrors(HandleInfo, EmitStats, BQS) of + {ok, BQS1} -> reply(ok, S(BQS1)); + {stop, Reason, BQS1} -> {stop, Reason, S(BQS1)} end; handle_call(sync_mirrors, _From, State) -> diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 9f12b34e..b63fccc9 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -230,7 +230,6 @@ handle_cast({sync_start, Ref, Syncer}, S = fun({TRefN, BQSN}) -> State1#state{depth_delta = undefined, rate_timer_ref = TRefN, backing_queue_state = BQSN} end, - %% [0] We can only sync when there are no pending acks case rabbit_mirror_queue_sync:slave( DD, Ref, TRef, Syncer, BQ, BQS, fun (BQN, BQSN) -> @@ -240,7 +239,7 @@ handle_cast({sync_start, Ref, Syncer}, {TRefN, BQSN1} end) of denied -> noreply(State1); - {ok, Res} -> noreply(set_delta(0, S(Res))); %% [0] + {ok, Res} -> noreply(set_delta(0, S(Res))); {failed, Res} -> noreply(S(Res)); {stop, Reason, Res} -> {stop, Reason, S(Res)} end; diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index 4d6b1fc9..b023823e 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -91,16 +91,16 @@ master_go(Syncer, Ref, Log, HandleInfo, EmitStats, BQ, BQS) -> end. master_go0(Args, BQ, BQS) -> - case BQ:fold(fun (Msg, MsgProps, false, Acc) -> - master_send(Msg, MsgProps, Args, Acc) + case BQ:fold(fun (Msg, MsgProps, Unacked, Acc) -> + master_send(Msg, MsgProps, Unacked, Args, Acc) end, {0, erlang:now()}, BQS) of {{shutdown, Reason}, BQS1} -> {shutdown, Reason, BQS1}; {{sync_died, Reason}, BQS1} -> {sync_died, Reason, BQS1}; {_, BQS1} -> master_done(Args, BQS1) end. -master_send(Msg, MsgProps, {Syncer, Ref, Log, HandleInfo, EmitStats, Parent}, - {I, Last}) -> +master_send(Msg, MsgProps, Unacked, + {Syncer, Ref, Log, HandleInfo, EmitStats, Parent}, {I, Last}) -> T = case timer:now_diff(erlang:now(), Last) > ?SYNC_PROGRESS_INTERVAL of true -> EmitStats({syncing, I}), Log("~p messages", [I]), @@ -119,7 +119,7 @@ master_send(Msg, MsgProps, {Syncer, Ref, Log, HandleInfo, EmitStats, Parent}, cancel_sync_mirrors} -> stop_syncer(Syncer, {cancel, Ref}), gen_server2:reply(From, ok), {stop, cancelled}; - {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps}, + {next, Ref} -> Syncer ! {msg, Ref, Msg, MsgProps, Unacked}, {cont, {I + 1, T}}; {'EXIT', Parent, Reason} -> {stop, {shutdown, Reason}}; {'EXIT', Syncer, Reason} -> {stop, {sync_died, Reason}} @@ -164,11 +164,11 @@ syncer(Ref, Log, MPid, SPids) -> syncer_loop(Ref, MPid, SPids) -> MPid ! {next, Ref}, receive - {msg, Ref, Msg, MsgProps} -> + {msg, Ref, Msg, MsgProps, Unacked} -> SPids1 = wait_for_credit(SPids), [begin credit_flow:send(SPid), - SPid ! {sync_msg, Ref, Msg, MsgProps} + SPid ! {sync_msg, Ref, Msg, MsgProps, Unacked} end || SPid <- SPids1], syncer_loop(Ref, MPid, SPids1); {cancel, Ref} -> @@ -204,7 +204,7 @@ slave(0, Ref, _TRef, Syncer, _BQ, _BQS, _UpdateRamDuration) -> slave(_DD, Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> MRef = erlang:monitor(process, Syncer), Syncer ! {sync_ready, Ref, self()}, - {_MsgCount, BQS1} = BQ:purge(BQS), + {_MsgCount, BQS1} = BQ:purge(BQ:purge_acks(BQS)), slave_sync_loop({Ref, MRef, Syncer, BQ, UpdateRamDuration, rabbit_misc:get_parent()}, TRef, BQS1). @@ -237,10 +237,16 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, update_ram_duration -> {TRef1, BQS1} = UpdateRamDuration(BQ, BQS), slave_sync_loop(Args, TRef1, BQS1); - {sync_msg, Ref, Msg, Props} -> + {sync_msg, Ref, Msg, Props, Unacked} -> credit_flow:ack(Syncer), Props1 = Props#message_properties{needs_confirming = false}, - BQS1 = BQ:publish(Msg, Props1, true, none, BQS), + BQS1 = case Unacked of + false -> BQ:publish(Msg, Props1, true, none, BQS); + true -> {_AckTag, BQS2} = BQ:publish_delivered( + Msg, Props1, none, BQS), + %% TODO do something w AckTag + BQS2 + end, slave_sync_loop(Args, TRef, BQS1); {'EXIT', Parent, Reason} -> {stop, Reason, {TRef, BQS}}; -- cgit v1.2.1 From f27c502034c9e5218e280c4a39da88562b466f51 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 19 Jan 2013 21:32:01 +0000 Subject: populate slave's msg_id_ack with sync'ed messages pending ack --- src/rabbit_mirror_queue_slave.erl | 9 +++++--- src/rabbit_mirror_queue_sync.erl | 45 +++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index b63fccc9..cd2a8042 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -227,9 +227,12 @@ handle_cast({sync_start, Ref, Syncer}, backing_queue = BQ, backing_queue_state = BQS }) -> State1 = #state{rate_timer_ref = TRef} = ensure_rate_timer(State), - S = fun({TRefN, BQSN}) -> State1#state{depth_delta = undefined, - rate_timer_ref = TRefN, - backing_queue_state = BQSN} end, + S = fun({MA, TRefN, BQSN}) -> + State1#state{depth_delta = undefined, + msg_id_ack = dict:from_list(MA), + rate_timer_ref = TRefN, + backing_queue_state = BQSN} + end, case rabbit_mirror_queue_sync:slave( DD, Ref, TRef, Syncer, BQ, BQS, fun (BQN, BQSN) -> diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl index b023823e..b8cfe4a9 100644 --- a/src/rabbit_mirror_queue_sync.erl +++ b/src/rabbit_mirror_queue_sync.erl @@ -57,6 +57,9 @@ -type(log_fun() :: fun ((string(), [any()]) -> 'ok')). -type(bq() :: atom()). -type(bqs() :: any()). +-type(ack() :: any()). +-type(slave_sync_state() :: {[{rabbit_types:msg_id(), ack()}], timer:tref(), + bqs()}). -spec(master_prepare/3 :: (reference(), log_fun(), [pid()]) -> pid()). -spec(master_go/7 :: (pid(), reference(), log_fun(), @@ -69,8 +72,8 @@ -spec(slave/7 :: (non_neg_integer(), reference(), timer:tref(), pid(), bq(), bqs(), fun((bq(), bqs()) -> {timer:tref(), bqs()})) -> 'denied' | - {'ok' | 'failed', {timer:tref(), bqs()}} | - {'stop', any(), {timer:tref(), bqs()}}). + {'ok' | 'failed', slave_sync_state()} | + {'stop', any(), slave_sync_state()}). -endif. @@ -206,10 +209,10 @@ slave(_DD, Ref, TRef, Syncer, BQ, BQS, UpdateRamDuration) -> Syncer ! {sync_ready, Ref, self()}, {_MsgCount, BQS1} = BQ:purge(BQ:purge_acks(BQS)), slave_sync_loop({Ref, MRef, Syncer, BQ, UpdateRamDuration, - rabbit_misc:get_parent()}, TRef, BQS1). + rabbit_misc:get_parent()}, {[], TRef, BQS1}). slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, - TRef, BQS) -> + State = {MA, TRef, BQS}) -> receive {'DOWN', MRef, process, Syncer, _Reason} -> %% If the master dies half way we are not in the usual @@ -218,40 +221,40 @@ slave_sync_loop(Args = {Ref, MRef, Syncer, BQ, UpdateRamDuration, Parent}, %% sync with a newly promoted master, or even just receive %% messages from it, we have a hole in the middle. So the %% only thing to do here is purge. - {_MsgCount, BQS1} = BQ:purge(BQS), + {_MsgCount, BQS1} = BQ:purge(BQ:purge_acks(BQS)), credit_flow:peer_down(Syncer), - {failed, {TRef, BQS1}}; + {failed, {[], TRef, BQS1}}; {bump_credit, Msg} -> credit_flow:handle_bump_msg(Msg), - slave_sync_loop(Args, TRef, BQS); + slave_sync_loop(Args, State); {sync_complete, Ref} -> erlang:demonitor(MRef, [flush]), credit_flow:peer_down(Syncer), - {ok, {TRef, BQS}}; + {ok, State}; {'$gen_cast', {set_maximum_since_use, Age}} -> ok = file_handle_cache:set_maximum_since_use(Age), - slave_sync_loop(Args, TRef, BQS); + slave_sync_loop(Args, State); {'$gen_cast', {set_ram_duration_target, Duration}} -> BQS1 = BQ:set_ram_duration_target(Duration, BQS), - slave_sync_loop(Args, TRef, BQS1); + slave_sync_loop(Args, {MA, TRef, BQS1}); update_ram_duration -> {TRef1, BQS1} = UpdateRamDuration(BQ, BQS), - slave_sync_loop(Args, TRef1, BQS1); + slave_sync_loop(Args, {MA, TRef1, BQS1}); {sync_msg, Ref, Msg, Props, Unacked} -> credit_flow:ack(Syncer), Props1 = Props#message_properties{needs_confirming = false}, - BQS1 = case Unacked of - false -> BQ:publish(Msg, Props1, true, none, BQS); - true -> {_AckTag, BQS2} = BQ:publish_delivered( - Msg, Props1, none, BQS), - %% TODO do something w AckTag - BQS2 - end, - slave_sync_loop(Args, TRef, BQS1); + {MA1, BQS1} = + case Unacked of + false -> {MA, BQ:publish(Msg, Props1, true, none, BQS)}; + true -> {AckTag, BQS2} = BQ:publish_delivered( + Msg, Props1, none, BQS), + {[{Msg#basic_message.id, AckTag} | MA], BQS2} + end, + slave_sync_loop(Args, {MA1, TRef, BQS1}); {'EXIT', Parent, Reason} -> - {stop, Reason, {TRef, BQS}}; + {stop, Reason, State}; %% If the master throws an exception {'$gen_cast', {gm, {delete_and_terminate, Reason}}} -> BQ:delete_and_terminate(Reason, BQS), - {stop, Reason, {TRef, undefined}} + {stop, Reason, {[], TRef, undefined}} end. -- cgit v1.2.1 From 488057258e8bd53a62348bd82ae0c70c268638ad Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 20 Jan 2013 13:20:53 +0000 Subject: cosmetic: move spec of internal function and make it more precise --- src/rabbit_mirror_queue_slave.erl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 9f12b34e..867aa2ed 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -37,18 +37,10 @@ -include("rabbit.hrl"). -%%---------------------------------------------------------------------------- - -include("gm_specs.hrl"). --ifdef(use_specs). -%% Shut dialyzer up --spec(promote_me/2 :: (_, _) -> no_return()). --endif. - %%---------------------------------------------------------------------------- - -define(CREATION_EVENT_KEYS, [pid, name, @@ -79,6 +71,8 @@ depth_delta }). +%%---------------------------------------------------------------------------- + start_link(Q) -> gen_server2:start_link(?MODULE, Q, []). set_maximum_since_use(QPid, Age) -> @@ -469,6 +463,9 @@ confirm_messages(MsgIds, State = #state { msg_id_status = MS }) -> handle_process_result({ok, State}) -> noreply(State); handle_process_result({stop, State}) -> {stop, normal, State}. +-ifdef(use_specs). +-spec(promote_me/2 :: ({pid(), term()}, #state{}) -> no_return()). +-endif. promote_me(From, #state { q = Q = #amqqueue { name = QName }, gm = GM, backing_queue = BQ, -- cgit v1.2.1 From 154510f6dc1ae3146b21b02d2c07a7a9d3ff8183 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 21 Jan 2013 13:06:59 +0000 Subject: USe pg_local rather than an ets table. --- src/rabbit_networking.erl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index ee430fb4..080e0987 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -41,8 +41,6 @@ -define(FIRST_TEST_BIND_PORT, 10000). --define(CONNECTION_TABLE, rabbit_connection). - %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -122,7 +120,6 @@ %%---------------------------------------------------------------------------- boot() -> - ets:new(?CONNECTION_TABLE, [public, named_table]), ok = start(), ok = boot_tcp(), ok = boot_ssl(). @@ -300,15 +297,15 @@ start_client(Sock) -> start_ssl_client(SslOpts, Sock) -> start_client(Sock, ssl_transform_fun(SslOpts)). -register_connection(Pid) -> ets:insert(?CONNECTION_TABLE, {Pid}), ok. +register_connection(Pid) -> pg_local:join(rabbit_connections, Pid). -unregister_connection(Pid) -> ets:delete(?CONNECTION_TABLE, Pid), ok. +unregister_connection(Pid) -> pg_local:leave(rabbit_connections, Pid). connections() -> rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running), rabbit_networking, connections_local, []). -connections_local() -> [P || {P} <- ets:tab2list(?CONNECTION_TABLE)]. +connections_local() -> pg_local:get_members(rabbit_connections). connection_info_keys() -> rabbit_reader:info_keys(). -- cgit v1.2.1 From 711290d644b3e43e0805ced7b83747b7520b8633 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Tue, 22 Jan 2013 12:16:09 +0000 Subject: revert spurious changes to test timings --- src/test_sup.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_sup.erl b/src/test_sup.erl index b84acdb4..955c44e6 100644 --- a/src/test_sup.erl +++ b/src/test_sup.erl @@ -50,7 +50,7 @@ test_supervisor_delayed_restart(SupPid) -> ok = exit_child(SupPid), timer:sleep(100), timeout = ping_child(SupPid), - timer:sleep(1100), + timer:sleep(1000), ok = ping_child(SupPid), passed. @@ -73,7 +73,7 @@ ping_child(SupPid) -> Ref = make_ref(), with_child_pid(SupPid, fun(ChildPid) -> ChildPid ! {ping, Ref, self()} end), receive {pong, Ref} -> ok - after 1100 -> timeout + after 1000 -> timeout end. exit_child(SupPid) -> -- cgit v1.2.1 From 82be213f82e7877c49f214bde6fcc7c4514e0734 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 22 Jan 2013 12:59:04 +0000 Subject: Remove knowledge of AMQP frames from the limiter. (attempt 2) --- src/rabbit_channel.erl | 24 +++++++++++++++++++++++- src/rabbit_limiter.erl | 10 ++-------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 5ee030b1..9cb37c4f 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -21,7 +21,8 @@ -behaviour(gen_server2). -export([start_link/11, do/2, do/3, do_flow/3, flush/1, shutdown/1]). --export([send_command/2, deliver/4, flushed/2]). +-export([send_command/2, deliver/4, send_credit_reply/2, send_drained/3, + flushed/2]). -export([list/0, info_keys/0, info/1, info/2, info_all/0, info_all/1]). -export([refresh_config_local/0, ready_for_close/1]). -export([force_event_refresh/0]). @@ -138,6 +139,12 @@ send_command(Pid, Msg) -> deliver(Pid, ConsumerTag, AckRequired, Msg) -> gen_server2:cast(Pid, {deliver, ConsumerTag, AckRequired, Msg}). +send_credit_reply(Pid, Len) -> + gen_server2:cast(Pid, {send_credit_reply, Len}). + +send_drained(Pid, ConsumerTag, Count) -> + gen_server2:cast(Pid, {send_drained, ConsumerTag, Count}). + flushed(Pid, QPid) -> gen_server2:cast(Pid, {flushed, QPid}). @@ -314,6 +321,21 @@ handle_cast({deliver, ConsumerTag, AckRequired, Content), noreply(record_sent(ConsumerTag, AckRequired, Msg, State)); +handle_cast({send_credit_reply, Len}, State = #ch{writer_pid = WriterPid}) -> + ok = rabbit_writer:send_command( + WriterPid, #'basic.credit_ok'{available = Len}), + noreply(State); + +handle_cast({send_drained, ConsumerTag, Count}, + State = #ch{writer_pid = WriterPid}) -> + ok = rabbit_writer:send_command( + WriterPid, #'basic.credit_state'{consumer_tag = ConsumerTag, + credit = 0, + count = Count, + available = 0, + drain = true}), + noreply(State); + handle_cast(force_event_refresh, State) -> rabbit_event:notify(channel_created, infos(?CREATION_EVENT_KEYS, State)), noreply(State); diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 57fd0c26..401723a4 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -148,8 +148,7 @@ inform(Limiter = #token{q_state = Credits}, {Unblock, Credits2} = update_credit( CTag, Len, ChPid, Credit, Count, Drain, Credits), case Reply of - true -> rabbit_channel:send_command( - ChPid, #'basic.credit_ok'{available = Len}); + true -> rabbit_channel:send_credit_reply(ChPid, Len); false -> ok end, {Unblock, Limiter#token{q_state = Credits2}}. @@ -194,12 +193,7 @@ maybe_drain(_, _, _, _, Credit, Count) -> {Credit, Count}. send_drained(ChPid, CTag, Count) -> - rabbit_channel:send_command(ChPid, - #'basic.credit_state'{consumer_tag = CTag, - credit = 0, - count = Count, - available = 0, - drain = true}). + rabbit_channel:send_drained(ChPid, CTag, Count). update_credit(CTag, Len, ChPid, Credit, Count0, Drain, Credits) -> Count = case dict:find(CTag, Credits) of -- cgit v1.2.1 From e65f518701cbd41ccfeb00691694eac92a902c1f Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 22 Jan 2013 13:06:44 +0000 Subject: Oh yeah, specs. --- src/rabbit_channel.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 9cb37c4f..8cd3a580 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -95,6 +95,9 @@ -spec(deliver/4 :: (pid(), rabbit_types:ctag(), boolean(), rabbit_amqqueue:qmsg()) -> 'ok'). +-spec(send_credit_reply/2 :: (pid(), non_neg_integer()) -> 'ok'). +-spec(send_drained/3 :: (pid(), rabbit_types:ctag(), non_neg_integer()) + -> 'ok'). -spec(flushed/2 :: (pid(), pid()) -> 'ok'). -spec(list/0 :: () -> [pid()]). -spec(list_local/0 :: () -> [pid()]). -- cgit v1.2.1 From 19e9691700293806f0255efe0f2fda93ced1d312 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 22 Jan 2013 17:53:06 +0000 Subject: Call me sentimental, but reinstate the idea of an ASCII-art rabbit... --- src/rabbit.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 1900f794..641f81c0 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -696,7 +696,7 @@ log_broker_started(Plugins) -> fun() -> error_logger:info_msg( "Server startup complete; plugins are: ~p~n", [Plugins]), - io:format("~nBroker running with ~p plugins.~n", + io:format("~n Broker running with ~p plugins.~n", [length(Plugins)]) end). @@ -711,10 +711,10 @@ erts_version_check() -> print_banner() -> {ok, Product} = application:get_key(id), {ok, Version} = application:get_key(vsn), - io:format("~n~s ~s. ~s~n~s~n~n", + io:format("~n## ## ~s ~s. ~s~n## ## ~s~n########## ~n", [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), - io:format("Logs: ~s~n ~s~n", [log_location(kernel), - log_location(sasl)]). + io:format("###### ## Logs: ~s~n########## ~s~n", + [log_location(kernel), log_location(sasl)]). log_banner() -> {ok, Product} = application:get_key(id), -- cgit v1.2.1 From 148232580f69c6436aada5644111f8fc2bdf0fe8 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 23 Jan 2013 16:19:50 +0000 Subject: Reject AMQP 1.0 TLS requests specifically --- src/rabbit_reader.erl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index ae832749..39affb17 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -699,10 +699,13 @@ handle_input(handshake, <<"AMQP", 1, 1, 9, 1>>, State) -> start_connection({8, 0, 0}, rabbit_framing_amqp_0_8, State); %% ... and finally, the 1.0 spec is crystal clear! Note that the -%% TLS uses a different protocol number, and would go here. handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>, State) -> become_1_0(amqp, {0, 1, 0, 0}, State); +%% 2 stands for TLS +handle_input(handshake, <<"AMQP", 2, 1, 0, 0>>, #v1{sock = Sock}) -> + refuse_1_0_connection(Sock, tls_request_refused); + %% 3 stands for "SASL" handle_input(handshake, <<"AMQP", 3, 1, 0, 0>>, State) -> become_1_0(sasl, {3, 1, 0, 0}, State); @@ -740,6 +743,10 @@ refuse_connection(Sock, Exception) -> ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",0,0,9,1>>) end), throw(Exception). +refuse_1_0_connection(Sock, Exception) -> + ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",0,1,0,0>>) end), + throw(Exception). + ensure_stats_timer(State = #v1{connection_state = running}) -> rabbit_event:ensure_stats_timer(State, #v1.stats_timer, emit_stats); ensure_stats_timer(State) -> -- cgit v1.2.1 From 5e5d65352238b22262e71031c75918c8299c7256 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 24 Jan 2013 11:35:17 +0000 Subject: improve/fix connection refusal - respond with a 0-9-1 header when the supplied header is not recognised *unless* it is a 1-0 header *and* the 1-0 plug-in is enabled - respond with a 1-0 header when a 1-0 header with an unsupported protocol id is supplied and the 1-0 plug-in is enabled - log a more informative error when a 1-0 header is supplied and the 1-0 plug-in is not enabled --- src/rabbit_reader.erl | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 39affb17..553e7172 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -699,16 +699,8 @@ handle_input(handshake, <<"AMQP", 1, 1, 9, 1>>, State) -> start_connection({8, 0, 0}, rabbit_framing_amqp_0_8, State); %% ... and finally, the 1.0 spec is crystal clear! Note that the -handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>, State) -> - become_1_0(amqp, {0, 1, 0, 0}, State); - -%% 2 stands for TLS -handle_input(handshake, <<"AMQP", 2, 1, 0, 0>>, #v1{sock = Sock}) -> - refuse_1_0_connection(Sock, tls_request_refused); - -%% 3 stands for "SASL" -handle_input(handshake, <<"AMQP", 3, 1, 0, 0>>, State) -> - become_1_0(sasl, {3, 1, 0, 0}, State); +handle_input(handshake, <<"AMQP", Id, 1, 0, 0>>, State) -> + become_1_0(Id, State); handle_input(handshake, <<"AMQP", A, B, C, D>>, #v1{sock = Sock}) -> refuse_connection(Sock, {bad_version, {A, B, C, D}}); @@ -739,13 +731,12 @@ start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, connection_state = starting}, frame_header, 7). -refuse_connection(Sock, Exception) -> - ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",0,0,9,1>>) end), +refuse_connection(Sock, Exception, {A, B, C, D}) -> + ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",A,B,C,D>>) end), throw(Exception). -refuse_1_0_connection(Sock, Exception) -> - ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",0,1,0,0>>) end), - throw(Exception). +refuse_connection(Sock, Exception) -> + refuse_connection(Sock, Exception, {0, 0, 9, 1}). ensure_stats_timer(State = #v1{connection_state = running}) -> rabbit_event:ensure_stats_timer(State, #v1.stats_timer, emit_stats); @@ -1015,15 +1006,19 @@ emit_stats(State) -> %% 1.0 stub -ifdef(use_specs). --spec(become_1_0/3 :: ('amqp' | 'sasl', - {non_neg_integer(), non_neg_integer(), - non_neg_integer(), non_neg_integer()}, - #v1{}) -> no_return()). +-spec(become_1_0/2 :: (non_neg_integer(), #v1{}) -> no_return()). -endif. -become_1_0(Mode, Version, State = #v1{sock = Sock}) -> +become_1_0(Id, State = #v1{sock = Sock}) -> case code:is_loaded(rabbit_amqp1_0_reader) of - false -> refuse_connection(Sock, {bad_version, Version}); - _ -> throw({become, {rabbit_amqp1_0_reader, become, + false -> refuse_connection(Sock, amqp1_0_plugin_not_enabled); + _ -> Mode = case Id of + 0 -> amqp; + 2 -> sasl; + _ -> refuse_connection( + Sock, {unsupported_amqp1_0_protocol_id, Id}, + {0, 1, 0, 0}) + end, + throw({become, {rabbit_amqp1_0_reader, become, [Mode, pack_for_1_0(State)]}}) end. -- cgit v1.2.1 From 528556fa93c522c12c10e761d4aea0e7fb0bbde2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 24 Jan 2013 12:37:39 +0000 Subject: add some tests for connection refusal some of this is also tested in the Java client, but I'd rather have it all in one place here. --- src/rabbit_tests.erl | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 87fc6078..95e23d29 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -62,6 +62,7 @@ all_tests() -> passed = test_runtime_parameters(), passed = test_policy_validation(), passed = test_server_status(), + passed = test_amqp_connection_refusal(), passed = test_confirms(), passed = do_if_secondary_node( @@ -1143,10 +1144,7 @@ test_server_status() -> rabbit_misc:r(<<"/">>, queue, <<"foo">>)), %% list connections - [#listener{host = H, port = P} | _] = - [L || L = #listener{node = N} <- rabbit_networking:active_listeners(), - N =:= node()], - + {H, P} = find_listener(), {ok, C} = gen_tcp:connect(H, P, []), gen_tcp:send(C, <<"AMQP", 0, 0, 9, 1>>), timer:sleep(100), @@ -1185,6 +1183,25 @@ test_server_status() -> passed. +test_amqp_connection_refusal() -> + [passed = test_amqp_connection_refusal(V) || + V <- [<<"AMQP",9,9,9,9>>, <<"AMQP",0,1,0,0>>, <<"XXXX",0,0,9,1>>]], + passed. + +test_amqp_connection_refusal(Header) -> + {H, P} = find_listener(), + {ok, C} = gen_tcp:connect(H, P, [binary, {active, false}]), + ok = gen_tcp:send(C, Header), + {ok, <<"AMQP",0,0,9,1>>} = gen_tcp:recv(C, 8, 100), + ok = gen_tcp:close(C), + passed. + +find_listener() -> + [#listener{host = H, port = P} | _] = + [L || L = #listener{node = N} <- rabbit_networking:active_listeners(), + N =:= node()], + {H, P}. + test_writer(Pid) -> receive shutdown -> ok; -- cgit v1.2.1 From 693e2aab0699c7860141e46e1283b012aff44cf1 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 24 Jan 2013 12:50:00 +0000 Subject: Quick patch to backing queue quickcheck correcting fold fun arity --- src/rabbit_backing_queue_qc.erl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/rabbit_backing_queue_qc.erl b/src/rabbit_backing_queue_qc.erl index 5b3b8aa8..5feaee46 100644 --- a/src/rabbit_backing_queue_qc.erl +++ b/src/rabbit_backing_queue_qc.erl @@ -334,7 +334,7 @@ postcondition(S, {call, ?BQMOD, fold, [FoldFun, Acc0, _BQ0]}, {Res, _BQ1}) -> {_, Model} = lists:foldl(fun ({_SeqId, {_MsgProps, _Msg}}, {stop, Acc}) -> {stop, Acc}; ({_SeqId, {MsgProps, Msg}}, {cont, Acc}) -> - FoldFun(Msg, MsgProps, Acc) + FoldFun(Msg, MsgProps, false, Acc) end, {cont, Acc0}, gb_trees:to_list(Messages)), true = Model =:= Res; @@ -397,10 +397,11 @@ rand_choice(List, Selection, N) -> N - 1). makefoldfun(Size) -> - fun (Msg, _MsgProps, Acc) -> - case length(Acc) > Size of - false -> {cont, [Msg | Acc]}; - true -> {stop, Acc} + fun (Msg, _MsgProps, Unacked, Acc) -> + case {length(Acc) > Size, Unacked} of + {false, false} -> {cont, [Msg | Acc]}; + {false, true} -> {cont, Acc}; + {true, _} -> {stop, Acc} end end. foldacc() -> []. -- cgit v1.2.1 From 50902853b2f18aa9c53271da41aad0e269533207 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 24 Jan 2013 13:01:30 +0000 Subject: recommend sasl --- src/rabbit_reader.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 553e7172..6d936bbb 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -1016,7 +1016,7 @@ become_1_0(Id, State = #v1{sock = Sock}) -> 2 -> sasl; _ -> refuse_connection( Sock, {unsupported_amqp1_0_protocol_id, Id}, - {0, 1, 0, 0}) + {2, 1, 0, 0}) end, throw({become, {rabbit_amqp1_0_reader, become, [Mode, pack_for_1_0(State)]}}) -- cgit v1.2.1 From 7424bac1a36e1d9512a32edcd1b0bd0b93ab31fc Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 24 Jan 2013 13:04:19 +0000 Subject: s/become/init --- src/rabbit_reader.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 6d936bbb..30ea6a5b 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -1018,7 +1018,7 @@ become_1_0(Id, State = #v1{sock = Sock}) -> Sock, {unsupported_amqp1_0_protocol_id, Id}, {2, 1, 0, 0}) end, - throw({become, {rabbit_amqp1_0_reader, become, + throw({become, {rabbit_amqp1_0_reader, init, [Mode, pack_for_1_0(State)]}}) end. -- cgit v1.2.1 From b7fd5abd547456ae07a8990d477e7821dbaab2bc Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 24 Jan 2013 13:09:35 +0000 Subject: improved connection refusal logic / error message plus some tests to go with that And a tweak to the "become 1.0" API --- src/rabbit_reader.erl | 34 ++++++++++++++++++---------------- src/rabbit_tests.erl | 25 +++++++++++++++++++++---- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index ae832749..30ea6a5b 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -699,13 +699,8 @@ handle_input(handshake, <<"AMQP", 1, 1, 9, 1>>, State) -> start_connection({8, 0, 0}, rabbit_framing_amqp_0_8, State); %% ... and finally, the 1.0 spec is crystal clear! Note that the -%% TLS uses a different protocol number, and would go here. -handle_input(handshake, <<"AMQP", 0, 1, 0, 0>>, State) -> - become_1_0(amqp, {0, 1, 0, 0}, State); - -%% 3 stands for "SASL" -handle_input(handshake, <<"AMQP", 3, 1, 0, 0>>, State) -> - become_1_0(sasl, {3, 1, 0, 0}, State); +handle_input(handshake, <<"AMQP", Id, 1, 0, 0>>, State) -> + become_1_0(Id, State); handle_input(handshake, <<"AMQP", A, B, C, D>>, #v1{sock = Sock}) -> refuse_connection(Sock, {bad_version, {A, B, C, D}}); @@ -736,10 +731,13 @@ start_connection({ProtocolMajor, ProtocolMinor, _ProtocolRevision}, connection_state = starting}, frame_header, 7). -refuse_connection(Sock, Exception) -> - ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",0,0,9,1>>) end), +refuse_connection(Sock, Exception, {A, B, C, D}) -> + ok = inet_op(fun () -> rabbit_net:send(Sock, <<"AMQP",A,B,C,D>>) end), throw(Exception). +refuse_connection(Sock, Exception) -> + refuse_connection(Sock, Exception, {0, 0, 9, 1}). + ensure_stats_timer(State = #v1{connection_state = running}) -> rabbit_event:ensure_stats_timer(State, #v1.stats_timer, emit_stats); ensure_stats_timer(State) -> @@ -1008,15 +1006,19 @@ emit_stats(State) -> %% 1.0 stub -ifdef(use_specs). --spec(become_1_0/3 :: ('amqp' | 'sasl', - {non_neg_integer(), non_neg_integer(), - non_neg_integer(), non_neg_integer()}, - #v1{}) -> no_return()). +-spec(become_1_0/2 :: (non_neg_integer(), #v1{}) -> no_return()). -endif. -become_1_0(Mode, Version, State = #v1{sock = Sock}) -> +become_1_0(Id, State = #v1{sock = Sock}) -> case code:is_loaded(rabbit_amqp1_0_reader) of - false -> refuse_connection(Sock, {bad_version, Version}); - _ -> throw({become, {rabbit_amqp1_0_reader, become, + false -> refuse_connection(Sock, amqp1_0_plugin_not_enabled); + _ -> Mode = case Id of + 0 -> amqp; + 2 -> sasl; + _ -> refuse_connection( + Sock, {unsupported_amqp1_0_protocol_id, Id}, + {2, 1, 0, 0}) + end, + throw({become, {rabbit_amqp1_0_reader, init, [Mode, pack_for_1_0(State)]}}) end. diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index b0ff5af9..a2442f17 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -61,6 +61,7 @@ all_tests() -> passed = test_runtime_parameters(), passed = test_policy_validation(), passed = test_server_status(), + passed = test_amqp_connection_refusal(), passed = test_confirms(), passed = do_if_secondary_node( @@ -1119,10 +1120,7 @@ test_server_status() -> rabbit_misc:r(<<"/">>, queue, <<"foo">>)), %% list connections - [#listener{host = H, port = P} | _] = - [L || L = #listener{node = N} <- rabbit_networking:active_listeners(), - N =:= node()], - + {H, P} = find_listener(), {ok, C} = gen_tcp:connect(H, P, []), gen_tcp:send(C, <<"AMQP", 0, 0, 9, 1>>), timer:sleep(100), @@ -1161,6 +1159,25 @@ test_server_status() -> passed. +test_amqp_connection_refusal() -> + [passed = test_amqp_connection_refusal(V) || + V <- [<<"AMQP",9,9,9,9>>, <<"AMQP",0,1,0,0>>, <<"XXXX",0,0,9,1>>]], + passed. + +test_amqp_connection_refusal(Header) -> + {H, P} = find_listener(), + {ok, C} = gen_tcp:connect(H, P, [binary, {active, false}]), + ok = gen_tcp:send(C, Header), + {ok, <<"AMQP",0,0,9,1>>} = gen_tcp:recv(C, 8, 100), + ok = gen_tcp:close(C), + passed. + +find_listener() -> + [#listener{host = H, port = P} | _] = + [L || L = #listener{node = N} <- rabbit_networking:active_listeners(), + N =:= node()], + {H, P}. + test_writer() -> test_writer(none). test_writer(Pid) -> -- cgit v1.2.1 From 16ce962c5081a0a101458626bc777513af7b49ae Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 24 Jan 2013 19:10:26 +0000 Subject: nuke active_consumer_count --- src/rabbit_amqqueue_process.erl | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 6ca6399a..fe3a6099 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -94,7 +94,6 @@ messages_unacknowledged, messages, consumers, - active_consumers, memory, slave_pids, synchronised_slave_pids, @@ -665,13 +664,8 @@ check_exclusive_access(none, true, State) -> false -> in_use end. -consumer_count() -> consumer_count(fun (_) -> false end). - -active_consumer_count() -> consumer_count(fun is_ch_blocked/1). - -consumer_count(Exclude) -> - lists:sum([Count || C = #cr{consumer_count = Count} <- all_ch_record(), - not Exclude(C)]). +consumer_count() -> + lists:sum([Count || #cr{consumer_count = Count} <- all_ch_record()]). is_unused(_State) -> consumer_count() == 0. @@ -922,8 +916,6 @@ i(messages, State) -> messages_unacknowledged]]); i(consumers, _) -> consumer_count(); -i(active_consumers, _) -> - active_consumer_count(); i(memory, _) -> {memory, M} = process_info(self(), memory), M; @@ -1141,7 +1133,7 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, handle_call(stat, _From, State) -> State1 = #q{backing_queue = BQ, backing_queue_state = BQS} = drop_expired_msgs(ensure_expiry_timer(State)), - reply({ok, BQ:len(BQS), active_consumer_count()}, State1); + reply({ok, BQ:len(BQS), consumer_count()}, State1); handle_call({delete, IfUnused, IfEmpty}, From, State = #q{backing_queue_state = BQS, backing_queue = BQ}) -> -- cgit v1.2.1 From ccd04a74a4780152d42cc0dce4abe9916fd7fdd7 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 25 Jan 2013 15:41:15 +0000 Subject: Swap SASL and TLS header codes --- src/rabbit_reader.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 8cbbc7c9..af7aac6f 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -1013,10 +1013,10 @@ become_1_0(Id, State = #v1{sock = Sock}) -> false -> refuse_connection(Sock, amqp1_0_plugin_not_enabled); _ -> Mode = case Id of 0 -> amqp; - 2 -> sasl; + 3 -> sasl; _ -> refuse_connection( Sock, {unsupported_amqp1_0_protocol_id, Id}, - {2, 1, 0, 0}) + {3, 1, 0, 0}) end, throw({become, {rabbit_amqp1_0_reader, init, [Mode, pack_for_1_0(State)]}}) -- cgit v1.2.1 From 45f7849be58f78a23e28061fcace8bfdbdaa1e9a Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 25 Jan 2013 15:47:22 +0000 Subject: Swap SASL and TLS header codes --- src/rabbit_reader.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 8cbbc7c9..af7aac6f 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -1013,10 +1013,10 @@ become_1_0(Id, State = #v1{sock = Sock}) -> false -> refuse_connection(Sock, amqp1_0_plugin_not_enabled); _ -> Mode = case Id of 0 -> amqp; - 2 -> sasl; + 3 -> sasl; _ -> refuse_connection( Sock, {unsupported_amqp1_0_protocol_id, Id}, - {2, 1, 0, 0}) + {3, 1, 0, 0}) end, throw({become, {rabbit_amqp1_0_reader, init, [Mode, pack_for_1_0(State)]}}) -- cgit v1.2.1 From 488c3781a3b9211078b8c11295333e159fce7a02 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 27 Jan 2013 19:28:51 +0000 Subject: move bits of docs around in order to eliminate "forward references" to new features --- src/supervisor2.erl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 89e78703..cbca993c 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -5,7 +5,14 @@ %% %% 2) a find_child/2 utility function has been added %% -%% 3) child specifications can contain, as the restart type, a tuple +%% 3) Added an 'intrinsic' restart type. Like the transient type, this +%% type means the child should only be restarted if the child exits +%% abnormally. Unlike the transient type, if the child exits +%% normally, the supervisor itself also exits normally. If the +%% child is a supervisor and it exits normally (i.e. with reason of +%% 'shutdown') then the child's parent also exits normally. +%% +%% 4) child specifications can contain, as the restart type, a tuple %% {permanent, Delay} | {transient, Delay} | {intrinsic, Delay} %% where Delay >= 0 (see point (4) below for intrinsic). The delay, %% in seconds, indicates what should happen if a child, upon being @@ -38,13 +45,6 @@ %% perspective it's a normal exit, whilst from supervisor's %% perspective, it's an abnormal exit. %% -%% 4) Added an 'intrinsic' restart type. Like the transient type, this -%% type means the child should only be restarted if the child exits -%% abnormally. Unlike the transient type, if the child exits -%% normally, the supervisor itself also exits normally. If the -%% child is a supervisor and it exits normally (i.e. with reason of -%% 'shutdown') then the child's parent also exits normally. -%% %% 5) normal, and {shutdown, _} exit reasons are all treated the same %% (i.e. are regarded as normal exits) %% -- cgit v1.2.1 From 0f26c7ae57c04790b3275d85bf45b5d3717bca19 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 27 Jan 2013 19:29:15 +0000 Subject: cosmetic - indent like OTP --- src/supervisor2.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index cbca993c..a762defa 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -76,7 +76,7 @@ start_child/2, restart_child/2, delete_child/2, terminate_child/2, which_children/1, count_children/1, - find_child/2, check_childspecs/1]). + find_child/2, check_childspecs/1]). %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, -- cgit v1.2.1 From 4c8ed37e06d15e5c62b211b5d129ed72059d0b51 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 27 Jan 2013 19:29:40 +0000 Subject: move find_child implementation to a better place --- src/supervisor2.erl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index a762defa..1f1f9246 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -285,6 +285,15 @@ which_children(Supervisor) -> count_children(Supervisor) -> call(Supervisor, count_children). +-ifdef(use_specs). +-spec find_child(Supervisor, Name) -> [pid()] when + Supervisor :: sup_ref(), + Name :: child_id(). +-endif. +find_child(Supervisor, Name) -> + [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), + Name1 =:= Name]. + call(Supervisor, Req) -> gen_server:call(Supervisor, Req, infinity). @@ -313,15 +322,6 @@ try_again_restart(Supervisor, Child) -> cast(Supervisor, Req) -> gen_server:cast(Supervisor, Req). --ifdef(use_specs). --spec find_child(Supervisor, Name) -> [pid()] when - Supervisor :: sup_ref(), - Name :: child_id(). --endif. -find_child(Supervisor, Name) -> - [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), - Name1 =:= Name]. - %%% --------------------------------------------------- %%% %%% Initialize the supervisor. -- cgit v1.2.1 From 90ddce2d53a84bcddfad4c4ab7242e943ee518d2 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 28 Jan 2013 20:48:06 +0000 Subject: single io:format in order to prevent output interleaving --- src/rabbit.erl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index c9cf7ea4..6b730fda 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -716,10 +716,13 @@ erts_version_check() -> print_banner() -> {ok, Product} = application:get_key(id), {ok, Version} = application:get_key(vsn), - io:format("~n## ## ~s ~s. ~s~n## ## ~s~n########## ~n", - [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), - io:format("###### ## Logs: ~s~n########## ~s~n", - [log_location(kernel), log_location(sasl)]). + io:format("~n## ## ~s ~s. ~s" + "~n## ## ~s" + "~n##########" + "~n###### ## Logs: ~s" + "~n########## ~s~n", + [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE, + log_location(kernel), log_location(sasl)]). log_banner() -> {ok, Product} = application:get_key(id), -- cgit v1.2.1 From 55eeddf70f52604f35b4ec98fdba5827d54631f9 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 29 Jan 2013 13:39:03 +0000 Subject: Get rid of credit_map, allow initial credit setting through an argument to basic.consume. --- src/rabbit_channel.erl | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 56a10676..dae21389 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -39,7 +39,7 @@ queue_names, queue_monitors, consumer_mapping, blocking, queue_consumers, delivering_queues, queue_collector_pid, stats_timer, confirm_enabled, publish_seqno, - unconfirmed, confirmed, capabilities, trace_state, credit_map}). + unconfirmed, confirmed, capabilities, trace_state}). -define(MAX_PERMISSION_CACHE_SIZE, 12). @@ -219,8 +219,7 @@ init([Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, VHost, unconfirmed = dtree:empty(), confirmed = [], capabilities = Capabilities, - trace_state = rabbit_trace:init(VHost), - credit_map = dict:new()}, + trace_state = rabbit_trace:init(VHost)}, State1 = rabbit_event:init_stats_timer(State, #ch.stats_timer), rabbit_event:notify(channel_created, infos(?CREATION_EVENT_KEYS, State1)), rabbit_event:if_enabled(State1, #ch.stats_timer, @@ -720,11 +719,11 @@ handle_method(#'basic.consume'{queue = QueueNameBin, no_local = _, % FIXME: implement no_ack = NoAck, exclusive = ExclusiveConsume, - nowait = NoWait}, + nowait = NoWait, + arguments = Args}, _, State = #ch{conn_pid = ConnPid, limiter = Limiter, - consumer_mapping = ConsumerMapping, - credit_map = CreditMap}) -> + consumer_mapping = ConsumerMapping}) -> case dict:find(ConsumerTag, ConsumerMapping) of error -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), @@ -742,15 +741,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, case rabbit_amqqueue:with_exclusive_access_or_die( QueueName, ConnPid, fun (Q) -> - case dict:find(ActualConsumerTag, CreditMap) of - {ok, {Credit, Count, Drain}} -> - ok = rabbit_amqqueue:inform_limiter( - Q, self(), - {basic_credit, ActualConsumerTag, - Credit, Count, Drain, false}); - error -> - ok - end, + maybe_set_initial_credit(Args, ActualConsumerTag, Q), {rabbit_amqqueue:basic_consume( Q, NoAck, self(), Limiter, ActualConsumerTag, ExclusiveConsume, @@ -760,11 +751,9 @@ handle_method(#'basic.consume'{queue = QueueNameBin, end) of {ok, Q = #amqqueue{pid = QPid, name = QName}} -> CM1 = dict:store(ActualConsumerTag, Q, ConsumerMapping), - CrM1 = dict:erase(ActualConsumerTag, CreditMap), State1 = monitor_delivering_queue( NoAck, QPid, QName, - State#ch{consumer_mapping = CM1, - credit_map = CrM1}), + State#ch{consumer_mapping = CM1}), {noreply, case NoWait of true -> consumer_monitor(ActualConsumerTag, State1); @@ -1131,16 +1120,13 @@ handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, count = Count, drain = Drain}, _, - State = #ch{consumer_mapping = Consumers, - credit_map = CMap}) -> + State = #ch{consumer_mapping = Consumers}) -> case dict:find(CTag, Consumers) of {ok, Q} -> ok = rabbit_amqqueue:inform_limiter( Q, self(), {basic_credit, CTag, Credit, Count, Drain, true}), {noreply, State}; - error -> CMap2 = dict:store(CTag, {Credit, Count, Drain}, CMap), - {reply, #'basic.credit_ok'{available = 0}, - State#ch{credit_map = CMap2}} + error -> precondition_failed("unknown consumer tag '~s'", [CTag]) end; handle_method(_MethodRecord, _Content, _State) -> @@ -1209,6 +1195,21 @@ handle_consuming_queue_down(QPid, handle_delivering_queue_down(QPid, State = #ch{delivering_queues = DQ}) -> State#ch{delivering_queues = sets:del_element(QPid, DQ)}. +maybe_set_initial_credit(Arguments, CTag, Q) -> + case rabbit_misc:table_lookup(Arguments, <<"x-credit">>) of + {table, T} -> case {rabbit_misc:table_lookup(T, <<"credit">>), + rabbit_misc:table_lookup(T, <<"drain">>)} of + {{long, Credit}, {boolean, Drain}} -> + ok = rabbit_amqqueue:inform_limiter( + Q, self(), + {basic_credit, CTag, Credit, 0, Drain, + false}); + _ -> + ok + end; + undefined -> ok + end. + binding_action(Fun, ExchangeNameBin, DestinationType, DestinationNameBin, RoutingKey, Arguments, ReturnMethod, NoWait, State = #ch{virtual_host = VHostPath, -- cgit v1.2.1 From 18c857531e971ab691bc24afb076c376d22510f3 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 29 Jan 2013 13:56:24 +0000 Subject: Simplify: convert basic.credit_state to basic.credit_drained, which implicitly asserts that credit is 0, length 0 and drain is true, and tells you how much credit was discarded rather than the new delivery count (so we can soon remove all delivery count code from the broker). --- src/rabbit_channel.erl | 13 +++++-------- src/rabbit_limiter.erl | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index dae21389..9ffb9112 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -145,8 +145,8 @@ deliver(Pid, ConsumerTag, AckRequired, Msg) -> send_credit_reply(Pid, Len) -> gen_server2:cast(Pid, {send_credit_reply, Len}). -send_drained(Pid, ConsumerTag, Count) -> - gen_server2:cast(Pid, {send_drained, ConsumerTag, Count}). +send_drained(Pid, ConsumerTag, CreditDrained) -> + gen_server2:cast(Pid, {send_drained, ConsumerTag, CreditDrained}). flushed(Pid, QPid) -> gen_server2:cast(Pid, {flushed, QPid}). @@ -330,14 +330,11 @@ handle_cast({send_credit_reply, Len}, State = #ch{writer_pid = WriterPid}) -> WriterPid, #'basic.credit_ok'{available = Len}), noreply(State); -handle_cast({send_drained, ConsumerTag, Count}, +handle_cast({send_drained, ConsumerTag, CreditDrained}, State = #ch{writer_pid = WriterPid}) -> ok = rabbit_writer:send_command( - WriterPid, #'basic.credit_state'{consumer_tag = ConsumerTag, - credit = 0, - count = Count, - available = 0, - drain = true}), + WriterPid, #'basic.credit_drained'{consumer_tag = ConsumerTag, + credit_drained = CreditDrained}), noreply(State); handle_cast(force_event_refresh, State) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index b97d1073..232be83c 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -186,14 +186,14 @@ decr_credit(CTag, Len, ChPid, Cred, Credits) -> maybe_drain(0, true, CTag, ChPid, Credit, Count) -> %% Drain, so advance til credit = 0 NewCount = serial_add(Count, Credit - 2), - send_drained(ChPid, CTag, NewCount), + send_drained(ChPid, CTag, Credit), {0, NewCount}; %% Magic reduction to 0 maybe_drain(_, _, _, _, Credit, Count) -> {Credit, Count}. -send_drained(ChPid, CTag, Count) -> - rabbit_channel:send_drained(ChPid, CTag, Count). +send_drained(ChPid, CTag, CreditDrained) -> + rabbit_channel:send_drained(ChPid, CTag, CreditDrained). update_credit(CTag, Len, ChPid, Credit, Count0, Drain, Credits) -> Count = case dict:find(CTag, Credits) of -- cgit v1.2.1 From 3cd142a2d4027e64883681237de936b27fb0ecf5 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 29 Jan 2013 14:03:20 +0000 Subject: Remove knowledge of delivery-count from the broker. --- src/rabbit_channel.erl | 5 ++--- src/rabbit_limiter.erl | 40 +++++++++++++++------------------------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 9ffb9112..7f6dc3c8 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1115,13 +1115,12 @@ handle_method(#'channel.flow'{active = false}, _, handle_method(#'basic.credit'{consumer_tag = CTag, credit = Credit, - count = Count, drain = Drain}, _, State = #ch{consumer_mapping = Consumers}) -> case dict:find(CTag, Consumers) of {ok, Q} -> ok = rabbit_amqqueue:inform_limiter( Q, self(), - {basic_credit, CTag, Credit, Count, Drain, true}), + {basic_credit, CTag, Credit, Drain, true}), {noreply, State}; error -> precondition_failed("unknown consumer tag '~s'", [CTag]) end; @@ -1199,7 +1198,7 @@ maybe_set_initial_credit(Arguments, CTag, Q) -> {{long, Credit}, {boolean, Drain}} -> ok = rabbit_amqqueue:inform_limiter( Q, self(), - {basic_credit, CTag, Credit, 0, Drain, + {basic_credit, CTag, Credit, Drain, false}); _ -> ok diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 232be83c..ae9c7918 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -75,7 +75,7 @@ %% notified of a change in the limit or volume that may allow it to %% deliver more messages via the limiter's channel. --record(credit, {count = 0, credit = 0, drain = false}). +-record(credit, {credit = 0, drain = false}). %%---------------------------------------------------------------------------- %% API @@ -144,9 +144,9 @@ is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). inform(Limiter = #token{q_state = Credits}, - ChPid, Len, {basic_credit, CTag, Credit, Count, Drain, Reply}) -> + ChPid, Len, {basic_credit, CTag, Credit, Drain, Reply}) -> {Unblock, Credits2} = update_credit( - CTag, Len, ChPid, Credit, Count, Drain, Credits), + CTag, Len, ChPid, Credit, Drain, Credits), case Reply of true -> rabbit_channel:send_credit_reply(ChPid, Len); false -> ok @@ -178,40 +178,30 @@ record_send_q(CTag, Len, ChPid, Credits) -> end. decr_credit(CTag, Len, ChPid, Cred, Credits) -> - #credit{credit = Credit, count = Count, drain = Drain} = Cred, - {NewCredit, NewCount} = maybe_drain(Len - 1, Drain, CTag, ChPid, - Credit - 1, serial_add(Count, 1)), - write_credit(CTag, NewCredit, NewCount, Drain, Credits). - -maybe_drain(0, true, CTag, ChPid, Credit, Count) -> - %% Drain, so advance til credit = 0 - NewCount = serial_add(Count, Credit - 2), + #credit{credit = Credit, drain = Drain} = Cred, + NewCredit = maybe_drain(Len - 1, Drain, CTag, ChPid, Credit - 1), + write_credit(CTag, NewCredit, Drain, Credits). + +maybe_drain(0, true, CTag, ChPid, Credit) -> send_drained(ChPid, CTag, Credit), - {0, NewCount}; %% Magic reduction to 0 + 0; %% Magic reduction to 0 -maybe_drain(_, _, _, _, Credit, Count) -> - {Credit, Count}. +maybe_drain(_, _, _, _, Credit) -> + Credit. send_drained(ChPid, CTag, CreditDrained) -> rabbit_channel:send_drained(ChPid, CTag, CreditDrained). -update_credit(CTag, Len, ChPid, Credit, Count0, Drain, Credits) -> - Count = case dict:find(CTag, Credits) of - %% Use our count if we can, more accurate - {ok, #credit{ count = LocalCount }} -> LocalCount; - %% But if this is new, take it from the adapter - _ -> Count0 - end, - {NewCredit, NewCount} = maybe_drain(Len, Drain, CTag, ChPid, Credit, Count), - NewCredits = write_credit(CTag, NewCredit, NewCount, Drain, Credits), +update_credit(CTag, Len, ChPid, Credit, Drain, Credits) -> + NewCredit = maybe_drain(Len, Drain, CTag, ChPid, Credit), + NewCredits = write_credit(CTag, NewCredit, Drain, Credits), case NewCredit > 0 of true -> {[CTag], NewCredits}; false -> {[], NewCredits} end. -write_credit(CTag, Credit, Count, Drain, Credits) -> +write_credit(CTag, Credit, Drain, Credits) -> dict:store(CTag, #credit{credit = Credit, - count = Count, drain = Drain}, Credits). %%---------------------------------------------------------------------------- -- cgit v1.2.1 From f3025ce7562d1f511baef3dbfb7231651b9e3509 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 29 Jan 2013 14:50:16 +0000 Subject: inform_limiter -> credit. --- src/rabbit_amqqueue.erl | 9 +++++---- src/rabbit_amqqueue_process.erl | 5 +++-- src/rabbit_channel.erl | 11 ++++------- src/rabbit_limiter.erl | 11 ++++++----- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index cb7e961d..b9d41c25 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -28,7 +28,7 @@ -export([consumers/1, consumers_all/1, consumer_info_keys/0]). -export([basic_get/3, basic_consume/7, basic_cancel/4]). -export([notify_sent/2, notify_sent_queue_down/1, unblock/2, flush_all/2]). --export([notify_down_all/2, limit_all/3, inform_limiter/3]). +-export([notify_down_all/2, limit_all/3, credit/6]). -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). -export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1, @@ -145,7 +145,8 @@ -spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). -spec(limit_all/3 :: (qpids(), pid(), rabbit_limiter:token()) -> ok_or_errors()). --spec(inform_limiter/3 :: (rabbit_types:amqqueue(), pid(), any()) -> 'ok'). +-spec(credit/6 :: (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), + non_neg_integer(), boolean(), boolean()) -> 'ok'). -spec(basic_get/3 :: (rabbit_types:amqqueue(), pid(), boolean()) -> {'ok', non_neg_integer(), qmsg()} | 'empty'). -spec(basic_consume/7 :: @@ -533,8 +534,8 @@ notify_down_all(QPids, ChPid) -> limit_all(QPids, ChPid, Limiter) -> delegate:cast(QPids, {limit, ChPid, Limiter}). -inform_limiter(#amqqueue{pid = QPid}, ChPid, Msg) -> - delegate:cast(QPid, {inform_limiter, ChPid, Msg}). +credit(#amqqueue{pid = QPid}, ChPid, CTag, Credit, Drain, Reply) -> + delegate:cast(QPid, {credit, ChPid, CTag, Credit, Drain, Reply}). basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> delegate:call(QPid, {basic_get, ChPid, NoAck}). diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 4e249365..aaa4b537 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1329,12 +1329,13 @@ handle_cast(stop_mirroring, State = #q{backing_queue = BQ, noreply(State#q{backing_queue = BQ1, backing_queue_state = BQS1}); -handle_cast({inform_limiter, ChPid, Msg}, +handle_cast({credit, ChPid, CTag, Credit, Drain, Reply}, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> #cr{limiter = Lim, blocked_ctags = BCTags} = ch_record(ChPid), - {Unblock, Lim2} = rabbit_limiter:inform(Lim, ChPid, BQ:len(BQS), Msg), + {Unblock, Lim2} = rabbit_limiter:credit( + Lim, ChPid, CTag, Credit, Drain, Reply, BQ:len(BQS)), noreply(possibly_unblock( State, ChPid, fun(C) -> C#cr{blocked_ctags = BCTags -- Unblock, limiter = Lim2} end)); diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 7f6dc3c8..f719d8f3 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1118,9 +1118,8 @@ handle_method(#'basic.credit'{consumer_tag = CTag, drain = Drain}, _, State = #ch{consumer_mapping = Consumers}) -> case dict:find(CTag, Consumers) of - {ok, Q} -> ok = rabbit_amqqueue:inform_limiter( - Q, self(), - {basic_credit, CTag, Credit, Drain, true}), + {ok, Q} -> ok = rabbit_amqqueue:credit( + Q, self(), CTag, Credit, Drain, true), {noreply, State}; error -> precondition_failed("unknown consumer tag '~s'", [CTag]) end; @@ -1196,10 +1195,8 @@ maybe_set_initial_credit(Arguments, CTag, Q) -> {table, T} -> case {rabbit_misc:table_lookup(T, <<"credit">>), rabbit_misc:table_lookup(T, <<"drain">>)} of {{long, Credit}, {boolean, Drain}} -> - ok = rabbit_amqqueue:inform_limiter( - Q, self(), - {basic_credit, CTag, Credit, Drain, - false}); + ok = rabbit_amqqueue:credit( + Q, self(), CTag, Credit, Drain, false); _ -> ok end; diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index ae9c7918..d9019bfa 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -27,7 +27,7 @@ -export([limit/2, can_ch_send/3, can_cons_send/2, record_cons_send/4, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). --export([inform/4, forget_consumer/2, copy_queue_state/2]). +-export([credit/7, forget_consumer/2, copy_queue_state/2]). -import(rabbit_misc, [serial_add/2, serial_diff/2]). @@ -57,8 +57,9 @@ -spec(block/1 :: (token()) -> 'ok'). -spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). -spec(is_blocked/1 :: (token()) -> boolean()). --spec(inform/4 :: (token(), pid(), non_neg_integer(), any()) -> - {[rabbit_types:ctag()], token()}). +-spec(credit/7 :: (token(), pid(), rabbit_types:ctag(), + non_neg_integer(), boolean(), boolean(), non_neg_integer()) + -> {[rabbit_types:ctag()], token()}). -spec(forget_consumer/2 :: (token(), rabbit_types:ctag()) -> token()). -spec(copy_queue_state/2 :: (token(), token()) -> token()). @@ -143,8 +144,8 @@ unblock(Limiter) -> is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). -inform(Limiter = #token{q_state = Credits}, - ChPid, Len, {basic_credit, CTag, Credit, Drain, Reply}) -> +credit(Limiter = #token{q_state = Credits}, + ChPid, CTag, Credit, Drain, Reply, Len) -> {Unblock, Credits2} = update_credit( CTag, Len, ChPid, Credit, Drain, Credits), case Reply of -- cgit v1.2.1 From c5f449a5e3ab6c7c546b098cb8a4bd555a5f7221 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 29 Jan 2013 15:21:29 +0000 Subject: Unify the various checks before sending, and make sure we don't drop the new limiter on the floor. --- src/rabbit_amqqueue_process.erl | 39 +++++++++++++++++-------------------- src/rabbit_limiter.erl | 43 +++++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index aaa4b537..5c1b68f4 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -437,7 +437,9 @@ deliver_msgs_to_consumers(DeliverFun, false, deliver_msgs_to_consumers(DeliverFun, Stop, State1) end. -deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> +deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, + State = #q{backing_queue = BQ, + backing_queue_state = BQS}) -> C = ch_record(ChPid), case is_ch_blocked(C) of true -> @@ -446,36 +448,30 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> false -> #cr{limiter = Limiter, ch_pid = ChPid, blocked_ctags = BCTags} = C, #consumer{tag = CTag} = Consumer, - case rabbit_limiter:can_cons_send(Limiter, CTag) of - false -> + case rabbit_limiter:can_send( + Limiter, self(), Consumer#consumer.ack_required, + ChPid, CTag, BQ:len(BQS)) of + consumer_blocked -> block_consumer(C#cr{blocked_ctags = [CTag | BCTags]}, E), {false, State}; - true -> - case rabbit_limiter:can_ch_send( - Limiter, self(), Consumer#consumer.ack_required) of - false -> - block_consumer(C#cr{is_limit_active = true}, E), - {false, State}; - true -> - AC1 = queue:in(E, State#q.active_consumers), - deliver_msg_to_consumer( - DeliverFun, Consumer, C, - State#q{active_consumers = AC1}) - end + channel_blocked -> + block_consumer(C#cr{is_limit_active = true}, E), + {false, State}; + Limiter2 -> + AC1 = queue:in(E, State#q.active_consumers), + deliver_msg_to_consumer( + DeliverFun, Limiter2, Consumer, C, + State#q{active_consumers = AC1}) end end. -deliver_msg_to_consumer(DeliverFun, +deliver_msg_to_consumer(DeliverFun, NewLimiter, #consumer{tag = ConsumerTag, ack_required = AckRequired}, C = #cr{ch_pid = ChPid, acktags = ChAckTags, - limiter = Limiter, unsent_message_count = Count}, - State = #q{q = #amqqueue{name = QName}, - backing_queue = BQ, - backing_queue_state = BQS}) -> - rabbit_limiter:record_cons_send(Limiter, ChPid, ConsumerTag, BQ:len(BQS)), + State = #q{q = #amqqueue{name = QName}}) -> {{Message, IsDelivered, AckTag}, Stop, State1} = DeliverFun(AckRequired, State), rabbit_channel:deliver(ChPid, ConsumerTag, AckRequired, @@ -485,6 +481,7 @@ deliver_msg_to_consumer(DeliverFun, false -> ChAckTags end, update_ch_record(C#cr{acktags = ChAckTags1, + limiter = NewLimiter, unsent_message_count = Count + 1}), {Stop, State1}. diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index d9019bfa..5b4ce802 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -24,8 +24,7 @@ -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). --export([limit/2, can_ch_send/3, can_cons_send/2, record_cons_send/4, - ack/2, register/2, unregister/2]). +-export([limit/2, can_send/6, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). -export([credit/7, forget_consumer/2, copy_queue_state/2]). @@ -48,8 +47,9 @@ -spec(enable/2 :: (token(), non_neg_integer()) -> token()). -spec(disable/1 :: (token()) -> token()). -spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). --spec(can_ch_send/3 :: (token(), pid(), boolean()) -> boolean()). --spec(can_cons_send/2 :: (token(), rabbit_types:ctag()) -> boolean()). +-spec(can_send/6 :: (token(), pid(), boolean(), pid(), rabbit_types:ctag(), + non_neg_integer()) + -> token() | 'consumer_blocked' | 'channel_blocked'). -spec(ack/2 :: (token(), non_neg_integer()) -> 'ok'). -spec(register/2 :: (token(), pid()) -> 'ok'). -spec(unregister/2 :: (token(), pid()) -> 'ok'). @@ -103,25 +103,30 @@ limit(Limiter, PrefetchCount) -> %% breaching a limit. Note that we don't use maybe_call here in order %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. -can_ch_send(#token{pid = Pid, enabled = true}, QPid, AckRequired) -> +can_send(Token, QPid, AckRequired, ChPid, CTag, Len) -> rabbit_misc:with_exit_handler( fun () -> true end, - fun () -> - gen_server2:call(Pid, {can_send, QPid, AckRequired}, infinity) - end); -can_ch_send(_, _, _) -> - true. - -can_cons_send(#token{q_state = Credits}, CTag) -> - case dict:find(CTag, Credits) of - {ok, #credit{credit = C}} when C > 0 -> true; - {ok, #credit{}} -> false; - error -> true + fun () -> can_send0(Token, QPid, AckRequired, ChPid, CTag, Len) end). + +can_send0(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, + QPid, AckRequired, ChPid, CTag, Len) -> + ConsAllows = case dict:find(CTag, Credits) of + {ok, #credit{credit = C}} when C > 0 -> true; + {ok, #credit{}} -> false; + error -> true + end, + case ConsAllows of + true -> case Enabled andalso + gen_server2:call( + Pid, {can_send, QPid, AckRequired}, infinity) of + true -> Credits2 = record_send_q( + CTag, Len, ChPid, Credits), + Token#token{q_state = Credits2}; + false -> channel_blocked + end; + false -> consumer_blocked end. -record_cons_send(#token{q_state = QState} = Token, ChPid, CTag, Len) -> - Token#token{q_state = record_send_q(CTag, Len, ChPid, QState)}. - %% Let the limiter know that the channel has received some acks from a %% consumer ack(Limiter, Count) -> maybe_cast(Limiter, {ack, Count}). -- cgit v1.2.1 From a68e32eb4d256be319015a255c64024265f7dce7 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 29 Jan 2013 15:26:16 +0000 Subject: Reduce distance to default. --- src/rabbit_limiter.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 5b4ce802..cc605b5c 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -15,13 +15,11 @@ %% -module(rabbit_limiter). --include("rabbit_framing.hrl"). -behaviour(gen_server2). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, prioritise_call/3]). - -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). -export([limit/2, can_send/6, ack/2, register/2, unregister/2]). -- cgit v1.2.1 From 5727b84d107c4116169d3798855c015fe531630e Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 29 Jan 2013 15:39:15 +0000 Subject: Derp. --- 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 cc605b5c..4796fc0e 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -114,7 +114,7 @@ can_send0(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, error -> true end, case ConsAllows of - true -> case Enabled andalso + true -> case not Enabled orelse gen_server2:call( Pid, {can_send, QPid, AckRequired}, infinity) of true -> Credits2 = record_send_q( -- cgit v1.2.1 From 39ac6385e1e3b0d215349ec3dc3476eda9432c7d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 29 Jan 2013 16:06:46 +0000 Subject: rabbit_limiter:initial_credit/6. --- src/rabbit_amqqueue.erl | 23 ++++++++++++----------- src/rabbit_amqqueue_process.erl | 20 +++++++++++++++----- src/rabbit_channel.erl | 17 +++++++---------- src/rabbit_limiter.erl | 22 ++++++++++++++-------- 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index b9d41c25..3673d06e 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -26,9 +26,9 @@ -export([list/0, list/1, info_keys/0, info/1, info/2, info_all/1, info_all/2]). -export([force_event_refresh/0, wake_up/1]). -export([consumers/1, consumers_all/1, consumer_info_keys/0]). --export([basic_get/3, basic_consume/7, basic_cancel/4]). +-export([basic_get/3, basic_consume/8, basic_cancel/4]). -export([notify_sent/2, notify_sent_queue_down/1, unblock/2, flush_all/2]). --export([notify_down_all/2, limit_all/3, credit/6]). +-export([notify_down_all/2, limit_all/3, credit/5]). -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). -export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1, @@ -145,13 +145,14 @@ -spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). -spec(limit_all/3 :: (qpids(), pid(), rabbit_limiter:token()) -> ok_or_errors()). --spec(credit/6 :: (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), - non_neg_integer(), boolean(), boolean()) -> 'ok'). +-spec(credit/5 :: (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), + non_neg_integer(), boolean()) -> 'ok'). -spec(basic_get/3 :: (rabbit_types:amqqueue(), pid(), boolean()) -> {'ok', non_neg_integer(), qmsg()} | 'empty'). --spec(basic_consume/7 :: +-spec(basic_consume/8 :: (rabbit_types:amqqueue(), boolean(), pid(), - rabbit_limiter:token(), rabbit_types:ctag(), boolean(), any()) + rabbit_limiter:token(), rabbit_types:ctag(), boolean(), + {non_neg_integer(), boolean()} | 'none', any()) -> rabbit_types:ok_or_error('exclusive_consume_unavailable')). -spec(basic_cancel/4 :: (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), any()) -> 'ok'). @@ -534,16 +535,16 @@ notify_down_all(QPids, ChPid) -> limit_all(QPids, ChPid, Limiter) -> delegate:cast(QPids, {limit, ChPid, Limiter}). -credit(#amqqueue{pid = QPid}, ChPid, CTag, Credit, Drain, Reply) -> - delegate:cast(QPid, {credit, ChPid, CTag, Credit, Drain, Reply}). +credit(#amqqueue{pid = QPid}, ChPid, CTag, Credit, Drain) -> + delegate:cast(QPid, {credit, ChPid, CTag, Credit, Drain}). basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> delegate:call(QPid, {basic_get, ChPid, NoAck}). basic_consume(#amqqueue{pid = QPid}, NoAck, ChPid, Limiter, - ConsumerTag, ExclusiveConsume, OkMsg) -> - delegate:call(QPid, {basic_consume, NoAck, ChPid, - Limiter, ConsumerTag, ExclusiveConsume, OkMsg}). + ConsumerTag, ExclusiveConsume, CreditArgs, OkMsg) -> + delegate:call(QPid, {basic_consume, NoAck, ChPid, Limiter, + ConsumerTag, ExclusiveConsume, CreditArgs, OkMsg}). basic_cancel(#amqqueue{pid = QPid}, ChPid, ConsumerTag, OkMsg) -> delegate:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}). diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 5c1b68f4..0594e250 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1096,14 +1096,24 @@ handle_call({basic_get, ChPid, NoAck}, _From, end; handle_call({basic_consume, NoAck, ChPid, Limiter, - ConsumerTag, ExclusiveConsume, OkMsg}, - _From, State = #q{exclusive_consumer = Holder}) -> + ConsumerTag, ExclusiveConsume, CreditArgs, OkMsg}, + _From, State = #q{exclusive_consumer = Holder, + backing_queue = BQ, + backing_queue_state = BQS}) -> case check_exclusive_access(Holder, ExclusiveConsume, State) of in_use -> reply({error, exclusive_consume_unavailable}, State); ok -> C = ch_record(ChPid), - C1 = update_consumer_count(C#cr{limiter = Limiter}, +1), + Limiter2 = case CreditArgs of + none -> + Limiter; + {Credit, Drain} -> + rabbit_limiter:initial_credit( + Limiter, ChPid, ConsumerTag, Credit, Drain, + BQ:len(BQS)) + end, + C1 = update_consumer_count(C#cr{limiter = Limiter2}, +1), Consumer = #consumer{tag = ConsumerTag, ack_required = not NoAck}, ExclusiveConsumer = if ExclusiveConsume -> {ChPid, ConsumerTag}; @@ -1326,13 +1336,13 @@ handle_cast(stop_mirroring, State = #q{backing_queue = BQ, noreply(State#q{backing_queue = BQ1, backing_queue_state = BQS1}); -handle_cast({credit, ChPid, CTag, Credit, Drain, Reply}, +handle_cast({credit, ChPid, CTag, Credit, Drain}, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> #cr{limiter = Lim, blocked_ctags = BCTags} = ch_record(ChPid), {Unblock, Lim2} = rabbit_limiter:credit( - Lim, ChPid, CTag, Credit, Drain, Reply, BQ:len(BQS)), + Lim, ChPid, CTag, Credit, Drain, BQ:len(BQS)), noreply(possibly_unblock( State, ChPid, fun(C) -> C#cr{blocked_ctags = BCTags -- Unblock, limiter = Lim2} end)); diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index f719d8f3..20976932 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -717,7 +717,7 @@ handle_method(#'basic.consume'{queue = QueueNameBin, no_ack = NoAck, exclusive = ExclusiveConsume, nowait = NoWait, - arguments = Args}, + arguments = Arguments}, _, State = #ch{conn_pid = ConnPid, limiter = Limiter, consumer_mapping = ConsumerMapping}) -> @@ -738,10 +738,10 @@ handle_method(#'basic.consume'{queue = QueueNameBin, case rabbit_amqqueue:with_exclusive_access_or_die( QueueName, ConnPid, fun (Q) -> - maybe_set_initial_credit(Args, ActualConsumerTag, Q), {rabbit_amqqueue:basic_consume( Q, NoAck, self(), Limiter, ActualConsumerTag, ExclusiveConsume, + parse_credit_args(Arguments), ok_msg(NoWait, #'basic.consume_ok'{ consumer_tag = ActualConsumerTag})), Q} @@ -1119,7 +1119,7 @@ handle_method(#'basic.credit'{consumer_tag = CTag, State = #ch{consumer_mapping = Consumers}) -> case dict:find(CTag, Consumers) of {ok, Q} -> ok = rabbit_amqqueue:credit( - Q, self(), CTag, Credit, Drain, true), + Q, self(), CTag, Credit, Drain), {noreply, State}; error -> precondition_failed("unknown consumer tag '~s'", [CTag]) end; @@ -1190,17 +1190,14 @@ handle_consuming_queue_down(QPid, handle_delivering_queue_down(QPid, State = #ch{delivering_queues = DQ}) -> State#ch{delivering_queues = sets:del_element(QPid, DQ)}. -maybe_set_initial_credit(Arguments, CTag, Q) -> +parse_credit_args(Arguments) -> case rabbit_misc:table_lookup(Arguments, <<"x-credit">>) of {table, T} -> case {rabbit_misc:table_lookup(T, <<"credit">>), rabbit_misc:table_lookup(T, <<"drain">>)} of - {{long, Credit}, {boolean, Drain}} -> - ok = rabbit_amqqueue:credit( - Q, self(), CTag, Credit, Drain, false); - _ -> - ok + {{long, Credit}, {boolean, Drain}} -> {Credit, Drain}; + _ -> none end; - undefined -> ok + undefined -> none end. binding_action(Fun, ExchangeNameBin, DestinationType, DestinationNameBin, diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 4796fc0e..865c4677 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -24,7 +24,7 @@ disable/1]). -export([limit/2, can_send/6, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). --export([credit/7, forget_consumer/2, copy_queue_state/2]). +-export([initial_credit/6, credit/6, forget_consumer/2, copy_queue_state/2]). -import(rabbit_misc, [serial_add/2, serial_diff/2]). @@ -55,8 +55,11 @@ -spec(block/1 :: (token()) -> 'ok'). -spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). -spec(is_blocked/1 :: (token()) -> boolean()). --spec(credit/7 :: (token(), pid(), rabbit_types:ctag(), - non_neg_integer(), boolean(), boolean(), non_neg_integer()) +-spec(initial_credit/6 :: (token(), pid(), rabbit_types:ctag(), + non_neg_integer(), boolean(), non_neg_integer()) + -> token()). +-spec(credit/6 :: (token(), pid(), rabbit_types:ctag(), + non_neg_integer(), boolean(), non_neg_integer()) -> {[rabbit_types:ctag()], token()}). -spec(forget_consumer/2 :: (token(), rabbit_types:ctag()) -> token()). -spec(copy_queue_state/2 :: (token(), token()) -> token()). @@ -147,14 +150,17 @@ unblock(Limiter) -> is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). +initial_credit(Limiter = #token{q_state = Credits}, + ChPid, CTag, Credit, Drain, Len) -> + {[], Credits2} = update_credit( + CTag, Len, ChPid, Credit, Drain, Credits), + Limiter#token{q_state = Credits2}. + credit(Limiter = #token{q_state = Credits}, - ChPid, CTag, Credit, Drain, Reply, Len) -> + ChPid, CTag, Credit, Drain, Len) -> {Unblock, Credits2} = update_credit( CTag, Len, ChPid, Credit, Drain, Credits), - case Reply of - true -> rabbit_channel:send_credit_reply(ChPid, Len); - false -> ok - end, + rabbit_channel:send_credit_reply(ChPid, Len), {Unblock, Limiter#token{q_state = Credits2}}. forget_consumer(Limiter = #token{q_state = Credits}, CTag) -> -- cgit v1.2.1 From bebd25e9724ac92941a3903bb44af2c805124821 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 29 Jan 2013 17:50:12 +0000 Subject: remove spurious generality after spending hours trawling through the qi code and its history, Matthew and I are convinced that qi:add_to_journal/3 is unnecessarily general, handling a case that can never arise, namely adding an 'ack' when we do have an entry for the given sequence number but that entry does no contain a 'del'. add_to_journal/3 gets called, indirectly, from four places: 1) load_journal/1. This is always called with no segements in the State. So all the segment journal entries originate from the very add_journal/3 code. And the only way we'd end up with an entry of the form {Pub, no_del, no_ack} and get an 'ack' is if the journal contained a pub and (later) an ack, with no del inbetween. That can only happen through a misuse of the qi API. Which doesn't happen. And there are plenty of other cases (e.g. duplicate dels or acks) where qi insists on callers doing the right thing. 2) publish/5 This ends up adding a {?PUB, no_del, no_ack} entry, so is of no direct concern to our investigation. 3) deliver_or_ack/3 This would hit the aforementioned {Pub, no_del, no_ack} & 'ack' case only if we lost a 'del'. 4) recover_message/5 this only adds an 'ack' to the segment journal if either a) the combination of the segment entries and the segment journal produces an entry {?PUB, del, no_ack}, or b) it's just added a 'del' (thus making a {Pub, no_del, no_ack} entry impossible). Re (a)... for there to be a combined entry of {?PUB, del, no_ack} when the segment journal contains {Pub, no_del, no_ack} (which would trigger the case we are concerned about), the segment would have to contain a 'del' w/o a 'pub', which is impossible. --- src/rabbit_queue_index.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_queue_index.erl b/src/rabbit_queue_index.erl index 4559bb8a..3841b680 100644 --- a/src/rabbit_queue_index.erl +++ b/src/rabbit_queue_index.erl @@ -616,8 +616,8 @@ add_to_journal(RelSeq, Action, JEntries) -> end; ({Pub, no_del, no_ack}) when Action == del -> {Pub, del, no_ack}; - ({Pub, Del, no_ack}) when Action == ack -> - {Pub, Del, ack} + ({Pub, del, no_ack}) when Action == ack -> + {Pub, del, ack} end, array:set(RelSeq, Val, JEntries). -- cgit v1.2.1 From 1c50d72ccdd59c94b1973093749528c899a26bf1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 30 Jan 2013 11:07:10 +0000 Subject: remove out-of-date comment --- src/rabbit_variable_queue.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 18cab48b..faa1b0b1 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -883,8 +883,7 @@ cons_if(true, E, L) -> [E | L]; cons_if(false, _E, L) -> L. gb_sets_maybe_insert(false, _Val, Set) -> Set; -%% when requeueing, we re-add a msg_id to the unconfirmed set -gb_sets_maybe_insert(true, Val, Set) -> gb_sets:add(Val, Set). +gb_sets_maybe_insert(true, Val, Set) -> gb_sets:add(Val, Set). msg_status(IsPersistent, IsDelivered, SeqId, Msg = #basic_message {id = MsgId}, MsgProps) -> -- cgit v1.2.1 From 612f2df8a3c62bbf47069323409e55c9f501d9dd Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 30 Jan 2013 13:41:28 +0000 Subject: only keep track of *unconfirmed* unsync'ed messages ids in qi ...so we don't bloat memory with stuff nobody cares about, and reduce the qi->queue callbacks (to zero when no confirms/tx are used) This does expose qi to a bit more messaging semantics, but imo to an acceptable degree. related changes: - rename the state var to better capture its (revised) meaning - only invoke the OnSyncFun when we have something to say --- src/rabbit_queue_index.erl | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/rabbit_queue_index.erl b/src/rabbit_queue_index.erl index 4559bb8a..7dc69458 100644 --- a/src/rabbit_queue_index.erl +++ b/src/rabbit_queue_index.erl @@ -162,7 +162,7 @@ %%---------------------------------------------------------------------------- -record(qistate, { dir, segments, journal_handle, dirty_count, - max_journal_entries, on_sync, unsynced_msg_ids }). + max_journal_entries, on_sync, unconfirmed }). -record(segment, { num, path, journal_entries, unacked }). @@ -190,7 +190,7 @@ dirty_count :: integer(), max_journal_entries :: non_neg_integer(), on_sync :: on_sync_fun(), - unsynced_msg_ids :: gb_set() + unconfirmed :: gb_set() }). -type(contains_predicate() :: fun ((rabbit_types:msg_id()) -> boolean())). -type(walker(A) :: fun ((A) -> 'finished' | @@ -269,13 +269,16 @@ delete_and_terminate(State) -> State1. publish(MsgId, SeqId, MsgProps, IsPersistent, - State = #qistate { unsynced_msg_ids = UnsyncedMsgIds }) + State = #qistate { unconfirmed = Unconfirmed }) when is_binary(MsgId) -> ?MSG_ID_BYTES = size(MsgId), {JournalHdl, State1} = get_journal_handle( - State #qistate { - unsynced_msg_ids = gb_sets:add_element(MsgId, UnsyncedMsgIds) }), + case MsgProps#message_properties.needs_confirming of + true -> Unconfirmed1 = gb_sets:add_element(MsgId, Unconfirmed), + State #qistate { unconfirmed = Unconfirmed1 }; + false -> State + end), ok = file_handle_cache:append( JournalHdl, [<<(case IsPersistent of true -> ?PUB_PERSIST_JPREFIX; @@ -398,7 +401,7 @@ blank_state_dir(Dir) -> dirty_count = 0, max_journal_entries = MaxJournal, on_sync = fun (_) -> ok end, - unsynced_msg_ids = gb_sets:new() }. + unconfirmed = gb_sets:new() }. clean_filename(Dir) -> filename:join(Dir, ?CLEAN_FILENAME). @@ -732,9 +735,12 @@ deliver_or_ack(Kind, SeqIds, State) -> add_to_journal(SeqId, Kind, StateN) end, State1, SeqIds)). -notify_sync(State = #qistate { unsynced_msg_ids = UG, on_sync = OnSyncFun }) -> - OnSyncFun(UG), - State #qistate { unsynced_msg_ids = gb_sets:new() }. +notify_sync(State = #qistate { unconfirmed = UC, on_sync = OnSyncFun }) -> + case gb_sets:is_empty(UC) of + true -> State; + false -> OnSyncFun(UC), + State #qistate { unconfirmed = gb_sets:new() } + end. %%---------------------------------------------------------------------------- %% segment manipulation -- cgit v1.2.1 From 6dccc04b620268999e96e6eec5cd44efbc8893fd Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 30 Jan 2013 14:05:08 +0000 Subject: don't hold on to 'complete' in-memory qi journal entries When a pub, del and ack for a message are all recorded in the journal then we don't bother writing any of that to the segment files when the journal is flushed, since the message is well and truly in the past and forgotten. So... there is no point keeping the entry in the in-memory journal either, where it just eats up space until a flush for no good reason. --- src/rabbit_queue_index.erl | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/rabbit_queue_index.erl b/src/rabbit_queue_index.erl index 7dc69458..07117f9a 100644 --- a/src/rabbit_queue_index.erl +++ b/src/rabbit_queue_index.erl @@ -610,19 +610,21 @@ add_to_journal(RelSeq, Action, end}; add_to_journal(RelSeq, Action, JEntries) -> - Val = case array:get(RelSeq, JEntries) of - undefined -> - case Action of - ?PUB -> {Action, no_del, no_ack}; - del -> {no_pub, del, no_ack}; - ack -> {no_pub, no_del, ack} - end; - ({Pub, no_del, no_ack}) when Action == del -> - {Pub, del, no_ack}; - ({Pub, Del, no_ack}) when Action == ack -> - {Pub, Del, ack} - end, - array:set(RelSeq, Val, JEntries). + case array:get(RelSeq, JEntries) of + undefined -> + array:set(RelSeq, + case Action of + ?PUB -> {Action, no_del, no_ack}; + del -> {no_pub, del, no_ack}; + ack -> {no_pub, no_del, ack} + end, JEntries); + ({?PUB, del, no_ack}) when Action == ack -> + array:reset(RelSeq, JEntries); + ({Pub, no_del, no_ack}) when Action == del -> + array:set(RelSeq, {Pub, del, no_ack}, JEntries); + ({Pub, Del, no_ack}) when Action == ack -> + array:set(RelSeq, {Pub, Del, ack}, JEntries) + end. maybe_flush_journal(State = #qistate { dirty_count = DCount, max_journal_entries = MaxJournal }) -- cgit v1.2.1 From a39bc21538e580476fca3b5a9252dd360ea82147 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 30 Jan 2013 14:29:16 +0000 Subject: reduce the default qi journal size a large journal size improves performance by - writing larger blocks to disk in one go (since in many scenarios we end up writing the entire journal in one go) - reducing fsync frequency (since in many scenarios we only sync when rolling the journal) - records for messages which get published, delivered and acked in the lifetime of a journal don't need to be written to segment files. To the point where we may be able to skip writing segment files altogether. BUT a large size also has downsides: - increased memory consumption - increased amount of data loss in the event of a crash So how do we pick a figure? Well, ultimately we may want to dynamically adjust the size based on memory pressure, but that is a fairly involved / risky change. Meanwhile, what would be the *lowest* figure that still delivers all of the benefits, just somewhat less than currently? A single qi segment holds entries for 16k messages. We want the journal to be able to hold at least an entire segment's worth of publishes, delivers and acks, which would be 16k x 3 entries. That way the aforementioned segment write reductions can happen when the queue is small. However, with a size of *exactly* 16k x 3, we would generally only avoid whole segment writes when messages get published, delivered and ack'ed straight away, which only happens when consuming in noack mode from empty queues. So we want to add some headroom in order to extend the benefits to a wider range of modes of queue operation. Hence 16k x 4 is a good choice. It should allow most "near empty" fast moving queues to avoid segment writes. --- ebin/rabbit_app.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebin/rabbit_app.in b/ebin/rabbit_app.in index 16dfd196..f4d4ec3d 100644 --- a/ebin/rabbit_app.in +++ b/ebin/rabbit_app.in @@ -27,7 +27,7 @@ {frame_max, 131072}, {heartbeat, 600}, {msg_store_file_size_limit, 16777216}, - {queue_index_max_journal_entries, 262144}, + {queue_index_max_journal_entries, 65536}, {default_user, <<"guest">>}, {default_pass, <<"guest">>}, {default_user_tags, [administrator]}, -- cgit v1.2.1 From 7f6543ca82df40f3710262aed52a3076898310dc Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 30 Jan 2013 21:16:03 +0000 Subject: ask qi whether it needs sync'ing, and why --- src/rabbit_queue_index.erl | 9 ++++++--- src/rabbit_variable_queue.erl | 40 +++++++++++----------------------------- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/rabbit_queue_index.erl b/src/rabbit_queue_index.erl index 07117f9a..a055742e 100644 --- a/src/rabbit_queue_index.erl +++ b/src/rabbit_queue_index.erl @@ -210,7 +210,7 @@ -spec(deliver/2 :: ([seq_id()], qistate()) -> qistate()). -spec(ack/2 :: ([seq_id()], qistate()) -> qistate()). -spec(sync/1 :: (qistate()) -> qistate()). --spec(needs_sync/1 :: (qistate()) -> boolean()). +-spec(needs_sync/1 :: (qistate()) -> 'confirms' | boolean()). -spec(flush/1 :: (qistate()) -> qistate()). -spec(read/3 :: (seq_id(), seq_id(), qistate()) -> {[{rabbit_types:msg_id(), seq_id(), @@ -305,8 +305,11 @@ sync(State = #qistate { journal_handle = JournalHdl }) -> needs_sync(#qistate { journal_handle = undefined }) -> false; -needs_sync(#qistate { journal_handle = JournalHdl }) -> - file_handle_cache:needs_sync(JournalHdl). +needs_sync(#qistate { journal_handle = JournalHdl, unconfirmed = UC }) -> + case gb_sets:is_empty(UC) of + true -> file_handle_cache:needs_sync(JournalHdl); + false -> confirms + end. flush(State = #qistate { dirty_count = 0 }) -> State; flush(State) -> flush_journal(State). diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 1acc9ef0..0be81569 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -755,20 +755,17 @@ ram_duration(State = #vqstate { ram_ack_count_prev = RamAckCount }}. needs_timeout(State = #vqstate { index_state = IndexState }) -> - case must_sync_index(State) of - true -> timed; - false -> - case rabbit_queue_index:needs_sync(IndexState) of - true -> idle; - false -> case reduce_memory_use( - fun (_Quota, State1) -> {0, State1} end, - fun (_Quota, State1) -> State1 end, - fun (_Quota, State1) -> {0, State1} end, - State) of - {true, _State} -> idle; - {false, _State} -> false - end - end + case rabbit_queue_index:needs_sync(IndexState) of + confirms -> timed; + true -> idle; + false -> case reduce_memory_use( + fun (_Quota, State1) -> {0, State1} end, + fun (_Quota, State1) -> State1 end, + fun (_Quota, State1) -> {0, State1} end, + State) of + {true, _State} -> idle; + {false, _State} -> false + end end. timeout(State = #vqstate { index_state = IndexState }) -> @@ -1304,21 +1301,6 @@ record_confirms(MsgIdSet, State = #vqstate { msgs_on_disk = MOD, unconfirmed = rabbit_misc:gb_sets_difference(UC, MsgIdSet), confirmed = gb_sets:union(C, MsgIdSet) }. -must_sync_index(#vqstate { msg_indices_on_disk = MIOD, - unconfirmed = UC }) -> - %% If UC is empty then by definition, MIOD and MOD are also empty - %% and there's nothing that can be pending a sync. - - %% If UC is not empty, then we want to find is_empty(UC - MIOD), - %% but the subtraction can be expensive. Thus instead, we test to - %% see if UC is a subset of MIOD. This can only be the case if - %% MIOD == UC, which would indicate that every message in UC is - %% also in MIOD and is thus _all_ pending on a msg_store sync, not - %% on a qi sync. Thus the negation of this is sufficient. Because - %% is_subset is short circuiting, this is more efficient than the - %% subtraction. - not (gb_sets:is_empty(UC) orelse gb_sets:is_subset(UC, MIOD)). - msgs_written_to_disk(Callback, MsgIdSet, ignored) -> Callback(?MODULE, fun (?MODULE, State) -> record_confirms(MsgIdSet, State) end); -- cgit v1.2.1 From 9720758be509d763cb7497fe22e7e4b40fb373e3 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 31 Jan 2013 15:23:50 +0000 Subject: Avoid always going through with_exit_handler/2, since that's what the comment says! --- 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 865c4677..35703efa 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -104,22 +104,15 @@ limit(Limiter, PrefetchCount) -> %% breaching a limit. Note that we don't use maybe_call here in order %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. -can_send(Token, QPid, AckRequired, ChPid, CTag, Len) -> - rabbit_misc:with_exit_handler( - fun () -> true end, - fun () -> can_send0(Token, QPid, AckRequired, ChPid, CTag, Len) end). - -can_send0(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, - QPid, AckRequired, ChPid, CTag, Len) -> +can_send(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, + QPid, AckReq, ChPid, CTag, Len) -> ConsAllows = case dict:find(CTag, Credits) of {ok, #credit{credit = C}} when C > 0 -> true; {ok, #credit{}} -> false; error -> true end, case ConsAllows of - true -> case not Enabled orelse - gen_server2:call( - Pid, {can_send, QPid, AckRequired}, infinity) of + true -> case not Enabled orelse call_can_send(Pid, QPid, AckReq) of true -> Credits2 = record_send_q( CTag, Len, ChPid, Credits), Token#token{q_state = Credits2}; @@ -128,6 +121,13 @@ can_send0(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, false -> consumer_blocked end. +call_can_send(Pid, QPid, AckRequired) -> + rabbit_misc:with_exit_handler( + fun () -> true end, + fun () -> + gen_server2:call(Pid, {can_send, QPid, AckRequired}, infinity) + end). + %% Let the limiter know that the channel has received some acks from a %% consumer ack(Limiter, Count) -> maybe_cast(Limiter, {ack, Count}). -- cgit v1.2.1 From 33a40ada5818f20c45dddfaa98911af1855e717d Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 1 Feb 2013 11:02:19 +0000 Subject: Ensure supervisor try_again_restart observes delayed restart --- src/supervisor2.erl | 71 +++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 1f1f9246..7f04536a 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -81,7 +81,7 @@ %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([try_again_restart/2]). +-export([try_again_restart/3]). %%-------------------------------------------------------------------------- -ifdef(use_specs). @@ -312,12 +312,13 @@ check_childspecs(X) -> {error, {badarg, X}}. %%%----------------------------------------------------------------- %%% Called by timer:apply_after from restart/2 -ifdef(use_specs). --spec try_again_restart(SupRef, Child) -> ok when +-spec try_again_restart(SupRef, Child, Reason) -> ok when SupRef :: sup_ref(), - Child :: child_id() | pid(). + Child :: child_id() | pid(), + Reason :: term(). -endif. -try_again_restart(Supervisor, Child) -> - cast(Supervisor, {try_again_restart, Child}). +try_again_restart(Supervisor, Child, Reason) -> + cast(Supervisor, {try_again_restart, Child, Reason}). cast(Supervisor, Req) -> gen_server:cast(Supervisor, Req). @@ -362,7 +363,7 @@ init_children(State, StartSpec) -> case start_children(Children, SupName) of {ok, NChildren} -> {ok, State#state{children = NChildren}}; - {error, NChildren} -> + {error, _, NChildren} -> terminate_children(NChildren, SupName), {stop, shutdown} end; @@ -402,7 +403,7 @@ start_children([Child|Chs], NChildren, SupName) -> start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); {error, Reason} -> report_error(start_error, Reason, Child, SupName), - {error, lists:reverse(Chs) ++ [Child | NChildren]} + {error, Reason, lists:reverse(Chs) ++ [Child | NChildren]} end; start_children([], NChildren, _SupName) -> {ok, NChildren}. @@ -630,10 +631,10 @@ count_child(#child{pid = Pid, child_type = supervisor}, %%% timer:apply_after(0,...) in order to give gen_server the chance to %%% check it's inbox before trying again. -ifdef(use_specs). --spec handle_cast({try_again_restart, child_id() | pid()}, state()) -> +-spec handle_cast({try_again_restart, child_id() | pid(), term()}, state()) -> {'noreply', state()} | {stop, shutdown, state()}. -endif. -handle_cast({try_again_restart,Pid}, #state{children=[Child]}=State) +handle_cast({try_again_restart,Pid,Reason}, #state{children=[Child]}=State) when ?is_simple(State) -> RT = Child#child.restart_type, RPid = restarting(Pid), @@ -641,7 +642,7 @@ handle_cast({try_again_restart,Pid}, #state{children=[Child]}=State) {ok, Args} -> {M, F, _} = Child#child.mfargs, NChild = Child#child{pid = RPid, mfargs = {M, F, Args}}, - case restart(NChild,State) of + case restart_child(NChild,Reason,State) of {ok, State1} -> {noreply, State1}; {shutdown, State1} -> @@ -651,10 +652,10 @@ handle_cast({try_again_restart,Pid}, #state{children=[Child]}=State) {noreply, State} end; -handle_cast({try_again_restart,Name}, State) -> +handle_cast({try_again_restart,Name,Reason}, State) -> case lists:keyfind(Name,#child.name,State#state.children) of Child = #child{pid=?restarting(_)} -> - case restart(Child,State) of + case restart_child(Child,Reason,State) of {ok, State1} -> {noreply, State1}; {shutdown, State1} -> @@ -867,27 +868,27 @@ do_restart(temporary, Reason, Child, State) -> {ok, NState}. do_restart_delay({RestartType, Delay}, Reason, Child, State) -> - case restart1(Child, State) of + case add_restart(State) of {ok, NState} -> + restart(NState#state.strategy, Child, NState); + {try_again, Reason, NState} -> + %% See restart/2 for an explanation of try_again_restart + Id = if ?is_simple(State) -> Child#child.pid; + true -> Child#child.name + end, + timer:apply_after(0,?MODULE,try_again_restart,[self(),Id,Reason]), {ok, NState}; - {terminate, NState} -> - _TRef = erlang:send_after(trunc(Delay*1000), self(), - {delayed_restart, - {{RestartType, Delay}, Reason, Child}}), - {ok, state_del_child(Child, NState)} - end. - -restart1(Child, State) -> - case add_restart(State) of - {ok, NState} -> - restart(NState#state.strategy, Child, NState); - {terminate, _NState} -> + {terminate, _NState} -> %% we've reached the max restart intensity, but the %% add_restart will have added to the restarts %% field. Given we don't want to die here, we need to go %% back to the old restarts field otherwise we'll never - %% attempt to restart later. - {terminate, State} + %% attempt to restart later, which is why we ignore + %% NState for this clause. + _TRef = erlang:send_after(trunc(Delay*1000), self(), + {delayed_restart, + {{RestartType, Delay}, Reason, Child}}), + {ok, state_del_child(Child, State)} end. del_child_and_maybe_shutdown(intrinsic, Child, State) -> @@ -901,7 +902,7 @@ restart(Child, State) -> case add_restart(State) of {ok, NState} -> case restart(NState#state.strategy, Child, NState) of - {try_again,NState2} -> + {try_again, Reason, NState2} -> %% Leaving control back to gen_server before %% trying again. This way other incoming requsts %% for the supervisor can be handled - e.g. a @@ -910,7 +911,7 @@ restart(Child, State) -> Id = if ?is_simple(State) -> Child#child.pid; true -> Child#child.name end, - timer:apply_after(0,?MODULE,try_again_restart,[self(),Id]), + timer:apply_after(0,?MODULE,try_again_restart,[self(),Id,Reason]), {ok,NState2}; Other -> Other @@ -936,7 +937,7 @@ restart(simple_one_for_one, Child, State) -> NState = State#state{dynamics = ?DICT:store(restarting(OldPid), A, Dynamics)}, report_error(start_error, Error, Child, State#state.name), - {try_again, NState} + {try_again, Error, NState} end; restart(one_for_one, Child, State) -> OldPid = Child#child.pid, @@ -950,7 +951,7 @@ restart(one_for_one, Child, State) -> {error, Reason} -> NState = replace_child(Child#child{pid = restarting(OldPid)}, State), report_error(start_error, Reason, Child, State#state.name), - {try_again, NState} + {try_again, Reason, NState} end; restart(rest_for_one, Child, State) -> {ChAfter, ChBefore} = split_child(Child#child.pid, State#state.children), @@ -958,10 +959,10 @@ restart(rest_for_one, Child, State) -> case start_children(ChAfter2, State#state.name) of {ok, ChAfter3} -> {ok, State#state{children = ChAfter3 ++ ChBefore}}; - {error, ChAfter3} -> + {error, Reason, ChAfter3} -> NChild = Child#child{pid=restarting(Child#child.pid)}, NState = State#state{children = ChAfter3 ++ ChBefore}, - {try_again, replace_child(NChild,NState)} + {try_again, Reason, replace_child(NChild,NState)} end; restart(one_for_all, Child, State) -> Children1 = del_child(Child#child.pid, State#state.children), @@ -969,10 +970,10 @@ restart(one_for_all, Child, State) -> case start_children(Children2, State#state.name) of {ok, NChs} -> {ok, State#state{children = NChs}}; - {error, NChs} -> + {error, Reason, NChs} -> NChild = Child#child{pid=restarting(Child#child.pid)}, NState = State#state{children = NChs}, - {try_again, replace_child(NChild,NState)} + {try_again, Reason, replace_child(NChild,NState)} end. restarting(Pid) when is_pid(Pid) -> ?restarting(Pid); -- cgit v1.2.1 From b60a1362ea4739a4f0fc271ad7a29a2db18e9aee Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 1 Feb 2013 14:02:49 +0000 Subject: avoid badmatch in handle_info/delayed_restart --- src/supervisor2.erl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 7f04536a..693e0b6d 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -682,13 +682,11 @@ handle_info({'EXIT', Pid, Reason}, State) -> handle_info({delayed_restart, {RestartType, Reason, Child}}, State) when ?is_simple(State) -> - {ok, NState} = do_restart(RestartType, Reason, Child, State), - {noreply, NState}; + delayed_restart(RestartType, Reason, Child, State); handle_info({delayed_restart, {RestartType, Reason, Child}}, State) -> case get_child(Child#child.name, State) of {value, Child1} -> - {ok, NState} = do_restart(RestartType, Reason, Child1, State), - {noreply, NState}; + delayed_restart(RestartType, Reason, Child1, State); _What -> {noreply, State} end; @@ -698,6 +696,12 @@ handle_info(Msg, State) -> [Msg]), {noreply, State}. +delayed_restart(RestartType, Reason, Child, State) -> + case do_restart(RestartType, Reason, Child, State) of + {ok, NState} -> {noreply, NState}; + {try_again, _, NState} -> {noreply, NState} + end. + %% %% Terminate this server. %% -- cgit v1.2.1 From a284ca9d6d39898357359327ccf373cb958d4e21 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 1 Feb 2013 16:34:51 +0000 Subject: rabbit_parameter_validation:enum/1 --- src/rabbit_parameter_validation.erl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/rabbit_parameter_validation.erl b/src/rabbit_parameter_validation.erl index 39d0188c..a8dfdd3a 100644 --- a/src/rabbit_parameter_validation.erl +++ b/src/rabbit_parameter_validation.erl @@ -16,7 +16,7 @@ -module(rabbit_parameter_validation). --export([number/2, binary/2, boolean/2, list/2, regex/2, proplist/3]). +-export([number/2, binary/2, boolean/2, list/2, regex/2, proplist/3, enum/1]). number(_Name, Term) when is_number(Term) -> ok; @@ -73,3 +73,15 @@ proplist(Name, Constraints, Term) when is_list(Term) -> proplist(Name, _Constraints, Term) -> {error, "~s not a list ~p", [Name, Term]}. + +enum(OptionsA) -> + Options = [list_to_binary(atom_to_list(O)) || O <- OptionsA], + fun (Name, Term) when is_binary(Term) -> + case lists:member(Term, Options) of + true -> ok; + false -> {error, "~s should be one of ~p, actuaklly was ~p", + [Name, Options, Term]} + end; + (Name, Term) -> + {error, "~s should be binary, actually was ~p", [Name, Term]} + end. -- cgit v1.2.1 From 3cc52f2a7b4322316591a03a7fc564b754f24bef Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 5 Feb 2013 10:42:25 +0000 Subject: Slightly cleaner logging at startup: don't repeat the RabbitMQ or Erlang versions, and split copyright out into its own log message since it is not like the other bits. --- src/rabbit.erl | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 6b730fda..98178a62 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -725,34 +725,29 @@ print_banner() -> log_location(kernel), log_location(sasl)]). log_banner() -> - {ok, Product} = application:get_key(id), - {ok, Version} = application:get_key(vsn), + error_logger:info_msg("~s ~s~n", + [?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), Settings = [{"node", node()}, {"home dir", home_dir()}, {"config file(s)", config_files()}, {"cookie hash", rabbit_nodes:cookie_hash()}, {"log", log_location(kernel)}, {"sasl log", log_location(sasl)}, - {"database dir", rabbit_mnesia:dir()}, - {"erlang version", erlang:system_info(otp_release)}], + {"database dir", rabbit_mnesia:dir()}], DescrLen = 1 + lists:max([length(K) || {K, _V} <- Settings]), Format = fun (K, V) -> rabbit_misc:format( "~-" ++ integer_to_list(DescrLen) ++ "s: ~s~n", [K, V]) end, Banner = iolist_to_binary( - rabbit_misc:format( - "~s ~s~n~s~n~s~n", - [Product, Version, ?COPYRIGHT_MESSAGE, - ?INFORMATION_MESSAGE]) ++ - [case S of - {"config file(s)" = K, []} -> - Format(K, "(none)"); - {"config file(s)" = K, [V0 | Vs]} -> - Format(K, V0), [Format("", V) || V <- Vs]; - {K, V} -> - Format(K, V) - end || S <- Settings]), + [case S of + {"config file(s)" = K, []} -> + Format(K, "(none)"); + {"config file(s)" = K, [V0 | Vs]} -> + Format(K, V0), [Format("", V) || V <- Vs]; + {K, V} -> + Format(K, V) + end || S <- Settings]), error_logger:info_msg("~s", [Banner]). home_dir() -> -- cgit v1.2.1 From 5296207cb1fe423b141bc7b72716bc5c4c965a36 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 5 Feb 2013 10:48:33 +0000 Subject: Slightly cleaner logging at startup: make the plugin list look nicer. --- src/rabbit.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 98178a62..73399c03 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -699,8 +699,11 @@ force_event_refresh() -> log_broker_started(Plugins) -> rabbit_misc:with_local_io( fun() -> + PluginList = iolist_to_binary([rabbit_misc:format(" * ~s~n", [P]) + || P <- Plugins]), error_logger:info_msg( - "Server startup complete; plugins are: ~p~n", [Plugins]), + "Server startup complete; ~b plugins started.~n~s~n", + [length(Plugins), PluginList]), io:format("~n Broker running with ~p plugins.~n", [length(Plugins)]) end). -- cgit v1.2.1 From 8509e6813b697698b1f78300fb0547ae0e83bca6 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 5 Feb 2013 11:12:48 +0000 Subject: Spelling --- src/rabbit_parameter_validation.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_parameter_validation.erl b/src/rabbit_parameter_validation.erl index a8dfdd3a..a4bd5042 100644 --- a/src/rabbit_parameter_validation.erl +++ b/src/rabbit_parameter_validation.erl @@ -79,7 +79,7 @@ enum(OptionsA) -> fun (Name, Term) when is_binary(Term) -> case lists:member(Term, Options) of true -> ok; - false -> {error, "~s should be one of ~p, actuaklly was ~p", + false -> {error, "~s should be one of ~p, actually was ~p", [Name, Options, Term]} end; (Name, Term) -> -- cgit v1.2.1 From e1199f009198360d869c0977545a2ff42a05e5ef Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 8 Feb 2013 10:52:46 +0000 Subject: Change to 'confirms' | 'other' | 'false' --- src/rabbit_queue_index.erl | 7 +++++-- src/rabbit_variable_queue.erl | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/rabbit_queue_index.erl b/src/rabbit_queue_index.erl index 6495115b..ea70208f 100644 --- a/src/rabbit_queue_index.erl +++ b/src/rabbit_queue_index.erl @@ -210,7 +210,7 @@ -spec(deliver/2 :: ([seq_id()], qistate()) -> qistate()). -spec(ack/2 :: ([seq_id()], qistate()) -> qistate()). -spec(sync/1 :: (qistate()) -> qistate()). --spec(needs_sync/1 :: (qistate()) -> 'confirms' | boolean()). +-spec(needs_sync/1 :: (qistate()) -> 'confirms' | 'other' | 'false'). -spec(flush/1 :: (qistate()) -> qistate()). -spec(read/3 :: (seq_id(), seq_id(), qistate()) -> {[{rabbit_types:msg_id(), seq_id(), @@ -307,7 +307,10 @@ needs_sync(#qistate { journal_handle = undefined }) -> false; needs_sync(#qistate { journal_handle = JournalHdl, unconfirmed = UC }) -> case gb_sets:is_empty(UC) of - true -> file_handle_cache:needs_sync(JournalHdl); + true -> case file_handle_cache:needs_sync(JournalHdl) of + true -> other; + false -> false + end; false -> confirms end. diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index a23ebcb6..5d463f57 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -774,7 +774,7 @@ needs_timeout(State = #vqstate { index_state = IndexState, target_ram_count = TargetRamCount }) -> case rabbit_queue_index:needs_sync(IndexState) of confirms -> timed; - true -> idle; + other -> idle; false when TargetRamCount == infinity -> false; false -> case reduce_memory_use( fun (_Quota, State1) -> {0, State1} end, -- cgit v1.2.1 From 5415b94021756730f90af662b83164db1a0c7e62 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 8 Feb 2013 16:09:30 +0000 Subject: Don't repeat yourself. --- src/rabbit_auth_mechanism_amqplain.erl | 3 +-- src/rabbit_auth_mechanism_cr_demo.erl | 3 +-- src/rabbit_auth_mechanism_plain.erl | 3 +-- src/rabbit_exchange_type_direct.erl | 3 +-- src/rabbit_exchange_type_fanout.erl | 3 +-- src/rabbit_exchange_type_headers.erl | 3 +-- src/rabbit_exchange_type_topic.erl | 3 +-- 7 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/rabbit_auth_mechanism_amqplain.erl b/src/rabbit_auth_mechanism_amqplain.erl index 1ed54fef..847a38f5 100644 --- a/src/rabbit_auth_mechanism_amqplain.erl +++ b/src/rabbit_auth_mechanism_amqplain.erl @@ -33,8 +33,7 @@ %% referring generically to "SASL security mechanism", i.e. the above. description() -> - [{name, <<"AMQPLAIN">>}, - {description, <<"QPid AMQPLAIN mechanism">>}]. + [{description, <<"QPid AMQPLAIN mechanism">>}]. should_offer(_Sock) -> true. diff --git a/src/rabbit_auth_mechanism_cr_demo.erl b/src/rabbit_auth_mechanism_cr_demo.erl index e4494ab4..4b08e4be 100644 --- a/src/rabbit_auth_mechanism_cr_demo.erl +++ b/src/rabbit_auth_mechanism_cr_demo.erl @@ -37,8 +37,7 @@ %% SECURE-OK: "My password is ~s", [Password] description() -> - [{name, <<"RABBIT-CR-DEMO">>}, - {description, <<"RabbitMQ Demo challenge-response authentication " + [{description, <<"RabbitMQ Demo challenge-response authentication " "mechanism">>}]. should_offer(_Sock) -> diff --git a/src/rabbit_auth_mechanism_plain.erl b/src/rabbit_auth_mechanism_plain.erl index 5553a641..a35a133a 100644 --- a/src/rabbit_auth_mechanism_plain.erl +++ b/src/rabbit_auth_mechanism_plain.erl @@ -36,8 +36,7 @@ %% matching and will thus be much faster. description() -> - [{name, <<"PLAIN">>}, - {description, <<"SASL PLAIN authentication mechanism">>}]. + [{description, <<"SASL PLAIN authentication mechanism">>}]. should_offer(_Sock) -> true. diff --git a/src/rabbit_exchange_type_direct.erl b/src/rabbit_exchange_type_direct.erl index e54bd66e..213b24c4 100644 --- a/src/rabbit_exchange_type_direct.erl +++ b/src/rabbit_exchange_type_direct.erl @@ -31,8 +31,7 @@ {enables, kernel_ready}]}). description() -> - [{name, <<"direct">>}, - {description, <<"AMQP direct exchange, as per the AMQP specification">>}]. + [{description, <<"AMQP direct exchange, as per the AMQP specification">>}]. serialise_events() -> false. diff --git a/src/rabbit_exchange_type_fanout.erl b/src/rabbit_exchange_type_fanout.erl index 870b327a..5b17ed56 100644 --- a/src/rabbit_exchange_type_fanout.erl +++ b/src/rabbit_exchange_type_fanout.erl @@ -31,8 +31,7 @@ {enables, kernel_ready}]}). description() -> - [{name, <<"fanout">>}, - {description, <<"AMQP fanout exchange, as per the AMQP specification">>}]. + [{description, <<"AMQP fanout exchange, as per the AMQP specification">>}]. serialise_events() -> false. diff --git a/src/rabbit_exchange_type_headers.erl b/src/rabbit_exchange_type_headers.erl index b185cc4a..75899160 100644 --- a/src/rabbit_exchange_type_headers.erl +++ b/src/rabbit_exchange_type_headers.erl @@ -37,8 +37,7 @@ -endif. description() -> - [{name, <<"headers">>}, - {description, <<"AMQP headers exchange, as per the AMQP specification">>}]. + [{description, <<"AMQP headers exchange, as per the AMQP specification">>}]. serialise_events() -> false. diff --git a/src/rabbit_exchange_type_topic.erl b/src/rabbit_exchange_type_topic.erl index 70e32eaa..bd8ad1ac 100644 --- a/src/rabbit_exchange_type_topic.erl +++ b/src/rabbit_exchange_type_topic.erl @@ -34,8 +34,7 @@ %%---------------------------------------------------------------------------- description() -> - [{name, <<"topic">>}, - {description, <<"AMQP topic exchange, as per the AMQP specification">>}]. + [{description, <<"AMQP topic exchange, as per the AMQP specification">>}]. serialise_events() -> false. -- cgit v1.2.1 From 8ddba1382fb12fd781fb5ad58a6efda609311529 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 11 Feb 2013 11:22:24 +0000 Subject: That doesn't need a name either (in fact even less so, since it never ends up in the registry). --- src/rabbit_exchange_type_invalid.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_exchange_type_invalid.erl b/src/rabbit_exchange_type_invalid.erl index 4a48a458..6b07351a 100644 --- a/src/rabbit_exchange_type_invalid.erl +++ b/src/rabbit_exchange_type_invalid.erl @@ -24,8 +24,7 @@ add_binding/3, remove_bindings/3, assert_args_equivalence/2]). description() -> - [{name, <<"invalid">>}, - {description, + [{description, <<"Dummy exchange type, to be used when the intended one is not found.">> }]. -- cgit v1.2.1 From d4b5bfd64c43191bab8c13c122e57981c6c2eee6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 11 Feb 2013 12:01:18 +0000 Subject: a little bit of refactoring of channel.flow code --- src/rabbit_channel.erl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 160512a2..e74211af 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -544,14 +544,17 @@ check_name(_Kind, NameBin) -> queue_blocked(QPid, State = #ch{blocking = Blocking}) -> case sets:is_element(QPid, Blocking) of false -> State; - true -> Blocking1 = sets:del_element(QPid, Blocking), - case sets:size(Blocking1) of - 0 -> ok = send(#'channel.flow_ok'{active = false}, State); - _ -> ok - end, - State#ch{blocking = Blocking1} + true -> maybe_send_flow_ok( + State#ch{blocking = sets:del_element(QPid, Blocking)}) end. +maybe_send_flow_ok(State = #ch{blocking = Blocking}) -> + case sets:size(Blocking) of + 0 -> ok = send(#'channel.flow_ok'{active = false}, State); + _ -> ok + end, + State. + record_confirms([], State) -> State; record_confirms(MXs, State = #ch{confirmed = C}) -> @@ -1082,12 +1085,9 @@ handle_method(#'channel.flow'{active = false}, _, end, State1 = State#ch{limiter = Limiter1}, ok = rabbit_limiter:block(Limiter1), - case consumer_queues(Consumers) of - [] -> {reply, #'channel.flow_ok'{active = false}, State1}; - QPids -> State2 = State1#ch{blocking = sets:from_list(QPids)}, - ok = rabbit_amqqueue:flush_all(QPids, self()), - {noreply, State2} - end; + QPids = consumer_queues(Consumers), + ok = rabbit_amqqueue:flush_all(QPids, self()), + {noreply, maybe_send_flow_ok(State1#ch{blocking = sets:from_list(QPids)})}; handle_method(_MethodRecord, _Content, _State) -> rabbit_misc:protocol_error( -- cgit v1.2.1 From 357701e0ad63c1817e7f7290025ca3aaf5a1385c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 11 Feb 2013 13:13:50 +0000 Subject: First pass at ha-sync-mode. --- src/rabbit_mirror_queue_misc.erl | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl index 05036d35..4dd50bce 100644 --- a/src/rabbit_mirror_queue_misc.erl +++ b/src/rabbit_mirror_queue_misc.erl @@ -32,6 +32,8 @@ [policy_validator, <<"ha-mode">>, ?MODULE]}}, {mfa, {rabbit_registry, register, [policy_validator, <<"ha-params">>, ?MODULE]}}, + {mfa, {rabbit_registry, register, + [policy_validator, <<"ha-sync-mode">>, ?MODULE]}}, {requires, rabbit_registry}, {enables, recovery}]}). @@ -177,13 +179,19 @@ add_mirror(QName, MirrorNode) -> end end). -start_child(Name, MirrorNode, Q) -> +start_child(Name, MirrorNode, Q = #amqqueue{pid = QPid}) -> case rabbit_misc:with_exit_handler( rabbit_misc:const({ok, down}), fun () -> rabbit_mirror_queue_slave_sup:start_child(MirrorNode, [Q]) end) of {ok, SPid} when is_pid(SPid) -> + case rabbit_policy:get(<<"ha-sync-mode">>, Q) of + {ok,<<"automatic">>} -> + spawn(fun() -> rabbit_amqqueue:sync_mirrors(QPid) end); + _ -> + ok + end, rabbit_log:info("Adding mirror of ~s on node ~p: ~p~n", [rabbit_misc:rs(Name), MirrorNode, SPid]), {ok, started}; @@ -323,9 +331,18 @@ update_mirrors0(OldQ = #amqqueue{name = QName}, %%---------------------------------------------------------------------------- validate_policy(KeyList) -> - validate_policy( - proplists:get_value(<<"ha-mode">>, KeyList), - proplists:get_value(<<"ha-params">>, KeyList, none)). + case validate_policy( + proplists:get_value(<<"ha-mode">>, KeyList), + proplists:get_value(<<"ha-params">>, KeyList, none)) of + ok -> case proplists:get_value( + <<"ha-sync-mode">>, KeyList, <<"manual">>) of + <<"automatic">> -> ok; + <<"manual">> -> ok; + Mode -> {error, "ha-sync-mode must be \"manual\" " + "or \"automatic\", got ~p", [Mode]} + end; + E -> E + end. validate_policy(<<"all">>, none) -> ok; -- cgit v1.2.1 From 9033ea545e7c8ea7ab3193bd28782f62ff700f1e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 11 Feb 2013 13:57:48 +0000 Subject: a spot of inlining --- src/rabbit_limiter.erl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 35703efa..8ed1adc7 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -193,15 +193,12 @@ decr_credit(CTag, Len, ChPid, Cred, Credits) -> write_credit(CTag, NewCredit, Drain, Credits). maybe_drain(0, true, CTag, ChPid, Credit) -> - send_drained(ChPid, CTag, Credit), + rabbit_channel:send_drained(ChPid, CTag, Credit), 0; %% Magic reduction to 0 maybe_drain(_, _, _, _, Credit) -> Credit. -send_drained(ChPid, CTag, CreditDrained) -> - rabbit_channel:send_drained(ChPid, CTag, CreditDrained). - update_credit(CTag, Len, ChPid, Credit, Drain, Credits) -> NewCredit = maybe_drain(Len, Drain, CTag, ChPid, Credit), NewCredits = write_credit(CTag, NewCredit, Drain, Credits), -- cgit v1.2.1 From 3e6eb6cfeeba79356478f151cd8f166d8523ff95 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 11 Feb 2013 14:16:29 +0000 Subject: some inlining and moving around --- src/rabbit_limiter.erl | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 8ed1adc7..46b465bc 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -181,24 +181,13 @@ copy_queue_state(#token{q_state = Credits}, Token) -> record_send_q(CTag, Len, ChPid, Credits) -> case dict:find(CTag, Credits) of - {ok, Cred} -> - decr_credit(CTag, Len, ChPid, Cred, Credits); + {ok, #credit{credit = Credit, drain = Drain}} -> + NewCredit = maybe_drain(Len - 1, Drain, CTag, ChPid, Credit - 1), + write_credit(CTag, NewCredit, Drain, Credits); error -> Credits end. -decr_credit(CTag, Len, ChPid, Cred, Credits) -> - #credit{credit = Credit, drain = Drain} = Cred, - NewCredit = maybe_drain(Len - 1, Drain, CTag, ChPid, Credit - 1), - write_credit(CTag, NewCredit, Drain, Credits). - -maybe_drain(0, true, CTag, ChPid, Credit) -> - rabbit_channel:send_drained(ChPid, CTag, Credit), - 0; %% Magic reduction to 0 - -maybe_drain(_, _, _, _, Credit) -> - Credit. - update_credit(CTag, Len, ChPid, Credit, Drain, Credits) -> NewCredit = maybe_drain(Len, Drain, CTag, ChPid, Credit), NewCredits = write_credit(CTag, NewCredit, Drain, Credits), @@ -208,8 +197,14 @@ update_credit(CTag, Len, ChPid, Credit, Drain, Credits) -> end. write_credit(CTag, Credit, Drain, Credits) -> - dict:store(CTag, #credit{credit = Credit, - drain = Drain}, Credits). + dict:store(CTag, #credit{credit = Credit, drain = Drain}, Credits). + +maybe_drain(0, true, CTag, ChPid, Credit) -> + rabbit_channel:send_drained(ChPid, CTag, Credit), + 0; %% Magic reduction to 0 + +maybe_drain(_, _, _, _, Credit) -> + Credit. %%---------------------------------------------------------------------------- %% gen_server callbacks -- cgit v1.2.1 From 7183f57012eeb61d6de136548db5ef50abdb1d37 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 11 Feb 2013 17:44:40 +0000 Subject: Tweak the message to make it slightly more obvious if it has hung. --- src/rabbit.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 73399c03..a32acbc2 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -704,7 +704,7 @@ log_broker_started(Plugins) -> error_logger:info_msg( "Server startup complete; ~b plugins started.~n~s~n", [length(Plugins), PluginList]), - io:format("~n Broker running with ~p plugins.~n", + io:format(" startup complete with ~p plugins.~n", [length(Plugins)]) end). @@ -723,7 +723,9 @@ print_banner() -> "~n## ## ~s" "~n##########" "~n###### ## Logs: ~s" - "~n########## ~s~n", + "~n########## ~s" + "~n" + "~n Starting...", [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE, log_location(kernel), log_location(sasl)]). -- cgit v1.2.1 From a5433ec58fd8fe908a73d0aa45e6a99f56049736 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 11 Feb 2013 18:38:49 +0000 Subject: minor optimisation --- src/rabbit_amqqueue_process.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 0594e250..88d13290 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -621,11 +621,12 @@ possibly_unblock(State, ChPid, Update) -> not_found -> State; C -> - C1 = #cr{blocked_ctags = BCTags1} = Update(C), + C1 = #cr{blocked_ctags = BCTags} = Update(C), + IsBlocked = is_ch_blocked(C1), {Blocked, Unblocked} = lists:partition( fun({_ChPid, #consumer{tag = CTag}}) -> - is_ch_blocked(C1) orelse lists:member(CTag, BCTags1) + IsBlocked orelse lists:member(CTag, BCTags) end, queue:to_list(C1#cr.blocked_consumers)), case Unblocked of [] -> update_ch_record(C1), -- cgit v1.2.1 From 5d1494b77057c472e10b77957b31c14f926af719 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 12 Feb 2013 09:35:45 +0000 Subject: startup logging tweaks --- src/rabbit.erl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index a32acbc2..55736b28 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -435,8 +435,9 @@ start(normal, []) -> case erts_version_check() of ok -> {ok, Vsn} = application:get_key(rabbit, vsn), - error_logger:info_msg("Starting RabbitMQ ~s on Erlang ~s~n", - [Vsn, erlang:system_info(otp_release)]), + error_logger:info_msg("Starting RabbitMQ ~s on Erlang ~s~n~s~n~s~n", + [Vsn, erlang:system_info(otp_release), + ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), {ok, SupPid} = rabbit_sup:start_link(), true = register(rabbit, self()), print_banner(), @@ -704,8 +705,7 @@ log_broker_started(Plugins) -> error_logger:info_msg( "Server startup complete; ~b plugins started.~n~s~n", [length(Plugins), PluginList]), - io:format(" startup complete with ~p plugins.~n", - [length(Plugins)]) + io:format(" completed with ~p plugins.~n", [length(Plugins)]) end). erts_version_check() -> @@ -725,13 +725,11 @@ print_banner() -> "~n###### ## Logs: ~s" "~n########## ~s" "~n" - "~n Starting...", + "~n Starting broker...", [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE, log_location(kernel), log_location(sasl)]). log_banner() -> - error_logger:info_msg("~s ~s~n", - [?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), Settings = [{"node", node()}, {"home dir", home_dir()}, {"config file(s)", config_files()}, -- cgit v1.2.1 From 6f4290c5279bb1ac8c954bda6a1a97af9d437ec0 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Feb 2013 13:30:30 +0000 Subject: Move rabbit_channel:send_drained/2 invocations into the queue module, and make sure we send drained for all consumers in drain mode. --- src/rabbit_amqqueue_process.erl | 43 +++++++++++++++++---------- src/rabbit_channel.erl | 17 ++++++----- src/rabbit_limiter.erl | 65 +++++++++++++++++++---------------------- 3 files changed, 67 insertions(+), 58 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 88d13290..f5648eca 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -405,6 +405,21 @@ erase_ch_record(#cr{ch_pid = ChPid, erase({ch, ChPid}), ok. +maybe_send_drained(#q{backing_queue = BQ, + backing_queue_state = BQS}) -> + case BQ:len(BQS) of + 0 -> [maybe_send_drained(C) || C <- all_ch_record()]; + _ -> ok + end; +maybe_send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> + case rabbit_limiter:drained(Limiter) of + {[], Limiter} -> + ok; + {CTagCredit, Limiter2} -> + rabbit_channel:send_drained(ChPid, CTagCredit), + update_ch_record(C#cr{limiter = Limiter2}) + end. + update_consumer_count(C = #cr{consumer_count = 0, limiter = Limiter}, +1) -> ok = rabbit_limiter:register(Limiter, self()), update_ch_record(C#cr{consumer_count = 1}); @@ -437,9 +452,7 @@ deliver_msgs_to_consumers(DeliverFun, false, deliver_msgs_to_consumers(DeliverFun, Stop, State1) end. -deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, - State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> +deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> C = ch_record(ChPid), case is_ch_blocked(C) of true -> @@ -449,8 +462,7 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, #cr{limiter = Limiter, ch_pid = ChPid, blocked_ctags = BCTags} = C, #consumer{tag = CTag} = Consumer, case rabbit_limiter:can_send( - Limiter, self(), Consumer#consumer.ack_required, - ChPid, CTag, BQ:len(BQS)) of + Limiter, self(), Consumer#consumer.ack_required, CTag) of consumer_blocked -> block_consumer(C#cr{blocked_ctags = [CTag | BCTags]}, E), {false, State}; @@ -483,6 +495,7 @@ deliver_msg_to_consumer(DeliverFun, NewLimiter, update_ch_record(C#cr{acktags = ChAckTags1, limiter = NewLimiter, unsent_message_count = Count + 1}), + maybe_send_drained(State1), {Stop, State1}. deliver_from_queue_deliver(AckRequired, State) -> @@ -1098,9 +1111,7 @@ handle_call({basic_get, ChPid, NoAck}, _From, handle_call({basic_consume, NoAck, ChPid, Limiter, ConsumerTag, ExclusiveConsume, CreditArgs, OkMsg}, - _From, State = #q{exclusive_consumer = Holder, - backing_queue = BQ, - backing_queue_state = BQS}) -> + _From, State = #q{exclusive_consumer = Holder}) -> case check_exclusive_access(Holder, ExclusiveConsume, State) of in_use -> reply({error, exclusive_consume_unavailable}, State); @@ -1111,8 +1122,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, Limiter; {Credit, Drain} -> rabbit_limiter:initial_credit( - Limiter, ChPid, ConsumerTag, Credit, Drain, - BQ:len(BQS)) + Limiter, ConsumerTag, Credit, Drain) end, C1 = update_consumer_count(C#cr{limiter = Limiter2}, +1), Consumer = #consumer{tag = ConsumerTag, @@ -1132,6 +1142,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, AC1 = queue:in(E, State1#q.active_consumers), run_message_queue(State1#q{active_consumers = AC1}) end, + maybe_send_drained(State2), emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, not NoAck, qname(State2)), reply(ok, State2) @@ -1342,11 +1353,13 @@ handle_cast({credit, ChPid, CTag, Credit, Drain}, backing_queue_state = BQS}) -> #cr{limiter = Lim, blocked_ctags = BCTags} = ch_record(ChPid), - {Unblock, Lim2} = rabbit_limiter:credit( - Lim, ChPid, CTag, Credit, Drain, BQ:len(BQS)), - noreply(possibly_unblock( - State, ChPid, fun(C) -> C#cr{blocked_ctags = BCTags -- Unblock, - limiter = Lim2} end)); + {Unblock, Lim2} = rabbit_limiter:credit(Lim, CTag, Credit, Drain), + rabbit_channel:send_credit_reply(ChPid, BQ:len(BQS)), + State1 = possibly_unblock( + State, ChPid, fun(C) -> C#cr{blocked_ctags = BCTags -- Unblock, + limiter = Lim2} end), + maybe_send_drained(State1), + noreply(State1); handle_cast(wake_up, State) -> noreply(State). diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index c1eb126c..aed25344 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -21,7 +21,7 @@ -behaviour(gen_server2). -export([start_link/11, do/2, do/3, do_flow/3, flush/1, shutdown/1]). --export([send_command/2, deliver/4, send_credit_reply/2, send_drained/3, +-export([send_command/2, deliver/4, send_credit_reply/2, send_drained/2, flushed/2]). -export([list/0, info_keys/0, info/1, info/2, info_all/0, info_all/1]). -export([refresh_config_local/0, ready_for_close/1]). @@ -96,7 +96,7 @@ (pid(), rabbit_types:ctag(), boolean(), rabbit_amqqueue:qmsg()) -> 'ok'). -spec(send_credit_reply/2 :: (pid(), non_neg_integer()) -> 'ok'). --spec(send_drained/3 :: (pid(), rabbit_types:ctag(), non_neg_integer()) +-spec(send_drained/2 :: (pid(), [{rabbit_types:ctag(), non_neg_integer()}]) -> 'ok'). -spec(flushed/2 :: (pid(), pid()) -> 'ok'). -spec(list/0 :: () -> [pid()]). @@ -145,8 +145,8 @@ deliver(Pid, ConsumerTag, AckRequired, Msg) -> send_credit_reply(Pid, Len) -> gen_server2:cast(Pid, {send_credit_reply, Len}). -send_drained(Pid, ConsumerTag, CreditDrained) -> - gen_server2:cast(Pid, {send_drained, ConsumerTag, CreditDrained}). +send_drained(Pid, CTagCredit) -> + gen_server2:cast(Pid, {send_drained, CTagCredit}). flushed(Pid, QPid) -> gen_server2:cast(Pid, {flushed, QPid}). @@ -330,11 +330,12 @@ handle_cast({send_credit_reply, Len}, State = #ch{writer_pid = WriterPid}) -> WriterPid, #'basic.credit_ok'{available = Len}), noreply(State); -handle_cast({send_drained, ConsumerTag, CreditDrained}, +handle_cast({send_drained, CTagCredit}, State = #ch{writer_pid = WriterPid}) -> - ok = rabbit_writer:send_command( - WriterPid, #'basic.credit_drained'{consumer_tag = ConsumerTag, - credit_drained = CreditDrained}), + [ok = rabbit_writer:send_command( + WriterPid, #'basic.credit_drained'{consumer_tag = ConsumerTag, + credit_drained = CreditDrained}) + || {ConsumerTag, CreditDrained} <- CTagCredit], noreply(State); handle_cast(force_event_refresh, State) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 46b465bc..1ee5448e 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -22,9 +22,10 @@ handle_info/2, prioritise_call/3]). -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). --export([limit/2, can_send/6, ack/2, register/2, unregister/2]). +-export([limit/2, can_send/4, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_blocked/1]). --export([initial_credit/6, credit/6, forget_consumer/2, copy_queue_state/2]). +-export([initial_credit/4, credit/4, drained/1, forget_consumer/2, + copy_queue_state/2]). -import(rabbit_misc, [serial_add/2, serial_diff/2]). @@ -45,8 +46,7 @@ -spec(enable/2 :: (token(), non_neg_integer()) -> token()). -spec(disable/1 :: (token()) -> token()). -spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). --spec(can_send/6 :: (token(), pid(), boolean(), pid(), rabbit_types:ctag(), - non_neg_integer()) +-spec(can_send/4 :: (token(), pid(), boolean(), rabbit_types:ctag()) -> token() | 'consumer_blocked' | 'channel_blocked'). -spec(ack/2 :: (token(), non_neg_integer()) -> 'ok'). -spec(register/2 :: (token(), pid()) -> 'ok'). @@ -55,12 +55,12 @@ -spec(block/1 :: (token()) -> 'ok'). -spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). -spec(is_blocked/1 :: (token()) -> boolean()). --spec(initial_credit/6 :: (token(), pid(), rabbit_types:ctag(), - non_neg_integer(), boolean(), non_neg_integer()) - -> token()). --spec(credit/6 :: (token(), pid(), rabbit_types:ctag(), - non_neg_integer(), boolean(), non_neg_integer()) +-spec(initial_credit/4 :: (token(), rabbit_types:ctag(), + non_neg_integer(), boolean()) -> token()). +-spec(credit/4 :: (token(), rabbit_types:ctag(), non_neg_integer(), boolean()) -> {[rabbit_types:ctag()], token()}). +-spec(drained/1 :: (token()) + -> {[{rabbit_types:ctag(), non_neg_integer()}], token()}). -spec(forget_consumer/2 :: (token(), rabbit_types:ctag()) -> token()). -spec(copy_queue_state/2 :: (token(), token()) -> token()). @@ -105,7 +105,7 @@ limit(Limiter, PrefetchCount) -> %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. can_send(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, - QPid, AckReq, ChPid, CTag, Len) -> + QPid, AckReq, CTag) -> ConsAllows = case dict:find(CTag, Credits) of {ok, #credit{credit = C}} when C > 0 -> true; {ok, #credit{}} -> false; @@ -113,8 +113,7 @@ can_send(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, end, case ConsAllows of true -> case not Enabled orelse call_can_send(Pid, QPid, AckReq) of - true -> Credits2 = record_send_q( - CTag, Len, ChPid, Credits), + true -> Credits2 = record_send_q(CTag, Credits), Token#token{q_state = Credits2}; false -> channel_blocked end; @@ -150,19 +149,24 @@ unblock(Limiter) -> is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). -initial_credit(Limiter = #token{q_state = Credits}, - ChPid, CTag, Credit, Drain, Len) -> - {[], Credits2} = update_credit( - CTag, Len, ChPid, Credit, Drain, Credits), +initial_credit(Limiter = #token{q_state = Credits}, CTag, Credit, Drain) -> + {[], Credits2} = update_credit(CTag, Credit, Drain, Credits), Limiter#token{q_state = Credits2}. -credit(Limiter = #token{q_state = Credits}, - ChPid, CTag, Credit, Drain, Len) -> - {Unblock, Credits2} = update_credit( - CTag, Len, ChPid, Credit, Drain, Credits), - rabbit_channel:send_credit_reply(ChPid, Len), +credit(Limiter = #token{q_state = Credits}, CTag, Credit, Drain) -> + {Unblock, Credits2} = update_credit(CTag, Credit, Drain, Credits), {Unblock, Limiter#token{q_state = Credits2}}. +drained(Limiter = #token{q_state = Credits}) -> + {CTagCredits, Credits2} = + dict:fold( + fun (CTag, #credit{credit = C, drain = true}, {Acc, Creds0}) -> + {[{CTag, C} | Acc], write_credit(CTag, 0, false, Creds0)}; + (_CTag, #credit{credit = _C, drain = false}, {Acc, Creds0}) -> + {Acc, Creds0} + end, {[], Credits}, Credits), + {CTagCredits, Limiter#token{q_state = Credits2}}. + forget_consumer(Limiter = #token{q_state = Credits}, CTag) -> Limiter#token{q_state = dict:erase(CTag, Credits)}. @@ -179,19 +183,17 @@ copy_queue_state(#token{q_state = Credits}, Token) -> %% we get the queue to hold a bit of state for us (#token.q_state), and %% maintain a fiction that the limiter is making the decisions... -record_send_q(CTag, Len, ChPid, Credits) -> +record_send_q(CTag, Credits) -> case dict:find(CTag, Credits) of {ok, #credit{credit = Credit, drain = Drain}} -> - NewCredit = maybe_drain(Len - 1, Drain, CTag, ChPid, Credit - 1), - write_credit(CTag, NewCredit, Drain, Credits); + write_credit(CTag, Credit, Drain, Credits); error -> Credits end. -update_credit(CTag, Len, ChPid, Credit, Drain, Credits) -> - NewCredit = maybe_drain(Len, Drain, CTag, ChPid, Credit), - NewCredits = write_credit(CTag, NewCredit, Drain, Credits), - case NewCredit > 0 of +update_credit(CTag, Credit, Drain, Credits) -> + NewCredits = write_credit(CTag, Credit, Drain, Credits), + case Credit > 0 of true -> {[CTag], NewCredits}; false -> {[], NewCredits} end. @@ -199,13 +201,6 @@ update_credit(CTag, Len, ChPid, Credit, Drain, Credits) -> write_credit(CTag, Credit, Drain, Credits) -> dict:store(CTag, #credit{credit = Credit, drain = Drain}, Credits). -maybe_drain(0, true, CTag, ChPid, Credit) -> - rabbit_channel:send_drained(ChPid, CTag, Credit), - 0; %% Magic reduction to 0 - -maybe_drain(_, _, _, _, Credit) -> - Credit. - %%---------------------------------------------------------------------------- %% gen_server callbacks %%---------------------------------------------------------------------------- -- cgit v1.2.1 From 966b9a29d54321e965efd3a9e5a0112da4ed2e52 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Feb 2013 14:39:23 +0000 Subject: s/q_state/credits/g --- src/rabbit_limiter.erl | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 1ee5448e..0d836ca6 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -31,7 +31,7 @@ %%---------------------------------------------------------------------------- --record(token, {pid, enabled, q_state}). +-record(token, {pid, enabled, credits}). -ifdef(use_specs). @@ -87,7 +87,7 @@ start_link() -> gen_server2:start_link(?MODULE, [], []). make_token() -> make_token(undefined). make_token(Pid) -> #token{pid = Pid, enabled = false, - q_state = dict:new()}. + credits = dict:new()}. is_enabled(#token{enabled = Enabled}) -> Enabled. @@ -104,7 +104,7 @@ limit(Limiter, PrefetchCount) -> %% breaching a limit. Note that we don't use maybe_call here in order %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. -can_send(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, +can_send(Token = #token{pid = Pid, enabled = Enabled, credits = Credits}, QPid, AckReq, CTag) -> ConsAllows = case dict:find(CTag, Credits) of {ok, #credit{credit = C}} when C > 0 -> true; @@ -114,7 +114,7 @@ can_send(Token = #token{pid = Pid, enabled = Enabled, q_state = Credits}, case ConsAllows of true -> case not Enabled orelse call_can_send(Pid, QPid, AckReq) of true -> Credits2 = record_send_q(CTag, Credits), - Token#token{q_state = Credits2}; + Token#token{credits = Credits2}; false -> channel_blocked end; false -> consumer_blocked @@ -149,15 +149,15 @@ unblock(Limiter) -> is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). -initial_credit(Limiter = #token{q_state = Credits}, CTag, Credit, Drain) -> +initial_credit(Limiter = #token{credits = Credits}, CTag, Credit, Drain) -> {[], Credits2} = update_credit(CTag, Credit, Drain, Credits), - Limiter#token{q_state = Credits2}. + Limiter#token{credits = Credits2}. -credit(Limiter = #token{q_state = Credits}, CTag, Credit, Drain) -> +credit(Limiter = #token{credits = Credits}, CTag, Credit, Drain) -> {Unblock, Credits2} = update_credit(CTag, Credit, Drain, Credits), - {Unblock, Limiter#token{q_state = Credits2}}. + {Unblock, Limiter#token{credits = Credits2}}. -drained(Limiter = #token{q_state = Credits}) -> +drained(Limiter = #token{credits = Credits}) -> {CTagCredits, Credits2} = dict:fold( fun (CTag, #credit{credit = C, drain = true}, {Acc, Creds0}) -> @@ -165,13 +165,13 @@ drained(Limiter = #token{q_state = Credits}) -> (_CTag, #credit{credit = _C, drain = false}, {Acc, Creds0}) -> {Acc, Creds0} end, {[], Credits}, Credits), - {CTagCredits, Limiter#token{q_state = Credits2}}. + {CTagCredits, Limiter#token{credits = Credits2}}. -forget_consumer(Limiter = #token{q_state = Credits}, CTag) -> - Limiter#token{q_state = dict:erase(CTag, Credits)}. +forget_consumer(Limiter = #token{credits = Credits}, CTag) -> + Limiter#token{credits = dict:erase(CTag, Credits)}. -copy_queue_state(#token{q_state = Credits}, Token) -> - Token#token{q_state = Credits}. +copy_queue_state(#token{credits = Credits}, Token) -> + Token#token{credits = Credits}. %%---------------------------------------------------------------------------- %% Queue-local code @@ -180,7 +180,7 @@ copy_queue_state(#token{q_state = Credits}, Token) -> %% We want to do all the AMQP 1.0-ish link level credit calculations in the %% queue (to do them elsewhere introduces a ton of races). However, it's a big %% chunk of code that is conceptually very linked to the limiter concept. So -%% we get the queue to hold a bit of state for us (#token.q_state), and +%% we get the queue to hold a bit of state for us (#token.credits), and %% maintain a fiction that the limiter is making the decisions... record_send_q(CTag, Credits) -> -- cgit v1.2.1 From 4fe1e2e497cff37bc578775f89b6ca7361ff4c3a Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Feb 2013 14:55:13 +0000 Subject: Move blocked_ctags into the limiter. --- src/rabbit_amqqueue_process.erl | 24 +++++++++--------------- src/rabbit_limiter.erl | 31 ++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index f5648eca..d932d1b6 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -68,9 +68,6 @@ %% Queue of {ChPid, #consumer{}} for consumers which have %% been blocked for any reason blocked_consumers, - %% List of consumer tags which have individually been - %% blocked by the limiter. - blocked_ctags, %% The limiter itself limiter, %% Has the limiter imposed a channel-wide block, either @@ -375,7 +372,6 @@ ch_record(ChPid) -> acktags = queue:new(), consumer_count = 0, blocked_consumers = queue:new(), - blocked_ctags = [], is_limit_active = false, limiter = rabbit_limiter:make_token(), unsent_message_count = 0}, @@ -459,12 +455,12 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> block_consumer(C, E), {false, State}; false -> - #cr{limiter = Limiter, ch_pid = ChPid, blocked_ctags = BCTags} = C, + #cr{limiter = Limiter, ch_pid = ChPid} = C, #consumer{tag = CTag} = Consumer, case rabbit_limiter:can_send( Limiter, self(), Consumer#consumer.ack_required, CTag) of - consumer_blocked -> - block_consumer(C#cr{blocked_ctags = [CTag | BCTags]}, E), + {consumer_blocked, Limiter2} -> + block_consumer(C#cr{limiter = Limiter2}, E), {false, State}; channel_blocked -> block_consumer(C#cr{is_limit_active = true}, E), @@ -634,12 +630,12 @@ possibly_unblock(State, ChPid, Update) -> not_found -> State; C -> - C1 = #cr{blocked_ctags = BCTags} = Update(C), - IsBlocked = is_ch_blocked(C1), + C1 = #cr{limiter = Limiter} = Update(C), {Blocked, Unblocked} = lists:partition( fun({_ChPid, #consumer{tag = CTag}}) -> - IsBlocked orelse lists:member(CTag, BCTags) + is_ch_blocked(C1) orelse + rabbit_limiter:is_consumer_blocked(Limiter, CTag) end, queue:to_list(C1#cr.blocked_consumers)), case Unblocked of [] -> update_ch_record(C1), @@ -1351,13 +1347,11 @@ handle_cast(stop_mirroring, State = #q{backing_queue = BQ, handle_cast({credit, ChPid, CTag, Credit, Drain}, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - #cr{limiter = Lim, - blocked_ctags = BCTags} = ch_record(ChPid), - {Unblock, Lim2} = rabbit_limiter:credit(Lim, CTag, Credit, Drain), + #cr{limiter = Lim} = ch_record(ChPid), + Lim2 = rabbit_limiter:credit(Lim, CTag, Credit, Drain), rabbit_channel:send_credit_reply(ChPid, BQ:len(BQS)), State1 = possibly_unblock( - State, ChPid, fun(C) -> C#cr{blocked_ctags = BCTags -- Unblock, - limiter = Lim2} end), + State, ChPid, fun(C) -> C#cr{limiter = Lim2} end), maybe_send_drained(State1), noreply(State1); diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 0d836ca6..5774aee0 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -23,7 +23,7 @@ -export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, disable/1]). -export([limit/2, can_send/4, ack/2, register/2, unregister/2]). --export([get_limit/1, block/1, unblock/1, is_blocked/1]). +-export([get_limit/1, block/1, unblock/1, is_consumer_blocked/2, is_blocked/1]). -export([initial_credit/4, credit/4, drained/1, forget_consumer/2, copy_queue_state/2]). @@ -31,7 +31,7 @@ %%---------------------------------------------------------------------------- --record(token, {pid, enabled, credits}). +-record(token, {pid, enabled, credits, blocked_ctags}). -ifdef(use_specs). @@ -55,10 +55,11 @@ -spec(block/1 :: (token()) -> 'ok'). -spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). -spec(is_blocked/1 :: (token()) -> boolean()). +-spec(is_consumer_blocked/2 :: (token(), rabbit_types:ctag()) -> boolean()). -spec(initial_credit/4 :: (token(), rabbit_types:ctag(), non_neg_integer(), boolean()) -> token()). -spec(credit/4 :: (token(), rabbit_types:ctag(), non_neg_integer(), boolean()) - -> {[rabbit_types:ctag()], token()}). + -> token()). -spec(drained/1 :: (token()) -> {[{rabbit_types:ctag(), non_neg_integer()}], token()}). -spec(forget_consumer/2 :: (token(), rabbit_types:ctag()) -> token()). @@ -86,8 +87,10 @@ start_link() -> gen_server2:start_link(?MODULE, [], []). make_token() -> make_token(undefined). -make_token(Pid) -> #token{pid = Pid, enabled = false, - credits = dict:new()}. +make_token(Pid) -> #token{pid = Pid, + enabled = false, + credits = dict:new(), + blocked_ctags = []}. is_enabled(#token{enabled = Enabled}) -> Enabled. @@ -104,7 +107,8 @@ limit(Limiter, PrefetchCount) -> %% breaching a limit. Note that we don't use maybe_call here in order %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. -can_send(Token = #token{pid = Pid, enabled = Enabled, credits = Credits}, +can_send(Token = #token{pid = Pid, enabled = Enabled, credits = Credits, + blocked_ctags = BCTags}, QPid, AckReq, CTag) -> ConsAllows = case dict:find(CTag, Credits) of {ok, #credit{credit = C}} when C > 0 -> true; @@ -117,7 +121,7 @@ can_send(Token = #token{pid = Pid, enabled = Enabled, credits = Credits}, Token#token{credits = Credits2}; false -> channel_blocked end; - false -> consumer_blocked + false -> {consumer_blocked, Token#token{blocked_ctags = [CTag|BCTags]}} end. call_can_send(Pid, QPid, AckRequired) -> @@ -146,6 +150,9 @@ block(Limiter) -> unblock(Limiter) -> maybe_call(Limiter, {unblock, Limiter}, ok). +is_consumer_blocked(#token{blocked_ctags = BCTags}, CTag) -> + lists:member(CTag, BCTags). + is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). @@ -153,9 +160,11 @@ initial_credit(Limiter = #token{credits = Credits}, CTag, Credit, Drain) -> {[], Credits2} = update_credit(CTag, Credit, Drain, Credits), Limiter#token{credits = Credits2}. -credit(Limiter = #token{credits = Credits}, CTag, Credit, Drain) -> +credit(Limiter = #token{credits = Credits, blocked_ctags = BCTags}, + CTag, Credit, Drain) -> {Unblock, Credits2} = update_credit(CTag, Credit, Drain, Credits), - {Unblock, Limiter#token{credits = Credits2}}. + Limiter#token{credits = Credits2, + blocked_ctags = BCTags -- Unblock}. drained(Limiter = #token{credits = Credits}) -> {CTagCredits, Credits2} = @@ -170,8 +179,8 @@ drained(Limiter = #token{credits = Credits}) -> forget_consumer(Limiter = #token{credits = Credits}, CTag) -> Limiter#token{credits = dict:erase(CTag, Credits)}. -copy_queue_state(#token{credits = Credits}, Token) -> - Token#token{credits = Credits}. +copy_queue_state(#token{credits = Credits, blocked_ctags = BCTags}, Token) -> + Token#token{credits = Credits, blocked_ctags = BCTags}. %%---------------------------------------------------------------------------- %% Queue-local code -- cgit v1.2.1 From 7703c46ae0313f203cc2dfbc0f11348001967629 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Feb 2013 14:57:59 +0000 Subject: Remove tags from blocked_ctags when a consumer goes away. --- src/rabbit_limiter.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 5774aee0..efc499cd 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -176,8 +176,10 @@ drained(Limiter = #token{credits = Credits}) -> end, {[], Credits}, Credits), {CTagCredits, Limiter#token{credits = Credits2}}. -forget_consumer(Limiter = #token{credits = Credits}, CTag) -> - Limiter#token{credits = dict:erase(CTag, Credits)}. +forget_consumer(Limiter = #token{credits = Credits, + blocked_ctags = BCTags}, CTag) -> + Limiter#token{credits = dict:erase(CTag, Credits), + blocked_ctags = BCTags -- [CTag]}. copy_queue_state(#token{credits = Credits, blocked_ctags = BCTags}, Token) -> Token#token{credits = Credits, blocked_ctags = BCTags}. -- cgit v1.2.1 From 30272f48513eb72dde747709338e7e50000ab2a4 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Feb 2013 16:26:23 +0000 Subject: Clear drain flag when we run out of credit. --- 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 efc499cd..e76fc217 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -209,8 +209,11 @@ update_credit(CTag, Credit, Drain, Credits) -> false -> {[], NewCredits} end. -write_credit(CTag, Credit, Drain, Credits) -> - dict:store(CTag, #credit{credit = Credit, drain = Drain}, Credits). +write_credit(CTag, Credit, Drain, Credits) when Credit > 0 -> + dict:store(CTag, #credit{credit = Credit, drain = Drain}, Credits); +%% Using up all credit means we do not need to send a drained event +write_credit(CTag, Credit, _Drain, Credits) -> + dict:store(CTag, #credit{credit = Credit, drain = false}, Credits). %%---------------------------------------------------------------------------- %% gen_server callbacks -- cgit v1.2.1 From 023d62f9efb9f54a973454b7e86f6ea150c96eb1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 12 Feb 2013 16:50:25 +0000 Subject: simplify & optimise maybe_send_drained - use BQ:is_empty instead of BQ:len - make use of Stop flag --- src/rabbit_amqqueue_process.erl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index d932d1b6..35f19493 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -401,13 +401,15 @@ erase_ch_record(#cr{ch_pid = ChPid, erase({ch, ChPid}), ok. -maybe_send_drained(#q{backing_queue = BQ, - backing_queue_state = BQS}) -> - case BQ:len(BQS) of - 0 -> [maybe_send_drained(C) || C <- all_ch_record()]; - _ -> ok - end; -maybe_send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> +maybe_send_drained(#q{backing_queue = BQ, backing_queue_state = BQS}) -> + case BQ:is_empty(BQS) of + true -> send_drained(); + false -> ok + end. + +send_drained() -> [send_drained(C) || C <- all_ch_record()]. + +send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> case rabbit_limiter:drained(Limiter) of {[], Limiter} -> ok; @@ -435,6 +437,7 @@ is_ch_blocked(#cr{unsent_message_count = Count, is_limit_active = Limited}) -> Limited orelse Count >= ?UNSENT_MESSAGE_LIMIT. deliver_msgs_to_consumers(_DeliverFun, true, State) -> + send_drained(), {true, State}; deliver_msgs_to_consumers(DeliverFun, false, State = #q{active_consumers = ActiveConsumers}) -> @@ -491,7 +494,6 @@ deliver_msg_to_consumer(DeliverFun, NewLimiter, update_ch_record(C#cr{acktags = ChAckTags1, limiter = NewLimiter, unsent_message_count = Count + 1}), - maybe_send_drained(State1), {Stop, State1}. deliver_from_queue_deliver(AckRequired, State) -> -- cgit v1.2.1 From eae2569cdced54ac44f2119cf4e7ffb876347cb6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 12 Feb 2013 17:46:50 +0000 Subject: optimise --- src/rabbit_amqqueue_process.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index fe3a6099..3bae4cfb 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -714,7 +714,13 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) -> drop_expired_msgs(State = #q{backing_queue_state = BQS, backing_queue = BQ }) -> - Now = now_micros(), + case BQ:is_empty(BQS) of + true -> State; + false -> drop_expired_msgs(now_micros(), State) + end. + +drop_expired_msgs(Now, State = #q{backing_queue_state = BQS, + backing_queue = BQ }) -> ExpirePred = fun (#message_properties{expiry = Exp}) -> Now >= Exp end, {Props, State1} = with_dlx( -- cgit v1.2.1 From 37dd46271a805a90b49d993d49ade8b70c47f1ef Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Feb 2013 18:04:31 +0000 Subject: Tweak nodes policy to allow master removal and thus queue migration --- src/rabbit_mirror_queue_misc.erl | 49 ++++++++++++++++++++++++--------------- src/rabbit_mirror_queue_slave.erl | 27 ++++++++++++--------- src/rabbit_tests.erl | 46 +++++++++++++++++++++--------------- 3 files changed, 73 insertions(+), 49 deletions(-) diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl index 05036d35..cc2d7c77 100644 --- a/src/rabbit_mirror_queue_misc.erl +++ b/src/rabbit_mirror_queue_misc.erl @@ -235,13 +235,13 @@ suggested_queue_nodes(Q) -> %% rabbit_mnesia:cluster_nodes(running) out of a loop or %% transaction or both. suggested_queue_nodes(Q, PossibleNodes) -> - {MNode0, SNodes} = actual_queue_nodes(Q), + {MNode0, SNodes, SSNodes} = actual_queue_nodes(Q), MNode = case MNode0 of none -> node(); _ -> MNode0 end, suggested_queue_nodes(policy(<<"ha-mode">>, Q), policy(<<"ha-params">>, Q), - {MNode, SNodes}, PossibleNodes). + {MNode, SNodes, SSNodes}, PossibleNodes). policy(Policy, Q) -> case rabbit_policy:get(Policy, Q) of @@ -249,15 +249,20 @@ policy(Policy, Q) -> _ -> none end. -suggested_queue_nodes(<<"all">>, _Params, {MNode, _SNodes}, Possible) -> - {MNode, Possible -- [MNode]}; -suggested_queue_nodes(<<"nodes">>, Nodes0, {MNode, _SNodes}, Possible) -> +suggested_queue_nodes(<<"all">>, _Params, {MNode, _SNodes, _SSNodes}, Poss) -> + {MNode, Poss -- [MNode]}; +suggested_queue_nodes(<<"nodes">>, Nodes0, {MNode, _SNodes, SSNodes}, Poss) -> Nodes1 = [list_to_atom(binary_to_list(Node)) || Node <- Nodes0], - %% If the current master is currently not in the nodes specified, - %% act like it is for the purposes below - otherwise we will not - %% return it in the results... - Nodes = lists:usort([MNode | Nodes1]), - Unavailable = Nodes -- Possible, + %% If the current master is not in the nodes specified, then what we want + %% to do depends on whether there are any synchronised slaves. If there + %% are then we can just kill the current master - the admin has asked for + %% a migration and we should give it to them. If there are not however + %% then we must keep the master around so as not to lose messages. + Nodes = case SSNodes of + [] -> lists:usort([MNode | Nodes1]); + _ -> Nodes1 + end, + Unavailable = Nodes -- Poss, Available = Nodes -- Unavailable, case Available of [] -> %% We have never heard of anything? Not much we can do but @@ -265,21 +270,24 @@ suggested_queue_nodes(<<"nodes">>, Nodes0, {MNode, _SNodes}, Possible) -> {MNode, []}; _ -> case lists:member(MNode, Available) of true -> {MNode, Available -- [MNode]}; - false -> promote_slave(Available) + false -> %% Make the sure new master is synced! In order to + %% get here SSNodes must not be empty. + [NewMNode | _] = SSNodes, + {NewMNode, Available -- [NewMNode]} end end; %% When we need to add nodes, we randomise our candidate list as a %% crude form of load-balancing. TODO it would also be nice to -%% randomise the list of ones to remove when we have too many - but -%% that would fail to take account of synchronisation... -suggested_queue_nodes(<<"exactly">>, Count, {MNode, SNodes}, Possible) -> +%% randomise the list of ones to remove when we have too many - we +%% would have to take account of synchronisation though. +suggested_queue_nodes(<<"exactly">>, Count, {MNode, SNodes, _SSNodes}, Poss) -> SCount = Count - 1, {MNode, case SCount > length(SNodes) of - true -> Cand = shuffle((Possible -- [MNode]) -- SNodes), + true -> Cand = shuffle((Poss -- [MNode]) -- SNodes), SNodes ++ lists:sublist(Cand, SCount - length(SNodes)); false -> lists:sublist(SNodes, SCount) end}; -suggested_queue_nodes(_, _, {MNode, _}, _) -> +suggested_queue_nodes(_, _, {MNode, _, _}, _) -> {MNode, []}. shuffle(L) -> @@ -288,11 +296,14 @@ shuffle(L) -> {_, L1} = lists:unzip(lists:keysort(1, [{random:uniform(), N} || N <- L])), L1. -actual_queue_nodes(#amqqueue{pid = MPid, slave_pids = SPids}) -> +actual_queue_nodes(#amqqueue{pid = MPid, + slave_pids = SPids, + sync_slave_pids = SSPids}) -> + Nodes = fun (L) -> [node(Pid) || Pid <- L] end, {case MPid of none -> none; _ -> node(MPid) - end, [node(Pid) || Pid <- SPids]}. + end, Nodes(SPids), Nodes(SSPids)}. is_mirrored(Q) -> case policy(<<"ha-mode">>, Q) of @@ -313,7 +324,7 @@ update_mirrors(OldQ = #amqqueue{pid = QPid}, update_mirrors0(OldQ = #amqqueue{name = QName}, NewQ = #amqqueue{name = QName}) -> - All = fun ({A,B}) -> [A|B] end, + All = fun (Tuple) -> [element(1, Tuple) | element(2, Tuple)] end, OldNodes = All(actual_queue_nodes(OldQ)), NewNodes = All(suggested_queue_nodes(NewQ)), add_mirrors(QName, NewNodes -- OldNodes), diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 69a3be2b..b435e0f3 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -830,16 +830,21 @@ update_ram_duration(BQ, BQS) -> rabbit_memory_monitor:report_ram_duration(self(), RamDuration), BQ:set_ram_duration_target(DesiredDuration, BQS1). +%% [1] - the arrival of this newly synced slave may cause the master to die if +%% the admin has requested a migration-type change to policy. record_synchronised(#amqqueue { name = QName }) -> Self = self(), - rabbit_misc:execute_mnesia_transaction( - fun () -> - case mnesia:read({rabbit_queue, QName}) of - [] -> - ok; - [Q = #amqqueue { sync_slave_pids = SSPids }] -> - rabbit_mirror_queue_misc:store_updated_slaves( - Q #amqqueue { sync_slave_pids = [Self | SSPids] }), - ok - end - end). + case rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:read({rabbit_queue, QName}) of + [] -> + ok; + [Q1 = #amqqueue { sync_slave_pids = SSPids }] -> + Q2 = Q1#amqqueue{sync_slave_pids = [Self | SSPids]}, + rabbit_mirror_queue_misc:store_updated_slaves(Q2), + {ok, Q1, Q2} + end + end) of + ok -> ok; + {ok, Q1, Q2} -> rabbit_mirror_queue_misc:update_mirrors(Q1, Q2) %% [1] + end. diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index f5ea4fba..9bc4288d 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -912,10 +912,10 @@ test_arguments_parser() -> test_dynamic_mirroring() -> %% Just unit tests of the node selection logic, see multi node %% tests for the rest... - Test = fun ({NewM, NewSs, ExtraSs}, Policy, Params, {OldM, OldSs}, All) -> + Test = fun ({NewM, NewSs, ExtraSs}, Policy, Params, CurrentState, All) -> {NewM, NewSs0} = rabbit_mirror_queue_misc:suggested_queue_nodes( - Policy, Params, {OldM, OldSs}, All), + Policy, Params, CurrentState, All), NewSs1 = lists:sort(NewSs0), case dm_list_match(NewSs, NewSs1, ExtraSs) of ok -> ok; @@ -923,28 +923,36 @@ test_dynamic_mirroring() -> end end, - Test({a,[b,c],0},<<"all">>,'_',{a,[]}, [a,b,c]), - Test({a,[b,c],0},<<"all">>,'_',{a,[b,c]},[a,b,c]), - Test({a,[b,c],0},<<"all">>,'_',{a,[d]}, [a,b,c]), + Test({a,[b,c],0},<<"all">>,'_',{a,[], []}, [a,b,c]), + Test({a,[b,c],0},<<"all">>,'_',{a,[b,c],[b,c]},[a,b,c]), + Test({a,[b,c],0},<<"all">>,'_',{a,[d], [d]}, [a,b,c]), + + N = fun (Atoms) -> [list_to_binary(atom_to_list(A)) || A <- Atoms] end, %% Add a node - Test({a,[b,c],0},<<"nodes">>,[<<"a">>,<<"b">>,<<"c">>],{a,[b]},[a,b,c,d]), - Test({b,[a,c],0},<<"nodes">>,[<<"a">>,<<"b">>,<<"c">>],{b,[a]},[a,b,c,d]), + Test({a,[b,c],0},<<"nodes">>,N([a,b,c]),{a,[b],[b]},[a,b,c,d]), + Test({b,[a,c],0},<<"nodes">>,N([a,b,c]),{b,[a],[a]},[a,b,c,d]), %% Add two nodes and drop one - Test({a,[b,c],0},<<"nodes">>,[<<"a">>,<<"b">>,<<"c">>],{a,[d]},[a,b,c,d]), + Test({a,[b,c],0},<<"nodes">>,N([a,b,c]),{a,[d],[d]},[a,b,c,d]), %% Don't try to include nodes that are not running - Test({a,[b], 0},<<"nodes">>,[<<"a">>,<<"b">>,<<"f">>],{a,[b]},[a,b,c,d]), + Test({a,[b], 0},<<"nodes">>,N([a,b,f]),{a,[b],[b]},[a,b,c,d]), %% If we can't find any of the nodes listed then just keep the master - Test({a,[], 0},<<"nodes">>,[<<"f">>,<<"g">>,<<"h">>],{a,[b]},[a,b,c,d]), - %% And once that's happened, still keep the master even when not listed - Test({a,[b,c],0},<<"nodes">>,[<<"b">>,<<"c">>], {a,[]}, [a,b,c,d]), - - Test({a,[], 1},<<"exactly">>,2,{a,[]}, [a,b,c,d]), - Test({a,[], 2},<<"exactly">>,3,{a,[]}, [a,b,c,d]), - Test({a,[c], 0},<<"exactly">>,2,{a,[c]}, [a,b,c,d]), - Test({a,[c], 1},<<"exactly">>,3,{a,[c]}, [a,b,c,d]), - Test({a,[c], 0},<<"exactly">>,2,{a,[c,d]},[a,b,c,d]), - Test({a,[c,d],0},<<"exactly">>,3,{a,[c,d]},[a,b,c,d]), + Test({a,[], 0},<<"nodes">>,N([f,g,h]),{a,[b],[b]},[a,b,c,d]), + %% And once that's happened, still keep the master even when not listed, + %% if nothing is synced + Test({a,[b,c],0},<<"nodes">>,N([b,c]), {a,[], []}, [a,b,c,d]), + Test({a,[b,c],0},<<"nodes">>,N([b,c]), {a,[b],[]}, [a,b,c,d]), + %% But if something is synced we can lose the master - but make + %% sure we pick the new master from the nodes which are synced! + Test({b,[c], 0},<<"nodes">>,N([b,c]), {a,[b],[b]},[a,b,c,d]), + Test({b,[c], 0},<<"nodes">>,N([c,b]), {a,[b],[b]},[a,b,c,d]), + + Test({a,[], 1},<<"exactly">>,2,{a,[], []}, [a,b,c,d]), + Test({a,[], 2},<<"exactly">>,3,{a,[], []}, [a,b,c,d]), + Test({a,[c], 0},<<"exactly">>,2,{a,[c], [c]}, [a,b,c,d]), + Test({a,[c], 1},<<"exactly">>,3,{a,[c], [c]}, [a,b,c,d]), + Test({a,[c], 0},<<"exactly">>,2,{a,[c,d],[c,d]},[a,b,c,d]), + Test({a,[c,d],0},<<"exactly">>,3,{a,[c,d],[c,d]},[a,b,c,d]), passed. -- cgit v1.2.1 From 77ad4b912377de31ace4afbc4bc3db96188d74c7 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 12 Feb 2013 21:57:24 +0000 Subject: remove icky use of element/2 --- src/rabbit_mirror_queue_misc.erl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl index cc2d7c77..5cba2d43 100644 --- a/src/rabbit_mirror_queue_misc.erl +++ b/src/rabbit_mirror_queue_misc.erl @@ -324,10 +324,11 @@ update_mirrors(OldQ = #amqqueue{pid = QPid}, update_mirrors0(OldQ = #amqqueue{name = QName}, NewQ = #amqqueue{name = QName}) -> - All = fun (Tuple) -> [element(1, Tuple) | element(2, Tuple)] end, - OldNodes = All(actual_queue_nodes(OldQ)), - NewNodes = All(suggested_queue_nodes(NewQ)), - add_mirrors(QName, NewNodes -- OldNodes), + {OldMNode, OldSNodes, _} = actual_queue_nodes(OldQ), + {NewMNode, NewSNodes} = suggested_queue_nodes(NewQ), + OldNodes = [OldMNode | OldSNodes], + NewNodes = [NewMNode | NewSNodes], + add_mirrors (QName, NewNodes -- OldNodes), drop_mirrors(QName, OldNodes -- NewNodes), ok. -- cgit v1.2.1 From c38d0b886c801a4745d45730b4f565c41bd1f9b4 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 13 Feb 2013 10:57:31 +0000 Subject: Auto sync when policy changes to require it, in case we are already mirrored but have unsynced slaves at that point. --- src/rabbit_mirror_queue_misc.erl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl index 4dd50bce..8192b092 100644 --- a/src/rabbit_mirror_queue_misc.erl +++ b/src/rabbit_mirror_queue_misc.erl @@ -179,19 +179,14 @@ add_mirror(QName, MirrorNode) -> end end). -start_child(Name, MirrorNode, Q = #amqqueue{pid = QPid}) -> +start_child(Name, MirrorNode, Q) -> case rabbit_misc:with_exit_handler( rabbit_misc:const({ok, down}), fun () -> rabbit_mirror_queue_slave_sup:start_child(MirrorNode, [Q]) end) of {ok, SPid} when is_pid(SPid) -> - case rabbit_policy:get(<<"ha-sync-mode">>, Q) of - {ok,<<"automatic">>} -> - spawn(fun() -> rabbit_amqqueue:sync_mirrors(QPid) end); - _ -> - ok - end, + maybe_auto_sync(Q), rabbit_log:info("Adding mirror of ~s on node ~p: ~p~n", [rabbit_misc:rs(Name), MirrorNode, SPid]), {ok, started}; @@ -310,6 +305,14 @@ is_mirrored(Q) -> _ -> false end. +maybe_auto_sync(Q = #amqqueue{pid = QPid}) -> + case policy(<<"ha-sync-mode">>, Q) of + <<"automatic">> -> + spawn(fun() -> rabbit_amqqueue:sync_mirrors(QPid) end); + _ -> + ok + end. + update_mirrors(OldQ = #amqqueue{pid = QPid}, NewQ = #amqqueue{pid = QPid}) -> case {is_mirrored(OldQ), is_mirrored(NewQ)} of @@ -326,6 +329,7 @@ update_mirrors0(OldQ = #amqqueue{name = QName}, NewNodes = All(suggested_queue_nodes(NewQ)), add_mirrors(QName, NewNodes -- OldNodes), drop_mirrors(QName, OldNodes -- NewNodes), + maybe_auto_sync(NewQ), ok. %%---------------------------------------------------------------------------- -- cgit v1.2.1 From b57e18458b542429967d4a5c1e83a7cde0c2f516 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 13 Feb 2013 15:20:34 +0000 Subject: Today's arbitrary startup banner change: centre the rabbit. --- src/rabbit.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 55736b28..c004c489 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -719,13 +719,13 @@ erts_version_check() -> print_banner() -> {ok, Product} = application:get_key(id), {ok, Version} = application:get_key(vsn), - io:format("~n## ## ~s ~s. ~s" - "~n## ## ~s" - "~n##########" - "~n###### ## Logs: ~s" - "~n########## ~s" - "~n" - "~n Starting broker...", + io:format("~n ~s ~s. ~s" + "~n ## ## ~s" + "~n ## ##" + "~n ########## Logs: ~s" + "~n ###### ## ~s" + "~n ##########" + "~n Starting broker...", [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE, log_location(kernel), log_location(sasl)]). -- cgit v1.2.1 From f994b25859adf71955c64e20a74e85c076977c26 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 13 Feb 2013 21:22:54 +0000 Subject: don't invoke run_message_queue in handle_cast/run_backing_queue Since that would only be necessary of the BQ:invoke modified the consumers, which it can't, or added messages to the queue, which it shouldn't. --- 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 00a3a85a..4cd21c0a 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1216,8 +1216,7 @@ handle_cast(_, State = #q{delayed_stop = DS}) when DS =/= undefined -> handle_cast({run_backing_queue, Mod, Fun}, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - noreply(run_message_queue( - State#q{backing_queue_state = BQ:invoke(Mod, Fun, BQS)})); + noreply(State#q{backing_queue_state = BQ:invoke(Mod, Fun, BQS)}); handle_cast({deliver, Delivery = #delivery{sender = Sender}, Delivered, Flow}, State = #q{senders = Senders}) -> -- cgit v1.2.1 From 65491f7748c0c79029fe4f601638d101d75b8d56 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 13 Feb 2013 21:25:44 +0000 Subject: drop_expired_msgs only when the queue head changes And on recover. And when the timer goes off. That's all we need. new call sites: - in deliver_or_enqueue/3, when enqueuing a message (that we couldn't deliver to a consumer straight away) with an expiry to the head. the queue. NB: Previously we were always (re)setting a timer when enqueuing a message with an expiry, which is wasteful when the new message isn't at the head (i.e. the queue was non-empty) or when it needs expiring immediately. - requeue_and_run/2, since a message may get requeued to the head. This call site arises due to removal of the run_message_queue/1 call site (see below). unchanged call sites: - init_ttl/2 - this is the recovery case - fetch/2, after fetching - this is the basic "queue head changes" case - handle_info/drop_expired - this is the message expiry timer removed call sites: - run_message_queue/1 - this internally calls fetch/2 (see above) but also invoking drop_expired_msgs at the beginning. This now happens at the call sites, where it is necessary. Which actually only is in requeue_and_run, and not the others, none of which change the queue content prior to calling run_message_queue/1 - possibly_unblock/3 - unblocking of consumers - handle_call/basic_consumer - adding a consumer - handle_call/basic_get, prior to the call to fetch/2. - handle_call/stat --- src/rabbit_amqqueue_process.erl | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 4cd21c0a..4a0ccf81 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -519,13 +519,11 @@ discard(#delivery{sender = SenderPid, BQS1 = BQ:discard(MsgId, SenderPid, BQS), State1#q{backing_queue_state = BQS1}. -run_message_queue(State) -> - State1 = #q{backing_queue = BQ, backing_queue_state = BQS} = - drop_expired_msgs(State), - {_IsEmpty1, State2} = deliver_msgs_to_consumers( +run_message_queue(State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> + {_IsEmpty1, State1} = deliver_msgs_to_consumers( fun deliver_from_queue_deliver/2, - BQ:is_empty(BQS), State1), - State2. + BQ:is_empty(BQS), State), + State1. attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, Props, Delivered, State = #q{backing_queue = BQ, @@ -559,15 +557,27 @@ deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, {false, State2 = #q{ttl = 0, dlx = undefined}} -> discard(Delivery, State2); {false, State2 = #q{backing_queue = BQ, backing_queue_state = BQS}} -> + IsEmpty = BQ:is_empty(BQS), BQS1 = BQ:publish(Message, Props, Delivered, SenderPid, BQS), - ensure_ttl_timer(Props#message_properties.expiry, - State2#q{backing_queue_state = BQS1}) + State3 = State2#q{backing_queue_state = BQS1}, + %% optimisation: it would be perfectly safe to always + %% invoke drop_expired_msgs here, but that is expensive so + %% we only do that IFF the new message ends up at the head + %% of the queue (because the queue was empty) and has an + %% expiry. Only then may it need expiring straight away, + %% or, if expiry is not due yet, the expiry timer may need + %% (re)scheduling. + case {IsEmpty, Props#message_properties.expiry} of + {false, _} -> State3; + {true, undefined} -> State3; + {true, _} -> drop_expired_msgs(State3) + end end. requeue_and_run(AckTags, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), - run_message_queue(State#q{backing_queue_state = BQS1}). + run_message_queue(drop_expired_msgs(State#q{backing_queue_state = BQS1})). fetch(AckRequired, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> @@ -1064,7 +1074,7 @@ handle_call({basic_get, ChPid, NoAck}, _From, State = #q{q = #amqqueue{name = QName}}) -> AckRequired = not NoAck, State1 = ensure_expiry_timer(State), - case fetch(AckRequired, drop_expired_msgs(State1)) of + case fetch(AckRequired, State1) of {empty, State2} -> reply(empty, State2); {{Message, IsDelivered, AckTag}, State2} -> @@ -1137,7 +1147,7 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, handle_call(stat, _From, State) -> State1 = #q{backing_queue = BQ, backing_queue_state = BQS} = - drop_expired_msgs(ensure_expiry_timer(State)), + ensure_expiry_timer(State), reply({ok, BQ:len(BQS), consumer_count()}, State1); handle_call({delete, IfUnused, IfEmpty}, From, -- cgit v1.2.1 From 76780acfaa2c1396db2b1c5e30bfe3a03fe91d12 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 14 Feb 2013 11:44:47 +0000 Subject: remove superfluous vqstate field --- src/rabbit_backing_queue.erl | 2 -- src/rabbit_variable_queue.erl | 12 +++--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl index c2b52a7c..2f247448 100644 --- a/src/rabbit_backing_queue.erl +++ b/src/rabbit_backing_queue.erl @@ -18,8 +18,6 @@ -ifdef(use_specs). --export_type([async_callback/0]). - %% We can't specify a per-queue ack/state with callback signatures -type(ack() :: any()). -type(state() :: any()). diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl index 5d463f57..f7c6c729 100644 --- a/src/rabbit_variable_queue.erl +++ b/src/rabbit_variable_queue.erl @@ -262,8 +262,6 @@ durable, transient_threshold, - async_callback, - len, persistent_count, @@ -356,8 +354,6 @@ durable :: boolean(), transient_threshold :: non_neg_integer(), - async_callback :: rabbit_backing_queue:async_callback(), - len :: non_neg_integer(), persistent_count :: non_neg_integer(), @@ -426,7 +422,7 @@ init(Queue, Recover, AsyncCallback) -> init(#amqqueue { name = QueueName, durable = IsDurable }, false, AsyncCallback, MsgOnDiskFun, MsgIdxOnDiskFun) -> IndexState = rabbit_queue_index:init(QueueName, MsgIdxOnDiskFun), - init(IsDurable, IndexState, 0, [], AsyncCallback, + init(IsDurable, IndexState, 0, [], case IsDurable of true -> msg_store_client_init(?PERSISTENT_MSG_STORE, MsgOnDiskFun, AsyncCallback); @@ -454,7 +450,7 @@ init(#amqqueue { name = QueueName, durable = true }, true, rabbit_msg_store:contains(MsgId, PersistentClient) end, MsgIdxOnDiskFun), - init(true, IndexState, DeltaCount, Terms1, AsyncCallback, + init(true, IndexState, DeltaCount, Terms1, PersistentClient, TransientClient). terminate(_Reason, State) -> @@ -1004,7 +1000,7 @@ update_rate(Now, Then, Count, {OThen, OCount}) -> %% Internal major helpers for Public API %%---------------------------------------------------------------------------- -init(IsDurable, IndexState, DeltaCount, Terms, AsyncCallback, +init(IsDurable, IndexState, DeltaCount, Terms, PersistentClient, TransientClient) -> {LowSeqId, NextSeqId, IndexState1} = rabbit_queue_index:bounds(IndexState), @@ -1030,8 +1026,6 @@ init(IsDurable, IndexState, DeltaCount, Terms, AsyncCallback, durable = IsDurable, transient_threshold = NextSeqId, - async_callback = AsyncCallback, - len = DeltaCount1, persistent_count = DeltaCount1, -- cgit v1.2.1 From 2532b6c41a1179e745e2f42235040b54de3472fa Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 15 Feb 2013 00:02:15 +0000 Subject: explain --- src/rabbit_channel.erl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index e74211af..eb03bf54 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -589,6 +589,16 @@ handle_method(_Method, _, State = #ch{state = closing}) -> handle_method(#'channel.close'{}, _, State = #ch{reader_pid = ReaderPid}) -> {ok, State1} = notify_queues(State), + %% We issue the channel.close_ok response after a handshake with + %% the reader, the other half of which is the + %% ready_for_close. That way the reader forgets about the channel + %% before we send the response (and this channel process + %% terminates). If we didn't do that, a channel.open for the same + %% channel number, which a client is entitled to send as soon as + %% it has received the close_ok, might be received by the reader + %% before it has seen the termination and hence be sent to the + %% old, now dead/dying channel process, instead of a new process, + %% and thus lost. ReaderPid ! {channel_closing, self()}, {noreply, State1}; -- cgit v1.2.1 From 0f24c233d0e4b131906dcda8b2fbfc6f34183244 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 15 Feb 2013 00:02:52 +0000 Subject: tweak --- src/rabbit_channel.erl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index eb03bf54..0510afa9 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -590,15 +590,14 @@ handle_method(_Method, _, State = #ch{state = closing}) -> handle_method(#'channel.close'{}, _, State = #ch{reader_pid = ReaderPid}) -> {ok, State1} = notify_queues(State), %% We issue the channel.close_ok response after a handshake with - %% the reader, the other half of which is the - %% ready_for_close. That way the reader forgets about the channel - %% before we send the response (and this channel process - %% terminates). If we didn't do that, a channel.open for the same - %% channel number, which a client is entitled to send as soon as - %% it has received the close_ok, might be received by the reader - %% before it has seen the termination and hence be sent to the - %% old, now dead/dying channel process, instead of a new process, - %% and thus lost. + %% the reader, the other half of which is ready_for_close. That + %% way the reader forgets about the channel before we send the + %% response (and this channel process terminates). If we didn't do + %% that, a channel.open for the same channel number, which a + %% client is entitled to send as soon as it has received the + %% close_ok, might be received by the reader before it has seen + %% the termination and hence be sent to the old, now dead/dying + %% channel process, instead of a new process, and thus lost. ReaderPid ! {channel_closing, self()}, {noreply, State1}; -- cgit v1.2.1 From d8f3b60cd86d708abf4736f2e9acc04ab9a2f94a Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 15 Feb 2013 11:44:05 +0000 Subject: Typo --- src/rabbit_mirror_queue_misc.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl index 5cba2d43..d0291814 100644 --- a/src/rabbit_mirror_queue_misc.erl +++ b/src/rabbit_mirror_queue_misc.erl @@ -270,7 +270,7 @@ suggested_queue_nodes(<<"nodes">>, Nodes0, {MNode, _SNodes, SSNodes}, Poss) -> {MNode, []}; _ -> case lists:member(MNode, Available) of true -> {MNode, Available -- [MNode]}; - false -> %% Make the sure new master is synced! In order to + false -> %% Make sure the new master is synced! In order to %% get here SSNodes must not be empty. [NewMNode | _] = SSNodes, {NewMNode, Available -- [NewMNode]} -- cgit v1.2.1 From 81097e2b80cb2a31fb285f87b57c6440d1565e05 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 15 Feb 2013 16:14:14 +0000 Subject: make reader's handle_dependent_exit clearer - handle the no-op case (controlled exit of a channel we've forgotten about already) explicitly - better clause order and formatting. --- src/rabbit_reader.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index af7aac6f..f249bc9b 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -437,13 +437,13 @@ close_connection(State = #v1{queue_collector = Collector, handle_dependent_exit(ChPid, Reason, State) -> case {channel_cleanup(ChPid), termination_kind(Reason)} of - {undefined, uncontrolled} -> - exit({abnormal_dependent_exit, ChPid, Reason}); - {_Channel, controlled} -> - maybe_close(control_throttle(State)); - {Channel, uncontrolled} -> - maybe_close(handle_exception(control_throttle(State), - Channel, Reason)) + {undefined, controlled} -> State; + {undefined, uncontrolled} -> exit({abnormal_dependent_exit, + ChPid, Reason}); + {_Channel, controlled} -> maybe_close(control_throttle(State)); + {Channel, uncontrolled} -> maybe_close( + handle_exception(control_throttle(State), + Channel, Reason)) end. terminate_channels() -> -- cgit v1.2.1 From bee34f70c1174aaa99c189da3d017c474198f9fa Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 15 Feb 2013 17:27:47 +0000 Subject: make reader's handling of channel.close_ok more obviously correct by re-introducing a call to control_throttle. This was present in 3.0.x, and is needed there, but is actually not needed here, due to other changes made in the area. But the reason is quite subtle... For control_throttle to do something here, the channel_cleanup would have to be called for a channel for which we have run out of credit. Now, credit is only consumed when sending content-bearing methods to the channel with rabbit_channel:do_flow. There is a subsequent invocation of control_throttle in the code which will set the connection_state to 'blocking'. But this is immediately followed by a call to post_process_frame. The frame we are looking at must be the last frame of a content-bearing method, since otherwise the method would not be complete and we wouldn't have passed it to the channel. Hence that frame can only be a content_header or, more likely, a content_body. For both of these, post_process_frame invokes maybe_block, which will turn a 'blocking' into 'blocked'. And from that point onwards we will no longer read anything from the socket or process anything already in buf. So we certainly can't be processing a channel.close_ok. In other words, post_process_frame can only be invoked with a channel.close_ok frame when the connection_state is 'running', or blocking/blocked for a reason other than having run out of credit for a channel, i.e. an alarm. Therefore forgetting about the channel as part of the channel_cleanup call does not have an effect on credit_flow:blocked(). And hence an invocation of control_throttle will not alter the connection_state and is therefore unnecessary. --- src/rabbit_reader.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index f249bc9b..d2655a90 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -636,7 +636,8 @@ process_frame(Frame, Channel, State) -> post_process_frame({method, 'channel.close_ok', _}, ChPid, State) -> channel_cleanup(ChPid), - State; + %% this is not strictly necessary, but more obviously correct + control_throttle(State); post_process_frame({content_header, _, _, _, _}, _ChPid, State) -> maybe_block(State); post_process_frame({content_body, _}, _ChPid, State) -> -- cgit v1.2.1 From 8e613f029f032a24948c8f63a8d4b9d33cfa5af8 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 15 Feb 2013 18:55:22 +0000 Subject: refactor: more symmetry in rabbit_reader:handle_dependent_exit/3 It makes no difference whether we call handle_exception before or after control_throttle, so lets use an order that more clearly calls out the similarity to the controlled exit case. --- src/rabbit_reader.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index d2655a90..203ec3f0 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -441,9 +441,9 @@ handle_dependent_exit(ChPid, Reason, State) -> {undefined, uncontrolled} -> exit({abnormal_dependent_exit, ChPid, Reason}); {_Channel, controlled} -> maybe_close(control_throttle(State)); - {Channel, uncontrolled} -> maybe_close( - handle_exception(control_throttle(State), - Channel, Reason)) + {Channel, uncontrolled} -> State1 = handle_exception( + State, Channel, Reason), + maybe_close(control_throttle(State1)) end. terminate_channels() -> -- cgit v1.2.1 From a77f31a8bbbdf93637207c3d85cbb3fa41691a93 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 15 Feb 2013 18:55:47 +0000 Subject: explain a reader sublety --- src/rabbit_reader.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 203ec3f0..af331a62 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -636,7 +636,9 @@ process_frame(Frame, Channel, State) -> post_process_frame({method, 'channel.close_ok', _}, ChPid, State) -> channel_cleanup(ChPid), - %% this is not strictly necessary, but more obviously correct + %% This is not strictly necessary, but more obviously + %% correct. Also note that we do not need to call maybe_close/1 + %% since we cannot possibly be in the 'closing' state. control_throttle(State); post_process_frame({content_header, _, _, _, _}, _ChPid, State) -> maybe_block(State); -- cgit v1.2.1 From 05544ad98014efecd237a42af07958f82703867b Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 18 Feb 2013 16:47:40 +0000 Subject: cluster_cp_mode --- ebin/rabbit_app.in | 1 + src/rabbit_mnesia.erl | 6 ++++++ src/rabbit_node_monitor.erl | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/ebin/rabbit_app.in b/ebin/rabbit_app.in index ad961a44..d08d502b 100644 --- a/ebin/rabbit_app.in +++ b/ebin/rabbit_app.in @@ -44,6 +44,7 @@ {log_levels, [{connection, info}]}, {ssl_cert_login_from, distinguished_name}, {reverse_dns_lookups, false}, + {cluster_cp_mode, false}, {tcp_listen_options, [binary, {packet, raw}, {reuseaddr, true}, diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index c39e898c..ecb03f54 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -26,6 +26,7 @@ status/0, is_clustered/0, + majority/0, cluster_nodes/1, node_type/0, dir/0, @@ -67,6 +68,7 @@ -spec(status/0 :: () -> [{'nodes', [{node_type(), [node()]}]} | {'running_nodes', [node()]} | {'partitions', [{node(), [node()]}]}]). +-spec(majority/0 :: () -> boolean()). -spec(is_clustered/0 :: () -> boolean()). -spec(cluster_nodes/1 :: ('all' | 'disc' | 'ram' | 'running') -> [node()]). -spec(node_type/0 :: () -> node_type()). @@ -338,6 +340,10 @@ status() -> false -> [] end. +majority() -> + ensure_mnesia_running(), + (length(cluster_nodes(running)) / length(cluster_nodes(all))) > 0.5. + mnesia_partitions(Nodes) -> {Replies, _BadNodes} = rpc:multicall( Nodes, rabbit_node_monitor, partitions, []), diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 71c2c80a..7b7fed5c 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -250,6 +250,10 @@ handle_info({mnesia_system_event, ordsets:add_element(Node, ordsets:from_list(Partitions))), {noreply, State#state{partitions = Partitions1}}; +handle_info({mnesia_system_event, {mnesia_down, _Node}}, State) -> + handle_dead_according_to_mnesia_rabbit(), + {noreply, State}; + handle_info(_Info, State) -> {noreply, State}. @@ -272,6 +276,28 @@ handle_dead_rabbit(Node) -> ok = rabbit_alarm:on_node_down(Node), ok = rabbit_mnesia:on_node_down(Node). +%% Since we will be introspecting the cluster in response to this, we +%% must only do so based on Mnesia having noticed the other node being +%% down - otherwise we have a race. +handle_dead_according_to_mnesia_rabbit() -> + case application:get_env(rabbit, cluster_cp_mode) of + {ok, true} -> case rabbit_mnesia:majority() of + true -> ok; + false -> stop_and_halt() + end; + {ok, false} -> ok + end, + ok. + +stop_and_halt() -> + rabbit_log:warning("Cluster minority status detected - stopping~n", []), + spawn(fun () -> + %% If our group leader is inside an application we are about + %% to stop, application:stop/1 does not return. + group_leader(whereis(init), self()), + rabbit:stop_and_halt() + end). + handle_live_rabbit(Node) -> ok = rabbit_alarm:on_node_up(Node), ok = rabbit_mnesia:on_node_up(Node). -- cgit v1.2.1 From cbf31df3f7e86ecd91746df92a504d3c64b85b38 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 20 Feb 2013 12:05:22 +0000 Subject: Remove spurious blank line in the logs. --- src/rabbit.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index 4487f07c..f3ba022a 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -711,7 +711,7 @@ log_broker_started(Plugins) -> PluginList = iolist_to_binary([rabbit_misc:format(" * ~s~n", [P]) || P <- Plugins]), error_logger:info_msg( - "Server startup complete; ~b plugins started.~n~s~n", + "Server startup complete; ~b plugins started.~n~s", [length(Plugins), PluginList]), io:format(" completed with ~p plugins.~n", [length(Plugins)]) end). -- cgit v1.2.1 From 84da16816bd189a730398e05706f7b89f8c6f544 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 20 Feb 2013 14:27:17 +0000 Subject: revert test timings back to exactly what's on default --- src/test_sup.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_sup.erl b/src/test_sup.erl index 955c44e6..6a56e64a 100644 --- a/src/test_sup.erl +++ b/src/test_sup.erl @@ -50,7 +50,7 @@ test_supervisor_delayed_restart(SupPid) -> ok = exit_child(SupPid), timer:sleep(100), timeout = ping_child(SupPid), - timer:sleep(1000), + timer:sleep(1010), ok = ping_child(SupPid), passed. -- cgit v1.2.1 From 4bc0fdc948a9221d3136d306fc3beb4c04aa6575 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 20 Feb 2013 14:57:53 +0000 Subject: Neatness --- src/rabbit_amqqueue_process.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 35a28b6b..8ba9b4d2 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -471,12 +471,12 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> Limiter2 -> AC1 = queue:in(E, State#q.active_consumers), deliver_msg_to_consumer( - DeliverFun, Limiter2, Consumer, C, + DeliverFun, Consumer, C#cr{limiter = Limiter2}, State#q{active_consumers = AC1}) end end. -deliver_msg_to_consumer(DeliverFun, NewLimiter, +deliver_msg_to_consumer(DeliverFun, #consumer{tag = ConsumerTag, ack_required = AckRequired}, C = #cr{ch_pid = ChPid, @@ -492,7 +492,6 @@ deliver_msg_to_consumer(DeliverFun, NewLimiter, false -> ChAckTags end, update_ch_record(C#cr{acktags = ChAckTags1, - limiter = NewLimiter, unsent_message_count = Count + 1}), {Stop, State1}. -- cgit v1.2.1 From 21967553cfe335810ee6bf922e1b71ee8acfaa16 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 20 Feb 2013 17:20:02 +0000 Subject: Be more careful about where we send_drained from. --- src/rabbit_amqqueue_process.erl | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 8ba9b4d2..5c376681 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -407,6 +407,12 @@ maybe_send_drained(#q{backing_queue = BQ, backing_queue_state = BQS}) -> false -> ok end. +maybe_send_drained(C, #q{backing_queue = BQ, backing_queue_state = BQS}) -> + case BQ:is_empty(BQS) of + true -> send_drained(C); + false -> ok + end. + send_drained() -> [send_drained(C) || C <- all_ch_record()]. send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> @@ -437,7 +443,6 @@ is_ch_blocked(#cr{unsent_message_count = Count, is_limit_active = Limited}) -> Limited orelse Count >= ?UNSENT_MESSAGE_LIMIT. deliver_msgs_to_consumers(_DeliverFun, true, State) -> - send_drained(), {true, State}; deliver_msgs_to_consumers(DeliverFun, false, State = #q{active_consumers = ActiveConsumers}) -> @@ -603,12 +608,16 @@ deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, requeue_and_run(AckTags, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), - run_message_queue(drop_expired_msgs(State#q{backing_queue_state = BQS1})). + State1 = drop_expired_msgs(State#q{backing_queue_state = BQS1}), + maybe_send_drained(State1), + run_message_queue(State1). fetch(AckRequired, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {Result, BQS1} = BQ:fetch(AckRequired, BQS), - {Result, drop_expired_msgs(State#q{backing_queue_state = BQS1})}. + State1 = drop_expired_msgs(State#q{backing_queue_state = BQS1}), + maybe_send_drained(State1), + {Result, State1}. ack(AckTags, ChPid, State) -> subtract_acks(ChPid, AckTags, State, @@ -752,6 +761,11 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) -> T -> now_micros() + T * 1000 end. +%% Logically this function should invoke maybe_send_drained/1. However, that's +%% expensive, and some frequent callers of drop_expired_msgs/1 (in particular +%% deliver_or_enqueue/3) cannot possibly cause the queue to become empty, so +%% instead we push the responsibility to the call sites. So be cautious when +%% adding new ones. drop_expired_msgs(State = #q{backing_queue_state = BQS, backing_queue = BQ }) -> case BQ:is_empty(BQS) of @@ -1154,7 +1168,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, AC1 = queue:in(E, State1#q.active_consumers), run_message_queue(State1#q{active_consumers = AC1}) end, - maybe_send_drained(State2), + maybe_send_drained(C1, State), emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, not NoAck, qname(State2)), reply(ok, State2) @@ -1204,7 +1218,9 @@ handle_call({delete, IfUnused, IfEmpty}, From, handle_call(purge, _From, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {Count, BQS1} = BQ:purge(BQS), - reply({ok, Count}, State#q{backing_queue_state = BQS1}); + State1 = State#q{backing_queue_state = BQS1}, + maybe_send_drained(State1), + reply({ok, Count}, State1); handle_call({requeue, AckTags, ChPid}, From, State) -> gen_server2:reply(From, ok), @@ -1367,7 +1383,7 @@ handle_cast({credit, ChPid, CTag, Credit, Drain}, rabbit_channel:send_credit_reply(ChPid, BQ:len(BQS)), State1 = possibly_unblock( State, ChPid, fun(C) -> C#cr{limiter = Lim2} end), - maybe_send_drained(State1), + maybe_send_drained(lookup_ch(ChPid), State), noreply(State1); handle_cast(wake_up, State) -> @@ -1389,7 +1405,9 @@ handle_info(maybe_expire, State) -> end; handle_info(drop_expired, State) -> - noreply(drop_expired_msgs(State#q{ttl_timer_ref = undefined})); + State1 = drop_expired_msgs(State#q{ttl_timer_ref = undefined}), + maybe_send_drained(State1), + noreply(State1); handle_info(emit_stats, State) -> emit_stats(State), -- cgit v1.2.1 From 44a07f8a313dd5745355b4e148a74b572984f15d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 20 Feb 2013 17:23:39 +0000 Subject: Ahem --- 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 5c376681..78b0d23d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1168,7 +1168,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, AC1 = queue:in(E, State1#q.active_consumers), run_message_queue(State1#q{active_consumers = AC1}) end, - maybe_send_drained(C1, State), + maybe_send_drained(C1, State2), emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, not NoAck, qname(State2)), reply(ok, State2) @@ -1383,7 +1383,7 @@ handle_cast({credit, ChPid, CTag, Credit, Drain}, rabbit_channel:send_credit_reply(ChPid, BQ:len(BQS)), State1 = possibly_unblock( State, ChPid, fun(C) -> C#cr{limiter = Lim2} end), - maybe_send_drained(lookup_ch(ChPid), State), + maybe_send_drained(lookup_ch(ChPid), State1), noreply(State1); handle_cast(wake_up, State) -> -- cgit v1.2.1 From 773cdb96c27f1f36257ca020e33808c8402510df Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 20 Feb 2013 17:33:35 +0000 Subject: Only send_drained if we have become empty... --- src/rabbit_amqqueue_process.erl | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 78b0d23d..829798c7 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -401,13 +401,16 @@ erase_ch_record(#cr{ch_pid = ChPid, erase({ch, ChPid}), ok. -maybe_send_drained(#q{backing_queue = BQ, backing_queue_state = BQS}) -> +maybe_send_drained(true, _State) -> + ok; + +maybe_send_drained(false, #q{backing_queue = BQ, backing_queue_state = BQS}) -> case BQ:is_empty(BQS) of true -> send_drained(); false -> ok end. -maybe_send_drained(C, #q{backing_queue = BQ, backing_queue_state = BQS}) -> +maybe_send_drained_cons(C, #q{backing_queue = BQ, backing_queue_state = BQS}) -> case BQ:is_empty(BQS) of true -> send_drained(C); false -> ok @@ -609,14 +612,14 @@ requeue_and_run(AckTags, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), State1 = drop_expired_msgs(State#q{backing_queue_state = BQS1}), - maybe_send_drained(State1), + maybe_send_drained(BQ:is_empty(BQS), State1), run_message_queue(State1). fetch(AckRequired, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {Result, BQS1} = BQ:fetch(AckRequired, BQS), State1 = drop_expired_msgs(State#q{backing_queue_state = BQS1}), - maybe_send_drained(State1), + maybe_send_drained(Result =:= empty, State1), {Result, State1}. ack(AckTags, ChPid, State) -> @@ -1168,7 +1171,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, AC1 = queue:in(E, State1#q.active_consumers), run_message_queue(State1#q{active_consumers = AC1}) end, - maybe_send_drained(C1, State2), + maybe_send_drained_cons(C1, State2), emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, not NoAck, qname(State2)), reply(ok, State2) @@ -1219,7 +1222,7 @@ handle_call(purge, _From, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {Count, BQS1} = BQ:purge(BQS), State1 = State#q{backing_queue_state = BQS1}, - maybe_send_drained(State1), + maybe_send_drained(Count =:= 0, State1), reply({ok, Count}, State1); handle_call({requeue, AckTags, ChPid}, From, State) -> @@ -1383,7 +1386,7 @@ handle_cast({credit, ChPid, CTag, Credit, Drain}, rabbit_channel:send_credit_reply(ChPid, BQ:len(BQS)), State1 = possibly_unblock( State, ChPid, fun(C) -> C#cr{limiter = Lim2} end), - maybe_send_drained(lookup_ch(ChPid), State1), + maybe_send_drained_cons(lookup_ch(ChPid), State1), noreply(State1); handle_cast(wake_up, State) -> @@ -1404,9 +1407,10 @@ handle_info(maybe_expire, State) -> false -> noreply(ensure_expiry_timer(State)) end; -handle_info(drop_expired, State) -> +handle_info(drop_expired, State = #q{backing_queue = BQ, + backing_queue_state = BQS}) -> State1 = drop_expired_msgs(State#q{ttl_timer_ref = undefined}), - maybe_send_drained(State1), + maybe_send_drained(BQ:is_empty(BQS), State1), noreply(State1); handle_info(emit_stats, State) -> -- cgit v1.2.1 From 3378756291aca8f634e136fb96a118eb464ddad3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 17:48:37 +0000 Subject: cosmetic --- src/rabbit_amqqueue_process.erl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 829798c7..5fd3377a 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -403,10 +403,9 @@ erase_ch_record(#cr{ch_pid = ChPid, maybe_send_drained(true, _State) -> ok; - maybe_send_drained(false, #q{backing_queue = BQ, backing_queue_state = BQS}) -> case BQ:is_empty(BQS) of - true -> send_drained(); + true -> [send_drained(C) || C <- all_ch_record()]; false -> ok end. @@ -416,15 +415,12 @@ maybe_send_drained_cons(C, #q{backing_queue = BQ, backing_queue_state = BQS}) -> false -> ok end. -send_drained() -> [send_drained(C) || C <- all_ch_record()]. - send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> case rabbit_limiter:drained(Limiter) of - {[], Limiter} -> - ok; - {CTagCredit, Limiter2} -> - rabbit_channel:send_drained(ChPid, CTagCredit), - update_ch_record(C#cr{limiter = Limiter2}) + {[], Limiter} -> ok; + {CTagCredit, Limiter2} -> rabbit_channel:send_drained( + ChPid, CTagCredit), + update_ch_record(C#cr{limiter = Limiter2}) end. update_consumer_count(C = #cr{consumer_count = 0, limiter = Limiter}, +1) -> -- cgit v1.2.1 From 1a3bde0490e78fe41ff61f903a4c688b2ccf284d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 17:49:03 +0000 Subject: eliminate non-linear BQS usage --- src/rabbit_amqqueue_process.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 5fd3377a..de3e73ee 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -606,9 +606,10 @@ deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, requeue_and_run(AckTags, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> + WasEmpty = BQ:is_empty(BQS), {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), State1 = drop_expired_msgs(State#q{backing_queue_state = BQS1}), - maybe_send_drained(BQ:is_empty(BQS), State1), + maybe_send_drained(WasEmpty, State1), run_message_queue(State1). fetch(AckRequired, State = #q{backing_queue = BQ, -- cgit v1.2.1 From cf56799e48ed7dedbf0060f217b96d50c2868ddc Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 17:54:00 +0000 Subject: cosmetic --- 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 de3e73ee..9fd85bd6 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -761,11 +761,11 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) -> T -> now_micros() + T * 1000 end. -%% Logically this function should invoke maybe_send_drained/1. However, that's -%% expensive, and some frequent callers of drop_expired_msgs/1 (in particular -%% deliver_or_enqueue/3) cannot possibly cause the queue to become empty, so -%% instead we push the responsibility to the call sites. So be cautious when -%% adding new ones. +%% Logically this function should invoke maybe_send_drained/2. +%% However, that is expensive. Since some frequent callers of +%% drop_expired_msgs/1, in particular deliver_or_enqueue/3, cannot +%% possibly cause the queue to become empty, we push the +%% responsibility to the callers. So be cautious when adding new ones. drop_expired_msgs(State = #q{backing_queue_state = BQS, backing_queue = BQ }) -> case BQ:is_empty(BQS) of -- cgit v1.2.1 From e7a061a08b7c91393beaf7d377b5efb8a9c633db Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 17:55:23 +0000 Subject: cosmetic --- 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 4a0ccf81..be7ee097 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -738,8 +738,8 @@ drop_expired_msgs(Now, State = #q{backing_queue_state = BQS, fun () -> {Next, BQS1} = BQ:dropwhile(ExpirePred, BQS), {Next, State#q{backing_queue_state = BQS1}} end), ensure_ttl_timer(case Props of - undefined -> undefined; - #message_properties{expiry = Exp} -> Exp + undefined -> undefined; + #message_properties{expiry = Exp} -> Exp end, State1). with_dlx(undefined, _With, Without) -> Without(); -- cgit v1.2.1 From adf8c37c62b490fb42db906a318fe957ac39d299 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 19:41:11 +0000 Subject: introduce is_empty(State) helper and in the resulting refactor also remove a non-linear BQS access in handle_info/drop_expired. --- src/rabbit_amqqueue_process.erl | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 9fd85bd6..fb60a043 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -353,9 +353,10 @@ stop_ttl_timer(State) -> rabbit_misc:stop_timer(State, #q.ttl_timer_ref). ensure_stats_timer(State) -> rabbit_event:ensure_stats_timer(State, #q.stats_timer, emit_stats). -assert_invariant(#q{active_consumers = AC, - backing_queue = BQ, backing_queue_state = BQS}) -> - true = (queue:is_empty(AC) orelse BQ:is_empty(BQS)). +assert_invariant(State = #q{active_consumers = AC}) -> + true = (queue:is_empty(AC) orelse is_empty(State)). + +is_empty({backing_queue = BQ, backing_queue_state = BQS}) -> BQ:is_empty(BQS). lookup_ch(ChPid) -> case get({ch, ChPid}) of @@ -401,16 +402,14 @@ erase_ch_record(#cr{ch_pid = ChPid, erase({ch, ChPid}), ok. -maybe_send_drained(true, _State) -> - ok; -maybe_send_drained(false, #q{backing_queue = BQ, backing_queue_state = BQS}) -> - case BQ:is_empty(BQS) of +maybe_send_drained(WasEmpty, State) -> + case WasEmpty andalso is_empty(State) of true -> [send_drained(C) || C <- all_ch_record()]; false -> ok end. -maybe_send_drained_cons(C, #q{backing_queue = BQ, backing_queue_state = BQS}) -> - case BQ:is_empty(BQS) of +maybe_send_drained_cons(C, State) -> + case is_empty(State) of true -> send_drained(C); false -> ok end. @@ -500,9 +499,8 @@ deliver_msg_to_consumer(DeliverFun, {Stop, State1}. deliver_from_queue_deliver(AckRequired, State) -> - {Result, State1 = #q{backing_queue = BQ, backing_queue_state = BQS}} = - fetch(AckRequired, State), - {Result, BQ:is_empty(BQS), State1}. + {Result, State1} = fetch(AckRequired, State), + {Result, is_empty(State1), State1}. confirm_messages([], State) -> State; @@ -549,10 +547,10 @@ discard(#delivery{sender = SenderPid, BQS1 = BQ:discard(MsgId, SenderPid, BQS), State1#q{backing_queue_state = BQS1}. -run_message_queue(State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> +run_message_queue(State) -> {_IsEmpty1, State1} = deliver_msgs_to_consumers( fun deliver_from_queue_deliver/2, - BQ:is_empty(BQS), State), + is_empty(State), State), State1. attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, @@ -766,9 +764,8 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) -> %% drop_expired_msgs/1, in particular deliver_or_enqueue/3, cannot %% possibly cause the queue to become empty, we push the %% responsibility to the callers. So be cautious when adding new ones. -drop_expired_msgs(State = #q{backing_queue_state = BQS, - backing_queue = BQ }) -> - case BQ:is_empty(BQS) of +drop_expired_msgs(State) -> + case is_empty(State) of true -> State; false -> drop_expired_msgs(now_micros(), State) end. @@ -1404,10 +1401,10 @@ handle_info(maybe_expire, State) -> false -> noreply(ensure_expiry_timer(State)) end; -handle_info(drop_expired, State = #q{backing_queue = BQ, - backing_queue_state = BQS}) -> +handle_info(drop_expired, State) -> + WasEmpty = is_empty(State), State1 = drop_expired_msgs(State#q{ttl_timer_ref = undefined}), - maybe_send_drained(BQ:is_empty(BQS), State1), + maybe_send_drained(WasEmpty, State1), noreply(State1); handle_info(emit_stats, State) -> -- cgit v1.2.1 From d011068eabb9861e49e3c1323a9bf1a8465a2b30 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 19:42:27 +0000 Subject: refactor possibly_unblock --- src/rabbit_amqqueue_process.erl | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index fb60a043..0530d650 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -644,25 +644,26 @@ remove_consumers(ChPid, Queue, QName) -> possibly_unblock(State, ChPid, Update) -> case lookup_ch(ChPid) of - not_found -> + not_found -> State; + C -> possibly_unblock(State, Update(C)) + end. + +possibly_unblock(State, C = #cr{limiter = Limiter}) -> + IsChBlocked = is_ch_blocked(C), + case lists:partition( + fun({_ChPid, #consumer{tag = CTag}}) -> + IsChBlocked orelse + rabbit_limiter:is_consumer_blocked(Limiter, CTag) + end, queue:to_list(C#cr.blocked_consumers)) of + {_, []} -> + update_ch_record(C), State; - C -> - C1 = #cr{limiter = Limiter} = Update(C), - {Blocked, Unblocked} = - lists:partition( - fun({_ChPid, #consumer{tag = CTag}}) -> - is_ch_blocked(C1) orelse - rabbit_limiter:is_consumer_blocked(Limiter, CTag) - end, queue:to_list(C1#cr.blocked_consumers)), - case Unblocked of - [] -> update_ch_record(C1), - State; - _ -> update_ch_record( - C1#cr{blocked_consumers = queue:from_list(Blocked)}), - AC1 = queue:join(State#q.active_consumers, - queue:from_list(Unblocked)), - run_message_queue(State#q{active_consumers = AC1}) - end + {Blocked, Unblocked} -> + BlockedQ = queue:from_list(Blocked), + UnblockedQ = queue:from_list(Unblocked), + update_ch_record(C#cr{blocked_consumers = BlockedQ}), + AC1 = queue:join(State#q.active_consumers, UnblockedQ), + run_message_queue(State#q{active_consumers = AC1}) end. should_auto_delete(#q{q = #amqqueue{auto_delete = false}}) -> false; -- cgit v1.2.1 From cba68ce1a820cd6f57dc86482049714734d309cd Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 19:45:01 +0000 Subject: get rid of maybe_send_drained_cons and optimise handle_cast/credit along the way --- src/rabbit_amqqueue_process.erl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 0530d650..c0e74129 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -408,12 +408,6 @@ maybe_send_drained(WasEmpty, State) -> false -> ok end. -maybe_send_drained_cons(C, State) -> - case is_empty(State) of - true -> send_drained(C); - false -> ok - end. - send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> case rabbit_limiter:drained(Limiter) of {[], Limiter} -> ok; @@ -1166,7 +1160,10 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, AC1 = queue:in(E, State1#q.active_consumers), run_message_queue(State1#q{active_consumers = AC1}) end, - maybe_send_drained_cons(C1, State2), + case is_empty(State2) of + true -> send_drained(lookup_ch(ChPid)); + false -> ok + end, emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, not NoAck, qname(State2)), reply(ok, State2) @@ -1376,13 +1373,16 @@ handle_cast(stop_mirroring, State = #q{backing_queue = BQ, handle_cast({credit, ChPid, CTag, Credit, Drain}, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> - #cr{limiter = Lim} = ch_record(ChPid), - Lim2 = rabbit_limiter:credit(Lim, CTag, Credit, Drain), - rabbit_channel:send_credit_reply(ChPid, BQ:len(BQS)), - State1 = possibly_unblock( - State, ChPid, fun(C) -> C#cr{limiter = Lim2} end), - maybe_send_drained_cons(lookup_ch(ChPid), State1), - noreply(State1); + Len = BQ:len(BQS), + rabbit_channel:send_credit_reply(ChPid, Len), + C = #cr{limiter = Lim} = lookup_ch(ChPid), + C1 = C#cr{limiter = rabbit_limiter:credit(Lim, CTag, Credit, Drain)}, + noreply(case Drain andalso Len == 0 of + true -> update_ch_record(C1), + send_drained(C1), + State; + false -> possibly_unblock(State, C1) + end); handle_cast(wake_up, State) -> noreply(State). -- cgit v1.2.1 From 6bde32b8f6af20212b815d62e5aac051af14d62f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 19:54:22 +0000 Subject: introduce is_empty(State) helper in rabbit_amqqueue_process ported from bug 23749 --- src/rabbit_amqqueue_process.erl | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index be7ee097..4f0da702 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -347,9 +347,10 @@ stop_ttl_timer(State) -> rabbit_misc:stop_timer(State, #q.ttl_timer_ref). ensure_stats_timer(State) -> rabbit_event:ensure_stats_timer(State, #q.stats_timer, emit_stats). -assert_invariant(#q{active_consumers = AC, - backing_queue = BQ, backing_queue_state = BQS}) -> - true = (queue:is_empty(AC) orelse BQ:is_empty(BQS)). +assert_invariant(State = #q{active_consumers = AC}) -> + true = (queue:is_empty(AC) orelse is_empty(State)). + +is_empty({backing_queue = BQ, backing_queue_state = BQS}) -> BQ:is_empty(BQS). lookup_ch(ChPid) -> case get({ch, ChPid}) of @@ -470,9 +471,8 @@ deliver_msg_to_consumer(DeliverFun, {Stop, State1}. deliver_from_queue_deliver(AckRequired, State) -> - {Result, State1 = #q{backing_queue = BQ, backing_queue_state = BQS}} = - fetch(AckRequired, State), - {Result, BQ:is_empty(BQS), State1}. + {Result, State1} = fetch(AckRequired, State), + {Result, is_empty(State1), State1}. confirm_messages([], State) -> State; @@ -519,10 +519,10 @@ discard(#delivery{sender = SenderPid, BQS1 = BQ:discard(MsgId, SenderPid, BQS), State1#q{backing_queue_state = BQS1}. -run_message_queue(State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> +run_message_queue(State) -> {_IsEmpty1, State1} = deliver_msgs_to_consumers( fun deliver_from_queue_deliver/2, - BQ:is_empty(BQS), State), + is_empty(State), State), State1. attempt_delivery(Delivery = #delivery{sender = SenderPid, message = Message}, @@ -721,9 +721,8 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) -> T -> now_micros() + T * 1000 end. -drop_expired_msgs(State = #q{backing_queue_state = BQS, - backing_queue = BQ }) -> - case BQ:is_empty(BQS) of +drop_expired_msgs(State) -> + case is_empty(State) of true -> State; false -> drop_expired_msgs(now_micros(), State) end. -- cgit v1.2.1 From a18a941f0a8d06e310fd03787d3c35ce82cdeec5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 20:11:20 +0000 Subject: cosmetic move functions to a better place --- src/rabbit_amqqueue_process.erl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 830ad35b..c822c2e7 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -402,20 +402,6 @@ erase_ch_record(#cr{ch_pid = ChPid, erase({ch, ChPid}), ok. -maybe_send_drained(WasEmpty, State) -> - case WasEmpty andalso is_empty(State) of - true -> [send_drained(C) || C <- all_ch_record()]; - false -> ok - end. - -send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> - case rabbit_limiter:drained(Limiter) of - {[], Limiter} -> ok; - {CTagCredit, Limiter2} -> rabbit_channel:send_drained( - ChPid, CTagCredit), - update_ch_record(C#cr{limiter = Limiter2}) - end. - update_consumer_count(C = #cr{consumer_count = 0, limiter = Limiter}, +1) -> ok = rabbit_limiter:register(Limiter, self()), update_ch_record(C#cr{consumer_count = 1}); @@ -434,6 +420,20 @@ block_consumer(C = #cr{blocked_consumers = Blocked}, QEntry) -> is_ch_blocked(#cr{unsent_message_count = Count, is_limit_active = Limited}) -> Limited orelse Count >= ?UNSENT_MESSAGE_LIMIT. +maybe_send_drained(WasEmpty, State) -> + case WasEmpty andalso is_empty(State) of + true -> [send_drained(C) || C <- all_ch_record()]; + false -> ok + end. + +send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> + case rabbit_limiter:drained(Limiter) of + {[], Limiter} -> ok; + {CTagCredit, Limiter2} -> rabbit_channel:send_drained( + ChPid, CTagCredit), + update_ch_record(C#cr{limiter = Limiter2}) + end. + deliver_msgs_to_consumers(_DeliverFun, true, State) -> {true, State}; deliver_msgs_to_consumers(DeliverFun, false, -- cgit v1.2.1 From 6fdbdcfe1fd05d45ef06c9634726798e2e1d9539 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 20:24:32 +0000 Subject: oops --- src/rabbit_amqqueue_process.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c822c2e7..7a16865b 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -421,7 +421,7 @@ is_ch_blocked(#cr{unsent_message_count = Count, is_limit_active = Limited}) -> Limited orelse Count >= ?UNSENT_MESSAGE_LIMIT. maybe_send_drained(WasEmpty, State) -> - case WasEmpty andalso is_empty(State) of + case (not WasEmpty) andalso is_empty(State) of true -> [send_drained(C) || C <- all_ch_record()]; false -> ok end. -- cgit v1.2.1 From e7cc227a8f508bd8e2845be6e178de46617bef6f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 20:42:27 +0000 Subject: cosmetic - reduce distance to 'default' --- src/rabbit_amqqueue_process.erl | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 7a16865b..e30a9839 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -451,26 +451,23 @@ deliver_msgs_to_consumers(DeliverFun, false, deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> C = ch_record(ChPid), case is_ch_blocked(C) of - true -> - block_consumer(C, E), - {false, State}; - false -> - #cr{limiter = Limiter, ch_pid = ChPid} = C, - #consumer{tag = CTag} = Consumer, - case rabbit_limiter:can_send( - Limiter, self(), Consumer#consumer.ack_required, CTag) of - {consumer_blocked, Limiter2} -> - block_consumer(C#cr{limiter = Limiter2}, E), - {false, State}; - channel_blocked -> - block_consumer(C#cr{is_limit_active = true}, E), - {false, State}; - Limiter2 -> - AC1 = queue:in(E, State#q.active_consumers), - deliver_msg_to_consumer( - DeliverFun, Consumer, C#cr{limiter = Limiter2}, - State#q{active_consumers = AC1}) - end + true -> block_consumer(C, E), + {false, State}; + false -> case rabbit_limiter:can_send(C#cr.limiter, self(), + Consumer#consumer.ack_required, + Consumer#consumer.tag) of + {consumer_blocked, Limiter2} -> + block_consumer(C#cr{limiter = Limiter2}, E), + {false, State}; + channel_blocked -> + block_consumer(C#cr{is_limit_active = true}, E), + {false, State}; + Limiter2 -> + AC1 = queue:in(E, State#q.active_consumers), + deliver_msg_to_consumer( + DeliverFun, Consumer, C#cr{limiter = Limiter2}, + State#q{active_consumers = AC1}) + end end. deliver_msg_to_consumer(DeliverFun, -- cgit v1.2.1 From 9d115a8217adba13989efb9fb20660e6ea39241e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 20:47:45 +0000 Subject: refactor it's convenient for callers to have maybe_send_drained thread through the state --- src/rabbit_amqqueue_process.erl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index e30a9839..6de1d0a4 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -424,7 +424,8 @@ maybe_send_drained(WasEmpty, State) -> case (not WasEmpty) andalso is_empty(State) of true -> [send_drained(C) || C <- all_ch_record()]; false -> ok - end. + end, + State. send_drained(C = #cr{ch_pid = ChPid, limiter = Limiter}) -> case rabbit_limiter:drained(Limiter) of @@ -598,15 +599,13 @@ requeue_and_run(AckTags, State = #q{backing_queue = BQ, WasEmpty = BQ:is_empty(BQS), {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), State1 = drop_expired_msgs(State#q{backing_queue_state = BQS1}), - maybe_send_drained(WasEmpty, State1), - run_message_queue(State1). + run_message_queue(maybe_send_drained(WasEmpty, State1)). fetch(AckRequired, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {Result, BQS1} = BQ:fetch(AckRequired, BQS), State1 = drop_expired_msgs(State#q{backing_queue_state = BQS1}), - maybe_send_drained(Result =:= empty, State1), - {Result, State1}. + {Result, maybe_send_drained(Result =:= empty, State1)}. ack(AckTags, ChPid, State) -> subtract_acks(ChPid, AckTags, State, @@ -1211,8 +1210,7 @@ handle_call(purge, _From, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {Count, BQS1} = BQ:purge(BQS), State1 = State#q{backing_queue_state = BQS1}, - maybe_send_drained(Count =:= 0, State1), - reply({ok, Count}, State1); + reply({ok, Count}, maybe_send_drained(Count =:= 0, State1)); handle_call({requeue, AckTags, ChPid}, From, State) -> gen_server2:reply(From, ok), @@ -1402,8 +1400,7 @@ handle_info(maybe_expire, State) -> handle_info(drop_expired, State) -> WasEmpty = is_empty(State), State1 = drop_expired_msgs(State#q{ttl_timer_ref = undefined}), - maybe_send_drained(WasEmpty, State1), - noreply(State1); + noreply(maybe_send_drained(WasEmpty, State1)); handle_info(emit_stats, State) -> emit_stats(State), -- cgit v1.2.1 From 94dd69341f9da0970111072c44ecff9e140c7926 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 21:29:04 +0000 Subject: optimise possibly_unblock when the channel is blocked there is no point going through the expensive consumer re-partitioning --- src/rabbit_amqqueue_process.erl | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 6de1d0a4..2b11c90d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -639,21 +639,24 @@ possibly_unblock(State, ChPid, Update) -> end. possibly_unblock(State, C = #cr{limiter = Limiter}) -> - IsChBlocked = is_ch_blocked(C), - case lists:partition( - fun({_ChPid, #consumer{tag = CTag}}) -> - IsChBlocked orelse - rabbit_limiter:is_consumer_blocked(Limiter, CTag) - end, queue:to_list(C#cr.blocked_consumers)) of - {_, []} -> - update_ch_record(C), - State; - {Blocked, Unblocked} -> - BlockedQ = queue:from_list(Blocked), - UnblockedQ = queue:from_list(Unblocked), - update_ch_record(C#cr{blocked_consumers = BlockedQ}), - AC1 = queue:join(State#q.active_consumers, UnblockedQ), - run_message_queue(State#q{active_consumers = AC1}) + case is_ch_blocked(C) of + true -> update_ch_record(C), + State; + false -> case lists:partition( + fun({_ChPid, #consumer{tag = CTag}}) -> + rabbit_limiter:is_consumer_blocked( + Limiter, CTag) + end, queue:to_list(C#cr.blocked_consumers)) of + {_, []} -> + update_ch_record(C), + State; + {Blocked, Unblocked} -> + BlockedQ = queue:from_list(Blocked), + UnblockedQ = queue:from_list(Unblocked), + update_ch_record(C#cr{blocked_consumers = BlockedQ}), + AC1 = queue:join(State#q.active_consumers, UnblockedQ), + run_message_queue(State#q{active_consumers = AC1}) + end end. should_auto_delete(#q{q = #amqqueue{auto_delete = false}}) -> false; -- cgit v1.2.1 From 43ec388e97d0effb26b6ef4aef8d1a676acd9c94 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Feb 2013 21:54:45 +0000 Subject: oops --- src/rabbit_amqqueue_process.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 4f0da702..ef48bb5d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -350,7 +350,7 @@ ensure_stats_timer(State) -> assert_invariant(State = #q{active_consumers = AC}) -> true = (queue:is_empty(AC) orelse is_empty(State)). -is_empty({backing_queue = BQ, backing_queue_state = BQS}) -> BQ:is_empty(BQS). +is_empty(#q{backing_queue = BQ, backing_queue_state = BQS}) -> BQ:is_empty(BQS). lookup_ch(ChPid) -> case get({ch, ChPid}) of -- cgit v1.2.1 From 82cb8cff4c2a73b62ca014d21769729b6e14e2a5 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Feb 2013 11:38:53 +0000 Subject: simplify queue's basic_consume handler - the call to update_ch_record in the is_ch_blocked(C1) == false branch was superfluos since the preceding update_consumer_count calls update_ch_record - all the checking whether the channel is blocked, and associated branching was just an optimisation. And not a particularly important one, since a) the "a new consumer comes along while its channel is blocked" case is hardly on the critical path, and b) exactly the same check is performed as part of run_message_queue (in deliver_msg_to_consumer/3). So get rid of it. - the is_empty & send_drained logic can be invoked earlier, which allows us to use the #cr we have rather than looking it up again. We can do this since the only case we need to catch here is that of a consumer coming along while the queue is empty already. If it becomes empty as part of run_message_queue then send_drained will be invoked in 'fetch'. --- src/rabbit_amqqueue_process.erl | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index a43dbdcc..c02fd6b5 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1142,6 +1142,10 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, Limiter, ConsumerTag, Credit, Drain) end, C1 = update_consumer_count(C#cr{limiter = Limiter2}, +1), + case is_empty(State) of + true -> send_drained(C1); + false -> ok + end, Consumer = #consumer{tag = ConsumerTag, ack_required = not NoAck}, ExclusiveConsumer = if ExclusiveConsume -> {ChPid, ConsumerTag}; @@ -1150,22 +1154,10 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, State1 = State#q{has_had_consumers = true, exclusive_consumer = ExclusiveConsumer}, ok = maybe_send_reply(ChPid, OkMsg), - E = {ChPid, Consumer}, - State2 = - case is_ch_blocked(C1) of - true -> block_consumer(C1, E), - State1; - false -> update_ch_record(C1), - AC1 = queue:in(E, State1#q.active_consumers), - run_message_queue(State1#q{active_consumers = AC1}) - end, - case is_empty(State2) of - true -> send_drained(lookup_ch(ChPid)); - false -> ok - end, emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, - not NoAck, qname(State2)), - reply(ok, State2) + not NoAck, qname(State1)), + AC1 = queue:in({ChPid, Consumer}, State1#q.active_consumers), + reply(ok, run_message_queue(State1#q{active_consumers = AC1})) end; handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, -- cgit v1.2.1 From 97757bbfc78bac5f02b5d7d9b1a4630cb41852f7 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Feb 2013 11:50:19 +0000 Subject: Remove blocked_ctags, and a few knock-on simplifications. --- src/rabbit_amqqueue_process.erl | 6 ++--- src/rabbit_limiter.erl | 57 +++++++++++++++-------------------------- 2 files changed, 24 insertions(+), 39 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c02fd6b5..79a98208 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -457,8 +457,8 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> false -> case rabbit_limiter:can_send(C#cr.limiter, self(), Consumer#consumer.ack_required, Consumer#consumer.tag) of - {consumer_blocked, Limiter2} -> - block_consumer(C#cr{limiter = Limiter2}, E), + consumer_blocked -> + block_consumer(C, E), {false, State}; channel_blocked -> block_consumer(C#cr{is_limit_active = true}, E), @@ -1138,7 +1138,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, none -> Limiter; {Credit, Drain} -> - rabbit_limiter:initial_credit( + rabbit_limiter:credit( Limiter, ConsumerTag, Credit, Drain) end, C1 = update_consumer_count(C#cr{limiter = Limiter2}, +1), diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index e76fc217..ece3d1a4 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -24,14 +24,13 @@ disable/1]). -export([limit/2, can_send/4, ack/2, register/2, unregister/2]). -export([get_limit/1, block/1, unblock/1, is_consumer_blocked/2, is_blocked/1]). --export([initial_credit/4, credit/4, drained/1, forget_consumer/2, - copy_queue_state/2]). +-export([credit/4, drained/1, forget_consumer/2, copy_queue_state/2]). -import(rabbit_misc, [serial_add/2, serial_diff/2]). %%---------------------------------------------------------------------------- --record(token, {pid, enabled, credits, blocked_ctags}). +-record(token, {pid, enabled, credits}). -ifdef(use_specs). @@ -56,8 +55,6 @@ -spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). -spec(is_blocked/1 :: (token()) -> boolean()). -spec(is_consumer_blocked/2 :: (token(), rabbit_types:ctag()) -> boolean()). --spec(initial_credit/4 :: (token(), rabbit_types:ctag(), - non_neg_integer(), boolean()) -> token()). -spec(credit/4 :: (token(), rabbit_types:ctag(), non_neg_integer(), boolean()) -> token()). -spec(drained/1 :: (token()) @@ -87,10 +84,9 @@ start_link() -> gen_server2:start_link(?MODULE, [], []). make_token() -> make_token(undefined). -make_token(Pid) -> #token{pid = Pid, - enabled = false, - credits = dict:new(), - blocked_ctags = []}. +make_token(Pid) -> #token{pid = Pid, + enabled = false, + credits = dict:new()}. is_enabled(#token{enabled = Enabled}) -> Enabled. @@ -107,21 +103,15 @@ limit(Limiter, PrefetchCount) -> %% breaching a limit. Note that we don't use maybe_call here in order %% to avoid always going through with_exit_handler/2, even when the %% limiter is disabled. -can_send(Token = #token{pid = Pid, enabled = Enabled, credits = Credits, - blocked_ctags = BCTags}, +can_send(Token = #token{pid = Pid, enabled = Enabled, credits = Credits}, QPid, AckReq, CTag) -> - ConsAllows = case dict:find(CTag, Credits) of - {ok, #credit{credit = C}} when C > 0 -> true; - {ok, #credit{}} -> false; - error -> true - end, - case ConsAllows of - true -> case not Enabled orelse call_can_send(Pid, QPid, AckReq) of + case is_consumer_blocked(Token, CTag) of + false -> case not Enabled orelse call_can_send(Pid, QPid, AckReq) of true -> Credits2 = record_send_q(CTag, Credits), Token#token{credits = Credits2}; false -> channel_blocked end; - false -> {consumer_blocked, Token#token{blocked_ctags = [CTag|BCTags]}} + true -> consumer_blocked end. call_can_send(Pid, QPid, AckRequired) -> @@ -150,21 +140,18 @@ block(Limiter) -> unblock(Limiter) -> maybe_call(Limiter, {unblock, Limiter}, ok). -is_consumer_blocked(#token{blocked_ctags = BCTags}, CTag) -> - lists:member(CTag, BCTags). +is_consumer_blocked(#token{credits = Credits}, CTag) -> + case dict:find(CTag, Credits) of + {ok, #credit{credit = C}} when C > 0 -> false; + {ok, #credit{}} -> true; + error -> false + end. is_blocked(Limiter) -> maybe_call(Limiter, is_blocked, false). -initial_credit(Limiter = #token{credits = Credits}, CTag, Credit, Drain) -> - {[], Credits2} = update_credit(CTag, Credit, Drain, Credits), - Limiter#token{credits = Credits2}. - -credit(Limiter = #token{credits = Credits, blocked_ctags = BCTags}, - CTag, Credit, Drain) -> - {Unblock, Credits2} = update_credit(CTag, Credit, Drain, Credits), - Limiter#token{credits = Credits2, - blocked_ctags = BCTags -- Unblock}. +credit(Limiter = #token{credits = Credits}, CTag, Credit, Drain) -> + Limiter#token{credits = update_credit(CTag, Credit, Drain, Credits)}. drained(Limiter = #token{credits = Credits}) -> {CTagCredits, Credits2} = @@ -176,13 +163,11 @@ drained(Limiter = #token{credits = Credits}) -> end, {[], Credits}, Credits), {CTagCredits, Limiter#token{credits = Credits2}}. -forget_consumer(Limiter = #token{credits = Credits, - blocked_ctags = BCTags}, CTag) -> - Limiter#token{credits = dict:erase(CTag, Credits), - blocked_ctags = BCTags -- [CTag]}. +forget_consumer(Limiter = #token{credits = Credits}, CTag) -> + Limiter#token{credits = dict:erase(CTag, Credits)}. -copy_queue_state(#token{credits = Credits, blocked_ctags = BCTags}, Token) -> - Token#token{credits = Credits, blocked_ctags = BCTags}. +copy_queue_state(#token{credits = Credits}, Token) -> + Token#token{credits = Credits}. %%---------------------------------------------------------------------------- %% Queue-local code -- cgit v1.2.1 From ef744142e180f95adbeac63c7ef6628ec196793f Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Feb 2013 12:00:16 +0000 Subject: Use gb_trees rather than dict for performance. --- src/rabbit_limiter.erl | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index ece3d1a4..801b748e 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -86,7 +86,7 @@ start_link() -> gen_server2:start_link(?MODULE, [], []). make_token() -> make_token(undefined). make_token(Pid) -> #token{pid = Pid, enabled = false, - credits = dict:new()}. + credits = gb_trees:empty()}. is_enabled(#token{enabled = Enabled}) -> Enabled. @@ -141,10 +141,10 @@ unblock(Limiter) -> maybe_call(Limiter, {unblock, Limiter}, ok). is_consumer_blocked(#token{credits = Credits}, CTag) -> - case dict:find(CTag, Credits) of - {ok, #credit{credit = C}} when C > 0 -> false; - {ok, #credit{}} -> true; - error -> false + case gb_trees:lookup(CTag, Credits) of + {value, #credit{credit = C}} when C > 0 -> false; + {value, #credit{}} -> true; + none -> false end. is_blocked(Limiter) -> @@ -155,16 +155,16 @@ credit(Limiter = #token{credits = Credits}, CTag, Credit, Drain) -> drained(Limiter = #token{credits = Credits}) -> {CTagCredits, Credits2} = - dict:fold( - fun (CTag, #credit{credit = C, drain = true}, {Acc, Creds0}) -> + lists:foldl( + fun ({CTag, #credit{credit = C, drain = true}}, {Acc, Creds0}) -> {[{CTag, C} | Acc], write_credit(CTag, 0, false, Creds0)}; - (_CTag, #credit{credit = _C, drain = false}, {Acc, Creds0}) -> + ({_CTag, #credit{credit = _C, drain = false}}, {Acc, Creds0}) -> {Acc, Creds0} - end, {[], Credits}, Credits), + end, {[], Credits}, gb_trees:to_list(Credits)), {CTagCredits, Limiter#token{credits = Credits2}}. forget_consumer(Limiter = #token{credits = Credits}, CTag) -> - Limiter#token{credits = dict:erase(CTag, Credits)}. + Limiter#token{credits = gb_trees:delete_any(CTag, Credits)}. copy_queue_state(#token{credits = Credits}, Token) -> Token#token{credits = Credits}. @@ -180,10 +180,10 @@ copy_queue_state(#token{credits = Credits}, Token) -> %% maintain a fiction that the limiter is making the decisions... record_send_q(CTag, Credits) -> - case dict:find(CTag, Credits) of - {ok, #credit{credit = Credit, drain = Drain}} -> + case gb_trees:lookup(CTag, Credits) of + {value, #credit{credit = Credit, drain = Drain}} -> write_credit(CTag, Credit, Drain, Credits); - error -> + none -> Credits end. @@ -195,10 +195,16 @@ update_credit(CTag, Credit, Drain, Credits) -> end. write_credit(CTag, Credit, Drain, Credits) when Credit > 0 -> - dict:store(CTag, #credit{credit = Credit, drain = Drain}, Credits); + write_credit0(CTag, #credit{credit = Credit, drain = Drain}, Credits); %% Using up all credit means we do not need to send a drained event write_credit(CTag, Credit, _Drain, Credits) -> - dict:store(CTag, #credit{credit = Credit, drain = false}, Credits). + write_credit0(CTag, #credit{credit = Credit, drain = false}, Credits). + +write_credit0(CTag, Credit, Credits) -> + case gb_trees:is_defined(CTag, Credits) of + true -> gb_trees:update(CTag, Credit, Credits); + false -> gb_trees:insert(CTag, Credit, Credits) + end. %%---------------------------------------------------------------------------- %% gen_server callbacks -- cgit v1.2.1 From 905806e093153f0245d051b9a66927fcd9810715 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Feb 2013 12:10:31 +0000 Subject: Oops --- src/rabbit_limiter.erl | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 801b748e..dbad59f2 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -157,7 +157,7 @@ drained(Limiter = #token{credits = Credits}) -> {CTagCredits, Credits2} = lists:foldl( fun ({CTag, #credit{credit = C, drain = true}}, {Acc, Creds0}) -> - {[{CTag, C} | Acc], write_credit(CTag, 0, false, Creds0)}; + {[{CTag, C} | Acc], update_credit(CTag, 0, false, Creds0)}; ({_CTag, #credit{credit = _C, drain = false}}, {Acc, Creds0}) -> {Acc, Creds0} end, {[], Credits}, gb_trees:to_list(Credits)), @@ -182,25 +182,18 @@ copy_queue_state(#token{credits = Credits}, Token) -> record_send_q(CTag, Credits) -> case gb_trees:lookup(CTag, Credits) of {value, #credit{credit = Credit, drain = Drain}} -> - write_credit(CTag, Credit, Drain, Credits); + update_credit(CTag, Credit, Drain, Credits); none -> Credits end. -update_credit(CTag, Credit, Drain, Credits) -> - NewCredits = write_credit(CTag, Credit, Drain, Credits), - case Credit > 0 of - true -> {[CTag], NewCredits}; - false -> {[], NewCredits} - end. - -write_credit(CTag, Credit, Drain, Credits) when Credit > 0 -> - write_credit0(CTag, #credit{credit = Credit, drain = Drain}, Credits); +update_credit(CTag, Credit, Drain, Credits) when Credit > 0 -> + update_credit0(CTag, #credit{credit = Credit, drain = Drain}, Credits); %% Using up all credit means we do not need to send a drained event -write_credit(CTag, Credit, _Drain, Credits) -> - write_credit0(CTag, #credit{credit = Credit, drain = false}, Credits). +update_credit(CTag, Credit, _Drain, Credits) -> + update_credit0(CTag, #credit{credit = Credit, drain = false}, Credits). -write_credit0(CTag, Credit, Credits) -> +update_credit0(CTag, Credit, Credits) -> case gb_trees:is_defined(CTag, Credits) of true -> gb_trees:update(CTag, Credit, Credits); false -> gb_trees:insert(CTag, Credit, Credits) -- cgit v1.2.1 From f1fd5e8e2e0b210968b90f44c2935e2ffe3c7b89 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Feb 2013 12:12:51 +0000 Subject: Correct use of gb_trees APIs... --- src/rabbit_limiter.erl | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index dbad59f2..e16e5dc7 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -155,12 +155,12 @@ credit(Limiter = #token{credits = Credits}, CTag, Credit, Drain) -> drained(Limiter = #token{credits = Credits}) -> {CTagCredits, Credits2} = - lists:foldl( - fun ({CTag, #credit{credit = C, drain = true}}, {Acc, Creds0}) -> + rabbit_misc:gb_trees_fold( + fun (CTag, #credit{credit = C, drain = true}, {Acc, Creds0}) -> {[{CTag, C} | Acc], update_credit(CTag, 0, false, Creds0)}; - ({_CTag, #credit{credit = _C, drain = false}}, {Acc, Creds0}) -> + (_CTag, #credit{credit = _C, drain = false}, {Acc, Creds0}) -> {Acc, Creds0} - end, {[], Credits}, gb_trees:to_list(Credits)), + end, {[], Credits}, Credits), {CTagCredits, Limiter#token{credits = Credits2}}. forget_consumer(Limiter = #token{credits = Credits}, CTag) -> @@ -188,16 +188,10 @@ record_send_q(CTag, Credits) -> end. update_credit(CTag, Credit, Drain, Credits) when Credit > 0 -> - update_credit0(CTag, #credit{credit = Credit, drain = Drain}, Credits); + gb_trees:enter(CTag, #credit{credit = Credit, drain = Drain}, Credits); %% Using up all credit means we do not need to send a drained event update_credit(CTag, Credit, _Drain, Credits) -> - update_credit0(CTag, #credit{credit = Credit, drain = false}, Credits). - -update_credit0(CTag, Credit, Credits) -> - case gb_trees:is_defined(CTag, Credits) of - true -> gb_trees:update(CTag, Credit, Credits); - false -> gb_trees:insert(CTag, Credit, Credits) - end. + gb_trees:enter(CTag, #credit{credit = Credit, drain = false}, Credits). %%---------------------------------------------------------------------------- %% gen_server callbacks -- cgit v1.2.1 From 0ddcdb98560e45b65df0e9d5a5d0b4d3f5c1de29 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Feb 2013 12:21:26 +0000 Subject: simplifying refactor --- src/rabbit_limiter.erl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index e16e5dc7..fe46b876 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -107,8 +107,8 @@ can_send(Token = #token{pid = Pid, enabled = Enabled, credits = Credits}, QPid, AckReq, CTag) -> case is_consumer_blocked(Token, CTag) of false -> case not Enabled orelse call_can_send(Pid, QPid, AckReq) of - true -> Credits2 = record_send_q(CTag, Credits), - Token#token{credits = Credits2}; + true -> Token#token{ + credits = record_send_q(CTag, Credits)}; false -> channel_blocked end; true -> consumer_blocked @@ -173,11 +173,12 @@ copy_queue_state(#token{credits = Credits}, Token) -> %% Queue-local code %%---------------------------------------------------------------------------- -%% We want to do all the AMQP 1.0-ish link level credit calculations in the -%% queue (to do them elsewhere introduces a ton of races). However, it's a big -%% chunk of code that is conceptually very linked to the limiter concept. So -%% we get the queue to hold a bit of state for us (#token.credits), and -%% maintain a fiction that the limiter is making the decisions... +%% We want to do all the AMQP 1.0-ish link level credit calculations +%% in the queue (to do them elsewhere introduces a ton of +%% races). However, it's a big chunk of code that is conceptually very +%% linked to the limiter concept. So we get the queue to hold a bit of +%% state for us (#token.credits), and maintain a fiction that the +%% limiter is making the decisions... record_send_q(CTag, Credits) -> case gb_trees:lookup(CTag, Credits) of @@ -187,11 +188,10 @@ record_send_q(CTag, Credits) -> Credits end. -update_credit(CTag, Credit, Drain, Credits) when Credit > 0 -> - gb_trees:enter(CTag, #credit{credit = Credit, drain = Drain}, Credits); -%% Using up all credit means we do not need to send a drained event -update_credit(CTag, Credit, _Drain, Credits) -> - gb_trees:enter(CTag, #credit{credit = Credit, drain = false}, Credits). +update_credit(CTag, Credit, Drain, Credits) -> + %% Using up all credit implies no need to send a 'drained' event + Drain1 = Drain andalso Credit > 0, + gb_trees:enter(CTag, #credit{credit = Credit, drain = Drain1}, Credits). %%---------------------------------------------------------------------------- %% gen_server callbacks -- cgit v1.2.1 From 55a99c8b531251b90341667bc7934bf24fa6c39e Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Feb 2013 12:27:31 +0000 Subject: Well, that was embarassing. --- 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 fe46b876..1e806cd3 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -183,7 +183,7 @@ copy_queue_state(#token{credits = Credits}, Token) -> record_send_q(CTag, Credits) -> case gb_trees:lookup(CTag, Credits) of {value, #credit{credit = Credit, drain = Drain}} -> - update_credit(CTag, Credit, Drain, Credits); + update_credit(CTag, Credit - 1, Drain, Credits); none -> Credits end. -- cgit v1.2.1 From d1562e9de47255303213793205f648c64aa542d1 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Feb 2013 12:43:50 +0000 Subject: simplify queue's basic_consume handler backported from bug23749 branch - the call to update_ch_record in the is_ch_blocked(C1) == false branch was superfluos since the preceding update_consumer_count calls update_ch_record - all the checking whether the channel is blocked, and associated branching was just an optimisation. And not a particularly important one, since a) the "a new consumer comes along while its channel is blocked" case is hardly on the critical path, and b) exactly the same check is performed as part of run_message_queue (in deliver_msg_to_consumer/3). So get rid of it. --- src/rabbit_amqqueue_process.erl | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index ef48bb5d..fba58d38 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1097,7 +1097,7 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, reply({error, exclusive_consume_unavailable}, State); ok -> C = ch_record(ChPid), - C1 = update_consumer_count(C#cr{limiter = Limiter}, +1), + update_consumer_count(C#cr{limiter = Limiter}, +1), Consumer = #consumer{tag = ConsumerTag, ack_required = not NoAck}, ExclusiveConsumer = if ExclusiveConsume -> {ChPid, ConsumerTag}; @@ -1106,18 +1106,10 @@ handle_call({basic_consume, NoAck, ChPid, Limiter, State1 = State#q{has_had_consumers = true, exclusive_consumer = ExclusiveConsumer}, ok = maybe_send_reply(ChPid, OkMsg), - E = {ChPid, Consumer}, - State2 = - case is_ch_blocked(C1) of - true -> block_consumer(C1, E), - State1; - false -> update_ch_record(C1), - AC1 = queue:in(E, State1#q.active_consumers), - run_message_queue(State1#q{active_consumers = AC1}) - end, emit_consumer_created(ChPid, ConsumerTag, ExclusiveConsume, - not NoAck, qname(State2)), - reply(ok, State2) + not NoAck, qname(State1)), + AC1 = queue:in({ChPid, Consumer}, State1#q.active_consumers), + reply(ok, run_message_queue(State1#q{active_consumers = AC1})) end; handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, -- cgit v1.2.1 From 9a1acf8cb9c863c5f5e46309abec07009224252d Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 21 Feb 2013 18:05:35 +0000 Subject: Enforce queue limit with requeue --- src/rabbit_amqqueue_process.erl | 66 +++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 72d6ab43..08d68e4c 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -560,46 +560,51 @@ deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, %% The next one is an optimisation {false, State2 = #q{ttl = 0, dlx = undefined}} -> discard(Delivery, State2); - {false, State2} -> - State3 = #q{backing_queue = BQ, backing_queue_state = BQS} = - maybe_drop_head(State2), - IsEmpty = BQ:is_empty(BQS), + {false, State2 = #q{backing_queue = BQ, backing_queue_state = BQS}} -> BQS1 = BQ:publish(Message, Props, Delivered, SenderPid, BQS), - State4 = State3#q{backing_queue_state = BQS1}, + {Dropped, State3} = + maybe_drop_head(State2#q{backing_queue_state = BQS1}), + QLen = BQ:len(BQS1), %% optimisation: it would be perfectly safe to always %% invoke drop_expired_msgs here, but that is expensive so - %% we only do that IFF the new message ends up at the head - %% of the queue (because the queue was empty) and has an - %% expiry. Only then may it need expiring straight away, - %% or, if expiry is not due yet, the expiry timer may need - %% (re)scheduling. - case {IsEmpty, Props#message_properties.expiry} of - {false, _} -> State4; - {true, undefined} -> State4; - {true, _} -> drop_expired_msgs(State4) + %% we only do that if a new message that might have an + %% expiry ends up at the head of the queue. If the head + %% remains unchanged, or if the newly published message + %% has no expiry and becomes the head of the queue then + %% the call is unnecessary. + case {Dropped > 0, QLen =:= 1, Props#message_properties.expiry} of + {false, false, _} -> State3; + {true, true, undefined} -> State3; + {_, _, _} -> drop_expired_msgs(State3) end end. maybe_drop_head(State = #q{max_length = undefined}) -> - State; + {0, State}; maybe_drop_head(State = #q{max_length = MaxLen, backing_queue = BQ, backing_queue_state = BQS}) -> - case BQ:len(BQS) >= MaxLen of - true -> with_dlx( - State#q.dlx, - fun (X) -> dead_letter_maxlen_msgs(X, State) end, - fun () -> - {_, BQS1} = BQ:drop(false, BQS), - State#q{backing_queue_state = BQS1} - end); - false -> State + case BQ:len(BQS) - MaxLen of + Excess when Excess > 0 -> + {Excess, + with_dlx( + State#q.dlx, + fun (X) -> dead_letter_maxlen_msgs(X, Excess, State) end, + fun () -> + {_, BQS1} = lists:foldl(fun (_, {_, BQS0}) -> + BQ:drop(false, BQS0) + end, {ok, BQS}, + lists:seq(1, Excess)), + State#q{backing_queue_state = BQS1} + end)}; + _ -> {0, State} end. requeue_and_run(AckTags, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> {_MsgIds, BQS1} = BQ:requeue(AckTags, BQS), - run_message_queue(drop_expired_msgs(State#q{backing_queue_state = BQS1})). + {_Dropped, State1} = maybe_drop_head(State#q{backing_queue_state = BQS1}), + run_message_queue(drop_expired_msgs(State1)). fetch(AckRequired, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> @@ -783,12 +788,15 @@ dead_letter_rejected_msgs(AckTags, X, State = #q{backing_queue = BQ}) -> end, rejected, X, State), State1. -dead_letter_maxlen_msgs(X, State = #q{backing_queue = BQ}) -> +dead_letter_maxlen_msgs(X, Excess, State = #q{backing_queue = BQ}) -> {ok, State1} = dead_letter_msgs( - fun (DLFun, Acc, BQS1) -> - {{Msg, _, AckTag}, BQS2} = BQ:fetch(true, BQS1), - {ok, DLFun(Msg, AckTag, Acc), BQS2} + fun (DLFun, Acc, BQS) -> + lists:foldl(fun (_, {ok, Acc0, BQS0}) -> + {{Msg, _, AckTag}, BQS1} = + BQ:fetch(true, BQS0), + {ok, DLFun(Msg, AckTag, Acc0), BQS1} + end, {ok, Acc, BQS}, lists:seq(1, Excess)) end, maxlen, X, State), State1. -- cgit v1.2.1 From 2f6dee1b58d41bef547ae7292bada83d2feab6ec Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 25 Feb 2013 17:25:45 +0000 Subject: Keep name around for logging / info item. --- src/rabbit_reader.erl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index b8ff9c9f..54fd3c51 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -783,7 +783,7 @@ handle_method0(#'connection.start_ok'{mechanism = Mechanism, Connection#connection{ client_properties = ClientProperties, capabilities = Capabilities, - auth_mechanism = AuthMechanism, + auth_mechanism = {Mechanism, AuthMechanism}, auth_state = AuthMechanism:init(Sock)}}, auth_phase(Response, State); @@ -911,15 +911,14 @@ auth_mechanisms_binary(Sock) -> auth_phase(Response, State = #v1{connection = Connection = #connection{protocol = Protocol, - auth_mechanism = AuthMechanism, + auth_mechanism = {Name, AuthMechanism}, auth_state = AuthState}, sock = Sock}) -> case AuthMechanism:handle_response(Response, AuthState) of {refused, Msg, Args} -> rabbit_misc:protocol_error( access_refused, "~s login refused: ~s", - [proplists:get_value(name, AuthMechanism:description()), - io_lib:format(Msg, Args)]); + [Name, io_lib:format(Msg, Args)]); {protocol_error, Msg, Args} -> rabbit_misc:protocol_error(syntax_error, Msg, Args); {challenge, Challenge, AuthState1} -> @@ -979,10 +978,8 @@ ic(vhost, #connection{vhost = VHost}) -> VHost; ic(timeout, #connection{timeout_sec = Timeout}) -> Timeout; ic(frame_max, #connection{frame_max = FrameMax}) -> FrameMax; ic(client_properties, #connection{client_properties = CP}) -> CP; -ic(auth_mechanism, #connection{auth_mechanism = none}) -> - none; -ic(auth_mechanism, #connection{auth_mechanism = Mechanism}) -> - proplists:get_value(name, Mechanism:description()); +ic(auth_mechanism, #connection{auth_mechanism = none}) -> none; +ic(auth_mechanism, #connection{auth_mechanism = {Name, _Mod}}) -> Name; ic(Item, #connection{}) -> throw({bad_argument, Item}). socket_info(Get, Select, #v1{sock = Sock}) -> -- cgit v1.2.1 From 7c99d0f8b3d5ca07e19ab5736b26b5b8892e151e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 26 Feb 2013 15:31:01 +0000 Subject: move socket buffer tuning into tcp_acceptor --- src/rabbit_net.erl | 10 +--------- src/rabbit_reader.erl | 1 - src/tcp_acceptor.erl | 22 ++++++++++++++++++++-- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/rabbit_net.erl b/src/rabbit_net.erl index b8b03f56..b53c16bf 100644 --- a/src/rabbit_net.erl +++ b/src/rabbit_net.erl @@ -20,7 +20,7 @@ -export([is_ssl/1, ssl_info/1, controlling_process/2, getstat/2, recv/1, async_recv/3, port_command/2, getopts/2, setopts/2, send/2, close/1, fast_close/1, sockname/1, peername/1, peercert/1, - tune_buffer_size/1, connection_string/2, socket_ends/2]). + connection_string/2, socket_ends/2]). %%--------------------------------------------------------------------------- @@ -69,7 +69,6 @@ -spec(peercert/1 :: (socket()) -> 'nossl' | ok_val_or_error(rabbit_ssl:certificate())). --spec(tune_buffer_size/1 :: (socket()) -> ok_or_any_error()). -spec(connection_string/2 :: (socket(), 'inbound' | 'outbound') -> ok_val_or_error(string())). -spec(socket_ends/2 :: @@ -189,13 +188,6 @@ peername(Sock) when is_port(Sock) -> inet:peername(Sock). peercert(Sock) when ?IS_SSL(Sock) -> ssl:peercert(Sock#ssl_socket.ssl); peercert(Sock) when is_port(Sock) -> nossl. -tune_buffer_size(Sock) -> - case getopts(Sock, [sndbuf, recbuf, buffer]) of - {ok, BufSizes} -> BufSz = lists:max([Sz || {_Opt, Sz} <- BufSizes]), - setopts(Sock, [{buffer, BufSz}]); - Err -> Err - end. - connection_string(Sock, Direction) -> case socket_ends(Sock, Direction) of {ok, {FromAddress, FromPort, ToAddress, ToPort}} -> diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl index 2bdef92d..6294451b 100644 --- a/src/rabbit_reader.erl +++ b/src/rabbit_reader.erl @@ -250,7 +250,6 @@ start_connection(Parent, ConnSupPid, Collector, StartHeartbeatFun, Deb, last_blocked_by = none, last_blocked_at = never}}, try - ok = inet_op(fun () -> rabbit_net:tune_buffer_size(ClientSock) end), run({?MODULE, recvloop, [Deb, switch_callback(rabbit_event:init_stats_timer( State, #v1.stats_timer), diff --git a/src/tcp_acceptor.erl b/src/tcp_acceptor.erl index c76681c2..2725be31 100644 --- a/src/tcp_acceptor.erl +++ b/src/tcp_acceptor.erl @@ -55,8 +55,19 @@ handle_info({inet_async, LSock, Ref, {ok, Sock}}, inet_db:register_socket(Sock, Mod), %% handle - file_handle_cache:transfer(apply(M, F, A ++ [Sock])), - ok = file_handle_cache:obtain(), + case tune_buffer_size(Sock) of + ok -> file_handle_cache:transfer( + apply(M, F, A ++ [Sock])), + ok = file_handle_cache:obtain(); + {error, enotconn} -> catch port_close(Sock); + {error, Err} -> {ok, {IPAddress, Port}} = inet:sockname(LSock), + error_logger:error_msg( + "failed to tune buffer size of " + "connection accepted on ~s:~p - ~p (~s)~n", + [rabbit_misc:ntoab(IPAddress), Port, + Err, rabbit_misc:format_inet_error(Err)]), + catch port_close(Sock) + end, %% accept more accept(State); @@ -85,3 +96,10 @@ accept(State = #state{sock=LSock}) -> {ok, Ref} -> {noreply, State#state{ref=Ref}}; Error -> {stop, {cannot_accept, Error}, State} end. + +tune_buffer_size(Sock) -> + case inet:getopts(Sock, [sndbuf, recbuf, buffer]) of + {ok, BufSizes} -> BufSz = lists:max([Sz || {_Opt, Sz} <- BufSizes]), + inet:setopts(Sock, [{buffer, BufSz}]); + Error -> Error + end. -- cgit v1.2.1 From 4ebf0a93d4d8188b26da5d148b33d72e446aba96 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Tue, 26 Feb 2013 17:58:01 +0000 Subject: specifies boot file for rabbit_prelaunch call --- scripts/rabbitmq-server | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/rabbitmq-server b/scripts/rabbitmq-server index 3253bd7b..4af38eeb 100755 --- a/scripts/rabbitmq-server +++ b/scripts/rabbitmq-server @@ -83,6 +83,7 @@ esac RABBITMQ_EBIN_ROOT="${RABBITMQ_HOME}/ebin" if ! ${ERL_DIR}erl -pa "$RABBITMQ_EBIN_ROOT" \ + -boot "${CLEAN_BOOT_FILE}" \ -noinput \ -hidden \ -s rabbit_prelaunch \ -- cgit v1.2.1 From 6a8a3b788a351bcc8104acbbd7a2a2215c676126 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 27 Feb 2013 11:37:07 +0000 Subject: fixes Erlang FINAL_ROOTDIR --- packaging/standalone/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 1e548789..39ffadab 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -53,6 +53,10 @@ dist: # remove empty lib/rabbit-$(VERSION) folder rm -rf $(RLS_DIR)/lib/rabbit-$(VERSION) +# fix Erlang ROOTDIR + sed -e 's:%FINAL_ROOTDIR%:\$$(dirname \$$0)/../..:' $(RLS_DIR)/erts-5.9.3/bin/erl.src \ + > $(RLS_DIR)/erts-5.9.3/bin/erl + tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) rm -rf $(SOURCE_DIR) $(TARGET_DIR) -- cgit v1.2.1 From d15d2415fbbf4cc5dd518fb9d972b1e2e0d47562 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 27 Feb 2013 14:18:25 +0000 Subject: adds realpath to erl mac script --- packaging/standalone/Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 39ffadab..77d2bd9b 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -54,8 +54,12 @@ dist: rm -rf $(RLS_DIR)/lib/rabbit-$(VERSION) # fix Erlang ROOTDIR - sed -e 's:%FINAL_ROOTDIR%:\$$(dirname \$$0)/../..:' $(RLS_DIR)/erts-5.9.3/bin/erl.src \ - > $(RLS_DIR)/erts-5.9.3/bin/erl + sed -e 's:%FINAL_ROOTDIR%:\$$(dirname `realpath \$$0`)/../..:' $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.src \ + > $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp + sed -e '/^ROOTDIR.*/i \ + realpath() { [[ \$$1 = /* ]] && echo "\$$1" || echo "\$$PWD/\$${1#./}" ; }' \ + $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp > $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl + rm $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) rm -rf $(SOURCE_DIR) $(TARGET_DIR) -- cgit v1.2.1 From 2e9879c6cde8d540cd8cd3e56223f0a3bbe22556 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 27 Feb 2013 14:20:07 +0000 Subject: When we lose majority, stop the applications and wait for the cluster to come back. --- src/rabbit_node_monitor.erl | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 7b7fed5c..42df6e5d 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -283,21 +283,34 @@ handle_dead_according_to_mnesia_rabbit() -> case application:get_env(rabbit, cluster_cp_mode) of {ok, true} -> case rabbit_mnesia:majority() of true -> ok; - false -> stop_and_halt() + false -> await_cluster_recovery() end; {ok, false} -> ok end, ok. -stop_and_halt() -> - rabbit_log:warning("Cluster minority status detected - stopping~n", []), +await_cluster_recovery() -> + rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", + []), + Nodes = rabbit_mnesia:cluster_nodes(all), spawn(fun () -> %% If our group leader is inside an application we are about %% to stop, application:stop/1 does not return. group_leader(whereis(init), self()), - rabbit:stop_and_halt() + rabbit:stop(), + wait_for_cluster_recovery(Nodes) end). +wait_for_cluster_recovery(Nodes) -> + [erlang:disconnect_node(Node) || Node <- Nodes], + mnesia:start(), + case rabbit_mnesia:majority() of + true -> rabbit:start(); + false -> mnesia:stop(), + timer:sleep(1000), + wait_for_cluster_recovery(Nodes) + end. + handle_live_rabbit(Node) -> ok = rabbit_alarm:on_node_up(Node), ok = rabbit_mnesia:on_node_up(Node). -- cgit v1.2.1 From fad1b961ea4d53670381caa4b701add21ee406b4 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 27 Feb 2013 14:25:12 +0000 Subject: Filter out all those events that look like: =INFO REPORT==== 27-Feb-2013::14:17:46 === application: mnesia exited: stopped type: temporary since they are not very interesting and this bug makes them appear to a highly verbose extent. --- src/rabbit_error_logger_file_h.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rabbit_error_logger_file_h.erl b/src/rabbit_error_logger_file_h.erl index 3efc9c0c..c00c1df9 100644 --- a/src/rabbit_error_logger_file_h.erl +++ b/src/rabbit_error_logger_file_h.erl @@ -76,6 +76,8 @@ init_file(File, PrevHandler) -> Error -> Error end. +handle_event({info_report, _, {_, std_info, _}}, State) -> + ok; %% filter out "application: foo; exited: stopped; type: temporary" handle_event(Event, State) -> error_logger_file_h:handle_event(Event, State). -- cgit v1.2.1 From 2ecc9ff3c42959e4ac267b07a4e7dee962e710ad Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 27 Feb 2013 14:27:24 +0000 Subject: reorders sed calls --- packaging/standalone/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 77d2bd9b..91100628 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -54,11 +54,11 @@ dist: rm -rf $(RLS_DIR)/lib/rabbit-$(VERSION) # fix Erlang ROOTDIR - sed -e 's:%FINAL_ROOTDIR%:\$$(dirname `realpath \$$0`)/../..:' $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.src \ - > $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp sed -e '/^ROOTDIR.*/i \ realpath() { [[ \$$1 = /* ]] && echo "\$$1" || echo "\$$PWD/\$${1#./}" ; }' \ - $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp > $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl + $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.src > $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp + sed -e 's:%FINAL_ROOTDIR%:\$$(dirname `realpath \$$0`)/../..:' $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp \ + > $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl rm $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) -- cgit v1.2.1 From 9142ce00fec31cb22cb0351a4b4a257cdffbdfc9 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 27 Feb 2013 14:43:44 +0000 Subject: Base the whole thing off net_adm:ping/1 - because we might see other nodes come back but also be waiting (in the no-majority case, and RAM nodes). Better to detect they exist and come back than to stay stuck because they don't happen to be running Mnesia. --- src/rabbit_mnesia.erl | 6 ------ src/rabbit_node_monitor.erl | 15 ++++++++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index ecb03f54..c39e898c 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -26,7 +26,6 @@ status/0, is_clustered/0, - majority/0, cluster_nodes/1, node_type/0, dir/0, @@ -68,7 +67,6 @@ -spec(status/0 :: () -> [{'nodes', [{node_type(), [node()]}]} | {'running_nodes', [node()]} | {'partitions', [{node(), [node()]}]}]). --spec(majority/0 :: () -> boolean()). -spec(is_clustered/0 :: () -> boolean()). -spec(cluster_nodes/1 :: ('all' | 'disc' | 'ram' | 'running') -> [node()]). -spec(node_type/0 :: () -> node_type()). @@ -340,10 +338,6 @@ status() -> false -> [] end. -majority() -> - ensure_mnesia_running(), - (length(cluster_nodes(running)) / length(cluster_nodes(all))) > 0.5. - mnesia_partitions(Nodes) -> {Replies, _BadNodes} = rpc:multicall( Nodes, rabbit_node_monitor, partitions, []), diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 42df6e5d..249c17a4 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -281,7 +281,7 @@ handle_dead_rabbit(Node) -> %% down - otherwise we have a race. handle_dead_according_to_mnesia_rabbit() -> case application:get_env(rabbit, cluster_cp_mode) of - {ok, true} -> case rabbit_mnesia:majority() of + {ok, true} -> case majority() of true -> ok; false -> await_cluster_recovery() end; @@ -289,6 +289,13 @@ handle_dead_according_to_mnesia_rabbit() -> end, ok. +majority() -> + Nodes = rabbit_mnesia:cluster_nodes(all), + Alive = [Status || N <- Nodes, + Status <- [net_adm:ping(N)], + Status =:= pong], + length(Alive) / length(Nodes) > 0.5. + await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", []), @@ -303,11 +310,9 @@ await_cluster_recovery() -> wait_for_cluster_recovery(Nodes) -> [erlang:disconnect_node(Node) || Node <- Nodes], - mnesia:start(), - case rabbit_mnesia:majority() of + case majority() of true -> rabbit:start(); - false -> mnesia:stop(), - timer:sleep(1000), + false -> timer:sleep(1000), wait_for_cluster_recovery(Nodes) end. -- cgit v1.2.1 From cb97ff80e3a876bfce7e22d213be66fa780032e8 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 27 Feb 2013 14:47:03 +0000 Subject: Simplify --- src/rabbit_node_monitor.erl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 249c17a4..fd8080bc 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -291,9 +291,7 @@ handle_dead_according_to_mnesia_rabbit() -> majority() -> Nodes = rabbit_mnesia:cluster_nodes(all), - Alive = [Status || N <- Nodes, - Status <- [net_adm:ping(N)], - Status =:= pong], + Alive = [N || N <- Nodes, pong =:= net_adm:ping(N)], length(Alive) / length(Nodes) > 0.5. await_cluster_recovery() -> -- cgit v1.2.1 From 2279502946e436f40368d66477edafe5c6616f60 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 27 Feb 2013 15:30:43 +0000 Subject: We no longer need two different death detectors since we no longer look at Mnesia for majorityness. --- src/rabbit_node_monitor.erl | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index fd8080bc..ad2003a5 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -250,10 +250,6 @@ handle_info({mnesia_system_event, ordsets:add_element(Node, ordsets:from_list(Partitions))), {noreply, State#state{partitions = Partitions1}}; -handle_info({mnesia_system_event, {mnesia_down, _Node}}, State) -> - handle_dead_according_to_mnesia_rabbit(), - {noreply, State}; - handle_info(_Info, State) -> {noreply, State}. @@ -274,12 +270,7 @@ handle_dead_rabbit(Node) -> ok = rabbit_networking:on_node_down(Node), ok = rabbit_amqqueue:on_node_down(Node), ok = rabbit_alarm:on_node_down(Node), - ok = rabbit_mnesia:on_node_down(Node). - -%% Since we will be introspecting the cluster in response to this, we -%% must only do so based on Mnesia having noticed the other node being -%% down - otherwise we have a race. -handle_dead_according_to_mnesia_rabbit() -> + ok = rabbit_mnesia:on_node_down(Node), case application:get_env(rabbit, cluster_cp_mode) of {ok, true} -> case majority() of true -> ok; -- cgit v1.2.1 From 4c4b2942703c5c254c8af173656522665d9dd934 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 27 Feb 2013 15:46:12 +0000 Subject: removes sed invocations in favor of patch --- packaging/standalone/Makefile | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packaging/standalone/Makefile b/packaging/standalone/Makefile index 91100628..89ccde93 100644 --- a/packaging/standalone/Makefile +++ b/packaging/standalone/Makefile @@ -54,12 +54,7 @@ dist: rm -rf $(RLS_DIR)/lib/rabbit-$(VERSION) # fix Erlang ROOTDIR - sed -e '/^ROOTDIR.*/i \ - realpath() { [[ \$$1 = /* ]] && echo "\$$1" || echo "\$$PWD/\$${1#./}" ; }' \ - $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.src > $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp - sed -e 's:%FINAL_ROOTDIR%:\$$(dirname `realpath \$$0`)/../..:' $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp \ - > $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl - rm $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.tmp + patch -o $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl $(RLS_DIR)/erts-$(ERTS_VSN)/bin/erl.src < erl.diff tar -zcf $(TARGET_TARBALL).tar.gz -C $(TARGET_DIR)/release $(TARGET_DIR) rm -rf $(SOURCE_DIR) $(TARGET_DIR) -- cgit v1.2.1 From 6551ce1f20f5d6dfbac6e3c994f8cc2273fc1917 Mon Sep 17 00:00:00 2001 From: Alvaro Videla Date: Wed, 27 Feb 2013 15:46:53 +0000 Subject: adds missing patch --- packaging/standalone/erl.diff | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packaging/standalone/erl.diff diff --git a/packaging/standalone/erl.diff b/packaging/standalone/erl.diff new file mode 100644 index 00000000..c51bfe22 --- /dev/null +++ b/packaging/standalone/erl.diff @@ -0,0 +1,5 @@ +20c20,21 +< ROOTDIR="%FINAL_ROOTDIR%" +--- +> realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" ; } +> ROOTDIR="$(dirname `realpath $0`)/../.." -- cgit v1.2.1 From 9e125a71712752f84550490e357e867206e000a3 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 1 Mar 2013 14:34:35 +0000 Subject: Check queue length using correct version of backing queue state --- 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 08d68e4c..18b641d4 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -562,9 +562,9 @@ deliver_or_enqueue(Delivery = #delivery{message = Message, sender = SenderPid}, discard(Delivery, State2); {false, State2 = #q{backing_queue = BQ, backing_queue_state = BQS}} -> BQS1 = BQ:publish(Message, Props, Delivered, SenderPid, BQS), - {Dropped, State3} = + {Dropped, State3 = #q{backing_queue_state = BQS2}} = maybe_drop_head(State2#q{backing_queue_state = BQS1}), - QLen = BQ:len(BQS1), + QLen = BQ:len(BQS2), %% optimisation: it would be perfectly safe to always %% invoke drop_expired_msgs here, but that is expensive so %% we only do that if a new message that might have an -- cgit v1.2.1 From 5ae47d98f0fe2f67503646316d7a0df186be80b6 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 1 Mar 2013 14:47:47 +0000 Subject: Rename this thing, to make space for bug 25471 --- ebin/rabbit_app.in | 2 +- src/rabbit_node_monitor.erl | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ebin/rabbit_app.in b/ebin/rabbit_app.in index d08d502b..339fa69e 100644 --- a/ebin/rabbit_app.in +++ b/ebin/rabbit_app.in @@ -44,7 +44,7 @@ {log_levels, [{connection, info}]}, {ssl_cert_login_from, distinguished_name}, {reverse_dns_lookups, false}, - {cluster_cp_mode, false}, + {cluster_partition_handling, ignore}, {tcp_listen_options, [binary, {packet, raw}, {reuseaddr, true}, diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index ad2003a5..5d587977 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -271,12 +271,18 @@ handle_dead_rabbit(Node) -> ok = rabbit_amqqueue:on_node_down(Node), ok = rabbit_alarm:on_node_down(Node), ok = rabbit_mnesia:on_node_down(Node), - case application:get_env(rabbit, cluster_cp_mode) of - {ok, true} -> case majority() of - true -> ok; - false -> await_cluster_recovery() - end; - {ok, false} -> ok + case application:get_env(rabbit, cluster_partition_handling) of + {ok, pause_minority} -> + case majority() of + true -> ok; + false -> await_cluster_recovery() + end; + {ok, ignore} -> + ok; + {ok, Term} -> + rabbit_log:warning("cluster_partition_handling ~p unrecognised, " + "assuming 'ignore'~n", [Term]), + ok end, ok. -- cgit v1.2.1 From 30d4a9ae902c8a8ee9593a4ff603bda06563fbd0 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Mon, 4 Mar 2013 14:20:33 +0000 Subject: rebase with R16B --- src/supervisor2.erl | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 693e0b6d..3f807573 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -186,7 +186,9 @@ %%% SupName = {local, atom()} | {global, atom()}. %%% --------------------------------------------------- -ifdef(use_specs). --type startlink_err() :: {'already_started', pid()} | 'shutdown' | term(). +-type startlink_err() :: {'already_started', pid()} + | {'shutdown', term()} + | term(). -type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}. -spec start_link(Module, Args) -> startlink_ret() when @@ -331,8 +333,10 @@ cast(Supervisor, Req) -> -ifdef(use_specs). -type init_sup_name() :: sup_name() | 'self'. --type stop_rsn() :: 'shutdown' | {'bad_return', {module(),'init', term()}} - | {'bad_start_spec', term()} | {'start_spec', term()} +-type stop_rsn() :: {'shutdown', term()} + | {'bad_return', {module(),'init', term()}} + | {'bad_start_spec', term()} + | {'start_spec', term()} | {'supervisor_data', term()}. -spec init({init_sup_name(), module(), [term()]}) -> @@ -363,9 +367,9 @@ init_children(State, StartSpec) -> case start_children(Children, SupName) of {ok, NChildren} -> {ok, State#state{children = NChildren}}; - {error, _, NChildren} -> + {error, NChildren, Reason} -> terminate_children(NChildren, SupName), - {stop, shutdown} + {stop, {shutdown, Reason}} end; Error -> {stop, {start_spec, Error}} @@ -385,9 +389,9 @@ init_dynamic(_State, StartSpec) -> %% Func: start_children/2 %% Args: Children = [child_rec()] in start order %% SupName = {local, atom()} | {global, atom()} | {pid(), Mod} -%% Purpose: Start all children. The new list contains #child's +%% Purpose: Start all children. The new list contains #child's %% with pids. -%% Returns: {ok, NChildren} | {error, NChildren} +%% Returns: {ok, NChildren} | {error, NChildren, Reason} %% NChildren = [child_rec()] in termination order (reversed %% start order) %%----------------------------------------------------------------- @@ -403,7 +407,8 @@ start_children([Child|Chs], NChildren, SupName) -> start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); {error, Reason} -> report_error(start_error, Reason, Child, SupName), - {error, Reason, lists:reverse(Chs) ++ [Child | NChildren]} + {error, lists:reverse(Chs) ++ [Child | NChildren], + {failed_to_start_child,Child#child.name,Reason}} end; start_children([], NChildren, _SupName) -> {ok, NChildren}. @@ -963,7 +968,7 @@ restart(rest_for_one, Child, State) -> case start_children(ChAfter2, State#state.name) of {ok, ChAfter3} -> {ok, State#state{children = ChAfter3 ++ ChBefore}}; - {error, Reason, ChAfter3} -> + {error, ChAfter3, Reason} -> NChild = Child#child{pid=restarting(Child#child.pid)}, NState = State#state{children = ChAfter3 ++ ChBefore}, {try_again, Reason, replace_child(NChild,NState)} @@ -974,7 +979,7 @@ restart(one_for_all, Child, State) -> case start_children(Children2, State#state.name) of {ok, NChs} -> {ok, State#state{children = NChs}}; - {error, Reason, NChs} -> + {error, NChs, Reason} -> NChild = Child#child{pid=restarting(Child#child.pid)}, NState = State#state{children = NChs}, {try_again, Reason, replace_child(NChild,NState)} -- cgit v1.2.1 From d8be7a482b7ed4c339639f3c7404f6ef51f1c7d0 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 5 Mar 2013 16:01:56 +0000 Subject: We don't need this - net_adm:ping/1 will not return pong for nodes that have gone down before net_ticktime expires (it will hang for until then instead). --- src/rabbit_node_monitor.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 08c5a25f..55b078c3 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -208,9 +208,10 @@ handle_call(_Request, _From, State) -> %% mnesia information since the message can (and will) overtake the %% mnesia propagation. handle_cast({node_up, Node, NodeType}, - State = #state{monitors = Monitors}) -> + State = #state{monitors = Monitors, partitions = Partitions}) -> + State1 = State#state{partitions = Partitions -- [Node]}, case pmon:is_monitored({rabbit, Node}, Monitors) of - true -> {noreply, State}; + true -> {noreply, State1}; false -> rabbit_log:info("rabbit on node ~p up~n", [Node]), {AllNodes, DiscNodes, RunningNodes} = read_cluster_status(), write_cluster_status({add_node(Node, AllNodes), @@ -220,7 +221,7 @@ handle_cast({node_up, Node, NodeType}, end, add_node(Node, RunningNodes)}), ok = handle_live_rabbit(Node), - {noreply, State#state{ + {noreply, State1#state{ monitors = pmon:monitor({rabbit, Node}, Monitors)}} end; handle_cast({joined_cluster, Node, NodeType}, State) -> @@ -316,7 +317,6 @@ await_cluster_recovery() -> end). wait_for_cluster_recovery(Nodes) -> - [erlang:disconnect_node(Node) || Node <- Nodes], case majority() of true -> rabbit:start(); false -> timer:sleep(1000), -- cgit v1.2.1 From 971559d454c8873afdcc1a66fc1267fc0c26480c Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 6 Mar 2013 03:52:01 +0000 Subject: correct comment --- src/supervisor2.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 3f807573..67dbab76 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -1,4 +1,4 @@ -%% This file is a copy of supervisor.erl from the R15B-3 Erlang/OTP +%% This file is a copy of supervisor.erl from the R16B Erlang/OTP %% distribution, with the following modifications: %% %% 1) the module name is supervisor2 -- cgit v1.2.1 From febba0f6d331e91226eba91586a7889796e4ea1d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 6 Mar 2013 15:32:29 +0000 Subject: Unused variable --- src/rabbit_error_logger_file_h.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_error_logger_file_h.erl b/src/rabbit_error_logger_file_h.erl index c00c1df9..b6e2c15e 100644 --- a/src/rabbit_error_logger_file_h.erl +++ b/src/rabbit_error_logger_file_h.erl @@ -76,7 +76,7 @@ init_file(File, PrevHandler) -> Error -> Error end. -handle_event({info_report, _, {_, std_info, _}}, State) -> +handle_event({info_report, _, {_, std_info, _}}, _State) -> ok; %% filter out "application: foo; exited: stopped; type: temporary" handle_event(Event, State) -> error_logger_file_h:handle_event(Event, State). -- cgit v1.2.1 From 2e55dc4d5db9f4c782c96e16a0769bd9e86d9f1b Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 6 Mar 2013 15:45:47 +0000 Subject: Register the process name to make sure we only have one running at a time. --- src/rabbit_node_monitor.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 55b078c3..47c753e3 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -312,6 +312,9 @@ await_cluster_recovery() -> %% If our group leader is inside an application we are about %% to stop, application:stop/1 does not return. group_leader(whereis(init), self()), + %% Ensure only one restarting process at a time, will + %% exit(badarg) (harmlessly) if one is already running + register(rabbit_restarting_process, self()), rabbit:stop(), wait_for_cluster_recovery(Nodes) end). -- cgit v1.2.1 From d8b30c8d9baf22bdce5644b2af854f2edb080738 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Wed, 6 Mar 2013 20:29:50 +0000 Subject: Refactor try_again restart handling --- src/supervisor2.erl | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 3f807573..719c8b3c 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -703,8 +703,8 @@ handle_info(Msg, State) -> delayed_restart(RestartType, Reason, Child, State) -> case do_restart(RestartType, Reason, Child, State) of - {ok, NState} -> {noreply, NState}; - {try_again, _, NState} -> {noreply, NState} + {ok, NState} -> {noreply, NState}; + Other -> Other end. %% @@ -879,14 +879,7 @@ do_restart(temporary, Reason, Child, State) -> do_restart_delay({RestartType, Delay}, Reason, Child, State) -> case add_restart(State) of {ok, NState} -> - restart(NState#state.strategy, Child, NState); - {try_again, Reason, NState} -> - %% See restart/2 for an explanation of try_again_restart - Id = if ?is_simple(State) -> Child#child.pid; - true -> Child#child.name - end, - timer:apply_after(0,?MODULE,try_again_restart,[self(),Id,Reason]), - {ok, NState}; + maybe_restart(NState#state.strategy, Child, NState); {terminate, _NState} -> %% we've reached the max restart intensity, but the %% add_restart will have added to the restarts @@ -910,27 +903,30 @@ del_child_and_maybe_shutdown(_, Child, State) -> restart(Child, State) -> case add_restart(State) of {ok, NState} -> - case restart(NState#state.strategy, Child, NState) of - {try_again, Reason, NState2} -> - %% Leaving control back to gen_server before - %% trying again. This way other incoming requsts - %% for the supervisor can be handled - e.g. a - %% shutdown request for the supervisor or the - %% child. - Id = if ?is_simple(State) -> Child#child.pid; - true -> Child#child.name - end, - timer:apply_after(0,?MODULE,try_again_restart,[self(),Id,Reason]), - {ok,NState2}; - Other -> - Other - end; + maybe_restart(NState#state.strategy, Child, NState); {terminate, NState} -> report_error(shutdown, reached_max_restart_intensity, Child, State#state.name), {shutdown, remove_child(Child, NState)} end. +maybe_restart(Strategy, Child, State) -> + case restart(Strategy, Child, State) of + {try_again, Reason, NState2} -> + %% Leaving control back to gen_server before + %% trying again. This way other incoming requsts + %% for the supervisor can be handled - e.g. a + %% shutdown request for the supervisor or the + %% child. + Id = if ?is_simple(State) -> Child#child.pid; + true -> Child#child.name + end, + timer:apply_after(0,?MODULE,try_again_restart,[self(),Id,Reason]), + {ok,NState2}; + Other -> + Other + end. + restart(simple_one_for_one, Child, State) -> #child{pid = OldPid, mfargs = {M, F, A}} = Child, Dynamics = ?DICT:erase(OldPid, dynamics_db(Child#child.restart_type, -- cgit v1.2.1 From 01e9aa59c72a9937795619208e3d5c220d4a815b Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 6 Mar 2013 21:07:31 +0000 Subject: Permit exchange decorators to modify routing decisions --- src/rabbit_exchange.erl | 39 ++++++++++++++++++++++++++++----------- src/rabbit_exchange_decorator.erl | 6 +++++- src/rabbit_registry.erl | 11 ++++++----- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 88033f77..0a3849ef 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -117,14 +117,15 @@ callback(X = #exchange{type = XType}, Fun, Serial0, Args) -> is_atom(Serial0) -> fun (_Bool) -> Serial0 end end, [ok = apply(M, Fun, [Serial(M:serialise_events(X)) | Args]) || - M <- decorators()], + M <- registry_lookup(exchange_decorator)], Module = type_to_module(XType), apply(Module, Fun, [Serial(Module:serialise_events()) | Args]). policy_changed(X1, X2) -> callback(X1, policy_changed, none, [X1, X2]). serialise_events(X = #exchange{type = Type}) -> - lists:any(fun (M) -> M:serialise_events(X) end, decorators()) + lists:any(fun (M) -> M:serialise_events(X) end, + registry_lookup(exchange_decorator)) orelse (type_to_module(Type)):serialise_events(). serial(#exchange{name = XName} = X) -> @@ -136,8 +137,15 @@ serial(#exchange{name = XName} = X) -> (false) -> none end. -decorators() -> - [M || {_, M} <- rabbit_registry:lookup_all(exchange_decorator)]. +registry_lookup(exchange_decorator_route = Class) -> + case get(exchange_decorator_route_modules) of + undefined -> Mods = [M || {_, M} <- rabbit_registry:lookup_all(Class)], + put(exchange_decorator_route_modules, Mods), + Mods; + Mods -> Mods + end; +registry_lookup(Class) -> + [M || {_, M} <- rabbit_registry:lookup_all(Class)]. declare(XName, Type, Durable, AutoDelete, Internal, Args) -> X = rabbit_policy:set(#exchange{name = XName, @@ -304,16 +312,25 @@ info_all(VHostPath) -> map(VHostPath, fun (X) -> info(X) end). info_all(VHostPath, Items) -> map(VHostPath, fun (X) -> info(X, Items) end). -%% Optimisation -route(#exchange{name = #resource{name = <<"">>, virtual_host = VHost}}, - #delivery{message = #basic_message{routing_keys = RKs}}) -> - [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; +route(#exchange{name = #resource{name = RName, virtual_host = VHost} = XName} = X, + #delivery{message = #basic_message{routing_keys = RKs}} = Delivery) -> + case registry_lookup(exchange_decorator_route) == [] andalso + RName == <<"">> of + true -> [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; + false -> QNames = route1(Delivery, {[X], XName, []}), + lists:usort(decorate_route(X, Delivery, QNames)) + end. -route(X = #exchange{name = XName}, Delivery) -> - route1(Delivery, {[X], XName, []}). +decorate_route(X, Delivery, QNames) -> + {Add, Remove} = + lists:foldl(fun (Decorator, {Add, Remove}) -> + {A1, R1} = Decorator:route(X, Delivery, QNames), + {A1 ++ Add, R1 ++ Remove} + end, {[], []}, registry_lookup(exchange_decorator_route)), + QNames ++ Add -- Remove. route1(_, {[], _, QNames}) -> - lists:usort(QNames); + QNames; route1(Delivery, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> DstNames = process_alternate( X, ((type_to_module(Type)):route(X, Delivery))), diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index befbc462..4e395cbe 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -58,13 +58,17 @@ -callback policy_changed ( serial(), rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'. +-callback route ( rabbit_types:exchange(), rabbit_types:delivery(), + [rabbit_amqqueue:name()]) -> + {[rabbit_amqqueue:name()], [rabbit_amqqueue:name()]}. + -else. -export([behaviour_info/1]). behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 1}, {create, 2}, {delete, 3}, - {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 3}]; + {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 3}, {route, 3}]; behaviour_info(_Other) -> undefined. diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl index 60419856..3514e780 100644 --- a/src/rabbit_registry.erl +++ b/src/rabbit_registry.erl @@ -104,11 +104,12 @@ sanity_check_module(ClassModule, Module) -> true -> ok end. -class_module(exchange) -> rabbit_exchange_type; -class_module(auth_mechanism) -> rabbit_auth_mechanism; -class_module(runtime_parameter) -> rabbit_runtime_parameter; -class_module(exchange_decorator) -> rabbit_exchange_decorator; -class_module(policy_validator) -> rabbit_policy_validator. +class_module(exchange) -> rabbit_exchange_type; +class_module(auth_mechanism) -> rabbit_auth_mechanism; +class_module(runtime_parameter) -> rabbit_runtime_parameter; +class_module(exchange_decorator) -> rabbit_exchange_decorator; +class_module(exchange_decorator_route) -> rabbit_exchange_decorator; +class_module(policy_validator) -> rabbit_policy_validator. %%--------------------------------------------------------------------------- -- cgit v1.2.1 From 0f471c546032d497ef2090fab8c4e4c7ad6c7a27 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 7 Mar 2013 10:01:39 +0000 Subject: Optimisation --- src/rabbit_exchange.erl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 0a3849ef..0e7872f6 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -314,11 +314,16 @@ info_all(VHostPath, Items) -> map(VHostPath, fun (X) -> info(X, Items) end). route(#exchange{name = #resource{name = RName, virtual_host = VHost} = XName} = X, #delivery{message = #basic_message{routing_keys = RKs}} = Delivery) -> - case registry_lookup(exchange_decorator_route) == [] andalso - RName == <<"">> of - true -> [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; - false -> QNames = route1(Delivery, {[X], XName, []}), - lists:usort(decorate_route(X, Delivery, QNames)) + case {registry_lookup(exchange_decorator_route) == [], RName == <<"">>} of + {true, true} -> + [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; + {NoDecor, _} -> + QNames = route1(Delivery, {[X], XName, []}), + lists:usort( + case NoDecor of + true -> QNames; + false -> decorate_route(X, Delivery, QNames) + end) end. decorate_route(X, Delivery, QNames) -> -- cgit v1.2.1 From 42cff68581fe5772de741012b3d75f0c336a4fd6 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 7 Mar 2013 12:26:09 +0000 Subject: Oops --- src/rabbit_error_logger_file_h.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rabbit_error_logger_file_h.erl b/src/rabbit_error_logger_file_h.erl index b6e2c15e..eb6247e0 100644 --- a/src/rabbit_error_logger_file_h.erl +++ b/src/rabbit_error_logger_file_h.erl @@ -76,8 +76,9 @@ init_file(File, PrevHandler) -> Error -> Error end. -handle_event({info_report, _, {_, std_info, _}}, _State) -> - ok; %% filter out "application: foo; exited: stopped; type: temporary" +%% filter out "application: foo; exited: stopped; type: temporary" +handle_event({info_report, _, {_, std_info, _}}, State) -> + {ok, State}; handle_event(Event, State) -> error_logger_file_h:handle_event(Event, State). -- cgit v1.2.1 From bd2905c7b21c4f75fea8bd1d61e49043c7bf6dc2 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 8 Mar 2013 12:18:26 +0000 Subject: Mostly callback description --- src/rabbit_exchange.erl | 11 +++++------ src/rabbit_exchange_decorator.erl | 8 ++++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 0e7872f6..7179454d 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -317,13 +317,12 @@ route(#exchange{name = #resource{name = RName, virtual_host = VHost} = XName} = case {registry_lookup(exchange_decorator_route) == [], RName == <<"">>} of {true, true} -> [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; - {NoDecor, _} -> + {NoDecorator, _} -> QNames = route1(Delivery, {[X], XName, []}), - lists:usort( - case NoDecor of - true -> QNames; - false -> decorate_route(X, Delivery, QNames) - end) + lists:usort(case NoDecorator of + true -> QNames; + false -> decorate_route(X, Delivery, QNames) + end) end. decorate_route(X, Delivery, QNames) -> diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 4e395cbe..70ba4d22 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -21,9 +21,8 @@ %% 1) It applies to all exchanges as soon as it is installed, therefore %% 2) It is not allowed to affect validation, so no validate/1 or %% assert_args_equivalence/2 -%% 3) It also can't affect routing %% -%% It's possible in the future we might relax 3), or even make these +%% It's possible in the future we might make decorators %% able to manipulate messages as they are published. -ifdef(use_specs). @@ -58,6 +57,11 @@ -callback policy_changed ( serial(), rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'. +%% called after exchange routing +%% return value is a tuple of two lists: queues to be added +%% and queues to be removed from the list of destination queues. +%% decorators must register separately for this callback using +%% exchange_decorator_route. -callback route ( rabbit_types:exchange(), rabbit_types:delivery(), [rabbit_amqqueue:name()]) -> {[rabbit_amqqueue:name()], [rabbit_amqqueue:name()]}. -- cgit v1.2.1 From ad6585ac8fe190ec01fc76bbee76c7a845b016d6 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 8 Mar 2013 16:22:19 +0000 Subject: Align error reporting and restart handling with OTP --- src/supervisor2.erl | 106 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 31 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index e5a05f66..aba40626 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -141,6 +141,9 @@ -define(SETS, sets). -define(SET, set). +-define(is_explicit_restart(R), + R == {shutdown, restart}). + -ifdef(use_specs). -record(state, {name, strategy :: strategy(), @@ -850,31 +853,79 @@ restart_child(Pid, Reason, State) -> {ok, State} end. -do_restart({permanent = RestartType, Delay}, Reason, Child, State) -> - do_restart_delay({RestartType, Delay}, Reason, Child, State); -do_restart(permanent, Reason, Child, State) -> - report_error(child_terminated, Reason, Child, State#state.name), - restart(Child, State); -do_restart(Type, normal, Child, State) -> - del_child_and_maybe_shutdown(Type, Child, State); -do_restart({RestartType, Delay}, {shutdown, restart} = Reason, Child, State) - when RestartType =:= transient orelse RestartType =:= intrinsic -> - do_restart_delay({RestartType, Delay}, Reason, Child, State); -do_restart(Type, {shutdown, _}, Child, State) -> - del_child_and_maybe_shutdown(Type, Child, State); -do_restart(Type, shutdown, Child = #child{child_type = supervisor}, State) -> - del_child_and_maybe_shutdown(Type, Child, State); -do_restart({RestartType, Delay}, Reason, Child, State) - when RestartType =:= transient orelse RestartType =:= intrinsic -> - do_restart_delay({RestartType, Delay}, Reason, Child, State); -do_restart(Type, Reason, Child, State) when Type =:= transient orelse - Type =:= intrinsic -> - report_error(child_terminated, Reason, Child, State#state.name), +do_restart(RestartType, Reason, Child, State) -> + maybe_report_error(RestartType, Reason, Child, State), + handle_restart(RestartType, Reason, Child, State). + +maybe_report_error(permanent, Reason, Child, State) -> + report_child_termination(Reason, Child, State); +maybe_report_error({permanent, _}, Reason, Child, State) -> + report_child_termination(Reason, Child, State); +maybe_report_error(_Type, Reason, Child, State) -> + case is_abnormal_termination(Reason) of + true -> report_child_termination(Reason, Child, State); + false -> ok + end. + +report_child_termination(Reason, Child, State) -> + report_error(child_terminated, Reason, Child, State#state.name). + +handle_restart(permanent, _Reason, Child, State) -> restart(Child, State); -do_restart(temporary, Reason, Child, State) -> - report_error(child_terminated, Reason, Child, State#state.name), - NState = state_del_child(Child, State), - {ok, NState}. +handle_restart(transient, Reason, Child, State) -> + restart_if_explicit_or_abnormal(fun restart/2, + fun delete_child_and_continue/2, + Reason, Child, State); +handle_restart(intrinsic, Reason, Child, State) -> + restart_if_explicit_or_abnormal(fun restart/2, + fun delete_child_and_stop/2, + Reason, Child, State); +handle_restart(temporary, _Reason, Child, State) -> + delete_child_and_continue(Child, State); +handle_restart({_RestartType, _Delay}=Restart, Reason, Child, State) -> + handle_delayed_restart(Restart, Reason, Child, State). + +handle_delayed_restart({permanent, _Delay}=Restart, Reason, Child, State) -> + do_restart_delay(Restart, Reason, Child, State); +handle_delayed_restart({RestartType, _Delay}=Restart, Reason, Child, State) + when ?is_explicit_restart(Reason) andalso + (RestartType =:= transient orelse + RestartType =:= intrinsic) -> + do_restart_delay(Restart, Reason, Child, State); +handle_delayed_restart({transient, _Delay}=Restart, Reason, Child, State) -> + restart_if_explicit_or_abnormal(defer_to_restart_delay(Restart, Reason), + fun delete_child_and_continue/2, + Reason, Child, State); +handle_delayed_restart({intrinsic, _Delay}=Restart, Reason, Child, State) -> + restart_if_explicit_or_abnormal(defer_to_restart_delay(Restart, Reason), + fun delete_child_and_stop/2, + Reason, Child, State). + +restart_if_explicit_or_abnormal(RestartHow, _Otherwise, Reason, Child, State) + when is_function(RestartHow, 2) andalso + ?is_explicit_restart(Reason) -> + RestartHow(Child, State); +restart_if_explicit_or_abnormal(RestartHow, Otherwise, Reason, Child, State) + when is_function(RestartHow, 2) andalso + is_function(Otherwise, 2) -> + case is_abnormal_termination(Reason) of + true -> RestartHow(Child, State); + false -> Otherwise(Child, State) + end. + +defer_to_restart_delay(Restart, Reason) -> + fun(Child, State) -> do_restart_delay(Restart, Reason, Child, State) end. + +delete_child_and_continue(Child, State) -> + {ok, state_del_child(Child, State)}. + +delete_child_and_stop(Child, State) -> + {shutdown, state_del_child(Child, State)}. + +is_abnormal_termination(normal) -> false; +is_abnormal_termination(shutdown) -> false; +is_abnormal_termination({shutdown, _}) -> false; +is_abnormal_termination(_Other) -> true. do_restart_delay({RestartType, Delay}, Reason, Child, State) -> case add_restart(State) of @@ -893,13 +944,6 @@ do_restart_delay({RestartType, Delay}, Reason, Child, State) -> {ok, state_del_child(Child, State)} end. -del_child_and_maybe_shutdown(intrinsic, Child, State) -> - {shutdown, state_del_child(Child, State)}; -del_child_and_maybe_shutdown({intrinsic, _Delay}, Child, State) -> - {shutdown, state_del_child(Child, State)}; -del_child_and_maybe_shutdown(_, Child, State) -> - {ok, state_del_child(Child, State)}. - restart(Child, State) -> case add_restart(State) of {ok, NState} -> -- cgit v1.2.1 From fe1f803340caa83e9737399d9bb0148c9d8a9d10 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 8 Mar 2013 20:23:10 +0000 Subject: Translate return from do_restart properly --- src/supervisor2.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index aba40626..ff519acd 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -706,8 +706,8 @@ handle_info(Msg, State) -> delayed_restart(RestartType, Reason, Child, State) -> case do_restart(RestartType, Reason, Child, State) of - {ok, NState} -> {noreply, NState}; - Other -> Other + {ok, NState} -> {noreply, NState}; + {shutdown, State2} -> {stop, shutdown, State2} end. %% -- cgit v1.2.1 From 880c8b9e42628d18eac69baec31e6cbf1fdf562e Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 11 Mar 2013 14:16:32 +0000 Subject: Minimise routing exchange decorator API --- src/rabbit_exchange.erl | 28 +++++++++++++--------------- src/rabbit_exchange_decorator.erl | 14 ++++++-------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 7179454d..0d1e9831 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -312,26 +312,24 @@ info_all(VHostPath) -> map(VHostPath, fun (X) -> info(X) end). info_all(VHostPath, Items) -> map(VHostPath, fun (X) -> info(X, Items) end). -route(#exchange{name = #resource{name = RName, virtual_host = VHost} = XName} = X, +route(#exchange{name = #resource{virtual_host = VHost, + name = RName} = XName} = X, #delivery{message = #basic_message{routing_keys = RKs}} = Delivery) -> - case {registry_lookup(exchange_decorator_route) == [], RName == <<"">>} of - {true, true} -> + case {registry_lookup(exchange_decorator_route), RName == <<"">>} of + {[], true} -> + %% Optimisation [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; - {NoDecorator, _} -> + {Decorators, _} -> QNames = route1(Delivery, {[X], XName, []}), - lists:usort(case NoDecorator of - true -> QNames; - false -> decorate_route(X, Delivery, QNames) - end) + lists:usort(decorate_route(Decorators, X, Delivery, QNames)) end. -decorate_route(X, Delivery, QNames) -> - {Add, Remove} = - lists:foldl(fun (Decorator, {Add, Remove}) -> - {A1, R1} = Decorator:route(X, Delivery, QNames), - {A1 ++ Add, R1 ++ Remove} - end, {[], []}, registry_lookup(exchange_decorator_route)), - QNames ++ Add -- Remove. +decorate_route([], _X, _Delivery, QNames) -> + QNames; +decorate_route(Decorators, X, Delivery, QNames) -> + lists:foldl(fun (Decorator, QNamesAcc) -> + Decorator:route(X, Delivery) ++ QNamesAcc + end, QNames, Decorators). route1(_, {[], _, QNames}) -> QNames; diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 70ba4d22..05077f03 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -58,13 +58,11 @@ serial(), rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'. %% called after exchange routing -%% return value is a tuple of two lists: queues to be added -%% and queues to be removed from the list of destination queues. -%% decorators must register separately for this callback using -%% exchange_decorator_route. --callback route ( rabbit_types:exchange(), rabbit_types:delivery(), - [rabbit_amqqueue:name()]) -> - {[rabbit_amqqueue:name()], [rabbit_amqqueue:name()]}. +%% return value is a list of queues to be added to the list of +%% destination queues. decorators must register separately for +%% this callback using exchange_decorator_route. +-callback route ( rabbit_types:exchange(), rabbit_types:delivery()) -> + [rabbit_amqqueue:name()]. -else. @@ -72,7 +70,7 @@ behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 1}, {create, 2}, {delete, 3}, - {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 3}, {route, 3}]; + {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 3}, {route, 2}]; behaviour_info(_Other) -> undefined. -- cgit v1.2.1 From e4b8ffb341f1141ebd897b00e43a0640a8fab693 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Mon, 11 Mar 2013 14:34:39 +0000 Subject: track repeated attempts to restart properly --- src/supervisor2.erl | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index ff519acd..a181e7d4 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -650,25 +650,15 @@ handle_cast({try_again_restart,Pid,Reason}, #state{children=[Child]}=State) {ok, Args} -> {M, F, _} = Child#child.mfargs, NChild = Child#child{pid = RPid, mfargs = {M, F, Args}}, - case restart_child(NChild,Reason,State) of - {ok, State1} -> - {noreply, State1}; - {shutdown, State1} -> - {stop, shutdown, State1} - end; + try_restart(Child#child.restart_type, Reason, NChild, State); error -> {noreply, State} end; handle_cast({try_again_restart,Name,Reason}, State) -> case lists:keyfind(Name,#child.name,State#state.children) of - Child = #child{pid=?restarting(_)} -> - case restart_child(Child,Reason,State) of - {ok, State1} -> - {noreply, State1}; - {shutdown, State1} -> - {stop, shutdown, State1} - end; + Child = #child{pid=?restarting(_), restart_type=RestartType} -> + try_restart(RestartType, Reason, Child, State); _ -> {noreply,State} end. @@ -690,11 +680,11 @@ handle_info({'EXIT', Pid, Reason}, State) -> handle_info({delayed_restart, {RestartType, Reason, Child}}, State) when ?is_simple(State) -> - delayed_restart(RestartType, Reason, Child, State); + try_restart(RestartType, Reason, Child, State); handle_info({delayed_restart, {RestartType, Reason, Child}}, State) -> case get_child(Child#child.name, State) of {value, Child1} -> - delayed_restart(RestartType, Reason, Child1, State); + try_restart(RestartType, Reason, Child1, State); _What -> {noreply, State} end; @@ -704,12 +694,6 @@ handle_info(Msg, State) -> [Msg]), {noreply, State}. -delayed_restart(RestartType, Reason, Child, State) -> - case do_restart(RestartType, Reason, Child, State) of - {ok, NState} -> {noreply, NState}; - {shutdown, State2} -> {stop, shutdown, State2} - end. - %% %% Terminate this server. %% @@ -853,6 +837,12 @@ restart_child(Pid, Reason, State) -> {ok, State} end. +try_restart(RestartType, Reason, Child, State) -> + case do_restart(RestartType, Reason, Child, State) of + {ok, NState} -> {noreply, NState}; + {shutdown, State2} -> {stop, shutdown, State2} + end. + do_restart(RestartType, Reason, Child, State) -> maybe_report_error(RestartType, Reason, Child, State), handle_restart(RestartType, Reason, Child, State). -- cgit v1.2.1 From 1beda96451823e8f072a585b6eb9926260a6c41e Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 11 Mar 2013 16:59:29 +0000 Subject: Cosmetic --- src/rabbit_exchange_decorator.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 05077f03..414f9c60 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -54,14 +54,14 @@ [rabbit_types:binding()]) -> 'ok'. %% called when the policy attached to this exchange changes. --callback policy_changed ( +-callback policy_changed( serial(), rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'. %% called after exchange routing %% return value is a list of queues to be added to the list of %% destination queues. decorators must register separately for %% this callback using exchange_decorator_route. --callback route ( rabbit_types:exchange(), rabbit_types:delivery()) -> +-callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> [rabbit_amqqueue:name()]. -else. -- cgit v1.2.1 From fb4f1957d4560922abc5cabd5531f3cc9f43eb48 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 11 Mar 2013 17:22:52 +0000 Subject: Eliminate a foldl --- src/rabbit_exchange.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 0d1e9831..c5a6309a 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -327,9 +327,8 @@ route(#exchange{name = #resource{virtual_host = VHost, decorate_route([], _X, _Delivery, QNames) -> QNames; decorate_route(Decorators, X, Delivery, QNames) -> - lists:foldl(fun (Decorator, QNamesAcc) -> - Decorator:route(X, Delivery) ++ QNamesAcc - end, QNames, Decorators). + QNames ++ + lists:append([Decorator:route(X, Delivery) || Decorator <- Decorators]). route1(_, {[], _, QNames}) -> QNames; -- cgit v1.2.1 From 62d12b03e9abe95c60bb00e7faafb6849dc21f9f Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Mar 2013 16:57:29 +0000 Subject: Oops. This was part of an (early, wrong) attempt at bug 25474 which got committed as part of f1317bb80df9 (bug 25358) by mistake. Remove. --- src/rabbit_node_monitor.erl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 47c753e3..98e26a6a 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -208,10 +208,9 @@ handle_call(_Request, _From, State) -> %% mnesia information since the message can (and will) overtake the %% mnesia propagation. handle_cast({node_up, Node, NodeType}, - State = #state{monitors = Monitors, partitions = Partitions}) -> - State1 = State#state{partitions = Partitions -- [Node]}, + State = #state{monitors = Monitors}) -> case pmon:is_monitored({rabbit, Node}, Monitors) of - true -> {noreply, State1}; + true -> {noreply, State}; false -> rabbit_log:info("rabbit on node ~p up~n", [Node]), {AllNodes, DiscNodes, RunningNodes} = read_cluster_status(), write_cluster_status({add_node(Node, AllNodes), @@ -221,7 +220,7 @@ handle_cast({node_up, Node, NodeType}, end, add_node(Node, RunningNodes)}), ok = handle_live_rabbit(Node), - {noreply, State1#state{ + {noreply, State#state{ monitors = pmon:monitor({rabbit, Node}, Monitors)}} end; handle_cast({joined_cluster, Node, NodeType}, State) -> -- cgit v1.2.1 From 5dea4c63b56e8b9a7acdf0cedbe5fa051ea5e266 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Mar 2013 17:55:05 +0000 Subject: Treat {inconsistent_database, running_partitioned_network, Node} as being sort of like {node_up, Node, NodeType}. It's not perfect, but it's the best we're going to get. --- src/rabbit_node_monitor.erl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 98e26a6a..3d900d26 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -257,10 +257,19 @@ handle_info({'DOWN', _MRef, process, Pid, _Reason}, handle_info({mnesia_system_event, {inconsistent_database, running_partitioned_network, Node}}, - State = #state{partitions = Partitions}) -> + State = #state{partitions = Partitions, + monitors = Monitors}) -> + %% We will not get a node_up from this node - yet we should treat it as + %% up (mostly). + State1 = case pmon:is_monitored({rabbit, Node}, Monitors) of + true -> State; + false -> State#state{ + monitors = pmon:monitor({rabbit, Node}, Monitors)} + end, + ok = handle_live_rabbit(Node), Partitions1 = ordsets:to_list( ordsets:add_element(Node, ordsets:from_list(Partitions))), - {noreply, State#state{partitions = Partitions1}}; + {noreply, State1#state{partitions = Partitions1}}; handle_info(_Info, State) -> {noreply, State}. -- cgit v1.2.1 From 38f82dfc2e3743f101b2921750ea8d4bd679c5d2 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 12 Mar 2013 17:56:18 +0000 Subject: If we have been partitioned, and we are now in the only remaining partition, we no longer care about partitions - forget them. Note that we do not attempt to deal with individual (other) partitions going away, it's only safe to forget *any* of them when we have seen the back of *all* of them. --- src/rabbit_node_monitor.erl | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 3d900d26..558596ef 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -249,7 +249,8 @@ handle_info({'DOWN', _MRef, process, {rabbit, Node}, _Reason}, write_cluster_status({AllNodes, DiscNodes, del_node(Node, RunningNodes)}), ok = handle_dead_rabbit(Node), [P ! {node_down, Node} || P <- pmon:monitored(Subscribers)], - {noreply, State#state{monitors = pmon:erase({rabbit, Node}, Monitors)}}; + {noreply, handle_dead_rabbit_state( + State#state{monitors = pmon:erase({rabbit, Node}, Monitors)})}; handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #state{subscribers = Subscribers}) -> @@ -308,9 +309,14 @@ handle_dead_rabbit(Node) -> ok. majority() -> + length(alive_nodes()) / length(rabbit_mnesia:cluster_nodes(all)) > 0.5. + +%% mnesia:system_info(db_nodes) (and hence +%% rabbit_mnesia:cluster_nodes(running)) does not give reliable results +%% when partitioned. +alive_nodes() -> Nodes = rabbit_mnesia:cluster_nodes(all), - Alive = [N || N <- Nodes, pong =:= net_adm:ping(N)], - length(Alive) / length(Nodes) > 0.5. + [N || N <- Nodes, pong =:= net_adm:ping(N)]. await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", @@ -334,6 +340,18 @@ wait_for_cluster_recovery(Nodes) -> wait_for_cluster_recovery(Nodes) end. +handle_dead_rabbit_state(State = #state{partitions = Partitions}) -> + %% If we have been partitioned, and we are now in the only remaining + %% partition, we no longer care about partitions - forget them. Note + %% that we do not attempt to deal with individual (other) partitions + %% going away, it's only safe to forget *any* of them when we have seen + %% the back of *all* of them. + Partitions1 = case Partitions -- (Partitions -- alive_nodes()) of + [] -> []; + _ -> Partitions + end, + State#state{partitions = Partitions1}. + handle_live_rabbit(Node) -> ok = rabbit_alarm:on_node_up(Node), ok = rabbit_mnesia:on_node_up(Node). -- cgit v1.2.1 From 3bd41da26d35ac05d6408496bcdd5a3da54997ce Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 12 Mar 2013 18:43:24 +0000 Subject: don't leave garbage behind in policy validation test --- src/rabbit_tests.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 27807b62..1188c554 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1094,6 +1094,7 @@ test_policy_validation() -> {error_string, _} = SetPol("testpos", [-1, 0, 1]), {error_string, _} = SetPol("testeven", [ 1, 2, 3]), + ok = control_action(clear_policy, ["name"]), rabbit_runtime_parameters_test:unregister_policy_validator(), passed. -- cgit v1.2.1 From 3eb7ee986cf0900652532b76c6719da376980328 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 13 Mar 2013 17:16:41 +0000 Subject: rabbit_exchange_type:validate_binding/2. --- src/rabbit_binding.erl | 5 ++++- src/rabbit_exchange.erl | 3 ++- src/rabbit_exchange_type.erl | 7 ++++++- src/rabbit_exchange_type_direct.erl | 6 ++++-- src/rabbit_exchange_type_fanout.erl | 4 +++- src/rabbit_exchange_type_headers.erl | 4 +++- src/rabbit_exchange_type_invalid.erl | 6 ++++-- src/rabbit_exchange_type_topic.erl | 4 +++- 8 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/rabbit_binding.erl b/src/rabbit_binding.erl index 6096e07b..54136404 100644 --- a/src/rabbit_binding.erl +++ b/src/rabbit_binding.erl @@ -153,7 +153,10 @@ exists(Binding) -> add(Binding) -> add(Binding, fun (_Src, _Dst) -> ok end). -add(Binding, InnerFun) -> +add(Binding = #binding{source = XName}, InnerFun) -> + {ok, X = #exchange{type = XType}} = rabbit_exchange:lookup(XName), + Module = rabbit_exchange:type_to_module(XType), + Module:validate_binding(X, Binding), binding_action( Binding, fun (Src, Dst, B) -> diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index c5a6309a..94a37148 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -22,7 +22,7 @@ assert_equivalence/6, assert_args_equivalence/2, check_type/1, lookup/1, lookup_or_die/1, list/1, lookup_scratch/2, update_scratch/3, info_keys/0, info/1, info/2, info_all/1, info_all/2, - route/2, delete/2]). + route/2, delete/2, type_to_module/1]). %% these must be run inside a mnesia tx -export([maybe_auto_delete/1, serial/1, peek_serial/1, update/2]). @@ -83,6 +83,7 @@ (name(), boolean())-> 'ok' | rabbit_types:error('not_found') | rabbit_types:error('in_use')). +-spec(type_to_module/1 :: (type()) -> atom()). -spec(maybe_auto_delete/1:: (rabbit_types:exchange()) -> 'not_deleted' | {'deleted', rabbit_binding:deletions()}). diff --git a/src/rabbit_exchange_type.erl b/src/rabbit_exchange_type.erl index 1fbcb2d8..01001fa2 100644 --- a/src/rabbit_exchange_type.erl +++ b/src/rabbit_exchange_type.erl @@ -37,6 +37,10 @@ %% called BEFORE declaration, to check args etc; may exit with #amqp_error{} -callback validate(rabbit_types:exchange()) -> 'ok'. +%% called BEFORE declaration, to check args etc; may exit with #amqp_error{} +-callback validate_binding( + rabbit_types:exchange(), rabbit_types:binding()) -> 'ok'. + %% called after declaration and recovery -callback create(tx(), rabbit_types:exchange()) -> 'ok'. @@ -67,7 +71,8 @@ -export([behaviour_info/1]). behaviour_info(callbacks) -> - [{description, 0}, {serialise_events, 0}, {route, 2}, {validate, 1}, + [{description, 0}, {serialise_events, 0}, {route, 2}, + {validate, 1}, {validate_binding, 2}, {create, 2}, {delete, 3}, {add_binding, 3}, {remove_bindings, 3}, {assert_args_equivalence, 2}, {policy_changed, 3}]; behaviour_info(_Other) -> diff --git a/src/rabbit_exchange_type_direct.erl b/src/rabbit_exchange_type_direct.erl index 213b24c4..2f216678 100644 --- a/src/rabbit_exchange_type_direct.erl +++ b/src/rabbit_exchange_type_direct.erl @@ -20,8 +20,9 @@ -behaviour(rabbit_exchange_type). -export([description/0, serialise_events/0, route/2]). --export([validate/1, create/2, delete/3, policy_changed/3, - add_binding/3, remove_bindings/3, assert_args_equivalence/2]). +-export([validate/1, validate_binding/2, + create/2, delete/3, policy_changed/3, add_binding/3, + remove_bindings/3, assert_args_equivalence/2]). -rabbit_boot_step({?MODULE, [{description, "exchange type direct"}, @@ -40,6 +41,7 @@ route(#exchange{name = Name}, rabbit_router:match_routing_key(Name, Routes). validate(_X) -> ok. +validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. policy_changed(_Tx, _X1, _X2) -> ok. diff --git a/src/rabbit_exchange_type_fanout.erl b/src/rabbit_exchange_type_fanout.erl index 5b17ed56..612bf4d4 100644 --- a/src/rabbit_exchange_type_fanout.erl +++ b/src/rabbit_exchange_type_fanout.erl @@ -20,7 +20,8 @@ -behaviour(rabbit_exchange_type). -export([description/0, serialise_events/0, route/2]). --export([validate/1, create/2, delete/3, policy_changed/3, add_binding/3, +-export([validate/1, validate_binding/2, + create/2, delete/3, policy_changed/3, add_binding/3, remove_bindings/3, assert_args_equivalence/2]). -rabbit_boot_step({?MODULE, @@ -39,6 +40,7 @@ route(#exchange{name = Name}, _Delivery) -> rabbit_router:match_routing_key(Name, ['_']). validate(_X) -> ok. +validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. policy_changed(_Tx, _X1, _X2) -> ok. diff --git a/src/rabbit_exchange_type_headers.erl b/src/rabbit_exchange_type_headers.erl index 75899160..dbc587ae 100644 --- a/src/rabbit_exchange_type_headers.erl +++ b/src/rabbit_exchange_type_headers.erl @@ -21,7 +21,8 @@ -behaviour(rabbit_exchange_type). -export([description/0, serialise_events/0, route/2]). --export([validate/1, create/2, delete/3, policy_changed/3, add_binding/3, +-export([validate/1, validate_binding/2, + create/2, delete/3, policy_changed/3, add_binding/3, remove_bindings/3, assert_args_equivalence/2]). -rabbit_boot_step({?MODULE, @@ -113,6 +114,7 @@ headers_match([{PK, PT, PV} | PRest], [{DK, DT, DV} | DRest], headers_match(PRest, DRest, AllMatch1, AnyMatch1, MatchKind). validate(_X) -> ok. +validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. policy_changed(_Tx, _X1, _X2) -> ok. diff --git a/src/rabbit_exchange_type_invalid.erl b/src/rabbit_exchange_type_invalid.erl index 6b07351a..72607809 100644 --- a/src/rabbit_exchange_type_invalid.erl +++ b/src/rabbit_exchange_type_invalid.erl @@ -20,8 +20,9 @@ -behaviour(rabbit_exchange_type). -export([description/0, serialise_events/0, route/2]). --export([validate/1, create/2, delete/3, policy_changed/3, - add_binding/3, remove_bindings/3, assert_args_equivalence/2]). +-export([validate/1, validate_binding/2, + create/2, delete/3, policy_changed/3, add_binding/3, + remove_bindings/3, assert_args_equivalence/2]). description() -> [{description, @@ -41,6 +42,7 @@ route(#exchange{name = Name, type = Type}, _) -> [rabbit_misc:rs(Name), Type]). validate(_X) -> ok. +validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. policy_changed(_Tx, _X1, _X2) -> ok. diff --git a/src/rabbit_exchange_type_topic.erl b/src/rabbit_exchange_type_topic.erl index bd8ad1ac..22b65ec2 100644 --- a/src/rabbit_exchange_type_topic.erl +++ b/src/rabbit_exchange_type_topic.erl @@ -21,7 +21,8 @@ -behaviour(rabbit_exchange_type). -export([description/0, serialise_events/0, route/2]). --export([validate/1, create/2, delete/3, policy_changed/3, add_binding/3, +-export([validate/1, validate_binding/2, + create/2, delete/3, policy_changed/3, add_binding/3, remove_bindings/3, assert_args_equivalence/2]). -rabbit_boot_step({?MODULE, @@ -47,6 +48,7 @@ route(#exchange{name = X}, end || RKey <- Routes]). validate(_X) -> ok. +validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(transaction, #exchange{name = X}, _Bs) -> -- cgit v1.2.1 From 797354c2bf2baa43eb9e44eb4d636e876f403e5c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 13 Mar 2013 17:41:27 +0000 Subject: Take advantage of the new mechanism to validate x-match. --- src/rabbit_exchange_type_headers.erl | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/rabbit_exchange_type_headers.erl b/src/rabbit_exchange_type_headers.erl index dbc587ae..a78dce73 100644 --- a/src/rabbit_exchange_type_headers.erl +++ b/src/rabbit_exchange_type_headers.erl @@ -51,14 +51,25 @@ route(#exchange{name = Name}, rabbit_router:match_bindings( Name, fun (#binding{args = Spec}) -> headers_match(Spec, Headers) end). -default_headers_match_kind() -> all. +validate_binding(_X, #binding{args = Args}) -> + case rabbit_misc:table_lookup(Args, <<"x-match">>) of + {longstr, <<"all">>} -> ok; + {longstr, <<"any">>} -> ok; + {longstr, Other} -> rabbit_misc:protocol_error( + precondition_failed, + "Invalid x-match field value ~p; " + "expected all or any", [Other]); + {Type, Other} -> rabbit_misc:protocol_error( + precondition_failed, + "Invalid x-match field type ~p (value ~p); " + "expected longstr", [Type, Other]); + undefined -> rabbit_misc:protocol_error( + precondition_failed, + "x-match field missing", []) + end. 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(). +parse_x_match(<<"any">>) -> any. %% Horrendous matching algorithm. Depends for its merge-like %% (linear-time) behaviour on the lists:keysort @@ -69,17 +80,9 @@ parse_x_match(Other) -> %% 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(Args, Data) -> + {longstr, MK} = rabbit_misc:table_lookup(Args, <<"x-match">>), + headers_match(Args, Data, true, false, parse_x_match(MK)). headers_match([], _Data, AllMatch, _AnyMatch, all) -> AllMatch; @@ -114,7 +117,6 @@ headers_match([{PK, PT, PV} | PRest], [{DK, DT, DV} | DRest], headers_match(PRest, DRest, AllMatch1, AnyMatch1, MatchKind). validate(_X) -> ok. -validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. policy_changed(_Tx, _X1, _X2) -> ok. -- cgit v1.2.1 From 3356b11127663b75359de18873496793e83829ee Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 13 Mar 2013 18:34:05 +0000 Subject: cosmetic --- src/rabbit_exchange.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index c5a6309a..dcad68cb 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -440,8 +440,7 @@ peek_serial(XName, LockType) -> end. invalid_module(T) -> - rabbit_log:warning( - "Could not find exchange type ~s.~n", [T]), + rabbit_log:warning("Could not find exchange type ~s.~n", [T]), put({xtype_to_module, T}, rabbit_exchange_type_invalid), rabbit_exchange_type_invalid. -- cgit v1.2.1 From faf5177eebe306d79d91a8e1d6b59cd72daa81a8 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 14 Mar 2013 11:35:07 +0000 Subject: Move the check inside the tx - this means we only lookup the exchange once and there's no possible race. It means we have to convert the exception to an {error, #amqp_error{}} which then gets converted back to an exception, but never mind... --- src/rabbit_binding.erl | 30 +++++++++++++++++------------- src/rabbit_exchange.erl | 15 +++++++++++++-- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/rabbit_binding.erl b/src/rabbit_binding.erl index 54136404..6bc17482 100644 --- a/src/rabbit_binding.erl +++ b/src/rabbit_binding.erl @@ -153,22 +153,26 @@ exists(Binding) -> add(Binding) -> add(Binding, fun (_Src, _Dst) -> ok end). -add(Binding = #binding{source = XName}, InnerFun) -> - {ok, X = #exchange{type = XType}} = rabbit_exchange:lookup(XName), - Module = rabbit_exchange:type_to_module(XType), - Module:validate_binding(X, Binding), +add(Binding, InnerFun) -> binding_action( Binding, fun (Src, Dst, B) -> - %% this argument is used to check queue exclusivity; - %% in general, we want to fail on that in preference to - %% anything else - case InnerFun(Src, Dst) of - ok -> case mnesia:read({rabbit_route, B}) of - [] -> add(Src, Dst, B); - [_] -> fun rabbit_misc:const_ok/0 - end; - {error, _} = Err -> rabbit_misc:const(Err) + case rabbit_exchange:validate_binding(Src, B) of + ok -> + %% this argument is used to check queue exclusivity; + %% in general, we want to fail on that in preference to + %% anything else + case InnerFun(Src, Dst) of + ok -> + case mnesia:read({rabbit_route, B}) of + [] -> add(Src, Dst, B); + [_] -> fun rabbit_misc:const_ok/0 + end; + {error, _} = Err -> + rabbit_misc:const(Err) + end; + {error, _} = Err -> + rabbit_misc:const(Err) end end). diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 94a37148..18ec83c1 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -22,7 +22,7 @@ assert_equivalence/6, assert_args_equivalence/2, check_type/1, lookup/1, lookup_or_die/1, list/1, lookup_scratch/2, update_scratch/3, info_keys/0, info/1, info/2, info_all/1, info_all/2, - route/2, delete/2, type_to_module/1]). + route/2, delete/2, validate_binding/2]). %% these must be run inside a mnesia tx -export([maybe_auto_delete/1, serial/1, peek_serial/1, update/2]). @@ -83,7 +83,9 @@ (name(), boolean())-> 'ok' | rabbit_types:error('not_found') | rabbit_types:error('in_use')). --spec(type_to_module/1 :: (type()) -> atom()). +-spec(validate_binding/2 :: + (rabbit_types:exchange(), rabbit_types:binding()) + -> rabbit_types:ok_or_error(rabbit_types:amqp_error())). -spec(maybe_auto_delete/1:: (rabbit_types:exchange()) -> 'not_deleted' | {'deleted', rabbit_binding:deletions()}). @@ -400,6 +402,15 @@ delete(XName, IfUnused) -> end end). +validate_binding(X = #exchange{type = XType}, Binding) -> + Module = type_to_module(XType), + try + Module:validate_binding(X, Binding) + catch + exit:Error -> + {error, Error} + end. + maybe_auto_delete(#exchange{auto_delete = false}) -> not_deleted; maybe_auto_delete(#exchange{auto_delete = true} = X) -> -- cgit v1.2.1 From 50396d3a7c29d7e39d6c28383f7dbbf59ab46fc2 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 14 Mar 2013 17:27:24 +0000 Subject: rabbit_client_sup accepts supervision options --- src/rabbit_client_sup.erl | 16 ++++++++++------ src/rabbit_direct.erl | 2 +- src/rabbit_networking.erl | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/rabbit_client_sup.erl b/src/rabbit_client_sup.erl index 9602c512..54bb8671 100644 --- a/src/rabbit_client_sup.erl +++ b/src/rabbit_client_sup.erl @@ -37,12 +37,16 @@ %%---------------------------------------------------------------------------- -start_link(Callback) -> - supervisor2:start_link(?MODULE, Callback). +start_link(CallbackOpts) -> + supervisor2:start_link(?MODULE, CallbackOpts). -start_link(SupName, Callback) -> - supervisor2:start_link(SupName, ?MODULE, Callback). +start_link(SupName, CallbackOpts) -> + supervisor2:start_link(SupName, ?MODULE, CallbackOpts). -init({M,F,A}) -> +init({{M,F,A},Opts}) -> + {Shutdown, Type} = case rabbit_misc:pget(worker_type, Opts, supervisor) of + supervisor -> {infinity, supervisor}; + worker -> {?MAX_WAIT, worker} + end, {ok, {{simple_one_for_one_terminate, 0, 1}, - [{client, {M,F,A}, temporary, infinity, supervisor, [M]}]}}. + [{client, {M,F,A}, temporary, Shutdown, Type, [M]}]}}. diff --git a/src/rabbit_direct.erl b/src/rabbit_direct.erl index 53144f3f..036f354b 100644 --- a/src/rabbit_direct.erl +++ b/src/rabbit_direct.erl @@ -50,7 +50,7 @@ boot() -> rabbit_sup:start_supervisor_child( rabbit_direct_client_sup, rabbit_client_sup, [{local, rabbit_direct_client_sup}, - {rabbit_channel_sup, start_link, []}]). + {{rabbit_channel_sup, start_link, []}, []}]). force_event_refresh() -> [Pid ! force_event_refresh || Pid<- list()], diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index 0a0e51c5..517fa360 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -139,7 +139,7 @@ boot_ssl() -> start() -> rabbit_sup:start_supervisor_child( rabbit_tcp_client_sup, rabbit_client_sup, [{local, rabbit_tcp_client_sup}, - {rabbit_connection_sup,start_link,[]}]). + {{rabbit_connection_sup,start_link,[]}, []}]). ensure_ssl() -> ok = app_utils:start_applications([crypto, public_key, ssl]), -- cgit v1.2.1 From 74e8e188e249319d66fe816f448ca2c19a50330d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 15 Mar 2013 14:00:15 +0000 Subject: Push protocol errors closer to the edge. --- src/rabbit_binding.erl | 4 +++- src/rabbit_channel.erl | 2 ++ src/rabbit_exchange.erl | 9 ++------- src/rabbit_exchange_type.erl | 6 +++--- src/rabbit_exchange_type_headers.erl | 17 ++++++++--------- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/rabbit_binding.erl b/src/rabbit_binding.erl index 6bc17482..f1197a84 100644 --- a/src/rabbit_binding.erl +++ b/src/rabbit_binding.erl @@ -39,7 +39,9 @@ {'resources_missing', [{'not_found', (rabbit_types:binding_source() | rabbit_types:binding_destination())} | - {'absent', rabbit_types:amqqueue()}]})). + {'absent', rabbit_types:amqqueue()}]} | + {'binding_invalid', string(), [any()]})). + -type(bind_ok_or_error() :: 'ok' | bind_errors() | rabbit_types:error('binding_not_found')). -type(bind_res() :: bind_ok_or_error() | rabbit_misc:thunk(bind_ok_or_error())). diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 0510afa9..792a06c9 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1194,6 +1194,8 @@ binding_action(Fun, ExchangeNameBin, DestinationType, DestinationNameBin, not_found, "no binding ~s between ~s and ~s", [RoutingKey, rabbit_misc:rs(ExchangeName), rabbit_misc:rs(DestinationName)]); + {error, {binding_invalid, Fmt, Args}} -> + rabbit_misc:protocol_error(precondition_failed, Fmt, Args); {error, #amqp_error{} = Error} -> rabbit_misc:protocol_error(Error); ok -> return_ok(State, NoWait, ReturnMethod) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 18ec83c1..2de7f8a4 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -85,7 +85,7 @@ rabbit_types:error('in_use')). -spec(validate_binding/2 :: (rabbit_types:exchange(), rabbit_types:binding()) - -> rabbit_types:ok_or_error(rabbit_types:amqp_error())). + -> rabbit_types:ok_or_error({'binding_invalid', string(), [any()]})). -spec(maybe_auto_delete/1:: (rabbit_types:exchange()) -> 'not_deleted' | {'deleted', rabbit_binding:deletions()}). @@ -404,12 +404,7 @@ delete(XName, IfUnused) -> validate_binding(X = #exchange{type = XType}, Binding) -> Module = type_to_module(XType), - try - Module:validate_binding(X, Binding) - catch - exit:Error -> - {error, Error} - end. + Module:validate_binding(X, Binding). maybe_auto_delete(#exchange{auto_delete = false}) -> not_deleted; diff --git a/src/rabbit_exchange_type.erl b/src/rabbit_exchange_type.erl index 01001fa2..acb7d772 100644 --- a/src/rabbit_exchange_type.erl +++ b/src/rabbit_exchange_type.erl @@ -37,9 +37,9 @@ %% called BEFORE declaration, to check args etc; may exit with #amqp_error{} -callback validate(rabbit_types:exchange()) -> 'ok'. -%% called BEFORE declaration, to check args etc; may exit with #amqp_error{} --callback validate_binding( - rabbit_types:exchange(), rabbit_types:binding()) -> 'ok'. +%% called BEFORE declaration, to check args etc +-callback validate_binding(rabbit_types:exchange(), rabbit_types:binding()) -> + rabbit_types:ok_or_error({'binding_invalid', string(), [any()]}). %% called after declaration and recovery -callback create(tx(), rabbit_types:exchange()) -> 'ok'. diff --git a/src/rabbit_exchange_type_headers.erl b/src/rabbit_exchange_type_headers.erl index a78dce73..c03dc5d2 100644 --- a/src/rabbit_exchange_type_headers.erl +++ b/src/rabbit_exchange_type_headers.erl @@ -55,17 +55,16 @@ validate_binding(_X, #binding{args = Args}) -> case rabbit_misc:table_lookup(Args, <<"x-match">>) of {longstr, <<"all">>} -> ok; {longstr, <<"any">>} -> ok; - {longstr, Other} -> rabbit_misc:protocol_error( - precondition_failed, + {longstr, Other} -> {error, + {binding_invalid, "Invalid x-match field value ~p; " - "expected all or any", [Other]); - {Type, Other} -> rabbit_misc:protocol_error( - precondition_failed, + "expected all or any", [Other]}}; + {Type, Other} -> {error, + {binding_invalid, "Invalid x-match field type ~p (value ~p); " - "expected longstr", [Type, Other]); - undefined -> rabbit_misc:protocol_error( - precondition_failed, - "x-match field missing", []) + "expected longstr", [Type, Other]}}; + undefined -> {error, + {binding_invalid, "x-match field missing", []}} end. parse_x_match(<<"all">>) -> all; -- cgit v1.2.1 From ee334419d66b60de7c85bdf233ffc9042ed2df40 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 15 Mar 2013 14:24:46 +0000 Subject: move spec expansion to right place --- src/rabbit_binding.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rabbit_binding.erl b/src/rabbit_binding.erl index f1197a84..cb86e5ae 100644 --- a/src/rabbit_binding.erl +++ b/src/rabbit_binding.erl @@ -39,11 +39,12 @@ {'resources_missing', [{'not_found', (rabbit_types:binding_source() | rabbit_types:binding_destination())} | - {'absent', rabbit_types:amqqueue()}]} | - {'binding_invalid', string(), [any()]})). + {'absent', rabbit_types:amqqueue()}]})). -type(bind_ok_or_error() :: 'ok' | bind_errors() | - rabbit_types:error('binding_not_found')). + rabbit_types:error( + 'binding_not_found' | + {'binding_invalid', string(), [any()]})). -type(bind_res() :: bind_ok_or_error() | rabbit_misc:thunk(bind_ok_or_error())). -type(inner_fun() :: fun((rabbit_types:exchange(), -- cgit v1.2.1 From a4956c5164e652c38a7ad1af3e89b1f7af931672 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 15 Mar 2013 14:38:19 +0000 Subject: correct a spec error discovered by R16B dialyzer --- src/rabbit.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit.erl b/src/rabbit.erl index f3ba022a..3cfa21ba 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -236,7 +236,7 @@ {memory, any()}]). -spec(is_running/0 :: () -> boolean()). -spec(is_running/1 :: (node()) -> boolean()). --spec(environment/0 :: () -> [{param() | term()}]). +-spec(environment/0 :: () -> [{param(), term()}]). -spec(rotate_logs/1 :: (file_suffix()) -> rabbit_types:ok_or_error(any())). -spec(force_event_refresh/0 :: () -> 'ok'). -- cgit v1.2.1 From ba8dd4cb725663a646ee69faba6bc7c1c185d62e Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 17 Mar 2013 20:10:07 +0000 Subject: get rid of superfluous serial() arg to {XT,XD}:policy_changed --- src/rabbit_exchange.erl | 5 ++++- src/rabbit_exchange_decorator.erl | 6 +++--- src/rabbit_exchange_type.erl | 6 +++--- src/rabbit_exchange_type_direct.erl | 4 ++-- src/rabbit_exchange_type_fanout.erl | 4 ++-- src/rabbit_exchange_type_headers.erl | 4 ++-- src/rabbit_exchange_type_invalid.erl | 4 ++-- src/rabbit_exchange_type_topic.erl | 4 ++-- 8 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 27d60872..5f4fb9ec 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -124,7 +124,10 @@ callback(X = #exchange{type = XType}, Fun, Serial0, Args) -> Module = type_to_module(XType), apply(Module, Fun, [Serial(Module:serialise_events()) | Args]). -policy_changed(X1, X2) -> callback(X1, policy_changed, none, [X1, X2]). +policy_changed(X = #exchange{type = XType}, X1) -> + [ok = M:policy_changed(X, X1) || + M <- [type_to_module(XType) | registry_lookup(exchange_decorator)]], + ok. serialise_events(X = #exchange{type = Type}) -> lists:any(fun (M) -> M:serialise_events(X) end, diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 414f9c60..b7ef4c45 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -54,8 +54,8 @@ [rabbit_types:binding()]) -> 'ok'. %% called when the policy attached to this exchange changes. --callback policy_changed( - serial(), rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'. +-callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> + 'ok'. %% called after exchange routing %% return value is a list of queues to be added to the list of @@ -70,7 +70,7 @@ behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 1}, {create, 2}, {delete, 3}, - {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 3}, {route, 2}]; + {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 2}, {route, 2}]; behaviour_info(_Other) -> undefined. diff --git a/src/rabbit_exchange_type.erl b/src/rabbit_exchange_type.erl index acb7d772..fd37631b 100644 --- a/src/rabbit_exchange_type.erl +++ b/src/rabbit_exchange_type.erl @@ -63,8 +63,8 @@ 'ok' | rabbit_types:connection_exit(). %% called when the policy attached to this exchange changes. --callback policy_changed(serial(), rabbit_types:exchange(), - rabbit_types:exchange()) -> 'ok'. +-callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> + 'ok'. -else. @@ -74,7 +74,7 @@ behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 0}, {route, 2}, {validate, 1}, {validate_binding, 2}, {create, 2}, {delete, 3}, {add_binding, 3}, {remove_bindings, 3}, - {assert_args_equivalence, 2}, {policy_changed, 3}]; + {assert_args_equivalence, 2}, {policy_changed, 2}]; behaviour_info(_Other) -> undefined. diff --git a/src/rabbit_exchange_type_direct.erl b/src/rabbit_exchange_type_direct.erl index 2f216678..10a79c55 100644 --- a/src/rabbit_exchange_type_direct.erl +++ b/src/rabbit_exchange_type_direct.erl @@ -21,7 +21,7 @@ -export([description/0, serialise_events/0, route/2]). -export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/3, add_binding/3, + create/2, delete/3, policy_changed/2, add_binding/3, remove_bindings/3, assert_args_equivalence/2]). -rabbit_boot_step({?MODULE, @@ -44,7 +44,7 @@ validate(_X) -> ok. validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. -policy_changed(_Tx, _X1, _X2) -> ok. +policy_changed(_X1, _X2) -> ok. add_binding(_Tx, _X, _B) -> ok. remove_bindings(_Tx, _X, _Bs) -> ok. assert_args_equivalence(X, Args) -> diff --git a/src/rabbit_exchange_type_fanout.erl b/src/rabbit_exchange_type_fanout.erl index 612bf4d4..3ebd8548 100644 --- a/src/rabbit_exchange_type_fanout.erl +++ b/src/rabbit_exchange_type_fanout.erl @@ -21,7 +21,7 @@ -export([description/0, serialise_events/0, route/2]). -export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/3, add_binding/3, + create/2, delete/3, policy_changed/2, add_binding/3, remove_bindings/3, assert_args_equivalence/2]). -rabbit_boot_step({?MODULE, @@ -43,7 +43,7 @@ validate(_X) -> ok. validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. -policy_changed(_Tx, _X1, _X2) -> ok. +policy_changed(_X1, _X2) -> ok. add_binding(_Tx, _X, _B) -> ok. remove_bindings(_Tx, _X, _Bs) -> ok. assert_args_equivalence(X, Args) -> diff --git a/src/rabbit_exchange_type_headers.erl b/src/rabbit_exchange_type_headers.erl index c03dc5d2..cf2d3140 100644 --- a/src/rabbit_exchange_type_headers.erl +++ b/src/rabbit_exchange_type_headers.erl @@ -22,7 +22,7 @@ -export([description/0, serialise_events/0, route/2]). -export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/3, add_binding/3, + create/2, delete/3, policy_changed/2, add_binding/3, remove_bindings/3, assert_args_equivalence/2]). -rabbit_boot_step({?MODULE, @@ -118,7 +118,7 @@ headers_match([{PK, PT, PV} | PRest], [{DK, DT, DV} | DRest], validate(_X) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. -policy_changed(_Tx, _X1, _X2) -> ok. +policy_changed(_X1, _X2) -> ok. add_binding(_Tx, _X, _B) -> ok. remove_bindings(_Tx, _X, _Bs) -> ok. assert_args_equivalence(X, Args) -> diff --git a/src/rabbit_exchange_type_invalid.erl b/src/rabbit_exchange_type_invalid.erl index 72607809..07a8004a 100644 --- a/src/rabbit_exchange_type_invalid.erl +++ b/src/rabbit_exchange_type_invalid.erl @@ -21,7 +21,7 @@ -export([description/0, serialise_events/0, route/2]). -export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/3, add_binding/3, + create/2, delete/3, policy_changed/2, add_binding/3, remove_bindings/3, assert_args_equivalence/2]). description() -> @@ -45,7 +45,7 @@ validate(_X) -> ok. validate_binding(_X, _B) -> ok. create(_Tx, _X) -> ok. delete(_Tx, _X, _Bs) -> ok. -policy_changed(_Tx, _X1, _X2) -> ok. +policy_changed(_X1, _X2) -> ok. add_binding(_Tx, _X, _B) -> ok. remove_bindings(_Tx, _X, _Bs) -> ok. assert_args_equivalence(X, Args) -> diff --git a/src/rabbit_exchange_type_topic.erl b/src/rabbit_exchange_type_topic.erl index 22b65ec2..ce76ccb0 100644 --- a/src/rabbit_exchange_type_topic.erl +++ b/src/rabbit_exchange_type_topic.erl @@ -22,7 +22,7 @@ -export([description/0, serialise_events/0, route/2]). -export([validate/1, validate_binding/2, - create/2, delete/3, policy_changed/3, add_binding/3, + create/2, delete/3, policy_changed/2, add_binding/3, remove_bindings/3, assert_args_equivalence/2]). -rabbit_boot_step({?MODULE, @@ -59,7 +59,7 @@ delete(transaction, #exchange{name = X}, _Bs) -> delete(none, _Exchange, _Bs) -> ok. -policy_changed(_Tx, _X1, _X2) -> ok. +policy_changed(_X1, _X2) -> ok. add_binding(transaction, _Exchange, Binding) -> internal_add_binding(Binding); -- cgit v1.2.1 From 1233f304858989a08c12579248d5e17c858c5dd9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sun, 17 Mar 2013 20:14:47 +0000 Subject: cosmetic: make callback function def order match usage --- src/rabbit_exchange_decorator.erl | 10 +++++----- src/rabbit_exchange_type.erl | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index b7ef4c45..8f17adfc 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -45,6 +45,10 @@ -callback delete(tx(), rabbit_types:exchange(), [rabbit_types:binding()]) -> 'ok'. +%% called when the policy attached to this exchange changes. +-callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> + 'ok'. + %% called after a binding has been added or recovered -callback add_binding(serial(), rabbit_types:exchange(), rabbit_types:binding()) -> 'ok'. @@ -53,10 +57,6 @@ -callback remove_bindings(serial(), rabbit_types:exchange(), [rabbit_types:binding()]) -> 'ok'. -%% called when the policy attached to this exchange changes. --callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> - 'ok'. - %% called after exchange routing %% return value is a list of queues to be added to the list of %% destination queues. decorators must register separately for @@ -70,7 +70,7 @@ behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 1}, {create, 2}, {delete, 3}, - {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 2}, {route, 2}]; + {policy_changed, 2}, {add_binding, 3}, {remove_bindings, 3}, {route, 2}]; behaviour_info(_Other) -> undefined. diff --git a/src/rabbit_exchange_type.erl b/src/rabbit_exchange_type.erl index fd37631b..ebc59501 100644 --- a/src/rabbit_exchange_type.erl +++ b/src/rabbit_exchange_type.erl @@ -48,6 +48,10 @@ -callback delete(tx(), rabbit_types:exchange(), [rabbit_types:binding()]) -> 'ok'. +%% called when the policy attached to this exchange changes. +-callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> + 'ok'. + %% called after a binding has been added or recovered -callback add_binding(serial(), rabbit_types:exchange(), rabbit_types:binding()) -> 'ok'. @@ -62,19 +66,15 @@ rabbit_framing:amqp_table()) -> 'ok' | rabbit_types:connection_exit(). -%% called when the policy attached to this exchange changes. --callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> - 'ok'. - -else. -export([behaviour_info/1]). behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 0}, {route, 2}, - {validate, 1}, {validate_binding, 2}, + {validate, 1}, {validate_binding, 2}, {policy_changed, 2}, {create, 2}, {delete, 3}, {add_binding, 3}, {remove_bindings, 3}, - {assert_args_equivalence, 2}, {policy_changed, 2}]; + {assert_args_equivalence, 2}]; behaviour_info(_Other) -> undefined. -- cgit v1.2.1 From 8c1d2292c3b68f7251f8b8e593a77a388db90a7c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 18 Mar 2013 11:17:02 +0000 Subject: More archs --- packaging/debs/apt-repository/distributions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/debs/apt-repository/distributions b/packaging/debs/apt-repository/distributions index 183eb034..61fd778a 100644 --- a/packaging/debs/apt-repository/distributions +++ b/packaging/debs/apt-repository/distributions @@ -2,6 +2,6 @@ Origin: RabbitMQ Label: RabbitMQ Repository for Debian / Ubuntu etc Suite: testing Codename: kitten -Architectures: arm hppa ia64 mips mipsel s390 sparc i386 amd64 powerpc source +Architectures: AVR32 alpha amd64 arm armel armhf hppa hurd-i386 i386 ia64 kfreebsd-amd64 kfreebsd-i386 m32 m68k mips mipsel netbsd-alpha netbsd-i386 powerpc s390 s390x sh sparc Components: main Description: RabbitMQ Repository for Debian / Ubuntu etc -- cgit v1.2.1 From 01012b70dadc9f8e1bca13330caaacff04505f71 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 18 Mar 2013 12:01:48 +0000 Subject: Code comment about clearing partitions --- src/rabbit_node_monitor.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 558596ef..de53b7f0 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -344,8 +344,8 @@ handle_dead_rabbit_state(State = #state{partitions = Partitions}) -> %% If we have been partitioned, and we are now in the only remaining %% partition, we no longer care about partitions - forget them. Note %% that we do not attempt to deal with individual (other) partitions - %% going away, it's only safe to forget *any* of them when we have seen - %% the back of *all* of them. + %% going away. It's only safe to forget anything about partitions when + %% there are no partitions. Partitions1 = case Partitions -- (Partitions -- alive_nodes()) of [] -> []; _ -> Partitions -- cgit v1.2.1 From 8f15e63d08a2c30bd5db9252a49250f9269f0411 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 18 Mar 2013 13:59:51 +0000 Subject: Different registration of exchange decorators that modify routing --- src/rabbit_exchange_decorator.erl | 9 +------ src/rabbit_registry.erl | 49 ++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 414f9c60..491c9d27 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -57,20 +57,13 @@ -callback policy_changed( serial(), rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'. -%% called after exchange routing -%% return value is a list of queues to be added to the list of -%% destination queues. decorators must register separately for -%% this callback using exchange_decorator_route. --callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> - [rabbit_amqqueue:name()]. - -else. -export([behaviour_info/1]). behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 1}, {create, 2}, {delete, 3}, - {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 3}, {route, 2}]; + {add_binding, 3}, {remove_bindings, 3}, {policy_changed, 3}]; behaviour_info(_Other) -> undefined. diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl index 3514e780..db07dcdb 100644 --- a/src/rabbit_registry.erl +++ b/src/rabbit_registry.erl @@ -84,12 +84,44 @@ internal_binary_to_type(TypeBin) when is_binary(TypeBin) -> internal_register(Class, TypeName, ModuleName) when is_atom(Class), is_binary(TypeName), is_atom(ModuleName) -> ok = sanity_check_module(class_module(Class), ModuleName), - true = ets:insert(?ETS_NAME, - {{Class, internal_binary_to_type(TypeName)}, ModuleName}), + RegArg = {{Class, internal_binary_to_type(TypeName)}, ModuleName}, + true = ets:insert(?ETS_NAME, RegArg), + conditional_register(RegArg), ok. internal_unregister(Class, TypeName) -> - true = ets:delete(?ETS_NAME, {Class, internal_binary_to_type(TypeName)}), + UnregArg = {Class, internal_binary_to_type(TypeName)}, + conditional_unregister(UnregArg), + true = ets:delete(?ETS_NAME, UnregArg), + ok. + +conditional_register({{exchange_decorator, Type}, ModuleName}) -> + case erlang:function_exported(ModuleName, route, 2) of + true -> + true = ets:insert(?ETS_NAME, {{exchange_decorator_route, Type}, + ModuleName}), + ok; + false -> + ok + end; +conditional_register(_) -> + ok. + +conditional_unregister({exchange_decorator, Type}) -> + case lookup_module(exchange_decorator, Type) of + {ok, ModuleName} -> + case erlang:function_exported(ModuleName, route, 2) of + true -> + true = ets:delete(?ETS_NAME, {exchange_decorator_route, + Type}), + ok; + false -> + ok + end; + {error, not_found} -> + ok + end; +conditional_unregister(_) -> ok. sanity_check_module(ClassModule, Module) -> @@ -104,12 +136,11 @@ sanity_check_module(ClassModule, Module) -> true -> ok end. -class_module(exchange) -> rabbit_exchange_type; -class_module(auth_mechanism) -> rabbit_auth_mechanism; -class_module(runtime_parameter) -> rabbit_runtime_parameter; -class_module(exchange_decorator) -> rabbit_exchange_decorator; -class_module(exchange_decorator_route) -> rabbit_exchange_decorator; -class_module(policy_validator) -> rabbit_policy_validator. +class_module(exchange) -> rabbit_exchange_type; +class_module(auth_mechanism) -> rabbit_auth_mechanism; +class_module(runtime_parameter) -> rabbit_runtime_parameter; +class_module(exchange_decorator) -> rabbit_exchange_decorator; +class_module(policy_validator) -> rabbit_policy_validator. %%--------------------------------------------------------------------------- -- cgit v1.2.1 From fd238eda9de479403daf9debbbea760988291c19 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 19 Mar 2013 11:10:40 +0000 Subject: limiter API revision, part 1/2 - channel-side API --- src/rabbit_amqqueue.erl | 2 +- src/rabbit_amqqueue_process.erl | 19 ++-- src/rabbit_channel.erl | 68 ++++++------- src/rabbit_channel_sup.erl | 4 +- src/rabbit_limiter.erl | 207 +++++++++++++++++++--------------------- 5 files changed, 139 insertions(+), 161 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 82ac74fa..bd5de239 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -144,7 +144,7 @@ -spec(ack/3 :: (pid(), [msg_id()], pid()) -> 'ok'). -spec(reject/4 :: (pid(), [msg_id()], boolean(), pid()) -> 'ok'). -spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). --spec(limit_all/3 :: (qpids(), pid(), rabbit_limiter:token()) -> +-spec(limit_all/3 :: (qpids(), pid(), rabbit_limiter:lstate()) -> ok_or_errors()). -spec(basic_get/3 :: (rabbit_types:amqqueue(), pid(), boolean()) -> {'ok', non_neg_integer(), qmsg()} | 'empty'). diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 18b641d4..0ddc9eba 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -372,7 +372,7 @@ ch_record(ChPid) -> consumer_count = 0, blocked_consumers = queue:new(), is_limit_active = false, - limiter = rabbit_limiter:make_token(), + limiter = undefined, unsent_message_count = 0}, put(Key, C), C; @@ -395,18 +395,17 @@ store_ch_record(C = #cr{ch_pid = ChPid}) -> erase_ch_record(#cr{ch_pid = ChPid, limiter = Limiter, monitor_ref = MonitorRef}) -> - ok = rabbit_limiter:unregister(Limiter, self()), + ok = rabbit_limiter:unregister(Limiter), erlang:demonitor(MonitorRef), erase({ch, ChPid}), ok. update_consumer_count(C = #cr{consumer_count = 0, limiter = Limiter}, +1) -> - ok = rabbit_limiter:register(Limiter, self()), + ok = rabbit_limiter:register(Limiter), update_ch_record(C#cr{consumer_count = 1}); update_consumer_count(C = #cr{consumer_count = 1, limiter = Limiter}, -1) -> - ok = rabbit_limiter:unregister(Limiter, self()), - update_ch_record(C#cr{consumer_count = 0, - limiter = rabbit_limiter:make_token()}); + ok = rabbit_limiter:unregister(Limiter), + update_ch_record(C#cr{consumer_count = 0, limiter = undefined}); update_consumer_count(C = #cr{consumer_count = Count}, Delta) -> update_ch_record(C#cr{consumer_count = Count + Delta}). @@ -444,7 +443,7 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> case is_ch_blocked(C) of true -> block_consumer(C, E), {false, State}; - false -> case rabbit_limiter:can_send(C#cr.limiter, self(), + false -> case rabbit_limiter:can_send(C#cr.limiter, Consumer#consumer.ack_required) of false -> block_consumer(C#cr{is_limit_active = true}, E), {false, State}; @@ -1308,11 +1307,11 @@ handle_cast({limit, ChPid, Limiter}, State) -> limiter = OldLimiter, is_limit_active = OldLimited}) -> case (ConsumerCount =/= 0 andalso - not rabbit_limiter:is_enabled(OldLimiter)) of - true -> ok = rabbit_limiter:register(Limiter, self()); + not rabbit_limiter:is_active(OldLimiter)) of + true -> ok = rabbit_limiter:register(Limiter); false -> ok end, - Limited = OldLimited andalso rabbit_limiter:is_enabled(Limiter), + Limited = OldLimited andalso rabbit_limiter:is_active(Limiter), C#cr{limiter = Limiter, is_limit_active = Limited} end)); diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 792a06c9..cda5747a 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -82,7 +82,7 @@ (channel_number(), pid(), pid(), pid(), string(), rabbit_types:protocol(), rabbit_types:user(), rabbit_types:vhost(), rabbit_framing:amqp_table(), - pid(), rabbit_limiter:token()) -> rabbit_types:ok_pid_or_error()). + pid(), pid()) -> rabbit_types:ok_pid_or_error()). -spec(do/2 :: (pid(), rabbit_framing:amqp_method_record()) -> 'ok'). -spec(do/3 :: (pid(), rabbit_framing:amqp_method_record(), rabbit_types:maybe(rabbit_types:content())) -> 'ok'). @@ -180,7 +180,7 @@ force_event_refresh() -> %%--------------------------------------------------------------------------- init([Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, VHost, - Capabilities, CollectorPid, Limiter]) -> + Capabilities, CollectorPid, LimiterPid]) -> process_flag(trap_exit, true), ok = pg_local:join(rabbit_channels, self()), State = #ch{state = starting, @@ -190,7 +190,7 @@ init([Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User, VHost, writer_pid = WriterPid, conn_pid = ConnPid, conn_name = ConnName, - limiter = Limiter, + limiter = rabbit_limiter:new(LimiterPid), tx = none, next_tag = 1, unacked_message_q = queue:new(), @@ -804,18 +804,10 @@ handle_method(#'basic.qos'{prefetch_size = Size}, _, _State) when Size /= 0 -> "prefetch_size!=0 (~w)", [Size]); handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, - State = #ch{limiter = Limiter}) -> - Limiter1 = case {rabbit_limiter:is_enabled(Limiter), PrefetchCount} of - {false, 0} -> Limiter; - {false, _} -> enable_limiter(State); - {_, _} -> Limiter - end, - Limiter3 = case rabbit_limiter:limit(Limiter1, PrefetchCount) of - ok -> Limiter1; - {disabled, Limiter2} -> ok = limit_queues(Limiter2, State), - Limiter2 - end, - {reply, #'basic.qos_ok'{}, State#ch{limiter = Limiter3}}; + State = #ch{limiter = Limiter, unacked_message_q = UAMQ}) -> + Limiter1 = rabbit_limiter:limit(Limiter, PrefetchCount, queue:len(UAMQ)), + {reply, #'basic.qos_ok'{}, + maybe_limit_queues(Limiter, Limiter1, State#ch{limiter = Limiter1})}; handle_method(#'basic.recover_async'{requeue = true}, _, State = #ch{unacked_message_q = UAMQ, @@ -1078,25 +1070,23 @@ handle_method(#'confirm.select'{nowait = NoWait}, _, State) -> handle_method(#'channel.flow'{active = true}, _, State = #ch{limiter = Limiter}) -> - Limiter2 = case rabbit_limiter:unblock(Limiter) of - ok -> Limiter; - {disabled, Limiter1} -> ok = limit_queues(Limiter1, State), - Limiter1 - end, - {reply, #'channel.flow_ok'{active = true}, State#ch{limiter = Limiter2}}; + Limiter1 = rabbit_limiter:unblock(Limiter), + {reply, #'channel.flow_ok'{active = true}, + maybe_limit_queues(Limiter, Limiter1, State#ch{limiter = Limiter1})}; handle_method(#'channel.flow'{active = false}, _, State = #ch{consumer_mapping = Consumers, limiter = Limiter}) -> - Limiter1 = case rabbit_limiter:is_enabled(Limiter) of - true -> Limiter; - false -> enable_limiter(State) - end, - State1 = State#ch{limiter = Limiter1}, - ok = rabbit_limiter:block(Limiter1), - QPids = consumer_queues(Consumers), - ok = rabbit_amqqueue:flush_all(QPids, self()), - {noreply, maybe_send_flow_ok(State1#ch{blocking = sets:from_list(QPids)})}; + case rabbit_limiter:is_blocked(Limiter) of + true -> {noreply, maybe_send_flow_ok(State)}; + false -> Limiter1 = rabbit_limiter:block(Limiter), + State1 = maybe_limit_queues(Limiter, Limiter1, + State#ch{limiter = Limiter1}), + QPids = consumer_queues(Consumers), + ok = rabbit_amqqueue:flush_all(QPids, self()), + {noreply, maybe_send_flow_ok( + State1#ch{blocking = sets:from_list(QPids)})} + end; handle_method(_MethodRecord, _Content, _State) -> rabbit_misc:protocol_error( @@ -1332,14 +1322,14 @@ foreach_per_queue(F, UAL) -> end, gb_trees:empty(), UAL), rabbit_misc:gb_trees_foreach(F, T). -enable_limiter(State = #ch{unacked_message_q = UAMQ, - limiter = Limiter}) -> - Limiter1 = rabbit_limiter:enable(Limiter, queue:len(UAMQ)), - ok = limit_queues(Limiter1, State), - Limiter1. - -limit_queues(Limiter, #ch{consumer_mapping = Consumers}) -> - rabbit_amqqueue:limit_all(consumer_queues(Consumers), self(), Limiter). +maybe_limit_queues(OldLimiter, NewLimiter, State) -> + case ((not rabbit_limiter:is_active(OldLimiter)) andalso + rabbit_limiter:is_active(NewLimiter)) of + true -> Queues = consumer_queues(State#ch.consumer_mapping), + rabbit_amqqueue:limit_all(Queues, self(), NewLimiter); + false -> ok + end, + State. consumer_queues(Consumers) -> lists:usort([QPid || @@ -1350,7 +1340,7 @@ consumer_queues(Consumers) -> %% messages sent in a response to a basic.get (identified by their %% 'none' consumer tag) notify_limiter(Limiter, Acked) -> - case rabbit_limiter:is_enabled(Limiter) of + case rabbit_limiter:is_limited(Limiter) of false -> ok; true -> case lists:foldl(fun ({_, none, _}, Acc) -> Acc; ({_, _, _}, Acc) -> Acc + 1 diff --git a/src/rabbit_channel_sup.erl b/src/rabbit_channel_sup.erl index 8ea44a81..a0c7624b 100644 --- a/src/rabbit_channel_sup.erl +++ b/src/rabbit_channel_sup.erl @@ -58,7 +58,7 @@ start_link({tcp, Sock, Channel, FrameMax, ReaderPid, ConnName, Protocol, User, {channel, {rabbit_channel, start_link, [Channel, ReaderPid, WriterPid, ReaderPid, ConnName, Protocol, User, VHost, Capabilities, Collector, - rabbit_limiter:make_token(LimiterPid)]}, + LimiterPid]}, intrinsic, ?MAX_WAIT, worker, [rabbit_channel]}), {ok, AState} = rabbit_command_assembler:init(Protocol), {ok, SupPid, {ChannelPid, AState}}; @@ -72,7 +72,7 @@ start_link({direct, Channel, ClientChannelPid, ConnPid, ConnName, Protocol, {channel, {rabbit_channel, start_link, [Channel, ClientChannelPid, ClientChannelPid, ConnPid, ConnName, Protocol, User, VHost, Capabilities, Collector, - rabbit_limiter:make_token(LimiterPid)]}, + LimiterPid]}, intrinsic, ?MAX_WAIT, worker, [rabbit_channel]}), {ok, SupPid, {ChannelPid, none}}. diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 8a7d14fe..ae656328 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -18,38 +18,43 @@ -behaviour(gen_server2). +-export([start_link/0]). +-export([new/1, limit/3, unlimit/1, block/1, unblock/1, + is_limited/1, is_blocked/1, is_active/1, get_limit/1, ack/2]). +-export([can_send/2, register/1, unregister/1]). + -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, prioritise_call/3]). --export([start_link/0, make_token/0, make_token/1, is_enabled/1, enable/2, - disable/1]). --export([limit/2, can_send/3, ack/2, register/2, unregister/2]). --export([get_limit/1, block/1, unblock/1, is_blocked/1]). %%---------------------------------------------------------------------------- --record(token, {pid, enabled}). +-record(lstate, {pid, limited, blocked}). -ifdef(use_specs). --export_type([token/0]). +-export_type([lstate/0]). --opaque(token() :: #token{}). +-opaque(lstate() :: #lstate {pid :: pid(), + limited :: boolean(), + blocked :: boolean()}). -spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). --spec(make_token/0 :: () -> token()). --spec(make_token/1 :: ('undefined' | pid()) -> token()). --spec(is_enabled/1 :: (token()) -> boolean()). --spec(enable/2 :: (token(), non_neg_integer()) -> token()). --spec(disable/1 :: (token()) -> token()). --spec(limit/2 :: (token(), non_neg_integer()) -> 'ok' | {'disabled', token()}). --spec(can_send/3 :: (token(), pid(), boolean()) -> boolean()). --spec(ack/2 :: (token(), non_neg_integer()) -> 'ok'). --spec(register/2 :: (token(), pid()) -> 'ok'). --spec(unregister/2 :: (token(), pid()) -> 'ok'). --spec(get_limit/1 :: (token()) -> non_neg_integer()). --spec(block/1 :: (token()) -> 'ok'). --spec(unblock/1 :: (token()) -> 'ok' | {'disabled', token()}). --spec(is_blocked/1 :: (token()) -> boolean()). +-spec(new/1 :: (pid()) -> lstate()). + +-spec(limit/3 :: (lstate(), non_neg_integer(), non_neg_integer()) -> + lstate()). +-spec(unlimit/1 :: (lstate()) -> lstate()). +-spec(block/1 :: (lstate()) -> lstate()). +-spec(unblock/1 :: (lstate()) -> lstate()). +-spec(is_limited/1 :: (lstate()) -> boolean()). +-spec(is_blocked/1 :: (lstate()) -> boolean()). +-spec(is_active/1 :: (lstate()) -> boolean()). +-spec(get_limit/1 :: (lstate()) -> non_neg_integer()). +-spec(ack/2 :: (lstate(), non_neg_integer()) -> 'ok'). + +-spec(can_send/2 :: (lstate(), boolean()) -> boolean()). +-spec(register/1 :: (lstate()) -> 'ok'). +-spec(unregister/1 :: (lstate()) -> 'ok'). -endif. @@ -70,65 +75,95 @@ start_link() -> gen_server2:start_link(?MODULE, [], []). -make_token() -> make_token(undefined). -make_token(Pid) -> #token{pid = Pid, enabled = false}. +new(Pid) -> + %% this a 'call' to ensure that it is invoked at most once. + ok = gen_server:call(Pid, {new, self()}), + #lstate{pid = Pid, limited = false, blocked = false}. -is_enabled(#token{enabled = Enabled}) -> Enabled. +limit(L, PrefetchCount, UnackedCount) when PrefetchCount > 0 -> + ok = gen_server:call(L#lstate.pid, {limit, PrefetchCount, UnackedCount}), + L#lstate{limited = true}. -enable(#token{pid = Pid} = Token, Volume) -> - gen_server2:call(Pid, {enable, Token, self(), Volume}, infinity). +unlimit(L) -> + ok = gen_server:call(L#lstate.pid, unlimit), + L#lstate{limited = false}. -disable(#token{pid = Pid} = Token) -> - gen_server2:call(Pid, {disable, Token}, infinity). +block(L) -> + ok = gen_server:call(L#lstate.pid, block), + L#lstate{blocked = true}. -limit(Limiter, PrefetchCount) -> - maybe_call(Limiter, {limit, PrefetchCount, Limiter}, ok). +unblock(L) -> + ok = gen_server:call(L#lstate.pid, unblock), + L#lstate{blocked = false}. -%% Ask the limiter whether the queue can deliver a message without -%% breaching a limit. Note that we don't use maybe_call here in order -%% to avoid always going through with_exit_handler/2, even when the -%% limiter is disabled. -can_send(#token{pid = Pid, enabled = true}, QPid, AckRequired) -> - rabbit_misc:with_exit_handler( - fun () -> true end, - fun () -> - gen_server2:call(Pid, {can_send, QPid, AckRequired}, infinity) - end); -can_send(_, _, _) -> - true. +is_limited(#lstate{limited = Limited}) -> Limited. -%% Let the limiter know that the channel has received some acks from a -%% consumer -ack(Limiter, Count) -> maybe_cast(Limiter, {ack, Count}). +is_blocked(#lstate{blocked = Blocked}) -> Blocked. -register(Limiter, QPid) -> maybe_cast(Limiter, {register, QPid}). +is_active(L) -> is_limited(L) orelse is_blocked(L). -unregister(Limiter, QPid) -> maybe_cast(Limiter, {unregister, QPid}). +get_limit(#lstate{limited = false}) -> 0; +get_limit(L) -> gen_server:call(L#lstate.pid, get_limit). -get_limit(Limiter) -> - rabbit_misc:with_exit_handler( - fun () -> 0 end, - fun () -> maybe_call(Limiter, get_limit, 0) end). +ack(#lstate{limited = false}, _AckCount) -> ok; +ack(L, AckCount) -> gen_server:cast(L#lstate.pid, {ack, AckCount}). -block(Limiter) -> - maybe_call(Limiter, block, ok). +%% Ask the limiter whether the queue can deliver a message without +%% breaching a limit. +can_send(L, AckRequired) -> + case is_active(L) of + false -> true; + true -> rabbit_misc:with_exit_handler( + fun () -> true end, + fun () -> Msg = {can_send, self(), AckRequired}, + gen_server2:call(L#lstate.pid, Msg, infinity) + end) + end. -unblock(Limiter) -> - maybe_call(Limiter, {unblock, Limiter}, ok). +register(L) -> + case is_active(L) of + false -> ok; + true -> gen_server:cast(L#lstate.pid, {register, self()}) + end. -is_blocked(Limiter) -> - maybe_call(Limiter, is_blocked, false). +unregister(L) -> + case is_active(L) of + false -> ok; + true -> gen_server:cast(L#lstate.pid, {unregister, self()}) + end. %%---------------------------------------------------------------------------- %% gen_server callbacks %%---------------------------------------------------------------------------- -init([]) -> - {ok, #lim{}}. +init([]) -> {ok, #lim{}}. prioritise_call(get_limit, _From, _State) -> 9; prioritise_call(_Msg, _From, _State) -> 0. +handle_call({new, ChPid}, _From, State = #lim{ch_pid = undefined}) -> + {reply, ok, State#lim{ch_pid = ChPid}}; + +handle_call({limit, PrefetchCount, UnackedCount}, _From, State) -> + %% assertion + true = State#lim.prefetch_count == 0 orelse + State#lim.volume == UnackedCount, + {reply, ok, maybe_notify(State, State#lim{prefetch_count = PrefetchCount, + volume = UnackedCount})}; + +handle_call(unlimit, _From, State) -> + {reply, ok, maybe_notify(State, State#lim{prefetch_count = 0, + volume = 0})}; + +handle_call(block, _From, State) -> + {reply, ok, State#lim{blocked = true}}; + +handle_call(unblock, _From, State) -> + {reply, ok, maybe_notify(State, State#lim{blocked = false})}; + +handle_call(get_limit, _From, State = #lim{prefetch_count = PrefetchCount}) -> + {reply, PrefetchCount, State}; + handle_call({can_send, QPid, _AckRequired}, _From, State = #lim{blocked = true}) -> {reply, false, limit_queue(QPid, State)}; @@ -139,45 +174,13 @@ handle_call({can_send, QPid, AckRequired}, _From, false -> {reply, true, State#lim{volume = if AckRequired -> Volume + 1; true -> Volume end}} - end; - -handle_call(get_limit, _From, State = #lim{prefetch_count = PrefetchCount}) -> - {reply, PrefetchCount, State}; - -handle_call({limit, PrefetchCount, Token}, _From, State) -> - case maybe_notify(State, State#lim{prefetch_count = PrefetchCount}) of - {cont, State1} -> - {reply, ok, State1}; - {stop, State1} -> - {reply, {disabled, Token#token{enabled = false}}, State1} - end; - -handle_call(block, _From, State) -> - {reply, ok, State#lim{blocked = true}}; - -handle_call({unblock, Token}, _From, State) -> - case maybe_notify(State, State#lim{blocked = false}) of - {cont, State1} -> - {reply, ok, State1}; - {stop, State1} -> - {reply, {disabled, Token#token{enabled = false}}, State1} - end; - -handle_call(is_blocked, _From, State) -> - {reply, blocked(State), State}; - -handle_call({enable, Token, Channel, Volume}, _From, State) -> - {reply, Token#token{enabled = true}, - State#lim{ch_pid = Channel, volume = Volume}}; -handle_call({disable, Token}, _From, State) -> - {reply, Token#token{enabled = false}, State}. + end. handle_cast({ack, Count}, State = #lim{volume = Volume}) -> NewVolume = if Volume == 0 -> 0; true -> Volume - Count end, - {cont, State1} = maybe_notify(State, State#lim{volume = NewVolume}), - {noreply, State1}; + {noreply, maybe_notify(State, State#lim{volume = NewVolume})}; handle_cast({register, QPid}, State) -> {noreply, remember_queue(QPid, State)}; @@ -201,24 +204,10 @@ code_change(_, State, _) -> maybe_notify(OldState, NewState) -> case (limit_reached(OldState) orelse blocked(OldState)) andalso not (limit_reached(NewState) orelse blocked(NewState)) of - true -> NewState1 = notify_queues(NewState), - {case NewState1#lim.prefetch_count of - 0 -> stop; - _ -> cont - end, NewState1}; - false -> {cont, NewState} + true -> notify_queues(NewState); + false -> NewState end. -maybe_call(#token{pid = Pid, enabled = true}, Call, _Default) -> - gen_server2:call(Pid, Call, infinity); -maybe_call(_, _Call, Default) -> - Default. - -maybe_cast(#token{pid = Pid, enabled = true}, Cast) -> - gen_server2:cast(Pid, Cast); -maybe_cast(_, _Call) -> - ok. - limit_reached(#lim{prefetch_count = Limit, volume = Volume}) -> Limit =/= 0 andalso Volume >= Limit. -- cgit v1.2.1 From 7122822f79823b2e6fe80aa16c99aeb1af5d0e6b Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 19 Mar 2013 11:56:41 +0000 Subject: Perform route decoration differently --- src/rabbit_exchange.erl | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index c5a6309a..c2c7d947 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -320,34 +320,34 @@ route(#exchange{name = #resource{virtual_host = VHost, %% Optimisation [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; {Decorators, _} -> - QNames = route1(Delivery, {[X], XName, []}), - lists:usort(decorate_route(Decorators, X, Delivery, QNames)) + lists:usort(route1(Delivery, Decorators, {[X], XName, []})) end. -decorate_route([], _X, _Delivery, QNames) -> +route1(_, _, {[], _, QNames}) -> QNames; -decorate_route(Decorators, X, Delivery, QNames) -> - QNames ++ - lists:append([Decorator:route(X, Delivery) || Decorator <- Decorators]). - -route1(_, {[], _, QNames}) -> - QNames; -route1(Delivery, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> - DstNames = process_alternate( - X, ((type_to_module(Type)):route(X, Delivery))), - route1(Delivery, +route1(Delivery, Decorators, + {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> + ExchangeDests = (type_to_module(Type)):route(X, Delivery), + AlternateDest = process_alternate(X, ExchangeDests), + DecorateDests = process_decorators(Delivery, Decorators, X), + route1(Delivery, Decorators, lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, - DstNames)). + AlternateDest ++ DecorateDests ++ ExchangeDests)). -process_alternate(#exchange{arguments = []}, Results) -> %% optimisation - Results; +process_alternate(#exchange{arguments = []}, _Results) -> %% optimisation + []; process_alternate(#exchange{name = XName, arguments = Args}, []) -> case rabbit_misc:r_arg(XName, exchange, Args, <<"alternate-exchange">>) of undefined -> []; AName -> [AName] end; -process_alternate(_X, Results) -> - Results. +process_alternate(_X, _Results) -> + []. + +process_decorators(_Delivery, [], _X) -> + []; +process_decorators(Delivery, Decorators, X) -> + lists:append([Decorator:route(X, Delivery) || Decorator <- Decorators]). process_route(#resource{kind = exchange} = XName, {_WorkList, XName, _QNames} = Acc) -> -- cgit v1.2.1 From f593d0719d3bfcbf61c607a40d8f5a9767bc3836 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 19 Mar 2013 12:49:57 +0000 Subject: Cosmetic --- src/rabbit_exchange.erl | 4 ++-- src/rabbit_registry.erl | 25 ++++++++----------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index d0504591..a4a88661 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -334,11 +334,11 @@ route1(_, _, {[], _, QNames}) -> route1(Delivery, Decorators, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> ExchangeDests = (type_to_module(Type)):route(X, Delivery), - AlternateDest = process_alternate(X, ExchangeDests), + AlternateDests = process_alternate(X, ExchangeDests), DecorateDests = process_decorators(Delivery, Decorators, X), route1(Delivery, Decorators, lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, - AlternateDest ++ DecorateDests ++ ExchangeDests)). + AlternateDests ++ DecorateDests ++ ExchangeDests)). process_alternate(#exchange{arguments = []}, _Results) -> %% optimisation []; diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl index db07dcdb..41b82ba5 100644 --- a/src/rabbit_registry.erl +++ b/src/rabbit_registry.erl @@ -95,29 +95,20 @@ internal_unregister(Class, TypeName) -> true = ets:delete(?ETS_NAME, UnregArg), ok. -conditional_register({{exchange_decorator, Type}, ModuleName}) -> +conditional_register({{exchange_decorator, _Type}, ModuleName} = RegArg) -> case erlang:function_exported(ModuleName, route, 2) of - true -> - true = ets:insert(?ETS_NAME, {{exchange_decorator_route, Type}, - ModuleName}), - ok; - false -> - ok + true -> true = ets:insert(?ETS_NAME, RegArg); + false -> ok end; conditional_register(_) -> ok. -conditional_unregister({exchange_decorator, Type}) -> +conditional_unregister({exchange_decorator, Type} = UnregArg) -> case lookup_module(exchange_decorator, Type) of - {ok, ModuleName} -> - case erlang:function_exported(ModuleName, route, 2) of - true -> - true = ets:delete(?ETS_NAME, {exchange_decorator_route, - Type}), - ok; - false -> - ok - end; + {ok, ModName} -> case erlang:function_exported(ModName, route, 2) of + true -> true = ets:delete(?ETS_NAME, UnregArg); + false -> ok + end; {error, not_found} -> ok end; -- cgit v1.2.1 From 0a9d0322fa7de0f277730aa955edaada77861947 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 19 Mar 2013 13:38:27 +0000 Subject: limiter API revision - part 2/2 - queue-side API --- src/rabbit_amqqueue.erl | 29 +++++++------- src/rabbit_amqqueue_process.erl | 86 ++++++++++++++++++++-------------------- src/rabbit_channel.erl | 10 +++-- src/rabbit_limiter.erl | 87 ++++++++++++++++++++++++----------------- src/rabbit_tests.erl | 19 ++++----- 5 files changed, 123 insertions(+), 108 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index bd5de239..be8ab385 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -26,9 +26,9 @@ -export([list/0, list/1, info_keys/0, info/1, info/2, info_all/1, info_all/2]). -export([force_event_refresh/0, wake_up/1]). -export([consumers/1, consumers_all/1, consumer_info_keys/0]). --export([basic_get/3, basic_consume/7, basic_cancel/4]). --export([notify_sent/2, notify_sent_queue_down/1, unblock/2, flush_all/2]). --export([notify_down_all/2, limit_all/3]). +-export([basic_get/3, basic_consume/8, basic_cancel/4]). +-export([notify_sent/2, notify_sent_queue_down/1, resume/2, flush_all/2]). +-export([notify_down_all/2, activate_limit_all/2]). -export([on_node_down/1]). -export([update/2, store_queue/1, policy_changed/2]). -export([start_mirroring/1, stop_mirroring/1, sync_mirrors/1, @@ -144,19 +144,18 @@ -spec(ack/3 :: (pid(), [msg_id()], pid()) -> 'ok'). -spec(reject/4 :: (pid(), [msg_id()], boolean(), pid()) -> 'ok'). -spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). --spec(limit_all/3 :: (qpids(), pid(), rabbit_limiter:lstate()) -> - ok_or_errors()). +-spec(activate_limit_all/2 :: (qpids(), pid()) -> ok_or_errors()). -spec(basic_get/3 :: (rabbit_types:amqqueue(), pid(), boolean()) -> {'ok', non_neg_integer(), qmsg()} | 'empty'). --spec(basic_consume/7 :: - (rabbit_types:amqqueue(), boolean(), pid(), - rabbit_limiter:token(), rabbit_types:ctag(), boolean(), any()) +-spec(basic_consume/8 :: + (rabbit_types:amqqueue(), boolean(), pid(), pid(), boolean(), + rabbit_types:ctag(), boolean(), any()) -> rabbit_types:ok_or_error('exclusive_consume_unavailable')). -spec(basic_cancel/4 :: (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), any()) -> 'ok'). -spec(notify_sent/2 :: (pid(), pid()) -> 'ok'). -spec(notify_sent_queue_down/1 :: (pid()) -> 'ok'). --spec(unblock/2 :: (pid(), pid()) -> 'ok'). +-spec(resume/2 :: (pid(), pid()) -> 'ok'). -spec(flush_all/2 :: (qpids(), pid()) -> 'ok'). -spec(internal_delete/1 :: (name()) -> rabbit_types:ok_or_error('not_found') | @@ -538,16 +537,16 @@ notify_down_all(QPids, ChPid) -> Bads1 -> {error, Bads1} end. -limit_all(QPids, ChPid, Limiter) -> - delegate:cast(QPids, {limit, ChPid, Limiter}). +activate_limit_all(QPids, ChPid) -> + delegate:cast(QPids, {activate_limit, ChPid}). basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> delegate:call(QPid, {basic_get, ChPid, NoAck}). -basic_consume(#amqqueue{pid = QPid}, NoAck, ChPid, Limiter, +basic_consume(#amqqueue{pid = QPid}, NoAck, ChPid, LimiterPid, LimiterActive, ConsumerTag, ExclusiveConsume, OkMsg) -> - delegate:call(QPid, {basic_consume, NoAck, ChPid, - Limiter, ConsumerTag, ExclusiveConsume, OkMsg}). + delegate:call(QPid, {basic_consume, NoAck, ChPid, LimiterPid, LimiterActive, + ConsumerTag, ExclusiveConsume, OkMsg}). basic_cancel(#amqqueue{pid = QPid}, ChPid, ConsumerTag, OkMsg) -> delegate:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}). @@ -569,7 +568,7 @@ notify_sent_queue_down(QPid) -> erase({consumer_credit_to, QPid}), ok. -unblock(QPid, ChPid) -> delegate:cast(QPid, {unblock, ChPid}). +resume(QPid, ChPid) -> delegate:cast(QPid, {resume, ChPid}). flush_all(QPids, ChPid) -> delegate:cast(QPids, {flush, ChPid}). diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 0ddc9eba..37daa0df 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -68,7 +68,6 @@ consumer_count, blocked_consumers, limiter, - is_limit_active, unsent_message_count}). %%---------------------------------------------------------------------------- @@ -371,7 +370,6 @@ ch_record(ChPid) -> acktags = queue:new(), consumer_count = 0, blocked_consumers = queue:new(), - is_limit_active = false, limiter = undefined, unsent_message_count = 0}, put(Key, C), @@ -392,30 +390,18 @@ store_ch_record(C = #cr{ch_pid = ChPid}) -> put({ch, ChPid}, C), ok. -erase_ch_record(#cr{ch_pid = ChPid, - limiter = Limiter, - monitor_ref = MonitorRef}) -> - ok = rabbit_limiter:unregister(Limiter), +erase_ch_record(#cr{ch_pid = ChPid, monitor_ref = MonitorRef}) -> erlang:demonitor(MonitorRef), erase({ch, ChPid}), ok. -update_consumer_count(C = #cr{consumer_count = 0, limiter = Limiter}, +1) -> - ok = rabbit_limiter:register(Limiter), - update_ch_record(C#cr{consumer_count = 1}); -update_consumer_count(C = #cr{consumer_count = 1, limiter = Limiter}, -1) -> - ok = rabbit_limiter:unregister(Limiter), - update_ch_record(C#cr{consumer_count = 0, limiter = undefined}); -update_consumer_count(C = #cr{consumer_count = Count}, Delta) -> - update_ch_record(C#cr{consumer_count = Count + Delta}). - all_ch_record() -> [C || {{ch, _}, C} <- get()]. block_consumer(C = #cr{blocked_consumers = Blocked}, QEntry) -> update_ch_record(C#cr{blocked_consumers = queue:in(QEntry, Blocked)}). -is_ch_blocked(#cr{unsent_message_count = Count, is_limit_active = Limited}) -> - Limited orelse Count >= ?UNSENT_MESSAGE_LIMIT. +is_ch_blocked(#cr{unsent_message_count = Count, limiter = Limiter}) -> + Count >= ?UNSENT_MESSAGE_LIMIT orelse rabbit_limiter:is_suspended(Limiter). ch_record_state_transition(OldCR, NewCR) -> case {is_ch_blocked(OldCR), is_ch_blocked(NewCR)} of @@ -439,18 +425,20 @@ deliver_msgs_to_consumers(DeliverFun, false, end. deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> - C = ch_record(ChPid), + C = lookup_ch(ChPid), case is_ch_blocked(C) of true -> block_consumer(C, E), {false, State}; false -> case rabbit_limiter:can_send(C#cr.limiter, Consumer#consumer.ack_required) of - false -> block_consumer(C#cr{is_limit_active = true}, E), - {false, State}; - true -> AC1 = queue:in(E, State#q.active_consumers), - deliver_msg_to_consumer( - DeliverFun, Consumer, C, - State#q{active_consumers = AC1}) + {suspend, Limiter} -> + block_consumer(C#cr{limiter = Limiter}, E), + {false, State}; + {continue, Limiter} -> + AC1 = queue:in(E, State#q.active_consumers), + deliver_msg_to_consumer( + DeliverFun, Consumer, C#cr{limiter = Limiter}, + State#q{active_consumers = AC1}) end end. @@ -1127,15 +1115,25 @@ handle_call({basic_get, ChPid, NoAck}, _From, reply({ok, BQ:len(BQS), Msg}, State3) end; -handle_call({basic_consume, NoAck, ChPid, Limiter, +handle_call({basic_consume, NoAck, ChPid, LimiterPid, LimiterActive, ConsumerTag, ExclusiveConsume, OkMsg}, _From, State = #q{exclusive_consumer = Holder}) -> case check_exclusive_access(Holder, ExclusiveConsume, State) of in_use -> reply({error, exclusive_consume_unavailable}, State); ok -> - C = ch_record(ChPid), - update_consumer_count(C#cr{limiter = Limiter}, +1), + C = #cr{consumer_count = Count, + limiter = Limiter0} = ch_record(ChPid), + Limiter = case Limiter0 of + undefined -> rabbit_limiter:client(LimiterPid); + _ -> Limiter0 + end, + Limiter1 = case LimiterActive of + true -> rabbit_limiter:activate(Limiter); + false -> Limiter + end, + update_ch_record(C#cr{consumer_count = Count + 1, + limiter = Limiter1}), Consumer = #consumer{tag = ConsumerTag, ack_required = not NoAck}, ExclusiveConsumer = if ExclusiveConsume -> {ChPid, ConsumerTag}; @@ -1156,10 +1154,18 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, case lookup_ch(ChPid) of not_found -> reply(ok, State); - C = #cr{blocked_consumers = Blocked} -> + C = #cr{consumer_count = Count, + limiter = Limiter, + blocked_consumers = Blocked} -> emit_consumer_deleted(ChPid, ConsumerTag, qname(State)), Blocked1 = remove_consumer(ChPid, ConsumerTag, Blocked), - update_consumer_count(C#cr{blocked_consumers = Blocked1}, -1), + Limiter1 = case Count of + 1 -> rabbit_limiter:forget(Limiter); + _ -> Limiter + end, + update_ch_record(C#cr{consumer_count = Count - 1, + limiter = Limiter1, + blocked_consumers = Blocked1}), State1 = State#q{ exclusive_consumer = case Holder of {ChPid, ConsumerTag} -> none; @@ -1287,10 +1293,13 @@ handle_cast({reject, AckTags, false, ChPid}, State) -> handle_cast(delete_immediately, State) -> stop(State); -handle_cast({unblock, ChPid}, State) -> +handle_cast({resume, ChPid}, State) -> noreply( possibly_unblock(State, ChPid, - fun (C) -> C#cr{is_limit_active = false} end)); + fun (C = #cr{limiter = undefined}) -> C; + (C = #cr{limiter = Limiter}) -> + C#cr{limiter = rabbit_limiter:resume(Limiter)} + end)); handle_cast({notify_sent, ChPid, Credit}, State) -> noreply( @@ -1299,20 +1308,13 @@ handle_cast({notify_sent, ChPid, Credit}, State) -> C#cr{unsent_message_count = Count - Credit} end)); -handle_cast({limit, ChPid, Limiter}, State) -> +handle_cast({activate_limit, ChPid}, State) -> noreply( possibly_unblock( State, ChPid, - fun (C = #cr{consumer_count = ConsumerCount, - limiter = OldLimiter, - is_limit_active = OldLimited}) -> - case (ConsumerCount =/= 0 andalso - not rabbit_limiter:is_active(OldLimiter)) of - true -> ok = rabbit_limiter:register(Limiter); - false -> ok - end, - Limited = OldLimited andalso rabbit_limiter:is_active(Limiter), - C#cr{limiter = Limiter, is_limit_active = Limited} + fun (C = #cr{limiter = Limiter, consumer_count = Count}) -> + true = Limiter =/= undefined andalso Count =/= 0, %% assertion + C#cr{limiter = rabbit_limiter:activate(Limiter)} end)); handle_cast({flush, ChPid}, State) -> diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index cda5747a..005200f8 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -81,8 +81,8 @@ -spec(start_link/11 :: (channel_number(), pid(), pid(), pid(), string(), rabbit_types:protocol(), rabbit_types:user(), rabbit_types:vhost(), - rabbit_framing:amqp_table(), - pid(), pid()) -> rabbit_types:ok_pid_or_error()). + rabbit_framing:amqp_table(), pid(), pid()) -> + rabbit_types:ok_pid_or_error()). -spec(do/2 :: (pid(), rabbit_framing:amqp_method_record()) -> 'ok'). -spec(do/3 :: (pid(), rabbit_framing:amqp_method_record(), rabbit_types:maybe(rabbit_types:content())) -> 'ok'). @@ -728,7 +728,9 @@ handle_method(#'basic.consume'{queue = QueueNameBin, QueueName, ConnPid, fun (Q) -> {rabbit_amqqueue:basic_consume( - Q, NoAck, self(), Limiter, + Q, NoAck, self(), + rabbit_limiter:pid(Limiter), + rabbit_limiter:is_active(Limiter), ActualConsumerTag, ExclusiveConsume, ok_msg(NoWait, #'basic.consume_ok'{ consumer_tag = ActualConsumerTag})), @@ -1326,7 +1328,7 @@ maybe_limit_queues(OldLimiter, NewLimiter, State) -> case ((not rabbit_limiter:is_active(OldLimiter)) andalso rabbit_limiter:is_active(NewLimiter)) of true -> Queues = consumer_queues(State#ch.consumer_mapping), - rabbit_amqqueue:limit_all(Queues, self(), NewLimiter); + rabbit_amqqueue:activate_limit_all(Queues, self()); false -> ok end, State. diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index ae656328..235c69c2 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -19,24 +19,27 @@ -behaviour(gen_server2). -export([start_link/0]). +%% channel API -export([new/1, limit/3, unlimit/1, block/1, unblock/1, - is_limited/1, is_blocked/1, is_active/1, get_limit/1, ack/2]). --export([can_send/2, register/1, unregister/1]). - + is_limited/1, is_blocked/1, is_active/1, get_limit/1, ack/2, pid/1]). +%% queue API +-export([client/1, activate/1, can_send/2, resume/1, forget/1, is_suspended/1]). +%% callbacks -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, prioritise_call/3]). %%---------------------------------------------------------------------------- -record(lstate, {pid, limited, blocked}). +-record(qstate, {pid, state}). -ifdef(use_specs). --export_type([lstate/0]). - --opaque(lstate() :: #lstate {pid :: pid(), - limited :: boolean(), - blocked :: boolean()}). +-type(lstate() :: #lstate{pid :: pid(), + limited :: boolean(), + blocked :: boolean()}). +-type(qstate() :: #qstate{pid :: pid(), + state :: 'dormant' | 'active' | 'suspended'}). -spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). -spec(new/1 :: (pid()) -> lstate()). @@ -51,10 +54,15 @@ -spec(is_active/1 :: (lstate()) -> boolean()). -spec(get_limit/1 :: (lstate()) -> non_neg_integer()). -spec(ack/2 :: (lstate(), non_neg_integer()) -> 'ok'). +-spec(pid/1 :: (lstate()) -> pid()). --spec(can_send/2 :: (lstate(), boolean()) -> boolean()). --spec(register/1 :: (lstate()) -> 'ok'). --spec(unregister/1 :: (lstate()) -> 'ok'). +-spec(client/1 :: (pid()) -> qstate()). +-spec(activate/1 :: (qstate()) -> qstate()). +-spec(can_send/2 :: (qstate(), boolean()) -> + {'continue' | 'suspend', qstate()}). +-spec(resume/1 :: (qstate()) -> qstate()). +-spec(forget/1 :: (qstate()) -> undefined). +-spec(is_suspended/1 :: (qstate()) -> boolean()). -endif. @@ -108,29 +116,37 @@ get_limit(L) -> gen_server:call(L#lstate.pid, get_limit). ack(#lstate{limited = false}, _AckCount) -> ok; ack(L, AckCount) -> gen_server:cast(L#lstate.pid, {ack, AckCount}). -%% Ask the limiter whether the queue can deliver a message without -%% breaching a limit. -can_send(L, AckRequired) -> - case is_active(L) of - false -> true; - true -> rabbit_misc:with_exit_handler( - fun () -> true end, - fun () -> Msg = {can_send, self(), AckRequired}, - gen_server2:call(L#lstate.pid, Msg, infinity) - end) - end. +pid(#lstate{pid = Pid}) -> Pid. -register(L) -> - case is_active(L) of - false -> ok; - true -> gen_server:cast(L#lstate.pid, {register, self()}) - end. +client(Pid) -> #qstate{pid = Pid, state = dormant}. -unregister(L) -> - case is_active(L) of - false -> ok; - true -> gen_server:cast(L#lstate.pid, {unregister, self()}) - end. +activate(L = #qstate{state = dormant}) -> + ok = gen_server:cast(L#qstate.pid, {register, self()}), + L#qstate{state = active}; +activate(L) -> L. + +%% Ask the limiter whether the queue can deliver a message without +%% breaching a limit. +can_send(L = #qstate{state = active}, AckRequired) -> + rabbit_misc:with_exit_handler( + fun () -> {continue, L} end, + fun () -> Msg = {can_send, self(), AckRequired}, + case gen_server2:call(L#qstate.pid, Msg, infinity) of + true -> {continue, L}; + false -> {suspend, L#qstate{state = suspended}} + end + end); +can_send(L, _AckRequired) -> {continue, L}. + +resume(L) -> L#qstate{state = active}. + +forget(#qstate{state = dormant}) -> undefined; +forget(L) -> + ok = gen_server:cast(L#qstate.pid, {unregister, self()}), + undefined. + +is_suspended(#qstate{state = suspended}) -> true; +is_suspended(#qstate{}) -> false. %%---------------------------------------------------------------------------- %% gen_server callbacks @@ -220,10 +236,9 @@ remember_queue(QPid, State = #lim{queues = Queues}) -> true -> State end. -forget_queue(QPid, State = #lim{ch_pid = ChPid, queues = Queues}) -> +forget_queue(QPid, State = #lim{queues = Queues}) -> case orddict:find(QPid, Queues) of {ok, {MRef, _}} -> true = erlang:demonitor(MRef), - ok = rabbit_amqqueue:unblock(QPid, ChPid), State#lim{queues = orddict:erase(QPid, Queues)}; error -> State end. @@ -240,13 +255,13 @@ notify_queues(State = #lim{ch_pid = ChPid, queues = Queues}) -> end, {[], Queues}, Queues), case length(QList) of 0 -> ok; - 1 -> ok = rabbit_amqqueue:unblock(hd(QList), ChPid); %% common case + 1 -> ok = rabbit_amqqueue:resume(hd(QList), ChPid); %% common case L -> %% We randomly vary the position of queues in the list, %% thus ensuring that each queue has an equal chance of %% being notified first. {L1, L2} = lists:split(random:uniform(L), QList), - [[ok = rabbit_amqqueue:unblock(Q, ChPid) || Q <- L3] + [[ok = rabbit_amqqueue:resume(Q, ChPid) || Q <- L3] || L3 <- [L2, L1]], ok end, diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 1188c554..31a56ac8 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1100,20 +1100,14 @@ test_policy_validation() -> test_server_status() -> %% create a few things so there is some useful information to list - Writer = spawn(fun test_writer/0), - {ok, Ch} = rabbit_channel:start_link( - 1, self(), Writer, self(), "", rabbit_framing_amqp_0_9_1, - user(<<"user">>), <<"/">>, [], self(), - rabbit_limiter:make_token(self())), + {_Writer, Limiter, Ch} = test_channel(), [Q, Q2] = [Queue || Name <- [<<"foo">>, <<"bar">>], {new, Queue = #amqqueue{}} <- [rabbit_amqqueue:declare( rabbit_misc:r(<<"/">>, queue, Name), false, false, [], none)]], - ok = rabbit_amqqueue:basic_consume( - Q, true, Ch, rabbit_limiter:make_token(), - <<"ctag">>, true, undefined), + Q, true, Ch, Limiter, false, <<"ctag">>, true, undefined), %% list queues ok = info_action(list_queues, rabbit_amqqueue:info_keys(), true), @@ -1191,8 +1185,6 @@ find_listener() -> N =:= node()], {H, P}. -test_writer() -> test_writer(none). - test_writer(Pid) -> receive {'$gen_call', From, flush} -> gen_server:reply(From, ok), @@ -1202,13 +1194,18 @@ test_writer(Pid) -> shutdown -> ok end. -test_spawn() -> +test_channel() -> Me = self(), Writer = spawn(fun () -> test_writer(Me) end), + {ok, Limiter} = rabbit_limiter:start_link(), {ok, Ch} = rabbit_channel:start_link( 1, Me, Writer, Me, "", rabbit_framing_amqp_0_9_1, user(<<"guest">>), <<"/">>, [], Me, rabbit_limiter:make_token(self())), + {Writer, Limiter, Ch}. + +test_spawn() -> + {Writer, _Limiter, Ch} = test_channel(), ok = rabbit_channel:do(Ch, #'channel.open'{}), receive #'channel.open_ok'{} -> ok after ?TIMEOUT -> throw(failed_to_receive_channel_open_ok) -- cgit v1.2.1 From 96350c751fe4f112783dcb1497a7e48aedae6f9f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 19 Mar 2013 14:08:50 +0000 Subject: correctness++ --- src/rabbit_tests.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 31a56ac8..b67be544 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1200,8 +1200,7 @@ test_channel() -> {ok, Limiter} = rabbit_limiter:start_link(), {ok, Ch} = rabbit_channel:start_link( 1, Me, Writer, Me, "", rabbit_framing_amqp_0_9_1, - user(<<"guest">>), <<"/">>, [], Me, - rabbit_limiter:make_token(self())), + user(<<"guest">>), <<"/">>, [], Me, Limiter), {Writer, Limiter, Ch}. test_spawn() -> -- cgit v1.2.1 From f9edb57bd0b3ac55471f4cc9af74024896bab265 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 19 Mar 2013 14:45:32 +0000 Subject: niggles-- --- src/rabbit_amqqueue_process.erl | 29 ++++++++++++----------------- src/rabbit_channel.erl | 5 +++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 37daa0df..d9264736 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -401,14 +401,9 @@ block_consumer(C = #cr{blocked_consumers = Blocked}, QEntry) -> update_ch_record(C#cr{blocked_consumers = queue:in(QEntry, Blocked)}). is_ch_blocked(#cr{unsent_message_count = Count, limiter = Limiter}) -> - Count >= ?UNSENT_MESSAGE_LIMIT orelse rabbit_limiter:is_suspended(Limiter). - -ch_record_state_transition(OldCR, NewCR) -> - case {is_ch_blocked(OldCR), is_ch_blocked(NewCR)} of - {true, false} -> unblock; - {false, true} -> block; - {_, _} -> ok - end. + Count >= ?UNSENT_MESSAGE_LIMIT + orelse (Limiter =/= undefined andalso + rabbit_limiter:is_suspended(Limiter)). deliver_msgs_to_consumers(_DeliverFun, true, State) -> {true, State}; @@ -629,15 +624,15 @@ possibly_unblock(State, ChPid, Update) -> State; C -> C1 = Update(C), - case ch_record_state_transition(C, C1) of - ok -> update_ch_record(C1), - State; - unblock -> #cr{blocked_consumers = Consumers} = C1, - update_ch_record( - C1#cr{blocked_consumers = queue:new()}), - AC1 = queue:join(State#q.active_consumers, - Consumers), - run_message_queue(State#q{active_consumers = AC1}) + case is_ch_blocked(C) andalso not is_ch_blocked(C1) of + false -> update_ch_record(C1), + State; + true -> #cr{blocked_consumers = Consumers} = C1, + update_ch_record( + C1#cr{blocked_consumers = queue:new()}), + AC1 = queue:join(State#q.active_consumers, + Consumers), + run_message_queue(State#q{active_consumers = AC1}) end end. diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 005200f8..eb248a4c 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -805,6 +805,11 @@ handle_method(#'basic.qos'{prefetch_size = Size}, _, _State) when Size /= 0 -> rabbit_misc:protocol_error(not_implemented, "prefetch_size!=0 (~w)", [Size]); +handle_method(#'basic.qos'{prefetch_count = 0}, _, + State = #ch{limiter = Limiter}) -> + Limiter1 = rabbit_limiter:unlimit(Limiter), + {reply, #'basic.qos_ok'{}, State#ch{limiter = Limiter1}}; + handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{limiter = Limiter, unacked_message_q = UAMQ}) -> Limiter1 = rabbit_limiter:limit(Limiter, PrefetchCount, queue:len(UAMQ)), -- cgit v1.2.1 From 7045b90b2e9a2be678b6781db6815f20af120e87 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 19 Mar 2013 17:15:07 +0000 Subject: First pass at autohealing. --- src/rabbit_node_monitor.erl | 132 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 11 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index de53b7f0..aac2bf84 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -33,7 +33,7 @@ -define(SERVER, ?MODULE). -define(RABBIT_UP_RPC_TIMEOUT, 2000). --record(state, {monitors, partitions, subscribers}). +-record(state, {monitors, partitions, subscribers, autoheal}). %%---------------------------------------------------------------------------- @@ -196,7 +196,8 @@ init([]) -> {ok, _} = mnesia:subscribe(system), {ok, #state{monitors = pmon:new(), subscribers = pmon:new(), - partitions = []}}. + partitions = [], + autoheal = not_healing}}. handle_call(partitions, _From, State = #state{partitions = Partitions}) -> {reply, {node(), Partitions}, State}; @@ -268,9 +269,60 @@ handle_info({mnesia_system_event, monitors = pmon:monitor({rabbit, Node}, Monitors)} end, ok = handle_live_rabbit(Node), + State2 = case application:get_env(rabbit, cluster_partition_handling) of + {ok, autoheal} -> case ratio() of + 1.0 -> autoheal(State1); + _ -> State1 + end; + _ -> State1 + end, Partitions1 = ordsets:to_list( ordsets:add_element(Node, ordsets:from_list(Partitions))), - {noreply, State1#state{partitions = Partitions1}}; + {noreply, State2#state{partitions = Partitions1}}; + +handle_info({autoheal_request_winner, Node}, + State = #state{autoheal = {wait_for_winner_reqs,[Node], Notify}}) -> + %% TODO actually do something sensible to figure out who the winner is + Winner = self(), + [{?MODULE, N} ! {autoheal_winner, Winner} || N <- Notify], + {noreply, State#state{autoheal = wait_for_winner}}; + +handle_info({autoheal_request_winner, Node}, + State = #state{autoheal = {wait_for_winner_reqs, Nodes, Notify}}) -> + {noreply, State#state{autoheal = {wait_for_winner_reqs, + Nodes -- [Node], Notify}}}; + +handle_info({autoheal_winner, Winner}, + State = #state{autoheal = wait_for_winner, + partitions = Partitions}) -> + Node = node(Winner), + case lists:member(Node, Partitions) of + false -> case node() of + Node -> {noreply, + State#state{autoheal = {wait_for, Partitions, + Partitions}}}; + _ -> {noreply, State#state{autoheal = not_healing}} + end; + true -> autoheal_restart(Winner), + {noreply, State} + end; + +handle_info({autoheal_winner, _Winner}, State) -> + %% ignore, we already cancelled the autoheal process + {noreply, State}; + +handle_info({autoheal_node_stopped, Node}, + State = #state{autoheal = {wait_for, [Node], Notify}}) -> + [{rabbit_outside_app_process, N} ! autoheal_safe_to_start || N <- Notify], + {noreply, State#state{autoheal = not_healing}}; + +handle_info({autoheal_node_stopped, Node}, + State = #state{autoheal = {wait_for, WaitFor, Notify}}) -> + {noreply, State#state{autoheal = {wait_for, WaitFor -- [Node], Notify}}}; + +handle_info({autoheal_node_stopped, _Node}, State) -> + %% ignore, we already cancelled the autoheal process + {noreply, State}; handle_info(_Info, State) -> {noreply, State}. @@ -301,6 +353,8 @@ handle_dead_rabbit(Node) -> end; {ok, ignore} -> ok; + {ok, autoheal} -> + ok; {ok, Term} -> rabbit_log:warning("cluster_partition_handling ~p unrecognised, " "assuming 'ignore'~n", [Term]), @@ -308,8 +362,8 @@ handle_dead_rabbit(Node) -> end, ok. -majority() -> - length(alive_nodes()) / length(rabbit_mnesia:cluster_nodes(all)) > 0.5. +majority() -> ratio() > 0.5. +ratio() -> length(alive_nodes()) / length(rabbit_mnesia:cluster_nodes(all)). %% mnesia:system_info(db_nodes) (and hence %% rabbit_mnesia:cluster_nodes(running)) does not give reliable results @@ -322,15 +376,20 @@ await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", []), Nodes = rabbit_mnesia:cluster_nodes(all), + run_outside_applications(fun () -> + rabbit:stop(), + wait_for_cluster_recovery(Nodes) + end). + +run_outside_applications(Fun) -> spawn(fun () -> %% If our group leader is inside an application we are about %% to stop, application:stop/1 does not return. group_leader(whereis(init), self()), - %% Ensure only one restarting process at a time, will + %% Ensure only one such process at a time, will %% exit(badarg) (harmlessly) if one is already running - register(rabbit_restarting_process, self()), - rabbit:stop(), - wait_for_cluster_recovery(Nodes) + register(rabbit_outside_app_process, self()), + Fun() end). wait_for_cluster_recovery(Nodes) -> @@ -340,7 +399,54 @@ wait_for_cluster_recovery(Nodes) -> wait_for_cluster_recovery(Nodes) end. -handle_dead_rabbit_state(State = #state{partitions = Partitions}) -> +%% In order to autoheal we want to: +%% +%% * Find the winning partition +%% * Stop all nodes in other partitions +%% * Wait for them all to be stopped +%% * Start them again +%% +%% To keep things simple, we assume all nodes are up. We don't start +%% unless all nodes are up, and if a node goes down we abandon the +%% whole process. To further keep things simple we also defer the +%% decision as to the winning node to the "leader" - arbitrarily +%% selected as the first node in the cluster. +%% +%% To coordinate the restarting nodes we pick a special node from the +%% winning partition - the "winner". Restarting nodes then stop, tell +%% the winner they have done so, and wait for it to tell them it is +%% safe to start again. +%% +%% The winner and the leader are not necessarily the same node! Since +%% the leader may end up restarting, we also make sure that it does +%% not announce its decision (and thus cue other nodes to restart) +%% until it has seen a request from every node. +autoheal(State) -> + [Leader | _] = All = lists:usort(rabbit_mnesia:cluster_nodes(all)), + {?MODULE, Leader} ! {autoheal_request_winner, node()}, + State#state{autoheal = case node() of + Leader -> {wait_for_winner_reqs, All, All}; + _ -> wait_for_winner + end}. + +autoheal_restart(Winner) -> + rabbit_log:warning( + "Cluster partition healed; we were selected to restart~n", []), + run_outside_applications( + fun () -> + MRef = erlang:monitor(process, Winner), + rabbit:stop(), + Winner ! {autoheal_node_stopped, node()}, + receive + {'DOWN', MRef, process, Winner, _Reason} -> ok; + autoheal_safe_to_start -> ok + end, + erlang:demonitor(MRef, [flush]), + rabbit:start() + end). + +handle_dead_rabbit_state(State = #state{partitions = Partitions, + autoheal = Autoheal}) -> %% If we have been partitioned, and we are now in the only remaining %% partition, we no longer care about partitions - forget them. Note %% that we do not attempt to deal with individual (other) partitions @@ -350,7 +456,11 @@ handle_dead_rabbit_state(State = #state{partitions = Partitions}) -> [] -> []; _ -> Partitions end, - State#state{partitions = Partitions1}. + State#state{partitions = Partitions1, + autoheal = case Autoheal of + {wait_for, _Nodes, _Notify} -> Autoheal; + _ -> not_healing + end}. handle_live_rabbit(Node) -> ok = rabbit_alarm:on_node_up(Node), -- cgit v1.2.1 From 00eb6aefa5f0bdacad56aeacd53420a86a479350 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 19 Mar 2013 17:41:53 +0000 Subject: A node counts as down if it is not running rabbit in this case. --- src/rabbit_node_monitor.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index de53b7f0..3872f3df 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -318,6 +318,9 @@ alive_nodes() -> Nodes = rabbit_mnesia:cluster_nodes(all), [N || N <- Nodes, pong =:= net_adm:ping(N)]. +alive_rabbit_nodes() -> + [N || N <- alive_nodes(), rabbit_nodes:is_running(N, rabbit)]. + await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", []), @@ -346,7 +349,7 @@ handle_dead_rabbit_state(State = #state{partitions = Partitions}) -> %% that we do not attempt to deal with individual (other) partitions %% going away. It's only safe to forget anything about partitions when %% there are no partitions. - Partitions1 = case Partitions -- (Partitions -- alive_nodes()) of + Partitions1 = case Partitions -- (Partitions -- alive_rabbit_nodes()) of [] -> []; _ -> Partitions end, -- cgit v1.2.1 From 1f4d7608d714a376d57b5d3144ce682c6bcbe695 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 19 Mar 2013 20:21:50 +0000 Subject: some documentation --- src/rabbit_channel.erl | 15 +++++++++- src/rabbit_limiter.erl | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index eb248a4c..17bf5c83 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -1089,6 +1089,17 @@ handle_method(#'channel.flow'{active = false}, _, false -> Limiter1 = rabbit_limiter:block(Limiter), State1 = maybe_limit_queues(Limiter, Limiter1, State#ch{limiter = Limiter1}), + %% The semantics of channel.flow{active=false} + %% require that no messages are delivered after the + %% channel.flow_ok has been sent. We accomplish that + %% by "flushing" all messages in flight from the + %% consumer queues to us. To do this we tell all the + %% queues to invoke rabbit_channel:flushed/2, which + %% will send us a {flushed, ...} message that appears + %% *after* all the {deliver, ...} messages. We keep + %% track of all the QPids thus asked, and once all of + %% them have responded (or died) we send the + %% channel.flow_ok. QPids = consumer_queues(Consumers), ok = rabbit_amqqueue:flush_all(QPids, self()), {noreply, maybe_send_flow_ok( @@ -1347,7 +1358,9 @@ consumer_queues(Consumers) -> %% messages sent in a response to a basic.get (identified by their %% 'none' consumer tag) notify_limiter(Limiter, Acked) -> - case rabbit_limiter:is_limited(Limiter) of + %% optimisation: avoid the potentially expensive 'foldl' in the + %% common case. + case rabbit_limiter:is_limited(Limiter) of false -> ok; true -> case lists:foldl(fun ({_, none, _}, Acc) -> Acc; ({_, _, _}, Acc) -> Acc + 1 diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 235c69c2..4059fdb0 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -14,6 +14,85 @@ %% Copyright (c) 2007-2013 VMware, Inc. All rights reserved. %% +%% The purpose of the limiter is to stem the flow of messages from +%% queues to channels, in order to act upon various protocol-level +%% flow control mechanisms, specifically AMQP's basic.qos +%% prefetch_count and channel.flow. +%% +%% Each channel has an associated limiter process, created with +%% start_link/1, which it passes to queues on consumer creation with +%% rabbit_amqqueue:basic_consume/8. This process holds state that is, +%% in effect, shared between the channel and all queues from which the +%% channel is consuming. Essentially all these queues are competing +%% for access to a single, limited resource - the ability to deliver +%% messages via the channel - and it is the job of the limiter process +%% to mediate that access. +%% +%% The limiter process is separate from the channel process for two +%% reasons: separation of concerns, and efficiency. Channels can get +%% very busy, particularly if they are also dealing with publishes. +%% With a separate limiter process all the aforementioned access +%% mediation can take place without touching the channel. +%% +%% For efficiency, both the channel and the queues keep some local +%% state, initialised from the limiter pid with new/1 and client/1, +%% respectively. In particular this allows them to avoid any +%% interaction with the limiter process when it is 'inactive', i.e. no +%% protocol-level flow control is taking place. +%% +%% This optimisation does come at the cost of some complexity though: +%% when a limiter becomes active, the channel needs to inform all its +%% consumer queues of this change in status. It does this by invoking +%% rabbit_amqqueue:activate_limit_all/2. Note that there is no inverse +%% transition, i.e. once a queue has been told about an active +%% limiter, it is not subsequently told when that limiter becomes +%% inactive. In practice it is rare for that to happen, though we +%% could optimise this case in the future. +%% +%% The interactions with the limiter are as follows: +%% +%% 1. Channels tell the limiter about basic.qos prefetch counts - +%% that's what the limit/3, unlimit/1, is_limited/1, get_limit/1 +%% API functions are about - and channel.flow blocking - that's +%% what block/1, unblock/1 and is_blocked/1 are for. +%% +%% 2. Queues register with the limiter - this happens as part of +%% activate/1. +%% +%% 4. The limiter process maintains an internal counter of 'messages +%% sent but not yet acknowledged', called the 'volume'. +%% +%% 5. Queues ask the limiter for permission (with can_send/2) whenever +%% they want to deliver a message to a channel. The limiter checks +%% whether a) the channel isn't blocked by channel.flow, and b) the +%% volume has not yet reached the prefetch limit. If so it +%% increments the volume and tells the queue to proceed. Otherwise +%% it marks the queue as requiring notification (see below) and +%% tells the queue not to proceed. +%% +%% 6. A queue that has told to proceed (by the return value of +%% can_send/2) sends the message to the channel. Conversely, a +%% queue that has been told not to proceed, will not attempt to +%% deliver that message, or any future messages, to the +%% channel. This is accomplished by can_send/2 capturing the +%% outcome in the local state, where it can be accessed with +%% is_suspended/1. +%% +%% 7. When a channel receives an ack it tells the limiter (via ack/2) +%% how many messages were ack'ed. The limiter process decrements +%% the volume and if it falls below the prefetch_count then it +%% notifies (through rabbit_amqqueue:resume/2) all the queues +%% requiring notification, i.e. all those that had a can_send/2 +%% request denied. +%% +%% 8. Upon receipt of such a notification, queues resume delivery to +%% the channel, i.e. they will once again start asking limiter, as +%% described in (5). +%% +%% 9. When a queues has no more consumers associated with a particular +%% channel, it unregisters with the limiter and forgets about it - +%% all via forget/1. + -module(rabbit_limiter). -behaviour(gen_server2). -- cgit v1.2.1 From d9c5b6057c48fbf971e8fd5dd07ba9ff2c5e4c26 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Mar 2013 15:05:58 +0000 Subject: always hold a valid limiter in queue's channel records This is less fiddly, but does mean we have to pass the limiter pid in basic_get. --- src/rabbit_amqqueue.erl | 8 ++++---- src/rabbit_amqqueue_process.erl | 35 ++++++++++++++--------------------- src/rabbit_channel.erl | 5 ++++- src/rabbit_limiter.erl | 34 +++++++++++++++++++++------------- src/rabbit_tests.erl | 7 +++++-- 5 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index be8ab385..3f0a7f9c 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -26,7 +26,7 @@ -export([list/0, list/1, info_keys/0, info/1, info/2, info_all/1, info_all/2]). -export([force_event_refresh/0, wake_up/1]). -export([consumers/1, consumers_all/1, consumer_info_keys/0]). --export([basic_get/3, basic_consume/8, basic_cancel/4]). +-export([basic_get/4, basic_consume/8, basic_cancel/4]). -export([notify_sent/2, notify_sent_queue_down/1, resume/2, flush_all/2]). -export([notify_down_all/2, activate_limit_all/2]). -export([on_node_down/1]). @@ -145,7 +145,7 @@ -spec(reject/4 :: (pid(), [msg_id()], boolean(), pid()) -> 'ok'). -spec(notify_down_all/2 :: (qpids(), pid()) -> ok_or_errors()). -spec(activate_limit_all/2 :: (qpids(), pid()) -> ok_or_errors()). --spec(basic_get/3 :: (rabbit_types:amqqueue(), pid(), boolean()) -> +-spec(basic_get/4 :: (rabbit_types:amqqueue(), pid(), boolean(), pid()) -> {'ok', non_neg_integer(), qmsg()} | 'empty'). -spec(basic_consume/8 :: (rabbit_types:amqqueue(), boolean(), pid(), pid(), boolean(), @@ -540,8 +540,8 @@ notify_down_all(QPids, ChPid) -> activate_limit_all(QPids, ChPid) -> delegate:cast(QPids, {activate_limit, ChPid}). -basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> - delegate:call(QPid, {basic_get, ChPid, NoAck}). +basic_get(#amqqueue{pid = QPid}, ChPid, NoAck, LimiterPid) -> + delegate:call(QPid, {basic_get, ChPid, NoAck, LimiterPid}). basic_consume(#amqqueue{pid = QPid}, NoAck, ChPid, LimiterPid, LimiterActive, ConsumerTag, ExclusiveConsume, OkMsg) -> diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index d9264736..6fc79dca 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -361,16 +361,17 @@ lookup_ch(ChPid) -> C -> C end. -ch_record(ChPid) -> +ch_record(ChPid, LimiterPid) -> Key = {ch, ChPid}, case get(Key) of undefined -> MonitorRef = erlang:monitor(process, ChPid), + Limiter = rabbit_limiter:client(LimiterPid), C = #cr{ch_pid = ChPid, monitor_ref = MonitorRef, acktags = queue:new(), consumer_count = 0, blocked_consumers = queue:new(), - limiter = undefined, + limiter = Limiter, unsent_message_count = 0}, put(Key, C), C; @@ -401,9 +402,7 @@ block_consumer(C = #cr{blocked_consumers = Blocked}, QEntry) -> update_ch_record(C#cr{blocked_consumers = queue:in(QEntry, Blocked)}). is_ch_blocked(#cr{unsent_message_count = Count, limiter = Limiter}) -> - Count >= ?UNSENT_MESSAGE_LIMIT - orelse (Limiter =/= undefined andalso - rabbit_limiter:is_suspended(Limiter)). + Count >= ?UNSENT_MESSAGE_LIMIT orelse rabbit_limiter:is_suspended(Limiter). deliver_msgs_to_consumers(_DeliverFun, true, State) -> {true, State}; @@ -1090,7 +1089,7 @@ handle_call({notify_down, ChPid}, From, State) -> {stop, State1} -> stop(From, ok, State1) end; -handle_call({basic_get, ChPid, NoAck}, _From, +handle_call({basic_get, ChPid, NoAck, LimiterPid}, _From, State = #q{q = #amqqueue{name = QName}}) -> AckRequired = not NoAck, State1 = ensure_expiry_timer(State), @@ -1100,7 +1099,8 @@ handle_call({basic_get, ChPid, NoAck}, _From, {{Message, IsDelivered, AckTag}, State2} -> State3 = #q{backing_queue = BQ, backing_queue_state = BQS} = case AckRequired of - true -> C = #cr{acktags = ChAckTags} = ch_record(ChPid), + true -> C = #cr{acktags = ChAckTags} = + ch_record(ChPid, LimiterPid), ChAckTags1 = queue:in(AckTag, ChAckTags), update_ch_record(C#cr{acktags = ChAckTags1}), State2; @@ -1118,11 +1118,7 @@ handle_call({basic_consume, NoAck, ChPid, LimiterPid, LimiterActive, reply({error, exclusive_consume_unavailable}, State); ok -> C = #cr{consumer_count = Count, - limiter = Limiter0} = ch_record(ChPid), - Limiter = case Limiter0 of - undefined -> rabbit_limiter:client(LimiterPid); - _ -> Limiter0 - end, + limiter = Limiter} = ch_record(ChPid, LimiterPid), Limiter1 = case LimiterActive of true -> rabbit_limiter:activate(Limiter); false -> Limiter @@ -1155,7 +1151,7 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, emit_consumer_deleted(ChPid, ConsumerTag, qname(State)), Blocked1 = remove_consumer(ChPid, ConsumerTag, Blocked), Limiter1 = case Count of - 1 -> rabbit_limiter:forget(Limiter); + 1 -> rabbit_limiter:deactivate(Limiter); _ -> Limiter end, update_ch_record(C#cr{consumer_count = Count - 1, @@ -1291,8 +1287,7 @@ handle_cast(delete_immediately, State) -> handle_cast({resume, ChPid}, State) -> noreply( possibly_unblock(State, ChPid, - fun (C = #cr{limiter = undefined}) -> C; - (C = #cr{limiter = Limiter}) -> + fun (C = #cr{limiter = Limiter}) -> C#cr{limiter = rabbit_limiter:resume(Limiter)} end)); @@ -1305,12 +1300,10 @@ handle_cast({notify_sent, ChPid, Credit}, State) -> handle_cast({activate_limit, ChPid}, State) -> noreply( - possibly_unblock( - State, ChPid, - fun (C = #cr{limiter = Limiter, consumer_count = Count}) -> - true = Limiter =/= undefined andalso Count =/= 0, %% assertion - C#cr{limiter = rabbit_limiter:activate(Limiter)} - end)); + possibly_unblock(State, ChPid, + fun (C = #cr{limiter = Limiter}) -> + C#cr{limiter = rabbit_limiter:activate(Limiter)} + end)); handle_cast({flush, ChPid}, State) -> ok = rabbit_channel:flushed(ChPid, self()), diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 17bf5c83..67cabcfb 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -676,12 +676,15 @@ handle_method(#'basic.get'{queue = QueueNameBin, no_ack = NoAck}, _, State = #ch{writer_pid = WriterPid, conn_pid = ConnPid, + limiter = Limiter, next_tag = DeliveryTag}) -> QueueName = expand_queue_name_shortcut(QueueNameBin, State), check_read_permitted(QueueName, State), case rabbit_amqqueue:with_exclusive_access_or_die( QueueName, ConnPid, - fun (Q) -> rabbit_amqqueue:basic_get(Q, self(), NoAck) end) of + fun (Q) -> rabbit_amqqueue:basic_get( + Q, self(), NoAck, rabbit_limiter:pid(Limiter)) + end) of {ok, MessageCount, Msg = {QName, QPid, _MsgId, Redelivered, #basic_message{exchange_name = ExchangeName, diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 4059fdb0..b914306b 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -21,12 +21,17 @@ %% %% Each channel has an associated limiter process, created with %% start_link/1, which it passes to queues on consumer creation with -%% rabbit_amqqueue:basic_consume/8. This process holds state that is, -%% in effect, shared between the channel and all queues from which the -%% channel is consuming. Essentially all these queues are competing -%% for access to a single, limited resource - the ability to deliver -%% messages via the channel - and it is the job of the limiter process -%% to mediate that access. +%% rabbit_amqqueue:basic_consume/8, and rabbit_amqqueue:basic_get/4. +%% The latter isn't strictly necessary, since basic.get is not +%% subject to limiting, but it means that whenever a queue knows about +%% a channel, it also knows about its limiter, which is less fiddly. +%% +%% Th limiter process holds state that is, in effect, shared between +%% the channel and all queues from which the channel is +%% consuming. Essentially all these queues are competing for access to +%% a single, limited resource - the ability to deliver messages via +%% the channel - and it is the job of the limiter process to mediate +%% that access. %% %% The limiter process is separate from the channel process for two %% reasons: separation of concerns, and efficiency. Channels can get @@ -90,8 +95,10 @@ %% described in (5). %% %% 9. When a queues has no more consumers associated with a particular -%% channel, it unregisters with the limiter and forgets about it - -%% all via forget/1. +%% channel, it deactivates use of the limiter with deactivate/1, +%% which alters the local state such that no further interactions +%% with the limiter process take place until a subsequent +%% activate/1. -module(rabbit_limiter). @@ -102,7 +109,8 @@ -export([new/1, limit/3, unlimit/1, block/1, unblock/1, is_limited/1, is_blocked/1, is_active/1, get_limit/1, ack/2, pid/1]). %% queue API --export([client/1, activate/1, can_send/2, resume/1, forget/1, is_suspended/1]). +-export([client/1, activate/1, can_send/2, resume/1, deactivate/1, + is_suspended/1]). %% callbacks -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, prioritise_call/3]). @@ -140,7 +148,7 @@ -spec(can_send/2 :: (qstate(), boolean()) -> {'continue' | 'suspend', qstate()}). -spec(resume/1 :: (qstate()) -> qstate()). --spec(forget/1 :: (qstate()) -> undefined). +-spec(deactivate/1 :: (qstate()) -> qstate()). -spec(is_suspended/1 :: (qstate()) -> boolean()). -endif. @@ -219,10 +227,10 @@ can_send(L, _AckRequired) -> {continue, L}. resume(L) -> L#qstate{state = active}. -forget(#qstate{state = dormant}) -> undefined; -forget(L) -> +deactivate(L = #qstate{state = dormant}) -> L; +deactivate(L) -> ok = gen_server:cast(L#qstate.pid, {unregister, self()}), - undefined. + L#qstate{state = dormant}. is_suspended(#qstate{state = suspended}) -> true; is_suspended(#qstate{}) -> false. diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index b67be544..d1ae38be 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -2718,12 +2718,13 @@ test_queue_recover() -> end, rabbit_amqqueue:stop(), rabbit_amqqueue:start(rabbit_amqqueue:recover()), + {ok, Limiter} = rabbit_limiter:start_link(), rabbit_amqqueue:with_or_die( QName, fun (Q1 = #amqqueue { pid = QPid1 }) -> CountMinusOne = Count - 1, {ok, CountMinusOne, {QName, QPid1, _AckTag, true, _Msg}} = - rabbit_amqqueue:basic_get(Q1, self(), false), + rabbit_amqqueue:basic_get(Q1, self(), false, Limiter), exit(QPid1, shutdown), VQ1 = variable_queue_init(Q, true), {{_Msg1, true, _AckTag1}, VQ2} = @@ -2744,9 +2745,11 @@ test_variable_queue_delete_msg_store_files_callback() -> rabbit_amqqueue:set_ram_duration_target(QPid, 0), + {ok, Limiter} = rabbit_limiter:start_link(), + CountMinusOne = Count - 1, {ok, CountMinusOne, {QName, QPid, _AckTag, false, _Msg}} = - rabbit_amqqueue:basic_get(Q, self(), true), + rabbit_amqqueue:basic_get(Q, self(), true, Limiter), {ok, CountMinusOne} = rabbit_amqqueue:purge(Q), %% give the queue a second to receive the close_fds callback msg -- cgit v1.2.1 From e5a86b2fd17a316327975ce99bb9a6e81aadef37 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 20 Mar 2013 16:08:48 +0000 Subject: better names for the prefetch related part of the limiter API --- src/rabbit_channel.erl | 9 +++--- src/rabbit_limiter.erl | 88 ++++++++++++++++++++++++++------------------------ 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 67cabcfb..1787d688 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -810,12 +810,13 @@ handle_method(#'basic.qos'{prefetch_size = Size}, _, _State) when Size /= 0 -> handle_method(#'basic.qos'{prefetch_count = 0}, _, State = #ch{limiter = Limiter}) -> - Limiter1 = rabbit_limiter:unlimit(Limiter), + Limiter1 = rabbit_limiter:unlimit_prefetch(Limiter), {reply, #'basic.qos_ok'{}, State#ch{limiter = Limiter1}}; handle_method(#'basic.qos'{prefetch_count = PrefetchCount}, _, State = #ch{limiter = Limiter, unacked_message_q = UAMQ}) -> - Limiter1 = rabbit_limiter:limit(Limiter, PrefetchCount, queue:len(UAMQ)), + Limiter1 = rabbit_limiter:limit_prefetch(Limiter, + PrefetchCount, queue:len(UAMQ)), {reply, #'basic.qos_ok'{}, maybe_limit_queues(Limiter, Limiter1, State#ch{limiter = Limiter1})}; @@ -1363,7 +1364,7 @@ consumer_queues(Consumers) -> notify_limiter(Limiter, Acked) -> %% optimisation: avoid the potentially expensive 'foldl' in the %% common case. - case rabbit_limiter:is_limited(Limiter) of + case rabbit_limiter:is_prefetch_limited(Limiter) of false -> ok; true -> case lists:foldl(fun ({_, none, _}, Acc) -> Acc; ({_, _, _}, Acc) -> Acc + 1 @@ -1528,7 +1529,7 @@ i(messages_uncommitted, #ch{}) -> 0; i(acks_uncommitted, #ch{tx = {_Msgs, Acks}}) -> ack_len(Acks); i(acks_uncommitted, #ch{}) -> 0; i(prefetch_count, #ch{limiter = Limiter}) -> - rabbit_limiter:get_limit(Limiter); + rabbit_limiter:get_prefetch_limit(Limiter); i(client_flow_blocked, #ch{limiter = Limiter}) -> rabbit_limiter:is_blocked(Limiter); i(Item, _) -> diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index b914306b..430c2716 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -57,9 +57,10 @@ %% The interactions with the limiter are as follows: %% %% 1. Channels tell the limiter about basic.qos prefetch counts - -%% that's what the limit/3, unlimit/1, is_limited/1, get_limit/1 -%% API functions are about - and channel.flow blocking - that's -%% what block/1, unblock/1 and is_blocked/1 are for. +%% that's what the limit_prefetch/3, unlimit_prefetch/1, +%% is_prefetch_limited/1, get_prefetch_limit/1 API functions are +%% about - and channel.flow blocking - that's what block/1, +%% unblock/1 and is_blocked/1 are for. %% %% 2. Queues register with the limiter - this happens as part of %% activate/1. @@ -106,8 +107,9 @@ -export([start_link/0]). %% channel API --export([new/1, limit/3, unlimit/1, block/1, unblock/1, - is_limited/1, is_blocked/1, is_active/1, get_limit/1, ack/2, pid/1]). +-export([new/1, limit_prefetch/3, unlimit_prefetch/1, block/1, unblock/1, + is_prefetch_limited/1, is_blocked/1, is_active/1, + get_prefetch_limit/1, ack/2, pid/1]). %% queue API -export([client/1, activate/1, can_send/2, resume/1, deactivate/1, is_suspended/1]). @@ -117,31 +119,31 @@ %%---------------------------------------------------------------------------- --record(lstate, {pid, limited, blocked}). +-record(lstate, {pid, prefetch_limited, blocked}). -record(qstate, {pid, state}). -ifdef(use_specs). --type(lstate() :: #lstate{pid :: pid(), - limited :: boolean(), - blocked :: boolean()}). +-type(lstate() :: #lstate{pid :: pid(), + prefetch_limited :: boolean(), + blocked :: boolean()}). -type(qstate() :: #qstate{pid :: pid(), state :: 'dormant' | 'active' | 'suspended'}). -spec(start_link/0 :: () -> rabbit_types:ok_pid_or_error()). -spec(new/1 :: (pid()) -> lstate()). --spec(limit/3 :: (lstate(), non_neg_integer(), non_neg_integer()) -> - lstate()). --spec(unlimit/1 :: (lstate()) -> lstate()). --spec(block/1 :: (lstate()) -> lstate()). --spec(unblock/1 :: (lstate()) -> lstate()). --spec(is_limited/1 :: (lstate()) -> boolean()). --spec(is_blocked/1 :: (lstate()) -> boolean()). --spec(is_active/1 :: (lstate()) -> boolean()). --spec(get_limit/1 :: (lstate()) -> non_neg_integer()). --spec(ack/2 :: (lstate(), non_neg_integer()) -> 'ok'). --spec(pid/1 :: (lstate()) -> pid()). +-spec(limit_prefetch/3 :: (lstate(), non_neg_integer(), non_neg_integer()) + -> lstate()). +-spec(unlimit_prefetch/1 :: (lstate()) -> lstate()). +-spec(block/1 :: (lstate()) -> lstate()). +-spec(unblock/1 :: (lstate()) -> lstate()). +-spec(is_prefetch_limited/1 :: (lstate()) -> boolean()). +-spec(is_blocked/1 :: (lstate()) -> boolean()). +-spec(is_active/1 :: (lstate()) -> boolean()). +-spec(get_prefetch_limit/1 :: (lstate()) -> non_neg_integer()). +-spec(ack/2 :: (lstate(), non_neg_integer()) -> 'ok'). +-spec(pid/1 :: (lstate()) -> pid()). -spec(client/1 :: (pid()) -> qstate()). -spec(activate/1 :: (qstate()) -> qstate()). @@ -173,15 +175,16 @@ start_link() -> gen_server2:start_link(?MODULE, [], []). new(Pid) -> %% this a 'call' to ensure that it is invoked at most once. ok = gen_server:call(Pid, {new, self()}), - #lstate{pid = Pid, limited = false, blocked = false}. + #lstate{pid = Pid, prefetch_limited = false, blocked = false}. -limit(L, PrefetchCount, UnackedCount) when PrefetchCount > 0 -> - ok = gen_server:call(L#lstate.pid, {limit, PrefetchCount, UnackedCount}), - L#lstate{limited = true}. +limit_prefetch(L, PrefetchCount, UnackedCount) when PrefetchCount > 0 -> + ok = gen_server:call(L#lstate.pid, + {limit_prefetch, PrefetchCount, UnackedCount}), + L#lstate{prefetch_limited = true}. -unlimit(L) -> - ok = gen_server:call(L#lstate.pid, unlimit), - L#lstate{limited = false}. +unlimit_prefetch(L) -> + ok = gen_server:call(L#lstate.pid, unlimit_prefetch), + L#lstate{prefetch_limited = false}. block(L) -> ok = gen_server:call(L#lstate.pid, block), @@ -191,16 +194,16 @@ unblock(L) -> ok = gen_server:call(L#lstate.pid, unblock), L#lstate{blocked = false}. -is_limited(#lstate{limited = Limited}) -> Limited. +is_prefetch_limited(#lstate{prefetch_limited = Limited}) -> Limited. is_blocked(#lstate{blocked = Blocked}) -> Blocked. -is_active(L) -> is_limited(L) orelse is_blocked(L). +is_active(L) -> is_prefetch_limited(L) orelse is_blocked(L). -get_limit(#lstate{limited = false}) -> 0; -get_limit(L) -> gen_server:call(L#lstate.pid, get_limit). +get_prefetch_limit(#lstate{prefetch_limited = false}) -> 0; +get_prefetch_limit(L) -> gen_server:call(L#lstate.pid, get_prefetch_limit). -ack(#lstate{limited = false}, _AckCount) -> ok; +ack(#lstate{prefetch_limited = false}, _AckCount) -> ok; ack(L, AckCount) -> gen_server:cast(L#lstate.pid, {ack, AckCount}). pid(#lstate{pid = Pid}) -> Pid. @@ -212,8 +215,6 @@ activate(L = #qstate{state = dormant}) -> L#qstate{state = active}; activate(L) -> L. -%% Ask the limiter whether the queue can deliver a message without -%% breaching a limit. can_send(L = #qstate{state = active}, AckRequired) -> rabbit_misc:with_exit_handler( fun () -> {continue, L} end, @@ -241,20 +242,20 @@ is_suspended(#qstate{}) -> false. init([]) -> {ok, #lim{}}. -prioritise_call(get_limit, _From, _State) -> 9; -prioritise_call(_Msg, _From, _State) -> 0. +prioritise_call(get_prefetch_limit, _From, _State) -> 9; +prioritise_call(_Msg, _From, _State) -> 0. handle_call({new, ChPid}, _From, State = #lim{ch_pid = undefined}) -> {reply, ok, State#lim{ch_pid = ChPid}}; -handle_call({limit, PrefetchCount, UnackedCount}, _From, State) -> +handle_call({limit_prefetch, PrefetchCount, UnackedCount}, _From, State) -> %% assertion true = State#lim.prefetch_count == 0 orelse State#lim.volume == UnackedCount, {reply, ok, maybe_notify(State, State#lim{prefetch_count = PrefetchCount, volume = UnackedCount})}; -handle_call(unlimit, _From, State) -> +handle_call(unlimit_prefetch, _From, State) -> {reply, ok, maybe_notify(State, State#lim{prefetch_count = 0, volume = 0})}; @@ -264,7 +265,8 @@ handle_call(block, _From, State) -> handle_call(unblock, _From, State) -> {reply, ok, maybe_notify(State, State#lim{blocked = false})}; -handle_call(get_limit, _From, State = #lim{prefetch_count = PrefetchCount}) -> +handle_call(get_prefetch_limit, _From, + State = #lim{prefetch_count = PrefetchCount}) -> {reply, PrefetchCount, State}; handle_call({can_send, QPid, _AckRequired}, _From, @@ -272,7 +274,7 @@ handle_call({can_send, QPid, _AckRequired}, _From, {reply, false, limit_queue(QPid, State)}; handle_call({can_send, QPid, AckRequired}, _From, State = #lim{volume = Volume}) -> - case limit_reached(State) of + case prefetch_limit_reached(State) of true -> {reply, false, limit_queue(QPid, State)}; false -> {reply, true, State#lim{volume = if AckRequired -> Volume + 1; true -> Volume @@ -305,13 +307,13 @@ code_change(_, State, _) -> %%---------------------------------------------------------------------------- maybe_notify(OldState, NewState) -> - case (limit_reached(OldState) orelse blocked(OldState)) andalso - not (limit_reached(NewState) orelse blocked(NewState)) of + case (prefetch_limit_reached(OldState) orelse blocked(OldState)) andalso + not (prefetch_limit_reached(NewState) orelse blocked(NewState)) of true -> notify_queues(NewState); false -> NewState end. -limit_reached(#lim{prefetch_count = Limit, volume = Volume}) -> +prefetch_limit_reached(#lim{prefetch_count = Limit, volume = Volume}) -> Limit =/= 0 andalso Volume >= Limit. blocked(#lim{blocked = Blocked}) -> Blocked. -- cgit v1.2.1 From b5cc5097783245010a335f6a8f06c86eb55d2d62 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 20 Mar 2013 16:24:45 +0000 Subject: First attempt at pinging that doesn't really work well. --- src/rabbit_node_monitor.erl | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index de53b7f0..4aaf634e 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -32,8 +32,9 @@ -define(SERVER, ?MODULE). -define(RABBIT_UP_RPC_TIMEOUT, 2000). +-define(RABBIT_DOWN_PING_INTERVAL, 1000). --record(state, {monitors, partitions, subscribers}). +-record(state, {monitors, partitions, subscribers, down_ping_timer}). %%---------------------------------------------------------------------------- @@ -272,10 +273,22 @@ handle_info({mnesia_system_event, ordsets:add_element(Node, ordsets:from_list(Partitions))), {noreply, State1#state{partitions = Partitions1}}; +handle_info(ping_nodes, State) -> + %% We ping nodes when some are down to ensure that we find out + %% about healed partitions quickly. We ping all nodes rather than + %% just the ones we know are down for simplicity; it's not expensive. + State1 = State#state{down_ping_timer = undefined}, + %% ratio() both pings all the nodes and tells us if we need to again :-) + {noreply, case ratio() of + 1.0 -> State1; + _ -> ensure_ping_timer(State1) + end}; + handle_info(_Info, State) -> {noreply, State}. -terminate(_Reason, _State) -> +terminate(_Reason, State) -> + rabbit_misc:stop_timer(State, #state.down_ping_timer), ok. code_change(_OldVsn, State, _Extra) -> @@ -308,8 +321,8 @@ handle_dead_rabbit(Node) -> end, ok. -majority() -> - length(alive_nodes()) / length(rabbit_mnesia:cluster_nodes(all)) > 0.5. +majority() -> ratio() > 0.5. +ratio() -> length(alive_nodes()) / length(rabbit_mnesia:cluster_nodes(all)). %% mnesia:system_info(db_nodes) (and hence %% rabbit_mnesia:cluster_nodes(running)) does not give reliable results @@ -350,7 +363,11 @@ handle_dead_rabbit_state(State = #state{partitions = Partitions}) -> [] -> []; _ -> Partitions end, - State#state{partitions = Partitions1}. + ensure_ping_timer(State#state{partitions = Partitions1}). + +ensure_ping_timer(State) -> + rabbit_misc:ensure_timer( + State, #state.down_ping_timer, ?RABBIT_DOWN_PING_INTERVAL, ping_nodes). handle_live_rabbit(Node) -> ok = rabbit_alarm:on_node_up(Node), -- cgit v1.2.1 From ecc36c55bfd41a5eaf252057c87f5edc986bc35a Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 21 Mar 2013 10:02:31 +0000 Subject: Exchange decorator routing improvements --- src/rabbit_exchange.erl | 6 +++--- src/rabbit_exchange_decorator.erl | 7 +++++++ src/rabbit_registry.erl | 21 ++++++++++++++------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index a4a88661..a3b32c7b 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -335,7 +335,7 @@ route1(Delivery, Decorators, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> ExchangeDests = (type_to_module(Type)):route(X, Delivery), AlternateDests = process_alternate(X, ExchangeDests), - DecorateDests = process_decorators(Delivery, Decorators, X), + DecorateDests = process_decorators(X, Decorators, Delivery), route1(Delivery, Decorators, lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, AlternateDests ++ DecorateDests ++ ExchangeDests)). @@ -350,9 +350,9 @@ process_alternate(#exchange{name = XName, arguments = Args}, []) -> process_alternate(_X, _Results) -> []. -process_decorators(_Delivery, [], _X) -> +process_decorators(_, [], _) -> []; -process_decorators(Delivery, Decorators, X) -> +process_decorators(X, Decorators, Delivery) -> lists:append([Decorator:route(X, Delivery) || Decorator <- Decorators]). process_route(#resource{kind = exchange} = XName, diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index bf4add73..d8600835 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -57,6 +57,13 @@ -callback remove_bindings(serial(), rabbit_types:exchange(), [rabbit_types:binding()]) -> 'ok'. +%% %% called after exchange routing +%% %% return value is a list of queues to be added to the list of +%% %% destination queues. decorators must register separately for +%% %% this callback using exchange_decorator_route. +%% -callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> +%% [rabbit_amqqueue:name()]. + -else. -export([behaviour_info/1]). diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl index 41b82ba5..22700a7c 100644 --- a/src/rabbit_registry.erl +++ b/src/rabbit_registry.erl @@ -95,20 +95,27 @@ internal_unregister(Class, TypeName) -> true = ets:delete(?ETS_NAME, UnregArg), ok. -conditional_register({{exchange_decorator, _Type}, ModuleName} = RegArg) -> + +%% (un)register exchange decorator route callback only when implemented +%% to avoid decorators being called unnecessarily on the fast publishing path +conditional_register({{exchange_decorator, _Type}, ModuleName}) -> case erlang:function_exported(ModuleName, route, 2) of - true -> true = ets:insert(?ETS_NAME, RegArg); + true -> true = ets:insert(?ETS_NAME, + {{exchange_decorator_route, _Type}, + ModuleName}); false -> ok end; conditional_register(_) -> ok. -conditional_unregister({exchange_decorator, Type} = UnregArg) -> +conditional_unregister({exchange_decorator, Type}) -> case lookup_module(exchange_decorator, Type) of - {ok, ModName} -> case erlang:function_exported(ModName, route, 2) of - true -> true = ets:delete(?ETS_NAME, UnregArg); - false -> ok - end; + {ok, ModName} -> + case erlang:function_exported(ModName, route, 2) of + true -> true = ets:delete(?ETS_NAME, + {exchange_decorator_route, Type}); + false -> ok + end; {error, not_found} -> ok end; -- cgit v1.2.1 From 30965c881b81a27880c1a81fcc735f79dd1f8982 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Mar 2013 10:41:42 +0000 Subject: Merge the two can_sends and tidy up. --- src/rabbit_amqqueue_process.erl | 11 +++----- src/rabbit_limiter.erl | 56 ++++++++++++++++------------------------- 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 8f325e5c..22bbbd00 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -447,16 +447,13 @@ deliver_msg_to_consumer(DeliverFun, E = {ChPid, Consumer}, State) -> false -> case rabbit_limiter:can_send(C#cr.limiter, Consumer#consumer.ack_required, Consumer#consumer.tag) of - consumer_blocked -> - block_consumer(C, E), + {suspend, Limiter} -> + block_consumer(C#cr{limiter = Limiter}, E), {false, State}; - channel_blocked -> - block_consumer(C, E), - {false, State}; - Limiter2 -> + {continue, Limiter} -> AC1 = queue:in(E, State#q.active_consumers), deliver_msg_to_consumer( - DeliverFun, Consumer, C#cr{limiter = Limiter2}, + DeliverFun, Consumer, C#cr{limiter = Limiter}, State#q{active_consumers = AC1}) end end. diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 71ed2e73..6825622d 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -111,14 +111,13 @@ is_prefetch_limited/1, is_blocked/1, is_active/1, get_prefetch_limit/1, ack/2, pid/1]). %% queue API --export([client/1, activate/1, can_send/2, resume/1, deactivate/1, - is_suspended/1]). +-export([client/1, activate/1, can_send/3, resume/1, deactivate/1, + is_suspended/1, is_consumer_blocked/2, credit/4, drained/1, + forget_consumer/2]). %% callbacks -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, prioritise_call/3]). --export([is_consumer_blocked/2, credit/4, drained/1, forget_consumer/2]). - -import(rabbit_misc, [serial_add/2, serial_diff/2]). %%---------------------------------------------------------------------------- @@ -141,8 +140,6 @@ -> lstate()). -spec(unlimit_prefetch/1 :: (lstate()) -> lstate()). -spec(block/1 :: (lstate()) -> lstate()). --spec(can_send/4 :: (qstate(), pid(), boolean(), rabbit_types:ctag()) - -> qstate() | 'consumer_blocked' | 'channel_blocked'). -spec(unblock/1 :: (lstate()) -> lstate()). -spec(is_prefetch_limited/1 :: (lstate()) -> boolean()). -spec(is_blocked/1 :: (lstate()) -> boolean()). @@ -153,7 +150,7 @@ -spec(client/1 :: (pid()) -> qstate()). -spec(activate/1 :: (qstate()) -> qstate()). --spec(can_send/2 :: (qstate(), boolean()) -> +-spec(can_send/3 :: (qstate(), boolean(), rabbit_types:ctag()) -> {'continue' | 'suspend', qstate()}). -spec(resume/1 :: (qstate()) -> qstate()). -spec(deactivate/1 :: (qstate()) -> qstate()). @@ -210,24 +207,6 @@ unblock(L) -> is_prefetch_limited(#lstate{prefetch_limited = Limited}) -> Limited. -can_send(Token = #qstate{pid = Pid, state = State, credits = Credits}, - QPid, AckReq, CTag) -> - case is_consumer_blocked(Token, CTag) of - false -> case State =/= active orelse call_can_send(Pid, QPid, AckReq) of - true -> Token#qstate{ - credits = record_send_q(CTag, Credits)}; - false -> channel_blocked - end; - true -> consumer_blocked - end. - -call_can_send(Pid, QPid, AckRequired) -> - rabbit_misc:with_exit_handler( - fun () -> true end, - fun () -> - gen_server2:call(Pid, {can_send, QPid, AckRequired}, infinity) - end). - is_blocked(#lstate{blocked = Blocked}) -> Blocked. is_active(L) -> is_prefetch_limited(L) orelse is_blocked(L). @@ -247,16 +226,24 @@ activate(L = #qstate{state = dormant}) -> L#qstate{state = active}; activate(L) -> L. -can_send(L = #qstate{state = active}, AckRequired) -> +can_send(L = #qstate{pid = Pid, state = State, credits = Credits}, + AckReq, CTag) -> + case is_consumer_blocked(L, CTag) of + false -> case State =/= active orelse call_can_send( + Pid, self(), AckReq) of + true -> {continue, L#qstate{ + credits = record_send_q(CTag, Credits)}}; + false -> {suspend, L#qstate{state = suspended}} + end; + true -> {suspend, L#qstate{state = suspended}} + end. + +call_can_send(Pid, QPid, AckRequired) -> rabbit_misc:with_exit_handler( - fun () -> {continue, L} end, - fun () -> Msg = {can_send, self(), AckRequired}, - case gen_server2:call(L#qstate.pid, Msg, infinity) of - true -> {continue, L}; - false -> {suspend, L#qstate{state = suspended}} - end - end); -can_send(L, _AckRequired) -> {continue, L}. + fun () -> true end, + fun () -> + gen_server2:call(Pid, {can_send, QPid, AckRequired}, infinity) + end). resume(L) -> L#qstate{state = active}. @@ -275,7 +262,6 @@ is_consumer_blocked(#qstate{credits = Credits}, CTag) -> none -> false end. - credit(Limiter = #qstate{credits = Credits}, CTag, Credit, Drain) -> Limiter#qstate{credits = update_credit(CTag, Credit, Drain, Credits)}. -- cgit v1.2.1 From e169405354ca9567de824c561c97d3fda9cb8a45 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Mar 2013 11:10:52 +0000 Subject: oops --- src/rabbit_tests.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index f56fe8ee..7ecea035 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1131,7 +1131,7 @@ test_server_status() -> rabbit_misc:r(<<"/">>, queue, Name), false, false, [], none)]], ok = rabbit_amqqueue:basic_consume( - Q, true, Ch, Limiter, false, <<"ctag">>, true, undefined), + Q, true, Ch, Limiter, false, <<"ctag">>, true, none, undefined), %% list queues ok = info_action(list_queues, rabbit_amqqueue:info_keys(), true), -- cgit v1.2.1 From e54051656c68e06b7e4833e8a44ceb204f8d37be Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Mar 2013 11:22:14 +0000 Subject: Cosmetic, reduce distance to bug25461, remove dead comment --- src/rabbit_amqqueue.erl | 1 - src/rabbit_amqqueue_process.erl | 26 +++++++++++--------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 2dfed21d..8c00c85c 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -548,7 +548,6 @@ credit(#amqqueue{pid = QPid}, ChPid, CTag, Credit, Drain) -> basic_get(#amqqueue{pid = QPid}, ChPid, NoAck, LimiterPid) -> delegate:call(QPid, {basic_get, ChPid, NoAck, LimiterPid}). - basic_consume(#amqqueue{pid = QPid}, NoAck, ChPid, LimiterPid, LimiterActive, ConsumerTag, ExclusiveConsume, CreditArgs, OkMsg) -> delegate:call(QPid, {basic_consume, NoAck, ChPid, LimiterPid, LimiterActive, diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 22bbbd00..bff762f3 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -71,8 +71,6 @@ blocked_consumers, %% The limiter itself limiter, - %% Has the limiter imposed a channel-wide block, either - %% because of qos or channel flow? %% Internal flow control for queue -> writer unsent_message_count}). @@ -1157,16 +1155,14 @@ handle_call({basic_consume, NoAck, ChPid, LimiterPid, LimiterActive, ok -> C = #cr{consumer_count = Count, limiter = Limiter} = ch_record(ChPid, LimiterPid), - Limiter1 = case CreditArgs of - none -> - Limiter; - {Credit, Drain} -> - rabbit_limiter:credit( - Limiter, ConsumerTag, Credit, Drain) + Limiter1 = case LimiterActive of + true -> rabbit_limiter:activate(Limiter); + false -> Limiter end, - Limiter2 = case LimiterActive of - true -> rabbit_limiter:activate(Limiter1); - false -> Limiter1 + Limiter2 = case CreditArgs of + none -> Limiter1; + {Crd, Drain} -> rabbit_limiter:credit( + Limiter1, ConsumerTag, Crd, Drain) end, C1 = update_ch_record(C#cr{consumer_count = Count + 1, limiter = Limiter2}), @@ -1198,12 +1194,12 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, limiter = Limiter, blocked_consumers = Blocked} -> emit_consumer_deleted(ChPid, ConsumerTag, qname(State)), - Limiter1 = rabbit_limiter:forget_consumer(Limiter, ConsumerTag), Blocked1 = remove_consumer(ChPid, ConsumerTag, Blocked), - Limiter2 = case Count of - 1 -> rabbit_limiter:deactivate(Limiter1); - _ -> Limiter1 + Limiter1 = case Count of + 1 -> rabbit_limiter:deactivate(Limiter); + _ -> Limiter end, + Limiter2 = rabbit_limiter:forget_consumer(Limiter1, ConsumerTag), update_ch_record(C#cr{consumer_count = Count - 1, limiter = Limiter2, blocked_consumers = Blocked1}), -- cgit v1.2.1 From 4fd233bd49868359f55c50a509f7b87b8f55a8c5 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 21 Mar 2013 11:33:40 +0000 Subject: Modify decorator routing comment, variable and whitespace --- src/rabbit_exchange_decorator.erl | 8 +++----- src/rabbit_registry.erl | 5 ++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index d8600835..45d0cbf5 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -57,12 +57,10 @@ -callback remove_bindings(serial(), rabbit_types:exchange(), [rabbit_types:binding()]) -> 'ok'. -%% %% called after exchange routing -%% %% return value is a list of queues to be added to the list of -%% %% destination queues. decorators must register separately for -%% %% this callback using exchange_decorator_route. +%% Decorators can optionally implement route/2 which allows additional +%% queues to be added to the routing decision. %% -callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> -%% [rabbit_amqqueue:name()]. +%% [rabbit_amqqueue:name() | rabbit_types:exchange()]. -else. diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl index 22700a7c..f6bc4071 100644 --- a/src/rabbit_registry.erl +++ b/src/rabbit_registry.erl @@ -95,13 +95,12 @@ internal_unregister(Class, TypeName) -> true = ets:delete(?ETS_NAME, UnregArg), ok. - %% (un)register exchange decorator route callback only when implemented %% to avoid decorators being called unnecessarily on the fast publishing path -conditional_register({{exchange_decorator, _Type}, ModuleName}) -> +conditional_register({{exchange_decorator, Type}, ModuleName}) -> case erlang:function_exported(ModuleName, route, 2) of true -> true = ets:insert(?ETS_NAME, - {{exchange_decorator_route, _Type}, + {{exchange_decorator_route, Type}, ModuleName}); false -> ok end; -- cgit v1.2.1 From 5a5ff913f7bdbc081689e6ef235e828178a2ce47 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 21 Mar 2013 11:40:42 +0000 Subject: Comment --- src/rabbit_exchange_decorator.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 45d0cbf5..040b55db 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -58,9 +58,9 @@ [rabbit_types:binding()]) -> 'ok'. %% Decorators can optionally implement route/2 which allows additional -%% queues to be added to the routing decision. +%% destinations to be added to the routing decision. %% -callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> -%% [rabbit_amqqueue:name() | rabbit_types:exchange()]. +%% [rabbit_amqqueue:name() | rabbit_exchange:name()]. -else. -- cgit v1.2.1 From dc22fedbe7689b64cc35bbb03def0fc295540c05 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Mar 2013 11:57:00 +0000 Subject: cosmetic --- src/rabbit_exchange.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index a3b32c7b..9e98448d 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -333,9 +333,9 @@ route1(_, _, {[], _, QNames}) -> QNames; route1(Delivery, Decorators, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> - ExchangeDests = (type_to_module(Type)):route(X, Delivery), + ExchangeDests = (type_to_module(Type)):route(X, Delivery), + DecorateDests = process_decorators(X, Decorators, Delivery), AlternateDests = process_alternate(X, ExchangeDests), - DecorateDests = process_decorators(X, Decorators, Delivery), route1(Delivery, Decorators, lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, AlternateDests ++ DecorateDests ++ ExchangeDests)). @@ -350,7 +350,7 @@ process_alternate(#exchange{name = XName, arguments = Args}, []) -> process_alternate(_X, _Results) -> []. -process_decorators(_, [], _) -> +process_decorators(_, [], _) -> %% optimisation []; process_decorators(X, Decorators, Delivery) -> lists:append([Decorator:route(X, Delivery) || Decorator <- Decorators]). -- cgit v1.2.1 From 1b0febe01dae9aca674393cf708c768f80822e59 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Mar 2013 12:03:31 +0000 Subject: simplify --- src/rabbit_registry.erl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl index f6bc4071..9f1b52e6 100644 --- a/src/rabbit_registry.erl +++ b/src/rabbit_registry.erl @@ -108,16 +108,8 @@ conditional_register(_) -> ok. conditional_unregister({exchange_decorator, Type}) -> - case lookup_module(exchange_decorator, Type) of - {ok, ModName} -> - case erlang:function_exported(ModName, route, 2) of - true -> true = ets:delete(?ETS_NAME, - {exchange_decorator_route, Type}); - false -> ok - end; - {error, not_found} -> - ok - end; + true = ets:delete(?ETS_NAME, {exchange_decorator_route, Type}), + ok; conditional_unregister(_) -> ok. -- cgit v1.2.1 From ae743ee4ea4e6cec41fb43710b7f908c8f9ed71f Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Mar 2013 12:05:20 +0000 Subject: correct comment --- src/rabbit_registry.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl index 9f1b52e6..acdc2cff 100644 --- a/src/rabbit_registry.erl +++ b/src/rabbit_registry.erl @@ -95,8 +95,9 @@ internal_unregister(Class, TypeName) -> true = ets:delete(?ETS_NAME, UnregArg), ok. -%% (un)register exchange decorator route callback only when implemented -%% to avoid decorators being called unnecessarily on the fast publishing path +%% register exchange decorator route callback only when implemented, +%% in order to avoid unnecessary decorator calls on the fast +%% publishing path conditional_register({{exchange_decorator, Type}, ModuleName}) -> case erlang:function_exported(ModuleName, route, 2) of true -> true = ets:insert(?ETS_NAME, -- cgit v1.2.1 From 530c1203d73101eb781bf514b848cd613f42a857 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Mar 2013 12:27:25 +0000 Subject: Make CheckVHost actually do something. --- src/rabbit_tests.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 1188c554..cd8fa720 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -1580,7 +1580,7 @@ control_action(Command, Node, Args, Opts) -> info_action(Command, Args, CheckVHost) -> ok = control_action(Command, []), - if CheckVHost -> ok = control_action(Command, []); + if CheckVHost -> ok = control_action(Command, [], ["-p", "/"]); true -> ok end, ok = control_action(Command, lists:map(fun atom_to_list/1, Args)), -- cgit v1.2.1 From d7a121de182f25c906e5c2326b080421d016c88c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Mar 2013 13:22:03 +0000 Subject: suspended =/= has run out of credit. --- 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 6825622d..a187fd7b 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -235,7 +235,7 @@ can_send(L = #qstate{pid = Pid, state = State, credits = Credits}, credits = record_send_q(CTag, Credits)}}; false -> {suspend, L#qstate{state = suspended}} end; - true -> {suspend, L#qstate{state = suspended}} + true -> {suspend, L} end. call_can_send(Pid, QPid, AckRequired) -> -- cgit v1.2.1 From 54a5439a4ef96335d75b20d8807b24bc8c69ac3d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 21 Mar 2013 15:32:34 +0000 Subject: Do the pinging in a separate process so we don't block the node monitor. --- src/rabbit_node_monitor.erl | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 4aaf634e..16b52a4d 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -276,13 +276,25 @@ handle_info({mnesia_system_event, handle_info(ping_nodes, State) -> %% We ping nodes when some are down to ensure that we find out %% about healed partitions quickly. We ping all nodes rather than - %% just the ones we know are down for simplicity; it's not expensive. + %% just the ones we know are down for simplicity; it's not expensive + %% to ping the nodes that are up, after all. State1 = State#state{down_ping_timer = undefined}, - %% ratio() both pings all the nodes and tells us if we need to again :-) - {noreply, case ratio() of - 1.0 -> State1; - _ -> ensure_ping_timer(State1) - end}; + Self = self(), + %% ratio() both pings all the nodes and tells us if we need to again. + %% + %% We ping in a separate process since in a partition it might + %% take some noticeable length of time and we don't want to block + %% the node monitor for that long. + spawn_link(fun () -> + case ratio() of + 1.0 -> ok; + _ -> Self ! ping_again + end + end), + {noreply, State1}; + +handle_info(ping_again, State) -> + {noreply, ensure_ping_timer(State)}; handle_info(_Info, State) -> {noreply, State}. -- cgit v1.2.1 From 42d7a9385fd422e618124666369adadc7b9ac430 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Mar 2013 17:01:54 +0000 Subject: re-introduce state-transition optimisation for possibly_unblock --- src/rabbit_amqqueue_process.erl | 50 ++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index c6a8bf2f..e24568bb 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -644,28 +644,28 @@ remove_consumers(ChPid, Queue, QName) -> possibly_unblock(State, ChPid, Update) -> case lookup_ch(ChPid) of not_found -> State; - C -> possibly_unblock(State, Update(C)) + C -> C1 = Update(C), + case is_ch_blocked(C) andalso not is_ch_blocked(C1) of + false -> update_ch_record(C1), + State; + true -> unblock(State, C1) + end end. -possibly_unblock(State, C = #cr{limiter = Limiter}) -> - case is_ch_blocked(C) of - true -> update_ch_record(C), - State; - false -> case lists:partition( - fun({_ChPid, #consumer{tag = CTag}}) -> - rabbit_limiter:is_consumer_blocked( - Limiter, CTag) - end, queue:to_list(C#cr.blocked_consumers)) of - {_, []} -> - update_ch_record(C), - State; - {Blocked, Unblocked} -> - BlockedQ = queue:from_list(Blocked), - UnblockedQ = queue:from_list(Unblocked), - update_ch_record(C#cr{blocked_consumers = BlockedQ}), - AC1 = queue:join(State#q.active_consumers, UnblockedQ), - run_message_queue(State#q{active_consumers = AC1}) - end +unblock(State, C = #cr{limiter = Limiter}) -> + case lists:partition( + fun({_ChPid, #consumer{tag = CTag}}) -> + rabbit_limiter:is_consumer_blocked(Limiter, CTag) + end, queue:to_list(C#cr.blocked_consumers)) of + {_, []} -> + update_ch_record(C), + State; + {Blocked, Unblocked} -> + BlockedQ = queue:from_list(Blocked), + UnblockedQ = queue:from_list(Unblocked), + update_ch_record(C#cr{blocked_consumers = BlockedQ}), + AC1 = queue:join(State#q.active_consumers, UnblockedQ), + run_message_queue(State#q{active_consumers = AC1}) end. should_auto_delete(#q{q = #amqqueue{auto_delete = false}}) -> false; @@ -1389,13 +1389,17 @@ handle_cast({credit, ChPid, CTag, Credit, Drain}, backing_queue_state = BQS}) -> Len = BQ:len(BQS), rabbit_channel:send_credit_reply(ChPid, Len), - C = #cr{limiter = Lim} = lookup_ch(ChPid), - C1 = C#cr{limiter = rabbit_limiter:credit(Lim, CTag, Credit, Drain)}, + C = #cr{limiter = Limiter} = lookup_ch(ChPid), + C1 = C#cr{limiter = rabbit_limiter:credit(Limiter, CTag, Credit, Drain)}, noreply(case Drain andalso Len == 0 of true -> update_ch_record(C1), send_drained(C1), State; - false -> possibly_unblock(State, C1) + false -> case is_ch_blocked(C1) of + true -> update_ch_record(C1), + State; + false -> unblock(State, C1) + end end); handle_cast(wake_up, State) -> -- cgit v1.2.1 From 209677404df56137c57d33b6a566ab4555792ae4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Mar 2013 17:22:31 +0000 Subject: cosmetic(ish) --- src/rabbit_channel.erl | 3 +-- src/rabbit_limiter.erl | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl index 79a71b8d..39bd375a 100644 --- a/src/rabbit_channel.erl +++ b/src/rabbit_channel.erl @@ -330,8 +330,7 @@ handle_cast({send_credit_reply, Len}, State = #ch{writer_pid = WriterPid}) -> WriterPid, #'basic.credit_ok'{available = Len}), noreply(State); -handle_cast({send_drained, CTagCredit}, - State = #ch{writer_pid = WriterPid}) -> +handle_cast({send_drained, CTagCredit}, State = #ch{writer_pid = WriterPid}) -> [ok = rabbit_writer:send_command( WriterPid, #'basic.credit_drained'{consumer_tag = ConsumerTag, credit_drained = CreditDrained}) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index a187fd7b..602681e5 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -68,7 +68,7 @@ %% 4. The limiter process maintains an internal counter of 'messages %% sent but not yet acknowledged', called the 'volume'. %% -%% 5. Queues ask the limiter for permission (with can_send/2) whenever +%% 5. Queues ask the limiter for permission (with can_send/3) whenever %% they want to deliver a message to a channel. The limiter checks %% whether a) the channel isn't blocked by channel.flow, and b) the %% volume has not yet reached the prefetch limit. If so it @@ -77,10 +77,10 @@ %% tells the queue not to proceed. %% %% 6. A queue that has told to proceed (by the return value of -%% can_send/2) sends the message to the channel. Conversely, a +%% can_send/3) sends the message to the channel. Conversely, a %% queue that has been told not to proceed, will not attempt to %% deliver that message, or any future messages, to the -%% channel. This is accomplished by can_send/2 capturing the +%% channel. This is accomplished by can_send/3 capturing the %% outcome in the local state, where it can be accessed with %% is_suspended/1. %% @@ -88,7 +88,7 @@ %% how many messages were ack'ed. The limiter process decrements %% the volume and if it falls below the prefetch_count then it %% notifies (through rabbit_amqqueue:resume/2) all the queues -%% requiring notification, i.e. all those that had a can_send/2 +%% requiring notification, i.e. all those that had a can_send/3 %% request denied. %% %% 8. Upon receipt of such a notification, queues resume delivery to @@ -227,10 +227,10 @@ activate(L = #qstate{state = dormant}) -> activate(L) -> L. can_send(L = #qstate{pid = Pid, state = State, credits = Credits}, - AckReq, CTag) -> + AckRequired, CTag) -> case is_consumer_blocked(L, CTag) of - false -> case State =/= active orelse call_can_send( - Pid, self(), AckReq) of + false -> case (State =/= active orelse + safe_call(Pid, {can_send, self(), AckRequired}, true)) of true -> {continue, L#qstate{ credits = record_send_q(CTag, Credits)}}; false -> {suspend, L#qstate{state = suspended}} @@ -238,12 +238,10 @@ can_send(L = #qstate{pid = Pid, state = State, credits = Credits}, true -> {suspend, L} end. -call_can_send(Pid, QPid, AckRequired) -> +safe_call(Pid, Msg, ExitValue) -> rabbit_misc:with_exit_handler( - fun () -> true end, - fun () -> - gen_server2:call(Pid, {can_send, QPid, AckRequired}, infinity) - end). + fun () -> ExitValue end, + fun () -> gen_server2:call(Pid, Msg, infinity) end). resume(L) -> L#qstate{state = active}. -- cgit v1.2.1 From 3c54a951b3c29ad252eab9c6ae887fafed300a86 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Mar 2013 17:28:32 +0000 Subject: fix typo --- 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 602681e5..dfed5cb4 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -21,7 +21,7 @@ %% %% Each channel has an associated limiter process, created with %% start_link/1, which it passes to queues on consumer creation with -%% rabbit_amqqueue:basic_consume/8, and rabbit_amqqueue:basic_get/4. +%% rabbit_amqqueue:basic_consume/9, and rabbit_amqqueue:basic_get/4. %% The latter isn't strictly necessary, since basic.get is not %% subject to limiting, but it means that whenever a queue knows about %% a channel, it also knows about its limiter, which is less fiddly. -- cgit v1.2.1 From b357f2876a2a3de329aa67c9ef239bdfa26b1e96 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 21 Mar 2013 17:29:33 +0000 Subject: get rid of unused imports --- src/rabbit_limiter.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index dfed5cb4..489cfe37 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -118,8 +118,6 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, prioritise_call/3]). --import(rabbit_misc, [serial_add/2, serial_diff/2]). - %%---------------------------------------------------------------------------- -record(lstate, {pid, prefetch_limited, blocked}). -- cgit v1.2.1 From 9ab1619d08614af98fd0ccc48373470d37424530 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 22 Mar 2013 11:17:33 +0000 Subject: Move serial arithmetic out of the broker. --- src/rabbit_misc.erl | 51 --------------------------------------------------- src/rabbit_tests.erl | 24 ------------------------ 2 files changed, 75 deletions(-) diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 135f6443..c36fb147 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -69,7 +69,6 @@ -export([interval_operation/4]). -export([ensure_timer/4, stop_timer/2]). -export([get_parent/0]). --export([serial_add/2, serial_compare/2, serial_diff/2]). %% Horrible macro to use in guards -define(IS_BENIGN_EXIT(R), @@ -84,7 +83,6 @@ -ifdef(use_specs). -export_type([resource_name/0, thunk/1]). --export_type([serial_number/0]). -type(ok_or_error() :: rabbit_types:ok_or_error(any())). -type(thunk(T) :: fun(() -> T)). @@ -97,8 +95,6 @@ fun ((atom(), [term()]) -> [{digraph:vertex(), digraph_label()}])). -type(graph_edge_fun() :: fun ((atom(), [term()]) -> [{digraph:vertex(), digraph:vertex()}])). --type(serial_number() :: non_neg_integer()). --type(serial_compare_result() :: 'equal' | 'less' | 'greater'). -spec(method_record_type/1 :: (rabbit_framing:amqp_method_record()) -> rabbit_framing:amqp_method_name()). @@ -250,12 +246,6 @@ -spec(ensure_timer/4 :: (A, non_neg_integer(), non_neg_integer(), any()) -> A). -spec(stop_timer/2 :: (A, non_neg_integer()) -> A). -spec(get_parent/0 :: () -> pid()). --spec(serial_add/2 :: (serial_number(), non_neg_integer()) -> - serial_number()). --spec(serial_compare/2 :: (serial_number(), serial_number()) -> - serial_compare_result()). --spec(serial_diff/2 :: (serial_number(), serial_number()) -> - integer()). -endif. %%---------------------------------------------------------------------------- @@ -1109,44 +1099,3 @@ whereis_name(Name) -> %% End copypasta from gen_server2.erl %% ------------------------------------------------------------------------- - -%% Serial arithmetic for unsigned ints. -%% http://www.faqs.org/rfcs/rfc1982.html -%% SERIAL_BITS = 32 - -%% 2 ^ SERIAL_BITS --define(SERIAL_MAX, 16#100000000). -%% 2 ^ (SERIAL_BITS - 1) - 1 --define(SERIAL_MAX_ADDEND, 16#7fffffff). - -serial_add(S, N) when N =< ?SERIAL_MAX_ADDEND -> - (S + N) rem ?SERIAL_MAX; -serial_add(S, N) -> - exit({out_of_bound_serial_addition, S, N}). - -serial_compare(A, B) -> - if A =:= B -> - equal; - (A < B andalso B - A < ?SERIAL_MAX_ADDEND) orelse - (A > B andalso A - B > ?SERIAL_MAX_ADDEND) -> - less; - (A < B andalso B - A > ?SERIAL_MAX_ADDEND) orelse - (A > B andalso B - A < ?SERIAL_MAX_ADDEND) -> - greater; - true -> exit({indeterminate_serial_comparison, A, B}) - end. - --define(SERIAL_DIFF_BOUND, 16#80000000). - -serial_diff(A, B) -> - Diff = A - B, - if Diff > (?SERIAL_DIFF_BOUND) -> - %% B is actually greater than A - - (?SERIAL_MAX - Diff); - Diff < - (?SERIAL_DIFF_BOUND) -> - ?SERIAL_MAX + Diff; - Diff < ?SERIAL_DIFF_BOUND andalso Diff > -?SERIAL_DIFF_BOUND -> - Diff; - true -> - exit({indeterminate_serial_diff, A, B}) - end. diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 44b7fc4a..e7b69879 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -50,7 +50,6 @@ all_tests() -> passed = test_table_codec(), passed = test_content_framing(), passed = test_content_transcoding(), - passed = test_serial_arithmetic(), passed = test_topic_matching(), passed = test_log_management(), passed = test_app_management(), @@ -560,29 +559,6 @@ sequence_with_content(Sequence) -> rabbit_framing_amqp_0_9_1), Sequence). -test_serial_arithmetic() -> - 1 = rabbit_misc:serial_add(0, 1), - 16#7fffffff = rabbit_misc:serial_add(0, 16#7fffffff), - 0 = rabbit_misc:serial_add(16#ffffffff, 1), - %% Cannot add more than 2 ^ 31 - 1 - case catch rabbit_misc:serial_add(200, 16#80000000) of - {'EXIT', {out_of_bound_serial_addition, _, _}} -> ok; - _ -> exit(fail_out_of_bound_serial_addition) - end, - - 1 = rabbit_misc:serial_diff(1, 0), - 2 = rabbit_misc:serial_diff(1, 16#ffffffff), - -2 = rabbit_misc:serial_diff(16#ffffffff, 1), - case catch rabbit_misc:serial_diff(0, 16#80000000) of - {'EXIT', {indeterminate_serial_diff, _, _}} -> ok; - _ -> exit(fail_indeterminate_serial_difference) - end, - case catch rabbit_misc:serial_diff(16#ffffffff, 16#7fffffff) of - {'EXIT', {indeterminate_serial_diff, _, _}} -> ok; - _ -> exit(fail_indeterminate_serial_difference) - end, - passed. - test_topic_matching() -> XName = #resource{virtual_host = <<"/">>, kind = exchange, -- cgit v1.2.1 From 7aeba22413273a33eb3693f4d216d6d17425019d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 22 Mar 2013 12:05:17 +0000 Subject: Update essay for credit (and fix a few typos I couldn't be bothered to separate out). --- src/rabbit_limiter.erl | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl index 489cfe37..3a279940 100644 --- a/src/rabbit_limiter.erl +++ b/src/rabbit_limiter.erl @@ -16,8 +16,9 @@ %% The purpose of the limiter is to stem the flow of messages from %% queues to channels, in order to act upon various protocol-level -%% flow control mechanisms, specifically AMQP's basic.qos -%% prefetch_count and channel.flow. +%% flow control mechanisms, specifically AMQP 0-9-1's basic.qos +%% prefetch_count and channel.flow, and AMQP 1.0's link (aka consumer) +%% credit mechanism. %% %% Each channel has an associated limiter process, created with %% start_link/1, which it passes to queues on consumer creation with @@ -26,7 +27,7 @@ %% subject to limiting, but it means that whenever a queue knows about %% a channel, it also knows about its limiter, which is less fiddly. %% -%% Th limiter process holds state that is, in effect, shared between +%% The limiter process holds state that is, in effect, shared between %% the channel and all queues from which the channel is %% consuming. Essentially all these queues are competing for access to %% a single, limited resource - the ability to deliver messages via @@ -54,15 +55,27 @@ %% inactive. In practice it is rare for that to happen, though we %% could optimise this case in the future. %% +%% In addition, the consumer credit bookkeeping is local to queues, so +%% it is not necessary to store information about it in the limiter +%% process. But for abstraction we hide it from the queue behind the +%% limiter API, and it therefore becomes part of the queue local +%% state. +%% %% The interactions with the limiter are as follows: %% %% 1. Channels tell the limiter about basic.qos prefetch counts - %% that's what the limit_prefetch/3, unlimit_prefetch/1, %% is_prefetch_limited/1, get_prefetch_limit/1 API functions are %% about - and channel.flow blocking - that's what block/1, -%% unblock/1 and is_blocked/1 are for. +%% unblock/1 and is_blocked/1 are for. They also tell the limiter +%% queue state (via the queue) about consumer credit changes - +%% that's what credit/4 is for. +%% +%% 2. Queues also tell the limiter queue state about the queue +%% becoming empty (via drained/1) and consumers leaving (via +%% forget_consumer/2). %% -%% 2. Queues register with the limiter - this happens as part of +%% 3. Queues register with the limiter - this happens as part of %% activate/1. %% %% 4. The limiter process maintains an internal counter of 'messages @@ -70,13 +83,14 @@ %% %% 5. Queues ask the limiter for permission (with can_send/3) whenever %% they want to deliver a message to a channel. The limiter checks -%% whether a) the channel isn't blocked by channel.flow, and b) the -%% volume has not yet reached the prefetch limit. If so it -%% increments the volume and tells the queue to proceed. Otherwise -%% it marks the queue as requiring notification (see below) and -%% tells the queue not to proceed. +%% whether a) the channel isn't blocked by channel.flow, b) the +%% volume has not yet reached the prefetch limit, and c) whether +%% the consumer has enough credit. If so it increments the volume +%% and tells the queue to proceed. Otherwise it marks the queue as +%% requiring notification (see below) and tells the queue not to +%% proceed. %% -%% 6. A queue that has told to proceed (by the return value of +%% 6. A queue that has been told to proceed (by the return value of %% can_send/3) sends the message to the channel. Conversely, a %% queue that has been told not to proceed, will not attempt to %% deliver that message, or any future messages, to the @@ -95,7 +109,7 @@ %% the channel, i.e. they will once again start asking limiter, as %% described in (5). %% -%% 9. When a queues has no more consumers associated with a particular +%% 9. When a queue has no more consumers associated with a particular %% channel, it deactivates use of the limiter with deactivate/1, %% which alters the local state such that no further interactions %% with the limiter process take place until a subsequent -- cgit v1.2.1 From b48a5cd44111cfb3d8bdac86d2d0063a1027b1e9 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 22 Mar 2013 12:47:21 +0000 Subject: Move decorators to exchange record --- include/rabbit.hrl | 2 +- src/rabbit_exchange.erl | 57 +++++++++++++++++---------------------- src/rabbit_exchange_decorator.erl | 39 ++++++++++++++++++++++----- src/rabbit_policy.erl | 19 ++++++++----- src/rabbit_tests.erl | 5 ++-- 5 files changed, 73 insertions(+), 49 deletions(-) diff --git a/include/rabbit.hrl b/include/rabbit.hrl index eeee799e..4282755d 100644 --- a/include/rabbit.hrl +++ b/include/rabbit.hrl @@ -40,7 +40,7 @@ -record(resource, {virtual_host, kind, name}). -record(exchange, {name, type, durable, auto_delete, internal, arguments, - scratches, policy}). + scratches, policy, decorators}). -record(exchange_serial, {name, next}). -record(amqqueue, {name, durable, auto_delete, exclusive_owner = none, diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 9e98448d..16cfa8e3 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -115,23 +115,23 @@ recover() -> rabbit_durable_exchange), [XName || #exchange{name = XName} <- Xs]. -callback(X = #exchange{type = XType}, Fun, Serial0, Args) -> +callback(X = #exchange{type = XType, + decorators = Decorators}, Fun, Serial0, Args) -> Serial = if is_function(Serial0) -> Serial0; is_atom(Serial0) -> fun (_Bool) -> Serial0 end end, [ok = apply(M, Fun, [Serial(M:serialise_events(X)) | Args]) || - M <- registry_lookup(exchange_decorator)], + M <- rabbit_exchange_decorator:select(all, Decorators)], Module = type_to_module(XType), apply(Module, Fun, [Serial(Module:serialise_events()) | Args]). policy_changed(X = #exchange{type = XType}, X1) -> - [ok = M:policy_changed(X, X1) || - M <- [type_to_module(XType) | registry_lookup(exchange_decorator)]], - ok. + ok = (type_to_module(XType)):policy_changed(X, X1). -serialise_events(X = #exchange{type = Type}) -> - lists:any(fun (M) -> M:serialise_events(X) end, - registry_lookup(exchange_decorator)) +serialise_events(X = #exchange{type = Type, decorators = Decorators}) -> + lists:any(fun (M) -> + M:serialise_events(X) + end, rabbit_exchange_decorator:select(all, Decorators)) orelse (type_to_module(Type)):serialise_events(). serial(#exchange{name = XName} = X) -> @@ -143,23 +143,14 @@ serial(#exchange{name = XName} = X) -> (false) -> none end. -registry_lookup(exchange_decorator_route = Class) -> - case get(exchange_decorator_route_modules) of - undefined -> Mods = [M || {_, M} <- rabbit_registry:lookup_all(Class)], - put(exchange_decorator_route_modules, Mods), - Mods; - Mods -> Mods - end; -registry_lookup(Class) -> - [M || {_, M} <- rabbit_registry:lookup_all(Class)]. - declare(XName, Type, Durable, AutoDelete, Internal, Args) -> - X = rabbit_policy:set(#exchange{name = XName, - type = Type, - durable = Durable, - auto_delete = AutoDelete, - internal = Internal, - arguments = Args}), + X0 = rabbit_policy:set(#exchange{name = XName, + type = Type, + durable = Durable, + auto_delete = AutoDelete, + internal = Internal, + arguments = Args}), + X = rabbit_exchange_decorator:record(X0, rabbit_exchange_decorator:list()), XT = type_to_module(Type), %% We want to upset things if it isn't ok ok = XT:validate(X), @@ -318,25 +309,25 @@ info_all(VHostPath) -> map(VHostPath, fun (X) -> info(X) end). info_all(VHostPath, Items) -> map(VHostPath, fun (X) -> info(X, Items) end). -route(#exchange{name = #resource{virtual_host = VHost, - name = RName} = XName} = X, +route(#exchange{name = #resource{virtual_host = VHost, name = RName} = XName, + decorators = Decorators} = X, #delivery{message = #basic_message{routing_keys = RKs}} = Delivery) -> - case {registry_lookup(exchange_decorator_route), RName == <<"">>} of - {[], true} -> + case {RName, rabbit_exchange_decorator:select(route, Decorators)} of + {<<"">>, []} -> %% Optimisation [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; - {Decorators, _} -> - lists:usort(route1(Delivery, Decorators, {[X], XName, []})) + {_, RDecorators} -> + lists:usort(route1(Delivery, RDecorators, {[X], XName, []})) end. route1(_, _, {[], _, QNames}) -> QNames; -route1(Delivery, Decorators, +route1(Delivery, RDecorators, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> ExchangeDests = (type_to_module(Type)):route(X, Delivery), - DecorateDests = process_decorators(X, Decorators, Delivery), + DecorateDests = process_decorators(X, RDecorators, Delivery), AlternateDests = process_alternate(X, ExchangeDests), - route1(Delivery, Decorators, + route1(Delivery, RDecorators, lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, AlternateDests ++ DecorateDests ++ ExchangeDests)). diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 040b55db..240ddd9a 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -16,6 +16,10 @@ -module(rabbit_exchange_decorator). +-include("rabbit.hrl"). + +-export([list/0, select/2, record/2]). + %% This is like an exchange type except that: %% %% 1) It applies to all exchanges as soon as it is installed, therefore @@ -45,10 +49,6 @@ -callback delete(tx(), rabbit_types:exchange(), [rabbit_types:binding()]) -> 'ok'. -%% called when the policy attached to this exchange changes. --callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> - 'ok'. - %% called after a binding has been added or recovered -callback add_binding(serial(), rabbit_types:exchange(), rabbit_types:binding()) -> 'ok'. @@ -59,8 +59,12 @@ %% Decorators can optionally implement route/2 which allows additional %% destinations to be added to the routing decision. -%% -callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> -%% [rabbit_amqqueue:name() | rabbit_exchange:name()]. +-callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> + [rabbit_amqqueue:name() | rabbit_exchange:name()] | ok. + +%% Whether the decorator wishes to receive callbacks for the exchange +%% none:no callbacks, noroute:all callbacks except route, all:all callbacks +-callback active_for(rabbit_types:exchange()) -> 'none' | 'noroute' | 'all'. -else. @@ -68,8 +72,29 @@ behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 1}, {create, 2}, {delete, 3}, - {policy_changed, 2}, {add_binding, 3}, {remove_bindings, 3}]; + {policy_changed, 2}, {add_binding, 3}, {remove_bindings, 3}, + {active_for, 1}]; behaviour_info(_Other) -> undefined. -endif. + +%%---------------------------------------------------------------------------- + +list() -> [M || {_, M} <- rabbit_registry:lookup_all(exchange_decorator)]. + +%% select a subset of active decorators +select(all, {Route, NoRoute}) -> Route ++ NoRoute; +select(route, {Route, _NoRoute}) -> Route. + +%% record active decorators in an exchange +record(X, Decorators) -> + X#exchange{decorators = + lists:foldl(fun (D, {Route, NoRoute}) -> + Callbacks = D:active_for(X), + {cons_if_eq(all, Callbacks, D, Route), + cons_if_eq(noroute, Callbacks, D, NoRoute)} + end, {[], []}, Decorators)}. + +cons_if_eq(Select, Select, Item, List) -> [Item | List]; +cons_if_eq(_Select, _Other, _Item, List) -> List. diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl index 7398cd2d..22e9bdac 100644 --- a/src/rabbit_policy.erl +++ b/src/rabbit_policy.erl @@ -156,9 +156,10 @@ notify_clear(VHost, <<"policy">>, _Name) -> update_policies(VHost) -> Policies = list(VHost), + Decorators = rabbit_exchange_decorator:list(), {Xs, Qs} = rabbit_misc:execute_mnesia_transaction( fun() -> - {[update_exchange(X, Policies) || + {[update_exchange(X, Policies, Decorators) || X <- rabbit_exchange:list(VHost)], [update_queue(Q, Policies) || Q <- rabbit_amqqueue:list(VHost)]} @@ -167,12 +168,18 @@ update_policies(VHost) -> [catch notify(Q) || Q <- Qs], ok. -update_exchange(X = #exchange{name = XName, policy = OldPolicy}, Policies) -> +update_exchange(X = #exchange{name = XName, policy = OldPolicy}, + Policies, Decorators) -> case match(XName, Policies) of - OldPolicy -> no_change; - NewPolicy -> rabbit_exchange:update( - XName, fun(X1) -> X1#exchange{policy = NewPolicy} end), - {X, X#exchange{policy = NewPolicy}} + OldPolicy -> + no_change; + NewPolicy -> + rabbit_exchange:update( + XName, fun(X1) -> + rabbit_exchange_decorator:record( + X1#exchange{policy = NewPolicy}, Decorators) + end), + {X, X#exchange{policy = NewPolicy}} end. update_queue(Q = #amqqueue{name = QName, policy = OldPolicy}, Policies) -> diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index b2c80364..91f560cb 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -563,8 +563,9 @@ test_topic_matching() -> XName = #resource{virtual_host = <<"/">>, kind = exchange, name = <<"test_exchange">>}, - X = #exchange{name = XName, type = topic, durable = false, - auto_delete = false, arguments = []}, + X0 = #exchange{name = XName, type = topic, durable = false, + auto_delete = false, arguments = []}, + X = rabbit_exchange_decorator:record(X0, []), %% create rabbit_exchange_type_topic:validate(X), exchange_op_callback(X, create, []), -- cgit v1.2.1 From 4dbd221c2a2486676d7ee031c9b4b1ce8d5355aa Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 22 Mar 2013 13:43:07 +0000 Subject: Backout e2d962df4128 until we can make that work properly... --- src/rabbit_node_monitor.erl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 3872f3df..de53b7f0 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -318,9 +318,6 @@ alive_nodes() -> Nodes = rabbit_mnesia:cluster_nodes(all), [N || N <- Nodes, pong =:= net_adm:ping(N)]. -alive_rabbit_nodes() -> - [N || N <- alive_nodes(), rabbit_nodes:is_running(N, rabbit)]. - await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", []), @@ -349,7 +346,7 @@ handle_dead_rabbit_state(State = #state{partitions = Partitions}) -> %% that we do not attempt to deal with individual (other) partitions %% going away. It's only safe to forget anything about partitions when %% there are no partitions. - Partitions1 = case Partitions -- (Partitions -- alive_rabbit_nodes()) of + Partitions1 = case Partitions -- (Partitions -- alive_nodes()) of [] -> []; _ -> Partitions end, -- cgit v1.2.1 From 24667bfce161c25bfca584e85d3ebec65156930e Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 22 Mar 2013 14:40:47 +0000 Subject: Check if the rabbit process is running and thus avoid deadlocks in the application controller. --- src/rabbit_node_monitor.erl | 2 +- src/rabbit_nodes.erl | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 3872f3df..263f960d 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -319,7 +319,7 @@ alive_nodes() -> [N || N <- Nodes, pong =:= net_adm:ping(N)]. alive_rabbit_nodes() -> - [N || N <- alive_nodes(), rabbit_nodes:is_running(N, rabbit)]. + [N || N <- alive_nodes(), rabbit_nodes:is_process_running(N, rabbit)]. await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", diff --git a/src/rabbit_nodes.erl b/src/rabbit_nodes.erl index c92e5963..5640f12a 100644 --- a/src/rabbit_nodes.erl +++ b/src/rabbit_nodes.erl @@ -16,7 +16,8 @@ -module(rabbit_nodes). --export([names/1, diagnostics/1, make/1, parts/1, cookie_hash/0, is_running/2]). +-export([names/1, diagnostics/1, make/1, parts/1, cookie_hash/0, + is_running/2, is_process_running/2]). -define(EPMD_TIMEOUT, 30000). @@ -33,6 +34,7 @@ -spec(parts/1 :: (node() | string()) -> {string(), string()}). -spec(cookie_hash/0 :: () -> string()). -spec(is_running/2 :: (node(), atom()) -> boolean()). +-spec(is_process_running/2 :: (node(), atom()) -> boolean()). -endif. @@ -98,3 +100,10 @@ is_running(Node, Application) -> {badrpc, _} -> false; Apps -> proplists:is_defined(Application, Apps) end. + +is_process_running(Node, Process) -> + case rpc:call(Node, erlang, whereis, [Process]) of + {badrpc, _} -> false; + undefined -> false; + P when is_pid(P) -> true + end. -- cgit v1.2.1 From 96de9093402fb2fb789ebd7166ba578787590e9d Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 25 Mar 2013 11:12:54 +0000 Subject: Cosmetic --- src/rabbit_exchange.erl | 4 ++-- src/rabbit_exchange_decorator.erl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 16cfa8e3..aa697f07 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -325,11 +325,11 @@ route1(_, _, {[], _, QNames}) -> route1(Delivery, RDecorators, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> ExchangeDests = (type_to_module(Type)):route(X, Delivery), - DecorateDests = process_decorators(X, RDecorators, Delivery), + RDecorateDests = process_decorators(X, RDecorators, Delivery), AlternateDests = process_alternate(X, ExchangeDests), route1(Delivery, RDecorators, lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, - AlternateDests ++ DecorateDests ++ ExchangeDests)). + AlternateDests ++ RDecorateDests ++ ExchangeDests)). process_alternate(#exchange{arguments = []}, _Results) -> %% optimisation []; diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 240ddd9a..cdbc42bb 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -94,7 +94,7 @@ record(X, Decorators) -> Callbacks = D:active_for(X), {cons_if_eq(all, Callbacks, D, Route), cons_if_eq(noroute, Callbacks, D, NoRoute)} - end, {[], []}, Decorators)}. + end, {[], []}, Decorators)}. cons_if_eq(Select, Select, Item, List) -> [Item | List]; cons_if_eq(_Select, _Other, _Item, List) -> List. -- cgit v1.2.1 From e3938b32731757085de356093f9cc76835a575e9 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 25 Mar 2013 14:24:59 +0000 Subject: Use a macro --- src/rabbit_node_monitor.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 16b52a4d..dd59a0c7 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -361,7 +361,7 @@ await_cluster_recovery() -> wait_for_cluster_recovery(Nodes) -> case majority() of true -> rabbit:start(); - false -> timer:sleep(1000), + false -> timer:sleep(?RABBIT_DOWN_PING_INTERVAL), wait_for_cluster_recovery(Nodes) end. -- cgit v1.2.1 From ededc010f092f61aca0336abeadffa27c46703ee Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 25 Mar 2013 14:39:53 +0000 Subject: Don't call rabbit_mnesia:cluster_nodes(all) as much, don't use floating point if we don't have to. --- src/rabbit_node_monitor.erl | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index dd59a0c7..ead0c661 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -280,15 +280,15 @@ handle_info(ping_nodes, State) -> %% to ping the nodes that are up, after all. State1 = State#state{down_ping_timer = undefined}, Self = self(), - %% ratio() both pings all the nodes and tells us if we need to again. + %% all_nodes_up() both pings all the nodes and tells us if we need to again. %% %% We ping in a separate process since in a partition it might %% take some noticeable length of time and we don't want to block %% the node monitor for that long. spawn_link(fun () -> - case ratio() of - 1.0 -> ok; - _ -> Self ! ping_again + case all_nodes_up() of + true -> ok; + false -> Self ! ping_again end end), {noreply, State1}; @@ -333,15 +333,20 @@ handle_dead_rabbit(Node) -> end, ok. -majority() -> ratio() > 0.5. -ratio() -> length(alive_nodes()) / length(rabbit_mnesia:cluster_nodes(all)). +majority() -> + Nodes = rabbit_mnesia:cluster_nodes(all), + length(alive_nodes(Nodes)) / length(Nodes) > 0.5. + +all_nodes_up() -> + Nodes = rabbit_mnesia:cluster_nodes(all), + length(alive_nodes(Nodes)) =:= length(Nodes). %% mnesia:system_info(db_nodes) (and hence %% rabbit_mnesia:cluster_nodes(running)) does not give reliable results %% when partitioned. -alive_nodes() -> - Nodes = rabbit_mnesia:cluster_nodes(all), - [N || N <- Nodes, pong =:= net_adm:ping(N)]. +alive_nodes() -> alive_nodes(rabbit_mnesia:cluster_nodes(all)). + +alive_nodes(Nodes) -> [N || N <- Nodes, pong =:= net_adm:ping(N)]. await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", -- cgit v1.2.1 From 137e6b0568eef1603cdfc9921f906e85548ff79a Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 26 Mar 2013 16:48:57 +0000 Subject: Add some logging --- src/rabbit_node_monitor.erl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index aac2bf84..ca39440b 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -284,6 +284,7 @@ handle_info({autoheal_request_winner, Node}, State = #state{autoheal = {wait_for_winner_reqs,[Node], Notify}}) -> %% TODO actually do something sensible to figure out who the winner is Winner = self(), + rabbit_log:info("Autoheal: winner is ~p~n", [Winner]), [{?MODULE, N} ! {autoheal_winner, Winner} || N <- Notify], {noreply, State#state{autoheal = wait_for_winner}}; @@ -298,10 +299,15 @@ handle_info({autoheal_winner, Winner}, Node = node(Winner), case lists:member(Node, Partitions) of false -> case node() of - Node -> {noreply, + Node -> rabbit_log:info( + "Autoheal: waiting for nodes to stop: ~p~n", + [Partitions]), + {noreply, State#state{autoheal = {wait_for, Partitions, Partitions}}}; - _ -> {noreply, State#state{autoheal = not_healing}} + _ -> rabbit_log:info( + "Autoheal: nothing to do~n", []), + {noreply, State#state{autoheal = not_healing}} end; true -> autoheal_restart(Winner), {noreply, State} @@ -313,6 +319,7 @@ handle_info({autoheal_winner, _Winner}, State) -> handle_info({autoheal_node_stopped, Node}, State = #state{autoheal = {wait_for, [Node], Notify}}) -> + rabbit_log:info("Autoheal: final node has stopped, starting...~n",[]), [{rabbit_outside_app_process, N} ! autoheal_safe_to_start || N <- Notify], {noreply, State#state{autoheal = not_healing}}; @@ -423,6 +430,7 @@ wait_for_cluster_recovery(Nodes) -> %% until it has seen a request from every node. autoheal(State) -> [Leader | _] = All = lists:usort(rabbit_mnesia:cluster_nodes(all)), + rabbit_log:info("Autoheal: leader is ~p~n", [Leader]), {?MODULE, Leader} ! {autoheal_request_winner, node()}, State#state{autoheal = case node() of Leader -> {wait_for_winner_reqs, All, All}; @@ -431,7 +439,7 @@ autoheal(State) -> autoheal_restart(Winner) -> rabbit_log:warning( - "Cluster partition healed; we were selected to restart~n", []), + "Autoheal: we were selected to restart; winner is ~p~n", [node(Winner)]), run_outside_applications( fun () -> MRef = erlang:monitor(process, Winner), -- cgit v1.2.1 From 81ef7e1ba1ad331d29d697dd1fadde6dc7fc6f01 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 26 Mar 2013 16:49:15 +0000 Subject: Figure out a global view of partitions. --- src/rabbit_node_monitor.erl | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index ca39440b..c2c81545 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -281,8 +281,10 @@ handle_info({mnesia_system_event, {noreply, State2#state{partitions = Partitions1}}; handle_info({autoheal_request_winner, Node}, - State = #state{autoheal = {wait_for_winner_reqs,[Node], Notify}}) -> + State = #state{autoheal = {wait_for_winner_reqs,[Node], Notify}, + partitions = Partitions}) -> %% TODO actually do something sensible to figure out who the winner is + AllPartitions = all_partitions(Partitions), Winner = self(), rabbit_log:info("Autoheal: winner is ~p~n", [Winner]), [{?MODULE, N} ! {autoheal_winner, Winner} || N <- Notify], @@ -453,6 +455,24 @@ autoheal_restart(Winner) -> rabbit:start() end). +%% We have our local understanding of what partitions exist; but we +%% only know which nodes we have been partitioned from, not which +%% nodes are partitioned from each other. +%% +%% Note that here we assume that partition information is +%% consistent. If it isn't, what can we do? +all_partitions(PartitionedWith) -> + All = rabbit_mnesia:cluster_nodes(all), + OurPartition = All -- PartitionedWith, + all_partitions([OurPartition], PartitionedWith, All). + +all_partitions(AllPartitions, [], _) -> + AllPartitions; +all_partitions(AllPartitions, [One | _] = ToDo, All) -> + {One, PartitionedFrom} = rpc:call(One, rabbit_node_monitor, partitions, []), + Partition = All -- PartitionedFrom, + all_partitions([Partition | AllPartitions], ToDo -- Partition, All). + handle_dead_rabbit_state(State = #state{partitions = Partitions, autoheal = Autoheal}) -> %% If we have been partitioned, and we are now in the only remaining -- cgit v1.2.1 From 46eb23b9f4b1c693c37abfdf9e55203e688f50e8 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 26 Mar 2013 17:54:00 +0000 Subject: Simplistic way of selecting a winner: order by the number of connections, then the size of the partition. --- src/rabbit_node_monitor.erl | 47 +++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index c2c81545..957a9025 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -283,9 +283,7 @@ handle_info({mnesia_system_event, handle_info({autoheal_request_winner, Node}, State = #state{autoheal = {wait_for_winner_reqs,[Node], Notify}, partitions = Partitions}) -> - %% TODO actually do something sensible to figure out who the winner is - AllPartitions = all_partitions(Partitions), - Winner = self(), + Winner = autoheal_select_winner(all_partitions(Partitions)), rabbit_log:info("Autoheal: winner is ~p~n", [Winner]), [{?MODULE, N} ! {autoheal_winner, Winner} || N <- Notify], {noreply, State#state{autoheal = wait_for_winner}}; @@ -298,18 +296,17 @@ handle_info({autoheal_request_winner, Node}, handle_info({autoheal_winner, Winner}, State = #state{autoheal = wait_for_winner, partitions = Partitions}) -> - Node = node(Winner), - case lists:member(Node, Partitions) of + case lists:member(Winner, Partitions) of false -> case node() of - Node -> rabbit_log:info( - "Autoheal: waiting for nodes to stop: ~p~n", - [Partitions]), - {noreply, - State#state{autoheal = {wait_for, Partitions, - Partitions}}}; - _ -> rabbit_log:info( - "Autoheal: nothing to do~n", []), - {noreply, State#state{autoheal = not_healing}} + Winner -> rabbit_log:info( + "Autoheal: waiting for nodes to stop: ~p~n", + [Partitions]), + {noreply, + State#state{autoheal = {wait_for, Partitions, + Partitions}}}; + _ -> rabbit_log:info( + "Autoheal: nothing to do~n", []), + {noreply, State#state{autoheal = not_healing}} end; true -> autoheal_restart(Winner), {noreply, State} @@ -439,17 +436,29 @@ autoheal(State) -> _ -> wait_for_winner end}. +autoheal_select_winner(AllPartitions) -> + {_, [Winner | _]} = hd(lists:sort( + [{autoheal_value(P), P} || P <- AllPartitions])), + Winner. + +autoheal_value(Partition) -> + Connections = [Res || Node <- Partition, + Res <- [rpc:call(Node, rabbit_networking, + connections_local, [])], + is_list(Res)], + {length(lists:append(Connections)), length(Partition)}. + autoheal_restart(Winner) -> rabbit_log:warning( - "Autoheal: we were selected to restart; winner is ~p~n", [node(Winner)]), + "Autoheal: we were selected to restart; winner is ~p~n", [Winner]), run_outside_applications( fun () -> - MRef = erlang:monitor(process, Winner), + MRef = erlang:monitor(process, {?MODULE, Winner}), rabbit:stop(), - Winner ! {autoheal_node_stopped, node()}, + {?MODULE, Winner} ! {autoheal_node_stopped, node()}, receive - {'DOWN', MRef, process, Winner, _Reason} -> ok; - autoheal_safe_to_start -> ok + {'DOWN', MRef, process, {?MODULE, Winner}, _Reason} -> ok; + autoheal_safe_to_start -> ok end, erlang:demonitor(MRef, [flush]), rabbit:start() -- cgit v1.2.1 From c1039e1e1d64228b8ae93acc3acb3d919997bd88 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 27 Mar 2013 15:29:51 +0000 Subject: Cosmetic --- src/rabbit_node_monitor.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 843ccb1f..aaacc200 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -285,7 +285,8 @@ handle_info({autoheal_request_winner, Node}, State = #state{autoheal = {wait_for_winner_reqs,[Node], Notify}, partitions = Partitions}) -> Winner = autoheal_select_winner(all_partitions(Partitions)), - rabbit_log:info("Autoheal request winner from ~p: winner is ~p~n", [Node, Winner]), + rabbit_log:info("Autoheal request winner from ~p: winner is ~p~n", + [Node, Winner]), [{?MODULE, N} ! {autoheal_winner, Winner} || N <- Notify], {noreply, State#state{autoheal = wait_for_winner}}; -- cgit v1.2.1 From f722ead2119d5ffc9baf131999d783723e24190d Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 28 Mar 2013 13:26:30 +0000 Subject: WIP commit: start to deal with non-consistent partitions. all_partitions/1 needs attention though. --- src/rabbit_node_monitor.erl | 70 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index ffd02fc4..edb197fa 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -252,6 +252,7 @@ handle_info({'DOWN', _MRef, process, {rabbit, Node}, _Reason}, ok = handle_dead_rabbit(Node), [P ! {node_down, Node} || P <- pmon:monitored(Subscribers)], {noreply, handle_dead_rabbit_state( + Node, State#state{monitors = pmon:erase({rabbit, Node}, Monitors)})}; handle_info({'DOWN', _MRef, process, Pid, _Reason}, @@ -282,11 +283,40 @@ handle_info({mnesia_system_event, {noreply, State2#state{partitions = Partitions1}}; handle_info({autoheal_request_winner, Node}, - State = #state{autoheal = {wait_for_winner_reqs,[Node], Notify}, + State = #state{autoheal = not_healing, partitions = Partitions}) -> - Winner = autoheal_select_winner(all_partitions(Partitions)), - rabbit_log:info("Autoheal request winner from ~p: winner is ~p~n", - [Node, Winner]), + case all_nodes_up() of + false -> {noreply, State}; + true -> Nodes = rabbit_mnesia:cluster_nodes(all), + Partitioned = + [N || N <- Nodes -- [node()], + P <- [begin + {_, R} = rpc:call(N, rabbit_node_monitor, + partitions, []), + R + end], + is_list(P) andalso length(P) > 0], + Partitioned1 = case Partitions of + [] -> Partitioned; + _ -> [node() | Partitioned] + end, + rabbit_log:info( + "Autoheal leader start; partitioned nodes are ~p~n", + [Partitioned1]), + Autoheal = {wait_for_winner_reqs, Partitioned1, Partitioned1}, + handle_info({autoheal_request_winner, Node}, + State#state{autoheal = Autoheal}) + end; + +handle_info({autoheal_request_winner, Node}, + State = #state{autoheal = {wait_for_winner_reqs, [Node], Notify}, + partitions = Partitions}) -> + AllPartitions = all_partitions(Partitions), + io:format("all partitions: ~p~n", [AllPartitions]), + Winner = autoheal_select_winner(AllPartitions), + rabbit_log:info("Autoheal request winner from ~p~n" + " Partitions were determined to be ~p~n" + " Winner is ~p~n", [Node, AllPartitions, Winner]), [{?MODULE, N} ! {autoheal_winner, Winner} || N <- Notify], {noreply, State#state{autoheal = wait_for_winner}}; @@ -299,6 +329,7 @@ handle_info({autoheal_request_winner, Node}, handle_info({autoheal_winner, Winner}, State = #state{autoheal = wait_for_winner, partitions = Partitions}) -> + io:format("got winner ~p~n", [[Winner, Partitions]]), case lists:member(Winner, Partitions) of false -> case node() of Winner -> rabbit_log:info( @@ -461,13 +492,14 @@ wait_for_cluster_recovery(Nodes) -> %% The winner and the leader are not necessarily the same node! Since %% the leader may end up restarting, we also make sure that it does %% not announce its decision (and thus cue other nodes to restart) -%% until it has seen a request from every node. +%% until it has seen a request from every node that has experienced a +%% partition. autoheal(State = #state{autoheal = not_healing}) -> - [Leader | _] = All = lists:usort(rabbit_mnesia:cluster_nodes(all)), + [Leader | _] = lists:usort(rabbit_mnesia:cluster_nodes(all)), rabbit_log:info("Autoheal: leader is ~p~n", [Leader]), {?MODULE, Leader} ! {autoheal_request_winner, node()}, State#state{autoheal = case node() of - Leader -> {wait_for_winner_reqs, All, All}; + Leader -> not_healing; _ -> wait_for_winner end}; autoheal(State) -> @@ -506,7 +538,7 @@ autoheal_restart(Winner) -> %% nodes are partitioned from each other. %% %% Note that here we assume that partition information is -%% consistent. If it isn't, what can we do? +%% consistent - which it isn't. TODO fix this. all_partitions(PartitionedWith) -> All = rabbit_mnesia:cluster_nodes(all), OurPartition = All -- PartitionedWith, @@ -519,8 +551,8 @@ all_partitions(AllPartitions, [One | _] = ToDo, All) -> Partition = All -- PartitionedFrom, all_partitions([Partition | AllPartitions], ToDo -- Partition, All). -handle_dead_rabbit_state(State = #state{partitions = Partitions, - autoheal = Autoheal}) -> +handle_dead_rabbit_state(Node, State = #state{partitions = Partitions, + autoheal = Autoheal}) -> %% If we have been partitioned, and we are now in the only remaining %% partition, we no longer care about partitions - forget them. Note %% that we do not attempt to deal with individual (other) partitions @@ -530,12 +562,18 @@ handle_dead_rabbit_state(State = #state{partitions = Partitions, [] -> []; _ -> Partitions end, - ensure_ping_timer( - State#state{partitions = Partitions1, - autoheal = case Autoheal of - {wait_for, _Nodes, _Notify} -> Autoheal; - _ -> not_healing - end}). + Autoheal1 = case Autoheal of + {wait_for, _Nodes, _Notify} -> + Autoheal; + not_healing -> + not_healing; + _ -> + rabbit_log:info( + "Autoheal: aborting - ~p went down~n", [Node]), + not_healing + end, + ensure_ping_timer(State#state{partitions = Partitions1, + autoheal = Autoheal1}). ensure_ping_timer(State) -> rabbit_misc:ensure_timer( -- cgit v1.2.1 From 0a385f2fab060154f3700fcbd7036d055d1413b8 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 28 Mar 2013 17:24:13 +0000 Subject: Mnesia upgrade for exchange decorators --- src/rabbit_upgrade_functions.erl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/rabbit_upgrade_functions.erl b/src/rabbit_upgrade_functions.erl index 457b1567..5211987f 100644 --- a/src/rabbit_upgrade_functions.erl +++ b/src/rabbit_upgrade_functions.erl @@ -43,6 +43,7 @@ -rabbit_upgrade({sync_slave_pids, mnesia, [policy]}). -rabbit_upgrade({no_mirror_nodes, mnesia, [sync_slave_pids]}). -rabbit_upgrade({gm_pids, mnesia, [no_mirror_nodes]}). +-rabbit_upgrade({exchange_decorators, mnesia, [policy]}). %% ------------------------------------------------------------------- @@ -68,6 +69,7 @@ -spec(sync_slave_pids/0 :: () -> 'ok'). -spec(no_mirror_nodes/0 :: () -> 'ok'). -spec(gm_pids/0 :: () -> 'ok'). +-spec(exchange_decorators/0 :: () -> 'ok'). -endif. @@ -282,6 +284,19 @@ gm_pids() -> || T <- Tables], ok. +exchange_decorators() -> + ok = exchange_decorators(rabbit_exchange), + ok = exchange_decorators(rabbit_durable_exchange). + +exchange_decorators(Table) -> + transform( + Table, + fun ({exchange, Name, Type, Dur, AutoDel, Int, Args, Scratches, + Policy}) -> + {exchange, Name, Type, Dur, AutoDel, Int, Args, Scratches, Policy, + {[], []}} + end, + [name, type, durable, auto_delete, internal, arguments, scratches, policy, decorators]). %%-------------------------------------------------------------------- -- cgit v1.2.1 From 1b2653094fadfd005b035fb2e31238b08ff881bb Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 28 Mar 2013 17:26:07 +0000 Subject: Wrap --- src/rabbit_upgrade_functions.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_upgrade_functions.erl b/src/rabbit_upgrade_functions.erl index 5211987f..b7b1635b 100644 --- a/src/rabbit_upgrade_functions.erl +++ b/src/rabbit_upgrade_functions.erl @@ -296,7 +296,8 @@ exchange_decorators(Table) -> {exchange, Name, Type, Dur, AutoDel, Int, Args, Scratches, Policy, {[], []}} end, - [name, type, durable, auto_delete, internal, arguments, scratches, policy, decorators]). + [name, type, durable, auto_delete, internal, arguments, scratches, policy, + decorators]). %%-------------------------------------------------------------------- -- cgit v1.2.1 From e91e370baa19d18a7b2fbcf7450aa694330d0b11 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 4 Apr 2013 12:45:41 +0100 Subject: DLX without confirms --- src/rabbit_amqqueue_process.erl | 126 ++++++++-------------------------------- 1 file changed, 23 insertions(+), 103 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b016c4d2..1a70af0d 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -49,9 +49,6 @@ ttl_timer_ref, ttl_timer_expiry, senders, - publish_seqno, - unconfirmed, - delayed_stop, queue_monitors, dlx, dlx_routing_key, @@ -151,9 +148,6 @@ init_state(Q) -> has_had_consumers = false, active_consumers = queue:new(), senders = pmon:new(), - publish_seqno = 1, - unconfirmed = dtree:empty(), - queue_monitors = pmon:new(), msg_id_to_channel = gb_trees:empty(), status = running}, rabbit_event:init_stats_timer(State, #q.stats_timer). @@ -820,31 +814,20 @@ dead_letter_maxlen_msgs(X, Excess, State = #q{backing_queue = BQ}) -> State1. dead_letter_msgs(Fun, Reason, X, State = #q{dlx_routing_key = RK, - publish_seqno = SeqNo0, - unconfirmed = UC0, - queue_monitors = QMons0, backing_queue_state = BQS, backing_queue = BQ}) -> QName = qname(State), - {Res, {AckImm1, SeqNo1, UC1, QMons1}, BQS1} = - Fun(fun (Msg, AckTag, {AckImm, SeqNo, UC, QMons}) -> - case dead_letter_publish(Msg, Reason, - X, RK, SeqNo, QName) of - [] -> {[AckTag | AckImm], SeqNo, UC, QMons}; - QPids -> {AckImm, SeqNo + 1, - dtree:insert(SeqNo, QPids, AckTag, UC), - pmon:monitor_all(QPids, QMons)} - end - end, {[], SeqNo0, UC0, QMons0}, BQS), - {_Guids, BQS2} = BQ:ack(AckImm1, BQS1), - {Res, State#q{publish_seqno = SeqNo1, - unconfirmed = UC1, - queue_monitors = QMons1, - backing_queue_state = BQS2}}. - -dead_letter_publish(Msg, Reason, X, RK, MsgSeqNo, QName) -> + {Res, Acks1, BQS1} = + Fun(fun (Msg, AckTag, Acks) -> + dead_letter_publish(Msg, Reason, X, RK, QName), + [AckTag | Acks] + end, [], BQS), + {_Guids, BQS2} = BQ:ack(Acks1, BQS1), + {Res, State#q{backing_queue_state = BQS2}}. + +dead_letter_publish(Msg, Reason, X, RK, QName) -> DLMsg = make_dead_letter_msg(Msg, Reason, X#exchange.name, RK, QName), - Delivery = rabbit_basic:delivery(false, DLMsg, MsgSeqNo), + Delivery = rabbit_basic:delivery(false, DLMsg, undefined), {Queues, Cycles} = detect_dead_letter_cycles( DLMsg, rabbit_exchange:route(X, Delivery)), lists:foreach(fun log_cycle_once/1, Cycles), @@ -852,48 +835,11 @@ dead_letter_publish(Msg, Reason, X, RK, MsgSeqNo, QName) -> rabbit_amqqueue:lookup(Queues), Delivery), DeliveredQPids. -handle_queue_down(QPid, Reason, State = #q{queue_monitors = QMons, - unconfirmed = UC}) -> - case pmon:is_monitored(QPid, QMons) of - false -> noreply(State); - true -> case rabbit_misc:is_abnormal_exit(Reason) of - true -> {Lost, _UC1} = dtree:take_all(QPid, UC), - QNameS = rabbit_misc:rs(qname(State)), - rabbit_log:warning("DLQ ~p for ~s died with " - "~p unconfirmed messages~n", - [QPid, QNameS, length(Lost)]); - false -> ok - end, - {MsgSeqNoAckTags, UC1} = dtree:take(QPid, UC), - cleanup_after_confirm( - [AckTag || {_MsgSeqNo, AckTag} <- MsgSeqNoAckTags], - State#q{queue_monitors = pmon:erase(QPid, QMons), - unconfirmed = UC1}) - end. +stop(State) -> stop(noreply, State). -stop(State) -> stop(undefined, noreply, State). - -stop(From, Reply, State = #q{unconfirmed = UC}) -> - case {dtree:is_empty(UC), Reply} of - {true, noreply} -> {stop, normal, State}; - {true, _} -> {stop, normal, Reply, State}; - {false, _} -> noreply(State#q{delayed_stop = {From, Reply}}) - end. +stop(noreply, State) -> {stop, normal, State}; +stop(Reply, State) -> {stop, normal, Reply, State}. -cleanup_after_confirm(AckTags, State = #q{delayed_stop = DS, - unconfirmed = UC, - backing_queue = BQ, - backing_queue_state = BQS}) -> - {_Guids, BQS1} = BQ:ack(AckTags, BQS), - State1 = State#q{backing_queue_state = BQS1}, - case dtree:is_empty(UC) andalso DS =/= undefined of - true -> case DS of - {_, noreply} -> ok; - {From, Reply} -> gen_server2:reply(From, Reply) - end, - {stop, normal, State1}; - false -> noreply(State1) - end. detect_dead_letter_cycles(#basic_message{content = Content}, Queues) -> #content{properties = #'P_basic'{headers = Headers}} = @@ -1073,9 +1019,6 @@ prioritise_info(Msg, _Len, #q{q = #amqqueue{exclusive_owner = DownPid}}) -> _ -> 0 end. -handle_call(_, _, State = #q{delayed_stop = DS}) when DS =/= undefined -> - noreply(State); - handle_call({init, Recover}, From, State = #q{q = #amqqueue{exclusive_owner = none}}) -> declare(Recover, From, State); @@ -1115,16 +1058,15 @@ handle_call({deliver, Delivery, Delivered}, From, State) -> gen_server2:reply(From, ok), noreply(deliver_or_enqueue(Delivery, Delivered, State)); -handle_call({notify_down, ChPid}, From, State) -> +handle_call({notify_down, ChPid}, _From, State) -> %% we want to do this synchronously, so that auto_deleted queues %% are no longer visible by the time we send a response to the %% client. The queue is ultimately deleted in terminate/2; if we %% return stop with a reply, terminate/2 will be called by - %% gen_server2 *before* the reply is sent. FIXME: in case of a - %% delayed stop the reply is sent earlier. + %% gen_server2 *before* the reply is sent. case handle_ch_down(ChPid, State) of {ok, State1} -> reply(ok, State1); - {stop, State1} -> stop(From, ok, State1) + {stop, State1} -> stop(ok, State1) end; handle_call({basic_get, ChPid, NoAck, LimiterPid}, _From, @@ -1186,7 +1128,7 @@ handle_call({basic_consume, NoAck, ChPid, LimiterPid, LimiterActive, reply(ok, run_message_queue(State1#q{active_consumers = AC1})) end; -handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, +handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, _From, State = #q{exclusive_consumer = Holder}) -> ok = maybe_send_reply(ChPid, OkMsg), case lookup_ch(ChPid) of @@ -1215,7 +1157,7 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, From, State#q.active_consumers)}, case should_auto_delete(State1) of false -> reply(ok, ensure_expiry_timer(State1)); - true -> stop(From, ok, State1) + true -> stop(ok, State1) end end; @@ -1224,14 +1166,14 @@ handle_call(stat, _From, State) -> ensure_expiry_timer(State), reply({ok, BQ:len(BQS), consumer_count()}, State1); -handle_call({delete, IfUnused, IfEmpty}, From, +handle_call({delete, IfUnused, IfEmpty}, _From, State = #q{backing_queue_state = BQS, backing_queue = BQ}) -> IsEmpty = BQ:is_empty(BQS), IsUnused = is_unused(State), if IfEmpty and not(IsEmpty) -> reply({error, not_empty}, State); IfUnused and not(IsUnused) -> reply({error, in_use}, State); - true -> stop(From, {ok, BQ:len(BQS)}, State) + true -> stop({ok, BQ:len(BQS)}, State) end; handle_call(purge, _From, State = #q{backing_queue = BQ, @@ -1286,19 +1228,6 @@ handle_call(force_event_refresh, _From, end, reply(ok, State). -handle_cast({confirm, MsgSeqNos, QPid}, State = #q{unconfirmed = UC}) -> - {MsgSeqNoAckTags, UC1} = dtree:take(MsgSeqNos, QPid, UC), - State1 = case dtree:is_defined(QPid, UC1) of - false -> QMons = State#q.queue_monitors, - State#q{queue_monitors = pmon:demonitor(QPid, QMons)}; - true -> State - end, - cleanup_after_confirm([AckTag || {_MsgSeqNo, AckTag} <- MsgSeqNoAckTags], - State1#q{unconfirmed = UC1}); - -handle_cast(_, State = #q{delayed_stop = DS}) when DS =/= undefined -> - noreply(State); - handle_cast({run_backing_queue, Mod, Fun}, State = #q{backing_queue = BQ, backing_queue_state = BQS}) -> noreply(State#q{backing_queue_state = BQ:invoke(Mod, Fun, BQS)}); @@ -1405,15 +1334,6 @@ handle_cast({credit, ChPid, CTag, Credit, Drain}, handle_cast(wake_up, State) -> noreply(State). -%% We need to not ignore this as we need to remove outstanding -%% confirms due to queue death. -handle_info({'DOWN', _MonitorRef, process, DownPid, Reason}, - State = #q{delayed_stop = DS}) when DS =/= undefined -> - handle_queue_down(DownPid, Reason, State); - -handle_info(_, State = #q{delayed_stop = DS}) when DS =/= undefined -> - noreply(State); - handle_info(maybe_expire, State) -> case is_unused(State) of true -> stop(State); @@ -1442,10 +1362,10 @@ handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, %% unexpectedly. stop(State); -handle_info({'DOWN', _MonitorRef, process, DownPid, Reason}, State) -> +handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, State) -> case handle_ch_down(DownPid, State) of - {ok, State1} -> handle_queue_down(DownPid, Reason, State1); - {stop, State1} -> stop(State1) + {ok, State1} -> noreply(State1); + {stop, State1} -> {stop, normal, State1} end; handle_info(update_ram_duration, State = #q{backing_queue = BQ, -- cgit v1.2.1 From 2d420464faa7c904a800c5dc9bd540e188988acc Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 4 Apr 2013 16:47:29 +0100 Subject: Change client_sup API --- src/rabbit_client_sup.erl | 24 +++++++++++++----------- src/rabbit_direct.erl | 2 +- src/rabbit_networking.erl | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/rabbit_client_sup.erl b/src/rabbit_client_sup.erl index 54bb8671..1a15b0cb 100644 --- a/src/rabbit_client_sup.erl +++ b/src/rabbit_client_sup.erl @@ -18,7 +18,7 @@ -behaviour(supervisor2). --export([start_link/1, start_link/2]). +-export([start_link/1, start_link/2, start_link/3]). -export([init/1]). @@ -37,16 +37,18 @@ %%---------------------------------------------------------------------------- -start_link(CallbackOpts) -> - supervisor2:start_link(?MODULE, CallbackOpts). +start_link(Callback) -> + supervisor2:start_link(?MODULE, Callback). -start_link(SupName, CallbackOpts) -> - supervisor2:start_link(SupName, ?MODULE, CallbackOpts). +start_link(SupName, Callback) -> + supervisor2:start_link(SupName, ?MODULE, Callback). -init({{M,F,A},Opts}) -> - {Shutdown, Type} = case rabbit_misc:pget(worker_type, Opts, supervisor) of - supervisor -> {infinity, supervisor}; - worker -> {?MAX_WAIT, worker} - end, +start_link(SupName, Callback, worker) -> + supervisor2:start_link(SupName, ?MODULE, {Callback, worker}). + +init({M,F,A}) -> + {ok, {{simple_one_for_one_terminate, 0, 1}, + [{client, {M,F,A}, temporary, infinity, supervisor, [M]}]}}; +init({{M,F,A}, worker}) -> {ok, {{simple_one_for_one_terminate, 0, 1}, - [{client, {M,F,A}, temporary, Shutdown, Type, [M]}]}}. + [{client, {M,F,A}, temporary, ?MAX_WAIT, worker, [M]}]}}. diff --git a/src/rabbit_direct.erl b/src/rabbit_direct.erl index 036f354b..53144f3f 100644 --- a/src/rabbit_direct.erl +++ b/src/rabbit_direct.erl @@ -50,7 +50,7 @@ boot() -> rabbit_sup:start_supervisor_child( rabbit_direct_client_sup, rabbit_client_sup, [{local, rabbit_direct_client_sup}, - {{rabbit_channel_sup, start_link, []}, []}]). + {rabbit_channel_sup, start_link, []}]). force_event_refresh() -> [Pid ! force_event_refresh || Pid<- list()], diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index 517fa360..0a0e51c5 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -139,7 +139,7 @@ boot_ssl() -> start() -> rabbit_sup:start_supervisor_child( rabbit_tcp_client_sup, rabbit_client_sup, [{local, rabbit_tcp_client_sup}, - {{rabbit_connection_sup,start_link,[]}, []}]). + {rabbit_connection_sup,start_link,[]}]). ensure_ssl() -> ok = app_utils:start_applications([crypto, public_key, ssl]), -- cgit v1.2.1 From 68b870a5c85124f1bcf7657129e3c1cb58713815 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 5 Apr 2013 12:41:28 +0100 Subject: client_sup API change --- src/rabbit_client_sup.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rabbit_client_sup.erl b/src/rabbit_client_sup.erl index 1a15b0cb..7cc11fef 100644 --- a/src/rabbit_client_sup.erl +++ b/src/rabbit_client_sup.erl @@ -18,7 +18,7 @@ -behaviour(supervisor2). --export([start_link/1, start_link/2, start_link/3]). +-export([start_link/1, start_link/2, start_link_worker/2]). -export([init/1]). @@ -32,6 +32,8 @@ rabbit_types:ok_pid_or_error()). -spec(start_link/2 :: ({'local', atom()}, rabbit_types:mfargs()) -> rabbit_types:ok_pid_or_error()). +-spec(start_link_worker/2 :: ({'local', atom()}, rabbit_types:mfargs()) -> + rabbit_types:ok_pid_or_error()). -endif. @@ -43,7 +45,7 @@ start_link(Callback) -> start_link(SupName, Callback) -> supervisor2:start_link(SupName, ?MODULE, Callback). -start_link(SupName, Callback, worker) -> +start_link_worker(SupName, Callback) -> supervisor2:start_link(SupName, ?MODULE, {Callback, worker}). init({M,F,A}) -> -- cgit v1.2.1 From 8347524e1bac0c8331b4fd55947eb92ed7a040b7 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Fri, 5 Apr 2013 13:24:10 +0100 Subject: Minimise diff --- src/rabbit_amqqueue_process.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 1a70af0d..7ab0d774 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -1365,7 +1365,7 @@ handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, State) -> case handle_ch_down(DownPid, State) of {ok, State1} -> noreply(State1); - {stop, State1} -> {stop, normal, State1} + {stop, State1} -> stop(State1) end; handle_info(update_ram_duration, State = #q{backing_queue = BQ, -- cgit v1.2.1 From 79ecab90cfaded68f596cf3783f907ffe8c106f2 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 8 Apr 2013 15:56:01 +0100 Subject: Umm, order by *best* partition first! --- src/rabbit_node_monitor.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index edb197fa..7fd20f8c 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -506,8 +506,9 @@ autoheal(State) -> State. autoheal_select_winner(AllPartitions) -> - {_, [Winner | _]} = hd(lists:sort( - [{autoheal_value(P), P} || P <- AllPartitions])), + {_, [Winner | _]} = + hd(lists:reverse( + lists:sort([{autoheal_value(P), P} || P <- AllPartitions]))), Winner. autoheal_value(Partition) -> -- cgit v1.2.1 From c9bac515bb06c2a4a1514baba21ccbca28f525ee Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 8 Apr 2013 15:56:18 +0100 Subject: Deal with partial partitions. --- src/rabbit_node_monitor.erl | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 7fd20f8c..07898d34 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -537,20 +537,26 @@ autoheal_restart(Winner) -> %% We have our local understanding of what partitions exist; but we %% only know which nodes we have been partitioned from, not which %% nodes are partitioned from each other. -%% -%% Note that here we assume that partition information is -%% consistent - which it isn't. TODO fix this. all_partitions(PartitionedWith) -> - All = rabbit_mnesia:cluster_nodes(all), - OurPartition = All -- PartitionedWith, - all_partitions([OurPartition], PartitionedWith, All). - -all_partitions(AllPartitions, [], _) -> - AllPartitions; -all_partitions(AllPartitions, [One | _] = ToDo, All) -> - {One, PartitionedFrom} = rpc:call(One, rabbit_node_monitor, partitions, []), - Partition = All -- PartitionedFrom, - all_partitions([Partition | AllPartitions], ToDo -- Partition, All). + Nodes = rabbit_mnesia:cluster_nodes(all), + Partitions = [{node(), PartitionedWith} | + [rpc:call(Node, rabbit_node_monitor, partitions, []) + || Node <- Nodes -- [node()]]], + all_partitions(Partitions, [Nodes]). + +all_partitions([], Partitions) -> + Partitions; +all_partitions([{Node, CantSee} | Rest], Partitions) -> + {[Containing], Others} = + lists:partition(fun (Part) -> lists:member(Node, Part) end, Partitions), + A = Containing -- CantSee, + B = Containing -- A, + Partitions1 = case {A, B} of + {[], _} -> Partitions; + {_, []} -> Partitions; + _ -> [A, B | Others] + end, + all_partitions(Rest, Partitions1). handle_dead_rabbit_state(Node, State = #state{partitions = Partitions, autoheal = Autoheal}) -> -- cgit v1.2.1 From d3f6a2dc0ecce0ff7ced9f4bbfea0276c8f53043 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 8 Apr 2013 15:56:29 +0100 Subject: Remove io:formats --- src/rabbit_node_monitor.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 07898d34..2cdde14e 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -312,7 +312,6 @@ handle_info({autoheal_request_winner, Node}, State = #state{autoheal = {wait_for_winner_reqs, [Node], Notify}, partitions = Partitions}) -> AllPartitions = all_partitions(Partitions), - io:format("all partitions: ~p~n", [AllPartitions]), Winner = autoheal_select_winner(AllPartitions), rabbit_log:info("Autoheal request winner from ~p~n" " Partitions were determined to be ~p~n" @@ -329,7 +328,6 @@ handle_info({autoheal_request_winner, Node}, handle_info({autoheal_winner, Winner}, State = #state{autoheal = wait_for_winner, partitions = Partitions}) -> - io:format("got winner ~p~n", [[Winner, Partitions]]), case lists:member(Winner, Partitions) of false -> case node() of Winner -> rabbit_log:info( -- cgit v1.2.1 From 52f53d9390ceceee28d0a0216c240b3a754e1bab Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 10 Apr 2013 11:57:29 +0100 Subject: Allow use of esl-erlang --- packaging/debs/Debian/debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/debs/Debian/debian/control b/packaging/debs/Debian/debian/control index d4526d87..3a15c4b6 100644 --- a/packaging/debs/Debian/debian/control +++ b/packaging/debs/Debian/debian/control @@ -9,7 +9,7 @@ Standards-Version: 3.9.2 Package: rabbitmq-server Architecture: all -Depends: erlang-nox (>= 1:12.b.3), adduser, logrotate, ${misc:Depends} +Depends: erlang-nox (>= 1:12.b.3) | esl-erlang, adduser, logrotate, ${misc:Depends} Description: AMQP server written in Erlang RabbitMQ is an implementation of AMQP, the emerging standard for high performance enterprise messaging. The RabbitMQ server is a robust and -- cgit v1.2.1 From 4f68ebd4e31effe000208ef1b836575427663a36 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 10 Apr 2013 13:59:47 +0100 Subject: Alternative implementation of bytecode compatibility check --- src/rabbit_mnesia.erl | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index c39e898c..52af28ab 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -403,7 +403,9 @@ cluster_status(WhichNodes) -> end. node_info() -> - {erlang:system_info(otp_release), rabbit_misc:version(), + DelegateBeamLocation = code:which(delegate), + {ok, {delegate, DelegateBeamHash}} = beam_lib:md5(DelegateBeamLocation), + {erlang:system_info(otp_release), rabbit_misc:version(), DelegateBeamHash, cluster_status_from_mnesia()}. node_type() -> @@ -562,10 +564,10 @@ check_cluster_consistency(Node) -> case rpc:call(Node, rabbit_mnesia, node_info, []) of {badrpc, _Reason} -> {error, not_found}; - {_OTP, _Rabbit, {error, _}} -> + {_OTP, _Rabbit, _Hash, {error, _}} -> {error, not_found}; - {OTP, Rabbit, {ok, Status}} -> - case check_consistency(OTP, Rabbit, Node, Status) of + {OTP, Rabbit, Hash, {ok, Status}} -> + case check_consistency(OTP, Rabbit, Hash, Node, Status) of {error, _} = E -> E; {ok, Res} -> {ok, Res} end @@ -732,14 +734,17 @@ change_extra_db_nodes(ClusterNodes0, CheckOtherNodes) -> Nodes end. -check_consistency(OTP, Rabbit) -> +check_consistency(OTP, Rabbit, Hash) -> rabbit_misc:sequence_error( - [check_otp_consistency(OTP), check_rabbit_consistency(Rabbit)]). + [check_otp_consistency(OTP), + check_rabbit_consistency(Rabbit), + check_beam_compatibility(Hash)]). -check_consistency(OTP, Rabbit, Node, Status) -> +check_consistency(OTP, Rabbit, Hash, Node, Status) -> rabbit_misc:sequence_error( [check_otp_consistency(OTP), check_rabbit_consistency(Rabbit), + check_beam_compatibility(Hash), check_nodes_consistency(Node, Status)]). check_nodes_consistency(Node, RemoteStatus = {RemoteAllNodes, _, _}) -> @@ -780,6 +785,15 @@ check_rabbit_consistency(Remote) -> rabbit_misc:version(), Remote, "Rabbit", fun rabbit_misc:version_minor_equivalent/2). +check_beam_compatibility(RemoteHash) -> + DelegateBeamLocation = code:which(delegate), + {ok, {delegate, LocalHash}} = beam_lib:md5(DelegateBeamLocation), + case RemoteHash == LocalHash of + true -> ok; + false -> {error, {incompatible_bytecode, + "Incompatible Erlang bytecode found on nodes"}} + end. + %% This is fairly tricky. We want to know if the node is in the state %% that a `reset' would leave it in. We cannot simply check if the %% mnesia tables aren't there because restarted RAM nodes won't have @@ -806,10 +820,10 @@ find_good_node([]) -> find_good_node([Node | Nodes]) -> case rpc:call(Node, rabbit_mnesia, node_info, []) of {badrpc, _Reason} -> find_good_node(Nodes); - {OTP, Rabbit, _} -> case check_consistency(OTP, Rabbit) of - {error, _} -> find_good_node(Nodes); - ok -> {ok, Node} - end + {OTP, Rabbit, Hash, _} -> case check_consistency(OTP, Rabbit, Hash) of + {error, _} -> find_good_node(Nodes); + ok -> {ok, Node} + end end. is_only_clustered_disc_node() -> -- cgit v1.2.1 From 58ee0ccec52aff1e31e82d9c2f28570dbc9d672e Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 10 Apr 2013 18:13:38 +0100 Subject: Changes --- src/rabbit_exchange.erl | 25 ++++++++++++------------- src/rabbit_exchange_decorator.erl | 22 +++++++++++----------- src/rabbit_policy.erl | 13 ++++++------- src/rabbit_tests.erl | 2 +- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index aa697f07..f5fd9a65 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -144,13 +144,12 @@ serial(#exchange{name = XName} = X) -> end. declare(XName, Type, Durable, AutoDelete, Internal, Args) -> - X0 = rabbit_policy:set(#exchange{name = XName, - type = Type, - durable = Durable, - auto_delete = AutoDelete, - internal = Internal, - arguments = Args}), - X = rabbit_exchange_decorator:record(X0, rabbit_exchange_decorator:list()), + X = rabbit_policy:set(#exchange{name = XName, + type = Type, + durable = Durable, + auto_delete = AutoDelete, + internal = Internal, + arguments = Args}), XT = type_to_module(Type), %% We want to upset things if it isn't ok ok = XT:validate(X), @@ -316,20 +315,20 @@ route(#exchange{name = #resource{virtual_host = VHost, name = RName} = XName, {<<"">>, []} -> %% Optimisation [rabbit_misc:r(VHost, queue, RK) || RK <- lists:usort(RKs)]; - {_, RDecorators} -> - lists:usort(route1(Delivery, RDecorators, {[X], XName, []})) + {_, SelectedDecorators} -> + lists:usort(route1(Delivery, SelectedDecorators, {[X], XName, []})) end. route1(_, _, {[], _, QNames}) -> QNames; -route1(Delivery, RDecorators, +route1(Delivery, Decorators, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> ExchangeDests = (type_to_module(Type)):route(X, Delivery), - RDecorateDests = process_decorators(X, RDecorators, Delivery), + DecorateDests = process_decorators(X, Decorators, Delivery), AlternateDests = process_alternate(X, ExchangeDests), - route1(Delivery, RDecorators, + route1(Delivery, Decorators, lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, - AlternateDests ++ RDecorateDests ++ ExchangeDests)). + AlternateDests ++ DecorateDests ++ ExchangeDests)). process_alternate(#exchange{arguments = []}, _Results) -> %% optimisation []; diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index cdbc42bb..3748a844 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -18,7 +18,7 @@ -include("rabbit.hrl"). --export([list/0, select/2, record/2]). +-export([list/0, select/2, set/1]). %% This is like an exchange type except that: %% @@ -59,8 +59,8 @@ %% Decorators can optionally implement route/2 which allows additional %% destinations to be added to the routing decision. --callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> - [rabbit_amqqueue:name() | rabbit_exchange:name()] | ok. +%% -callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> +%% [rabbit_amqqueue:name() | rabbit_exchange:name()] | ok. %% Whether the decorator wishes to receive callbacks for the exchange %% none:no callbacks, noroute:all callbacks except route, all:all callbacks @@ -87,14 +87,14 @@ list() -> [M || {_, M} <- rabbit_registry:lookup_all(exchange_decorator)]. select(all, {Route, NoRoute}) -> Route ++ NoRoute; select(route, {Route, _NoRoute}) -> Route. -%% record active decorators in an exchange -record(X, Decorators) -> - X#exchange{decorators = - lists:foldl(fun (D, {Route, NoRoute}) -> - Callbacks = D:active_for(X), - {cons_if_eq(all, Callbacks, D, Route), - cons_if_eq(noroute, Callbacks, D, NoRoute)} - end, {[], []}, Decorators)}. +set(X) -> + X#exchange{ + decorators = + lists:foldl(fun (D, {Route, NoRoute}) -> + Callbacks = D:active_for(X), + {cons_if_eq(all, Callbacks, D, Route), + cons_if_eq(noroute, Callbacks, D, NoRoute)} + end, {[], []}, rabbit_exchange_decorator:list())}. cons_if_eq(Select, Select, Item, List) -> [Item | List]; cons_if_eq(_Select, _Other, _Item, List) -> List. diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl index 22e9bdac..d276c2fb 100644 --- a/src/rabbit_policy.erl +++ b/src/rabbit_policy.erl @@ -46,7 +46,8 @@ name0(undefined) -> none; name0(Policy) -> pget(name, Policy). set(Q = #amqqueue{name = Name}) -> Q#amqqueue{policy = set0(Name)}; -set(X = #exchange{name = Name}) -> X#exchange{policy = set0(Name)}. +set(X = #exchange{name = Name}) -> rabbit_exchange_decorator:set( + X#exchange{policy = set0(Name)}). set0(Name = #resource{virtual_host = VHost}) -> match(Name, list(VHost)). @@ -156,10 +157,9 @@ notify_clear(VHost, <<"policy">>, _Name) -> update_policies(VHost) -> Policies = list(VHost), - Decorators = rabbit_exchange_decorator:list(), {Xs, Qs} = rabbit_misc:execute_mnesia_transaction( fun() -> - {[update_exchange(X, Policies, Decorators) || + {[update_exchange(X, Policies) || X <- rabbit_exchange:list(VHost)], [update_queue(Q, Policies) || Q <- rabbit_amqqueue:list(VHost)]} @@ -168,16 +168,15 @@ update_policies(VHost) -> [catch notify(Q) || Q <- Qs], ok. -update_exchange(X = #exchange{name = XName, policy = OldPolicy}, - Policies, Decorators) -> +update_exchange(X = #exchange{name = XName, policy = OldPolicy}, Policies) -> case match(XName, Policies) of OldPolicy -> no_change; NewPolicy -> rabbit_exchange:update( XName, fun(X1) -> - rabbit_exchange_decorator:record( - X1#exchange{policy = NewPolicy}, Decorators) + rabbit_exchange_decorator:set( + X1#exchange{policy = NewPolicy}) end), {X, X#exchange{policy = NewPolicy}} end. diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index 91f560cb..93e2d046 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -565,7 +565,7 @@ test_topic_matching() -> name = <<"test_exchange">>}, X0 = #exchange{name = XName, type = topic, durable = false, auto_delete = false, arguments = []}, - X = rabbit_exchange_decorator:record(X0, []), + X = rabbit_exchange_decorator:set(X0), %% create rabbit_exchange_type_topic:validate(X), exchange_op_callback(X, create, []), -- cgit v1.2.1 From 1d82b4ffcd52d2f9b8ed3de776d2080d07366674 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 11 Apr 2013 15:37:59 +0100 Subject: Move mirrored queue modes into a behaviour and implementations thereof. --- src/rabbit_mirror_queue_misc.erl | 137 ++++++++++--------------------- src/rabbit_mirror_queue_mode.erl | 55 +++++++++++++ src/rabbit_mirror_queue_mode_all.erl | 41 +++++++++ src/rabbit_mirror_queue_mode_exactly.erl | 56 +++++++++++++ src/rabbit_mirror_queue_mode_nodes.erl | 70 ++++++++++++++++ src/rabbit_policy_validator.erl | 2 + src/rabbit_registry.erl | 3 +- src/rabbit_tests.erl | 9 +- 8 files changed, 276 insertions(+), 97 deletions(-) create mode 100644 src/rabbit_mirror_queue_mode.erl create mode 100644 src/rabbit_mirror_queue_mode_all.erl create mode 100644 src/rabbit_mirror_queue_mode_exactly.erl create mode 100644 src/rabbit_mirror_queue_mode_nodes.erl diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl index 4fb1fc3b..8787e966 100644 --- a/src/rabbit_mirror_queue_misc.erl +++ b/src/rabbit_mirror_queue_misc.erl @@ -22,7 +22,7 @@ is_mirrored/1, update_mirrors/2, validate_policy/1]). %% for testing only --export([suggested_queue_nodes/4]). +-export([module/1]). -include("rabbit.hrl"). @@ -237,14 +237,17 @@ suggested_queue_nodes(Q) -> %% This variant exists so we can pull a call to %% rabbit_mnesia:cluster_nodes(running) out of a loop or %% transaction or both. -suggested_queue_nodes(Q, PossibleNodes) -> +suggested_queue_nodes(Q, All) -> {MNode0, SNodes, SSNodes} = actual_queue_nodes(Q), MNode = case MNode0 of none -> node(); _ -> MNode0 end, - suggested_queue_nodes(policy(<<"ha-mode">>, Q), policy(<<"ha-params">>, Q), - {MNode, SNodes, SSNodes}, PossibleNodes). + Params = policy(<<"ha-params">>, Q), + case module(Q) of + {ok, M} -> M:suggested_queue_nodes(Params, MNode, SNodes, SSNodes, All); + _ -> {MNode, []} + end. policy(Policy, Q) -> case rabbit_policy:get(Policy, Q) of @@ -252,52 +255,26 @@ policy(Policy, Q) -> _ -> none end. -suggested_queue_nodes(<<"all">>, _Params, {MNode, _SNodes, _SSNodes}, Poss) -> - {MNode, Poss -- [MNode]}; -suggested_queue_nodes(<<"nodes">>, Nodes0, {MNode, _SNodes, SSNodes}, Poss) -> - Nodes1 = [list_to_atom(binary_to_list(Node)) || Node <- Nodes0], - %% If the current master is not in the nodes specified, then what we want - %% to do depends on whether there are any synchronised slaves. If there - %% are then we can just kill the current master - the admin has asked for - %% a migration and we should give it to them. If there are not however - %% then we must keep the master around so as not to lose messages. - Nodes = case SSNodes of - [] -> lists:usort([MNode | Nodes1]); - _ -> Nodes1 - end, - Unavailable = Nodes -- Poss, - Available = Nodes -- Unavailable, - case Available of - [] -> %% We have never heard of anything? Not much we can do but - %% keep the master alive. - {MNode, []}; - _ -> case lists:member(MNode, Available) of - true -> {MNode, Available -- [MNode]}; - false -> %% Make sure the new master is synced! In order to - %% get here SSNodes must not be empty. - [NewMNode | _] = SSNodes, - {NewMNode, Available -- [NewMNode]} - end +module(#amqqueue{} = Q) -> + case rabbit_policy:get(<<"ha-mode">>, Q) of + {ok, Mode} -> module(Mode); + _ -> not_mirrored end; -%% When we need to add nodes, we randomise our candidate list as a -%% crude form of load-balancing. TODO it would also be nice to -%% randomise the list of ones to remove when we have too many - we -%% would have to take account of synchronisation though. -suggested_queue_nodes(<<"exactly">>, Count, {MNode, SNodes, _SSNodes}, Poss) -> - SCount = Count - 1, - {MNode, case SCount > length(SNodes) of - true -> Cand = shuffle((Poss -- [MNode]) -- SNodes), - SNodes ++ lists:sublist(Cand, SCount - length(SNodes)); - false -> lists:sublist(SNodes, SCount) - end}; -suggested_queue_nodes(_, _, {MNode, _, _}, _) -> - {MNode, []}. - -shuffle(L) -> - {A1,A2,A3} = now(), - random:seed(A1, A2, A3), - {_, L1} = lists:unzip(lists:keysort(1, [{random:uniform(), N} || N <- L])), - L1. + +module(Mode) when is_binary(Mode) -> + case rabbit_registry:binary_to_type(Mode) of + {error, not_found} -> not_mirrored; + T -> case rabbit_registry:lookup_module(ha_mode, T) of + {ok, Module} -> {ok, Module}; + _ -> not_mirrored + end + end. + +is_mirrored(Q) -> + case module(Q) of + {ok, _} -> true; + _ -> false + end. actual_queue_nodes(#amqqueue{pid = MPid, slave_pids = SPids, @@ -308,14 +285,6 @@ actual_queue_nodes(#amqqueue{pid = MPid, _ -> node(MPid) end, Nodes(SPids), Nodes(SSPids)}. -is_mirrored(Q) -> - case policy(<<"ha-mode">>, Q) of - <<"all">> -> true; - <<"nodes">> -> true; - <<"exactly">> -> true; - _ -> false - end. - maybe_auto_sync(Q = #amqqueue{pid = QPid}) -> case policy(<<"ha-sync-mode">>, Q) of <<"automatic">> -> @@ -347,40 +316,24 @@ update_mirrors0(OldQ = #amqqueue{name = QName}, %%---------------------------------------------------------------------------- validate_policy(KeyList) -> - case validate_policy( - proplists:get_value(<<"ha-mode">>, KeyList), - proplists:get_value(<<"ha-params">>, KeyList, none)) of - ok -> case proplists:get_value( - <<"ha-sync-mode">>, KeyList, <<"manual">>) of - <<"automatic">> -> ok; - <<"manual">> -> ok; - Mode -> {error, "ha-sync-mode must be \"manual\" " - "or \"automatic\", got ~p", [Mode]} - end; - E -> E + Mode = proplists:get_value(<<"ha-mode">>, KeyList), + Params = proplists:get_value(<<"ha-params">>, KeyList, none), + case Mode of + undefined -> ok; + _ -> case module(Mode) of + {ok, M} -> case M:validate_policy(Params) of + ok -> validate_sync_mode(KeyList); + E -> E + end; + _ -> {error, + "~p is not a valid ha-mode value", [Mode]} + end end. -validate_policy(<<"all">>, none) -> - ok; -validate_policy(<<"all">>, _Params) -> - {error, "ha-mode=\"all\" does not take parameters", []}; - -validate_policy(<<"nodes">>, []) -> - {error, "ha-mode=\"nodes\" list must be non-empty", []}; -validate_policy(<<"nodes">>, Nodes) when is_list(Nodes) -> - case [I || I <- Nodes, not is_binary(I)] of - [] -> ok; - Invalid -> {error, "ha-mode=\"nodes\" takes a list of strings, " - "~p was not a string", [Invalid]} - end; -validate_policy(<<"nodes">>, Params) -> - {error, "ha-mode=\"nodes\" takes a list, ~p given", [Params]}; - -validate_policy(<<"exactly">>, N) when is_integer(N) andalso N > 0 -> - ok; -validate_policy(<<"exactly">>, Params) -> - {error, "ha-mode=\"exactly\" takes an integer, ~p given", [Params]}; - -validate_policy(Mode, _Params) -> - {error, "~p is not a valid ha-mode value", [Mode]}. - +validate_sync_mode(KeyList) -> + case proplists:get_value(<<"ha-sync-mode">>, KeyList, <<"manual">>) of + <<"automatic">> -> ok; + <<"manual">> -> ok; + Mode -> {error, "ha-sync-mode must be \"manual\" " + "or \"automatic\", got ~p", [Mode]} + end. diff --git a/src/rabbit_mirror_queue_mode.erl b/src/rabbit_mirror_queue_mode.erl new file mode 100644 index 00000000..de1c35e2 --- /dev/null +++ b/src/rabbit_mirror_queue_mode.erl @@ -0,0 +1,55 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is VMware, Inc. +%% Copyright (c) 2010-2013 VMware, Inc. All rights reserved. +%% + +-module(rabbit_mirror_queue_mode). + +-ifdef(use_specs). + +-type(master() :: node()). +-type(slave() :: node()). +-type(params() :: any()). + +-callback description() -> [proplists:property()]. + +%% Called whenever we think we might need to change nodes for a +%% mirrored queue. +%% +%% Takes: parameters set in the policy, +%% current master, +%% current slaves, +%% current synchronised slaves, +%% all nodes to consider +%% +%% Returns: tuple of new master, new slaves +%% +-callback suggested_queue_nodes( + params(), master(), [slave()], [slave()], [node()]) -> + {master(), [slave()]}. + +%% Are the parameters valid for this mode? +-callback validate_policy(params()) -> + rabbit_policy_validator:validate_results(). + +-else. + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{description, 0}, {suggested_queue_nodes, 5}, {validate_policy, 1}]. +behaviour_info(_Other) -> + undefined. + +-endif. diff --git a/src/rabbit_mirror_queue_mode_all.erl b/src/rabbit_mirror_queue_mode_all.erl new file mode 100644 index 00000000..84d75b9a --- /dev/null +++ b/src/rabbit_mirror_queue_mode_all.erl @@ -0,0 +1,41 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is VMware, Inc. +%% Copyright (c) 2010-2013 VMware, Inc. All rights reserved. +%% + +-module(rabbit_mirror_queue_mode_all). + +-include("rabbit.hrl"). + +-behaviour(rabbit_mirror_queue_mode). + +-export([description/0, suggested_queue_nodes/5, validate_policy/1]). + +-rabbit_boot_step({?MODULE, + [{description, "mirror mode all"}, + {mfa, {rabbit_registry, register, + [ha_mode, <<"all">>, ?MODULE]}}, + {requires, rabbit_registry}, + {enables, kernel_ready}]}). + +description() -> + [{description, <<"Mirror queue to all nodes">>}]. + +suggested_queue_nodes(_Params, MNode, _SNodes, _SSNodes, Poss) -> + {MNode, Poss -- [MNode]}. + +validate_policy(none) -> + ok; +validate_policy(_Params) -> + {error, "ha-mode=\"all\" does not take parameters", []}. diff --git a/src/rabbit_mirror_queue_mode_exactly.erl b/src/rabbit_mirror_queue_mode_exactly.erl new file mode 100644 index 00000000..2a42c383 --- /dev/null +++ b/src/rabbit_mirror_queue_mode_exactly.erl @@ -0,0 +1,56 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is VMware, Inc. +%% Copyright (c) 2010-2013 VMware, Inc. All rights reserved. +%% + +-module(rabbit_mirror_queue_mode_exactly). + +-include("rabbit.hrl"). + +-behaviour(rabbit_mirror_queue_mode). + +-export([description/0, suggested_queue_nodes/5, validate_policy/1]). + +-rabbit_boot_step({?MODULE, + [{description, "mirror mode exactly"}, + {mfa, {rabbit_registry, register, + [ha_mode, <<"exactly">>, ?MODULE]}}, + {requires, rabbit_registry}, + {enables, kernel_ready}]}). + +description() -> + [{description, <<"Mirror queue to a specified number of nodes">>}]. + +%% When we need to add nodes, we randomise our candidate list as a +%% crude form of load-balancing. TODO it would also be nice to +%% randomise the list of ones to remove when we have too many - we +%% would have to take account of synchronisation though. +suggested_queue_nodes(Count, MNode, SNodes, _SSNodes, Poss) -> + SCount = Count - 1, + {MNode, case SCount > length(SNodes) of + true -> Cand = shuffle((Poss -- [MNode]) -- SNodes), + SNodes ++ lists:sublist(Cand, SCount - length(SNodes)); + false -> lists:sublist(SNodes, SCount) + end}. + +shuffle(L) -> + {A1,A2,A3} = now(), + random:seed(A1, A2, A3), + {_, L1} = lists:unzip(lists:keysort(1, [{random:uniform(), N} || N <- L])), + L1. + +validate_policy(N) when is_integer(N) andalso N > 0 -> + ok; +validate_policy(Params) -> + {error, "ha-mode=\"exactly\" takes an integer, ~p given", [Params]}. diff --git a/src/rabbit_mirror_queue_mode_nodes.erl b/src/rabbit_mirror_queue_mode_nodes.erl new file mode 100644 index 00000000..aa62ad33 --- /dev/null +++ b/src/rabbit_mirror_queue_mode_nodes.erl @@ -0,0 +1,70 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is VMware, Inc. +%% Copyright (c) 2010-2013 VMware, Inc. All rights reserved. +%% + +-module(rabbit_mirror_queue_mode_nodes). + +-include("rabbit.hrl"). + +-behaviour(rabbit_mirror_queue_mode). + +-export([description/0, suggested_queue_nodes/5, validate_policy/1]). + +-rabbit_boot_step({?MODULE, + [{description, "mirror mode nodes"}, + {mfa, {rabbit_registry, register, + [ha_mode, <<"nodes">>, ?MODULE]}}, + {requires, rabbit_registry}, + {enables, kernel_ready}]}). + +description() -> + [{description, <<"Mirror queue to specified nodes">>}]. + +suggested_queue_nodes(Nodes0, MNode, _SNodes, SSNodes, Poss) -> + Nodes1 = [list_to_atom(binary_to_list(Node)) || Node <- Nodes0], + %% If the current master is not in the nodes specified, then what we want + %% to do depends on whether there are any synchronised slaves. If there + %% are then we can just kill the current master - the admin has asked for + %% a migration and we should give it to them. If there are not however + %% then we must keep the master around so as not to lose messages. + Nodes = case SSNodes of + [] -> lists:usort([MNode | Nodes1]); + _ -> Nodes1 + end, + Unavailable = Nodes -- Poss, + Available = Nodes -- Unavailable, + case Available of + [] -> %% We have never heard of anything? Not much we can do but + %% keep the master alive. + {MNode, []}; + _ -> case lists:member(MNode, Available) of + true -> {MNode, Available -- [MNode]}; + false -> %% Make sure the new master is synced! In order to + %% get here SSNodes must not be empty. + [NewMNode | _] = SSNodes, + {NewMNode, Available -- [NewMNode]} + end + end. + +validate_policy([]) -> + {error, "ha-mode=\"nodes\" list must be non-empty", []}; +validate_policy(Nodes) when is_list(Nodes) -> + case [I || I <- Nodes, not is_binary(I)] of + [] -> ok; + Invalid -> {error, "ha-mode=\"nodes\" takes a list of strings, " + "~p was not a string", [Invalid]} + end; +validate_policy(Params) -> + {error, "ha-mode=\"nodes\" takes a list, ~p given", [Params]}. diff --git a/src/rabbit_policy_validator.erl b/src/rabbit_policy_validator.erl index 75b88c39..f0bc1a30 100644 --- a/src/rabbit_policy_validator.erl +++ b/src/rabbit_policy_validator.erl @@ -18,6 +18,8 @@ -ifdef(use_specs). +-export_type([validate_results/0]). + -type(validate_results() :: 'ok' | {error, string(), [term()]} | [validate_results()]). diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl index acdc2cff..6aae8de6 100644 --- a/src/rabbit_registry.erl +++ b/src/rabbit_registry.erl @@ -130,7 +130,8 @@ class_module(exchange) -> rabbit_exchange_type; class_module(auth_mechanism) -> rabbit_auth_mechanism; class_module(runtime_parameter) -> rabbit_runtime_parameter; class_module(exchange_decorator) -> rabbit_exchange_decorator; -class_module(policy_validator) -> rabbit_policy_validator. +class_module(policy_validator) -> rabbit_policy_validator; +class_module(ha_mode) -> rabbit_mirror_queue_mode. %%--------------------------------------------------------------------------- diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index e7b69879..fc529797 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -912,10 +912,11 @@ test_arguments_parser() -> test_dynamic_mirroring() -> %% Just unit tests of the node selection logic, see multi node %% tests for the rest... - Test = fun ({NewM, NewSs, ExtraSs}, Policy, Params, CurrentState, All) -> - {NewM, NewSs0} = - rabbit_mirror_queue_misc:suggested_queue_nodes( - Policy, Params, CurrentState, All), + Test = fun ({NewM, NewSs, ExtraSs}, Policy, Params, + {MNode, SNodes, SSNodes}, All) -> + {ok, M} = rabbit_mirror_queue_misc:module(Policy), + {NewM, NewSs0} = M:suggested_queue_nodes( + Params, MNode, SNodes, SSNodes, All), NewSs1 = lists:sort(NewSs0), case dm_list_match(NewSs, NewSs1, ExtraSs) of ok -> ok; -- cgit v1.2.1 From d28654b6d985242ea3d6fc80c426f1d89dd59b1d Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 11 Apr 2013 17:53:15 +0100 Subject: Changes --- src/rabbit_exchange.erl | 10 +++++++--- src/rabbit_exchange_decorator.erl | 17 ++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index f5fd9a65..c924f53a 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -125,8 +125,12 @@ callback(X = #exchange{type = XType, Module = type_to_module(XType), apply(Module, Fun, [Serial(Module:serialise_events()) | Args]). -policy_changed(X = #exchange{type = XType}, X1) -> - ok = (type_to_module(XType)):policy_changed(X, X1). +policy_changed(X = #exchange{type = XType, + decorators = Decorators}, X1) -> + [ok = M:policy_changed(X, X1) || + M <- [type_to_module(XType) | + rabbit_exchange_decorator:select(all, Decorators)]], + ok. serialise_events(X = #exchange{type = Type, decorators = Decorators}) -> lists:any(fun (M) -> @@ -324,7 +328,7 @@ route1(_, _, {[], _, QNames}) -> route1(Delivery, Decorators, {[X = #exchange{type = Type} | WorkList], SeenXs, QNames}) -> ExchangeDests = (type_to_module(Type)):route(X, Delivery), - DecorateDests = process_decorators(X, Decorators, Delivery), + DecorateDests = process_decorators(X, Decorators, Delivery), AlternateDests = process_alternate(X, ExchangeDests), route1(Delivery, Decorators, lists:foldl(fun process_route/2, {WorkList, SeenXs, QNames}, diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 3748a844..3bc9de1e 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -18,7 +18,7 @@ -include("rabbit.hrl"). --export([list/0, select/2, set/1]). +-export([select/2, set/1]). %% This is like an exchange type except that: %% @@ -49,6 +49,10 @@ -callback delete(tx(), rabbit_types:exchange(), [rabbit_types:binding()]) -> 'ok'. +%% called when the policy attached to this exchange changes. +-callback policy_changed(rabbit_types:exchange(), rabbit_types:exchange()) -> + 'ok'. + %% called after a binding has been added or recovered -callback add_binding(serial(), rabbit_types:exchange(), rabbit_types:binding()) -> 'ok'. @@ -57,10 +61,9 @@ -callback remove_bindings(serial(), rabbit_types:exchange(), [rabbit_types:binding()]) -> 'ok'. -%% Decorators can optionally implement route/2 which allows additional -%% destinations to be added to the routing decision. -%% -callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> -%% [rabbit_amqqueue:name() | rabbit_exchange:name()] | ok. +%% Allows additional destinations to be added to the routing decision. +-callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> + [rabbit_amqqueue:name() | rabbit_exchange:name()] | ok. %% Whether the decorator wishes to receive callbacks for the exchange %% none:no callbacks, noroute:all callbacks except route, all:all callbacks @@ -73,7 +76,7 @@ behaviour_info(callbacks) -> [{description, 0}, {serialise_events, 1}, {create, 2}, {delete, 3}, {policy_changed, 2}, {add_binding, 3}, {remove_bindings, 3}, - {active_for, 1}]; + {route, 2}, {active_for, 1}]; behaviour_info(_Other) -> undefined. @@ -94,7 +97,7 @@ set(X) -> Callbacks = D:active_for(X), {cons_if_eq(all, Callbacks, D, Route), cons_if_eq(noroute, Callbacks, D, NoRoute)} - end, {[], []}, rabbit_exchange_decorator:list())}. + end, {[], []}, list())}. cons_if_eq(Select, Select, Item, List) -> [Item | List]; cons_if_eq(_Select, _Other, _Item, List) -> List. -- cgit v1.2.1 From b382e07f8ef319dfbe46e4d874aa97e01233694c Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 12 Apr 2013 14:42:11 +0100 Subject: Only ban cycles that are entirely due to expiry --- src/rabbit_amqqueue_process.erl | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b016c4d2..b0b37bb2 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -846,7 +846,7 @@ dead_letter_publish(Msg, Reason, X, RK, MsgSeqNo, QName) -> DLMsg = make_dead_letter_msg(Msg, Reason, X#exchange.name, RK, QName), Delivery = rabbit_basic:delivery(false, DLMsg, MsgSeqNo), {Queues, Cycles} = detect_dead_letter_cycles( - DLMsg, rabbit_exchange:route(X, Delivery)), + Reason, DLMsg, rabbit_exchange:route(X, Delivery)), lists:foreach(fun log_cycle_once/1, Cycles), {_, DeliveredQPids} = rabbit_amqqueue:deliver( rabbit_amqqueue:lookup(Queues), Delivery), @@ -895,7 +895,8 @@ cleanup_after_confirm(AckTags, State = #q{delayed_stop = DS, false -> noreply(State1) end. -detect_dead_letter_cycles(#basic_message{content = Content}, Queues) -> +detect_dead_letter_cycles(expired, + #basic_message{content = Content}, Queues) -> #content{properties = #'P_basic'{headers = Headers}} = rabbit_binary_parser:ensure_content_decoded(Content), NoCycles = {Queues, []}, @@ -904,22 +905,38 @@ detect_dead_letter_cycles(#basic_message{content = Content}, Queues) -> NoCycles; _ -> case rabbit_misc:table_lookup(Headers, <<"x-death">>) of - {array, DeathTables} -> - OldQueues = [rabbit_misc:table_lookup(D, <<"queue">>) || - {table, D} <- DeathTables], - OldQueues1 = [QName || {longstr, QName} <- OldQueues], - OldQueuesSet = ordsets:from_list(OldQueues1), + {array, Deaths} -> {Cycling, NotCycling} = lists:partition( - fun(Queue) -> - ordsets:is_element(Queue#resource.name, - OldQueuesSet) + fun (#resource{name = Queue}) -> + is_dead_letter_cycle(Queue, Deaths) end, Queues), + OldQueues = [rabbit_misc:table_lookup(D, <<"queue">>) || + {table, D} <- Deaths], + OldQueues1 = [QName || {longstr, QName} <- OldQueues], {NotCycling, [[QName | OldQueues1] || #resource{name = QName} <- Cycling]}; _ -> NoCycles end + end; +detect_dead_letter_cycles(_Reason, _Msg, Queues) -> + {Queues, []}. + +is_dead_letter_cycle(Queue, Deaths) -> + {Cycle, Rest} = + lists:splitwith( + fun ({table, D}) -> + {longstr, Queue} =/= rabbit_misc:table_lookup(D, <<"queue">>); + (_) -> + true + end, lists:reverse(Deaths)), + %% Is there a cycle, and if so, is it entirely due to expiry? + case Rest of + [] -> false; + [H|_] -> [] =:= [D || {table, D} <- Cycle ++ [H], + {longstr, <<"expired">>} =/= + rabbit_misc:table_lookup(D, <<"reason">>)] end. make_dead_letter_msg(Msg = #basic_message{content = Content, -- cgit v1.2.1 From 569cc88471263357776c316f7d6dfeff370bf687 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 12 Apr 2013 15:42:27 +0100 Subject: No, the newest dead letterings are at the head. --- src/rabbit_amqqueue_process.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index b0b37bb2..e6bc71fe 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -930,7 +930,7 @@ is_dead_letter_cycle(Queue, Deaths) -> {longstr, Queue} =/= rabbit_misc:table_lookup(D, <<"queue">>); (_) -> true - end, lists:reverse(Deaths)), + end, Deaths), %% Is there a cycle, and if so, is it entirely due to expiry? case Rest of [] -> false; -- cgit v1.2.1 From 97dc8b329186bc528034c7846a8c1244e138f802 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 12 Apr 2013 15:44:55 +0100 Subject: Maybe clearer? --- src/rabbit_amqqueue_process.erl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index e6bc71fe..fb2c16c8 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -934,9 +934,13 @@ is_dead_letter_cycle(Queue, Deaths) -> %% Is there a cycle, and if so, is it entirely due to expiry? case Rest of [] -> false; - [H|_] -> [] =:= [D || {table, D} <- Cycle ++ [H], - {longstr, <<"expired">>} =/= - rabbit_misc:table_lookup(D, <<"reason">>)] + [H|_] -> lists:all( + fun ({table, D}) -> + {longstr, <<"expired">>} =:= + rabbit_misc:table_lookup(D, <<"reason">>); + (_) -> + false + end, Cycle ++ [H]) end. make_dead_letter_msg(Msg = #basic_message{content = Content, -- cgit v1.2.1 From 59664eeaa335252c7b4bdb0c6fbc33a3ec52c302 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 15 Apr 2013 10:58:24 +0100 Subject: Cosmetic --- src/rabbit_exchange.erl | 5 ++--- src/rabbit_policy.erl | 16 +++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index c924f53a..6d111b83 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -133,9 +133,8 @@ policy_changed(X = #exchange{type = XType, ok. serialise_events(X = #exchange{type = Type, decorators = Decorators}) -> - lists:any(fun (M) -> - M:serialise_events(X) - end, rabbit_exchange_decorator:select(all, Decorators)) + lists:any(fun (M) -> M:serialise_events(X) end, + rabbit_exchange_decorator:select(all, Decorators)) orelse (type_to_module(Type)):serialise_events(). serial(#exchange{name = XName} = X) -> diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl index d276c2fb..ad2949b8 100644 --- a/src/rabbit_policy.erl +++ b/src/rabbit_policy.erl @@ -170,15 +170,13 @@ update_policies(VHost) -> update_exchange(X = #exchange{name = XName, policy = OldPolicy}, Policies) -> case match(XName, Policies) of - OldPolicy -> - no_change; - NewPolicy -> - rabbit_exchange:update( - XName, fun(X1) -> - rabbit_exchange_decorator:set( - X1#exchange{policy = NewPolicy}) - end), - {X, X#exchange{policy = NewPolicy}} + OldPolicy -> no_change; + NewPolicy -> rabbit_exchange:update( + XName, fun(X1) -> + rabbit_exchange_decorator:set( + X1#exchange{policy = NewPolicy}) + end), + {X, X#exchange{policy = NewPolicy}} end. update_queue(Q = #amqqueue{name = QName, policy = OldPolicy}, Policies) -> -- cgit v1.2.1 From e4cfae04a643a3a960dbd53272649c32cd2b13cb Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Mon, 15 Apr 2013 13:49:55 +0100 Subject: Ignore missing exchange decorators --- src/rabbit_exchange.erl | 9 +++++++++ src/rabbit_exchange_decorator.erl | 12 ++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 6d111b83..e7847673 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -113,8 +113,17 @@ recover() -> callback(X, create, map_create_tx(Tx), [X]) end, rabbit_durable_exchange), + report_missing_decorators(Xs), [XName || #exchange{name = XName} <- Xs]. +report_missing_decorators(Xs) -> + Mods = lists:usort(lists:append([rabbit_exchange_decorator:select(raw, D) || + #exchange{decorators = D} <- Xs])), + case [M || M <- Mods, code:which(M) =:= non_existing] of + [] -> ok; + M -> rabbit_log:warning("Missing exchange decorators: ~p~n", [M]) + end. + callback(X = #exchange{type = XType, decorators = Decorators}, Fun, Serial0, Args) -> Serial = if is_function(Serial0) -> Serial0; diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 3bc9de1e..19cbf92f 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -84,11 +84,13 @@ behaviour_info(_Other) -> %%---------------------------------------------------------------------------- -list() -> [M || {_, M} <- rabbit_registry:lookup_all(exchange_decorator)]. - %% select a subset of active decorators -select(all, {Route, NoRoute}) -> Route ++ NoRoute; -select(route, {Route, _NoRoute}) -> Route. +select(all, {Route, NoRoute}) -> filter(Route ++ NoRoute); +select(route, {Route, _NoRoute}) -> filter(Route); +select(raw, {Route, NoRoute}) -> Route ++ NoRoute. + +filter(Modules) -> + [M || M <- Modules, code:which(M) =/= non_existing]. set(X) -> X#exchange{ @@ -99,5 +101,7 @@ set(X) -> cons_if_eq(noroute, Callbacks, D, NoRoute)} end, {[], []}, list())}. +list() -> [M || {_, M} <- rabbit_registry:lookup_all(exchange_decorator)]. + cons_if_eq(Select, Select, Item, List) -> [Item | List]; cons_if_eq(_Select, _Other, _Item, List) -> List. -- cgit v1.2.1 From 8089c02708b9e19725f9ed68b335c0d1cbeb1bca Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 16 Apr 2013 11:59:44 +0100 Subject: Updates --- src/rabbit_exchange.erl | 18 +++++++++++------- src/rabbit_policy.erl | 14 ++++++++------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index e7847673..891caea2 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -134,11 +134,13 @@ callback(X = #exchange{type = XType, Module = type_to_module(XType), apply(Module, Fun, [Serial(Module:serialise_events()) | Args]). -policy_changed(X = #exchange{type = XType, - decorators = Decorators}, X1) -> - [ok = M:policy_changed(X, X1) || - M <- [type_to_module(XType) | - rabbit_exchange_decorator:select(all, Decorators)]], +policy_changed(X = #exchange{type = XType, + decorators = Decorators}, + X1 = #exchange{decorators = Decorators1}) -> + D = rabbit_exchange_decorator:select(all, Decorators), + D1 = rabbit_exchange_decorator:select(all, Decorators1), + Diff = (D -- D1) ++ (D1 -- D), + [ok = M:policy_changed(X, X1) || M <- [type_to_module(XType) | Diff]], ok. serialise_events(X = #exchange{type = Type, decorators = Decorators}) -> @@ -275,7 +277,8 @@ update_scratch(Name, App, Fun) -> Scratches2 = orddict:store( App, Fun(Scratch), Scratches1), X#exchange{scratches = Scratches2} - end) + end), + ok end). update(Name, Fun) -> @@ -286,7 +289,8 @@ update(Name, Fun) -> case Durable of true -> ok = mnesia:write(rabbit_durable_exchange, X1, write); _ -> ok - end; + end, + X1; [] -> ok end. diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl index ad2949b8..ae8d21b5 100644 --- a/src/rabbit_policy.erl +++ b/src/rabbit_policy.erl @@ -171,12 +171,14 @@ update_policies(VHost) -> update_exchange(X = #exchange{name = XName, policy = OldPolicy}, Policies) -> case match(XName, Policies) of OldPolicy -> no_change; - NewPolicy -> rabbit_exchange:update( - XName, fun(X1) -> - rabbit_exchange_decorator:set( - X1#exchange{policy = NewPolicy}) - end), - {X, X#exchange{policy = NewPolicy}} + NewPolicy -> case rabbit_exchange:update( + XName, fun (X0) -> + rabbit_exchange_decorator:set( + X0 #exchange{policy = NewPolicy}) + end) of + #exchange{} = X1 -> {X, X1}; + ok -> {X, X } + end end. update_queue(Q = #amqqueue{name = QName, policy = OldPolicy}, Policies) -> -- cgit v1.2.1 From 3afa3660873ba2affc9cc402c07cc126e2a78302 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 16 Apr 2013 12:45:12 +0100 Subject: Further excisions --- src/rabbit_amqqueue_process.erl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl index 7ab0d774..3712a625 100644 --- a/src/rabbit_amqqueue_process.erl +++ b/src/rabbit_amqqueue_process.erl @@ -49,7 +49,6 @@ ttl_timer_ref, ttl_timer_expiry, senders, - queue_monitors, dlx, dlx_routing_key, max_length, @@ -831,9 +830,8 @@ dead_letter_publish(Msg, Reason, X, RK, QName) -> {Queues, Cycles} = detect_dead_letter_cycles( DLMsg, rabbit_exchange:route(X, Delivery)), lists:foreach(fun log_cycle_once/1, Cycles), - {_, DeliveredQPids} = rabbit_amqqueue:deliver( - rabbit_amqqueue:lookup(Queues), Delivery), - DeliveredQPids. + rabbit_amqqueue:deliver( rabbit_amqqueue:lookup(Queues), Delivery), + ok. stop(State) -> stop(noreply, State). -- cgit v1.2.1 From 08957ca51b5fc40f54227350a38f258e10a3a4a9 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 16 Apr 2013 13:35:02 +0100 Subject: first stab at summarising processes by their ancestor chain --- src/rabbit_vm.erl | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index b3e9ec66..3d3ef59b 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -138,3 +138,45 @@ plugin_memory(App) -> is_plugin("rabbitmq_" ++ _) -> true; is_plugin(App) -> lists:member(App, ?MAGIC_PLUGINS). + +%% TODO support Pids, not just Names - need this for plugins +sum_processes(Names, Acc0) -> + sum_processes(Names, fun (_, X, Y) -> X + Y end, + [{Item, 0} || Item <- Acc0]). + +sum_processes(Names, Fun, Acc0) -> + Items = [Item || {Item, _Val0} <- Acc0], + Acc0Dict = orddict:from_list(Acc0), + NameAccs0 = orddict:from_list([{Name, Acc0Dict} || Name <- Names]), + {NameAccs, OtherAcc} = + lists:foldl(fun (Pid, Acc) -> + case process_info(Pid, [dictionary | Items]) of + undefined -> + Acc; + [{dictionary, D} | Vals] -> + ValsDict = orddict:from_list(Vals), + accumulate(find_ancestor(D, Names), Fun, + ValsDict, Acc) + end + end, {NameAccs0, Acc0Dict}, processes()), + %% these conversions aren't strictly necessary; we do them simply + %% for the sake of encapsulating the representation. + {[orddict:to_list(NameAcc) || NameAcc <- orddict:to_list(NameAccs)], + orddict:to_list(OtherAcc)}. + +find_ancestor(D, Names) -> + case lists:keyfind('$ancestors', 1, D) of + false -> undefined; + {_, Ancestors} -> case lists:splitwith( + fun (A) -> not lists:member(A, Names) end, + Ancestors) of + {_, []} -> undefined; + {_, [Name | _]} -> Name + end + end. + +accumulate(undefined, Fun, ValsDict, {NameAccs, OtherAcc}) -> + {NameAccs, orddict:merge(Fun, ValsDict, OtherAcc)}; +accumulate(Name, Fun, ValsDict, {NameAccs, OtherAcc}) -> + F = fun (NameAcc) -> orddict:merge(Fun, ValsDict, NameAcc) end, + {orddict:update(Name, F, NameAccs), OtherAcc}. -- cgit v1.2.1 From 6846869eeade78fccc3b5b9bdf4a881f63d80530 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 16 Apr 2013 14:57:48 +0100 Subject: Further tweaks --- src/rabbit_mnesia.erl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 52af28ab..7775aa3f 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -566,6 +566,9 @@ check_cluster_consistency(Node) -> {error, not_found}; {_OTP, _Rabbit, _Hash, {error, _}} -> {error, not_found}; + {_OTP, Rabbit, _Status} -> + %% pre-2013/04 format implies version mismatch + version_error("Rabbit", rabbit_misc:version(), Rabbit); {OTP, Rabbit, Hash, {ok, Status}} -> case check_consistency(OTP, Rabbit, Hash, Node, Status) of {error, _} = E -> E; @@ -819,8 +822,8 @@ find_good_node([]) -> none; find_good_node([Node | Nodes]) -> case rpc:call(Node, rabbit_mnesia, node_info, []) of - {badrpc, _Reason} -> find_good_node(Nodes); - {OTP, Rabbit, Hash, _} -> case check_consistency(OTP, Rabbit, Hash) of + {badrpc, _Reason} -> find_good_node(Nodes); + {OTP, Rabbit, Hash, _} -> case check_consistency(OTP, Rabbit, Hash) of {error, _} -> find_good_node(Nodes); ok -> {ok, Node} end -- cgit v1.2.1 From 7a3f0d0c44c6f37f12a36e624c5de73d269f78ea Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 16 Apr 2013 15:08:51 +0100 Subject: Handle potential incompatibility --- src/rabbit_mnesia.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 7775aa3f..5bef1277 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -823,6 +823,7 @@ find_good_node([]) -> find_good_node([Node | Nodes]) -> case rpc:call(Node, rabbit_mnesia, node_info, []) of {badrpc, _Reason} -> find_good_node(Nodes); + {_OTP, _Rabbit, _} -> find_good_node(Nodes); {OTP, Rabbit, Hash, _} -> case check_consistency(OTP, Rabbit, Hash) of {error, _} -> find_good_node(Nodes); ok -> {ok, Node} -- cgit v1.2.1 From d9610f82621d5e6ad4617fe9fa44ed3887237cea Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Tue, 16 Apr 2013 15:27:06 +0100 Subject: relocate is_explicit_restart macro --- src/supervisor2.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index a181e7d4..4014f9ee 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -141,9 +141,6 @@ -define(SETS, sets). -define(SET, set). --define(is_explicit_restart(R), - R == {shutdown, restart}). - -ifdef(use_specs). -record(state, {name, strategy :: strategy(), @@ -172,6 +169,8 @@ (is_tuple(R) andalso tuple_size(R) == 2 andalso element(1, R) =:= permanent))). +-define(is_explicit_restart(R), + R == {shutdown, restart}). -ifdef(use_specs). -callback init(Args :: term()) -> -- cgit v1.2.1 From 5b89f9f11418f0f5725494b88ed852540a7dbcbd Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Tue, 16 Apr 2013 15:28:32 +0100 Subject: drop function guards from restart_if_explicit_or_abnormal --- src/supervisor2.erl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 4014f9ee..8577a06e 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -891,12 +891,9 @@ handle_delayed_restart({intrinsic, _Delay}=Restart, Reason, Child, State) -> Reason, Child, State). restart_if_explicit_or_abnormal(RestartHow, _Otherwise, Reason, Child, State) - when is_function(RestartHow, 2) andalso - ?is_explicit_restart(Reason) -> + when ?is_explicit_restart(Reason) -> RestartHow(Child, State); -restart_if_explicit_or_abnormal(RestartHow, Otherwise, Reason, Child, State) - when is_function(RestartHow, 2) andalso - is_function(Otherwise, 2) -> +restart_if_explicit_or_abnormal(RestartHow, Otherwise, Reason, Child, State) -> case is_abnormal_termination(Reason) of true -> RestartHow(Child, State); false -> Otherwise(Child, State) -- cgit v1.2.1 From 3af08702100e0a7ca293798f5af591c46a03e7d2 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Tue, 16 Apr 2013 15:32:24 +0100 Subject: simplify restart_if_explicit_or_abnormal --- src/supervisor2.erl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 8577a06e..2f2e6692 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -890,11 +890,8 @@ handle_delayed_restart({intrinsic, _Delay}=Restart, Reason, Child, State) -> fun delete_child_and_stop/2, Reason, Child, State). -restart_if_explicit_or_abnormal(RestartHow, _Otherwise, Reason, Child, State) - when ?is_explicit_restart(Reason) -> - RestartHow(Child, State); restart_if_explicit_or_abnormal(RestartHow, Otherwise, Reason, Child, State) -> - case is_abnormal_termination(Reason) of + case ?is_explicit_restart(Reason) orelse is_abnormal_termination(Reason) of true -> RestartHow(Child, State); false -> Otherwise(Child, State) end. -- cgit v1.2.1 From 0c0b55cbb63bdb3b3f3a6604c8aa5690ef23222c Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Tue, 16 Apr 2013 15:38:02 +0100 Subject: rationalise restart handling --- src/supervisor2.erl | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 2f2e6692..533ec997 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -871,21 +871,13 @@ handle_restart(intrinsic, Reason, Child, State) -> Reason, Child, State); handle_restart(temporary, _Reason, Child, State) -> delete_child_and_continue(Child, State); -handle_restart({_RestartType, _Delay}=Restart, Reason, Child, State) -> - handle_delayed_restart(Restart, Reason, Child, State). - -handle_delayed_restart({permanent, _Delay}=Restart, Reason, Child, State) -> - do_restart_delay(Restart, Reason, Child, State); -handle_delayed_restart({RestartType, _Delay}=Restart, Reason, Child, State) - when ?is_explicit_restart(Reason) andalso - (RestartType =:= transient orelse - RestartType =:= intrinsic) -> +handle_restart({permanent, _Delay}=Restart, Reason, Child, State) -> do_restart_delay(Restart, Reason, Child, State); -handle_delayed_restart({transient, _Delay}=Restart, Reason, Child, State) -> +handle_restart({transient, _Delay}=Restart, Reason, Child, State) -> restart_if_explicit_or_abnormal(defer_to_restart_delay(Restart, Reason), fun delete_child_and_continue/2, Reason, Child, State); -handle_delayed_restart({intrinsic, _Delay}=Restart, Reason, Child, State) -> +handle_restart({intrinsic, _Delay}=Restart, Reason, Child, State) -> restart_if_explicit_or_abnormal(defer_to_restart_delay(Restart, Reason), fun delete_child_and_stop/2, Reason, Child, State). -- cgit v1.2.1 From edd2b1784af98f759e2d1cedafd05993824bdce7 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 16 Apr 2013 15:50:11 +0100 Subject: Cosmetic --- src/rabbit_mnesia.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 5bef1277..e331f62c 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -827,7 +827,7 @@ find_good_node([Node | Nodes]) -> {OTP, Rabbit, Hash, _} -> case check_consistency(OTP, Rabbit, Hash) of {error, _} -> find_good_node(Nodes); ok -> {ok, Node} - end + end end. is_only_clustered_disc_node() -> -- cgit v1.2.1 From 074fdf03ac7b9245d150cc03aa89c403001d8526 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Tue, 16 Apr 2013 17:36:34 +0100 Subject: Further modifications implementing feedback --- src/rabbit_exchange.erl | 13 ++++++++----- src/rabbit_exchange_decorator.erl | 2 +- src/rabbit_policy.erl | 5 +++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 891caea2..a1759c08 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -68,7 +68,10 @@ -spec(update_scratch/3 :: (name(), atom(), fun((any()) -> any())) -> 'ok'). -spec(update/2 :: (name(), - fun((rabbit_types:exchange()) -> rabbit_types:exchange())) -> 'ok'). + fun((rabbit_types:exchange()) -> rabbit_types:exchange())) + -> {exchange_not_found, rabbit_exchange:name()} | + {exchange_not_durable, rabbit_exchange:name()} | + rabbit_types:exchange()). -spec(info_keys/0 :: () -> rabbit_types:info_keys()). -spec(info/1 :: (rabbit_types:exchange()) -> rabbit_types:infos()). -spec(info/2 :: @@ -139,8 +142,8 @@ policy_changed(X = #exchange{type = XType, X1 = #exchange{decorators = Decorators1}) -> D = rabbit_exchange_decorator:select(all, Decorators), D1 = rabbit_exchange_decorator:select(all, Decorators1), - Diff = (D -- D1) ++ (D1 -- D), - [ok = M:policy_changed(X, X1) || M <- [type_to_module(XType) | Diff]], + DAll = lists:usort(D ++ D1), + [ok = M:policy_changed(X, X1) || M <- [type_to_module(XType) | DAll]], ok. serialise_events(X = #exchange{type = Type, decorators = Decorators}) -> @@ -288,11 +291,11 @@ update(Name, Fun) -> ok = mnesia:write(rabbit_exchange, X1, write), case Durable of true -> ok = mnesia:write(rabbit_durable_exchange, X1, write); - _ -> ok + _ -> {exchange_not_durable, Name} end, X1; [] -> - ok + {exchange_not_found, Name} end. info_keys() -> ?INFO_KEYS. diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 19cbf92f..79ea212f 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -63,7 +63,7 @@ %% Allows additional destinations to be added to the routing decision. -callback route(rabbit_types:exchange(), rabbit_types:delivery()) -> - [rabbit_amqqueue:name() | rabbit_exchange:name()] | ok. + [rabbit_amqqueue:name() | rabbit_exchange:name()]. %% Whether the decorator wishes to receive callbacks for the exchange %% none:no callbacks, noroute:all callbacks except route, all:all callbacks diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl index ae8d21b5..184e1c33 100644 --- a/src/rabbit_policy.erl +++ b/src/rabbit_policy.erl @@ -176,8 +176,9 @@ update_exchange(X = #exchange{name = XName, policy = OldPolicy}, Policies) -> rabbit_exchange_decorator:set( X0 #exchange{policy = NewPolicy}) end) of - #exchange{} = X1 -> {X, X1}; - ok -> {X, X } + #exchange{} = X1 -> {X, X1}; + {exchange_not_found, _} -> {X, X }; + {exchange_not_durable, _} -> {X, X } end end. -- cgit v1.2.1 From e9e634e40fc21fb53a69c69038a78c62b743f379 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 17 Apr 2013 08:17:53 +0100 Subject: Change exchange update return type --- src/rabbit_exchange.erl | 7 +++---- src/rabbit_policy.erl | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index a1759c08..1bed9344 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -69,8 +69,7 @@ -spec(update/2 :: (name(), fun((rabbit_types:exchange()) -> rabbit_types:exchange())) - -> {exchange_not_found, rabbit_exchange:name()} | - {exchange_not_durable, rabbit_exchange:name()} | + -> exchange_not_found | exchange_not_durable | rabbit_types:exchange()). -spec(info_keys/0 :: () -> rabbit_types:info_keys()). -spec(info/1 :: (rabbit_types:exchange()) -> rabbit_types:infos()). @@ -291,11 +290,11 @@ update(Name, Fun) -> ok = mnesia:write(rabbit_exchange, X1, write), case Durable of true -> ok = mnesia:write(rabbit_durable_exchange, X1, write); - _ -> {exchange_not_durable, Name} + _ -> exchange_not_durable end, X1; [] -> - {exchange_not_found, Name} + exchange_not_found end. info_keys() -> ?INFO_KEYS. diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl index 184e1c33..ca2837f5 100644 --- a/src/rabbit_policy.erl +++ b/src/rabbit_policy.erl @@ -176,9 +176,9 @@ update_exchange(X = #exchange{name = XName, policy = OldPolicy}, Policies) -> rabbit_exchange_decorator:set( X0 #exchange{policy = NewPolicy}) end) of - #exchange{} = X1 -> {X, X1}; - {exchange_not_found, _} -> {X, X }; - {exchange_not_durable, _} -> {X, X } + #exchange{} = X1 -> {X, X1}; + exchange_not_found -> {X, X }; + exchange_not_durable -> {X, X } end end. -- cgit v1.2.1 From 73b7ccda425668ebd26debc6458b265af4f599a8 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Wed, 17 Apr 2013 09:46:29 +0100 Subject: Abstract --- src/rabbit_mnesia.erl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index e331f62c..5d902b5d 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -403,10 +403,8 @@ cluster_status(WhichNodes) -> end. node_info() -> - DelegateBeamLocation = code:which(delegate), - {ok, {delegate, DelegateBeamHash}} = beam_lib:md5(DelegateBeamLocation), - {erlang:system_info(otp_release), rabbit_misc:version(), DelegateBeamHash, - cluster_status_from_mnesia()}. + {erlang:system_info(otp_release), rabbit_misc:version(), + delegate_beam_hash(), cluster_status_from_mnesia()}. node_type() -> DiscNodes = cluster_nodes(disc), @@ -789,14 +787,17 @@ check_rabbit_consistency(Remote) -> fun rabbit_misc:version_minor_equivalent/2). check_beam_compatibility(RemoteHash) -> - DelegateBeamLocation = code:which(delegate), - {ok, {delegate, LocalHash}} = beam_lib:md5(DelegateBeamLocation), - case RemoteHash == LocalHash of + case RemoteHash == delegate_beam_hash() of true -> ok; false -> {error, {incompatible_bytecode, "Incompatible Erlang bytecode found on nodes"}} end. +delegate_beam_hash() -> + DelegateBeamLocation = code:which(delegate), + {ok, {delegate, Hash}} = beam_lib:md5(DelegateBeamLocation), + Hash. + %% This is fairly tricky. We want to know if the node is in the state %% that a `reset' would leave it in. We cannot simply check if the %% mnesia tables aren't there because restarted RAM nodes won't have -- cgit v1.2.1 From cfa98cd3073cac972721c0884ae05189dda6a0f1 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 17 Apr 2013 10:41:20 +0100 Subject: Explain why we are doing this. --- src/rabbit_mnesia.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 5d902b5d..95182a7c 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -793,6 +793,9 @@ check_beam_compatibility(RemoteHash) -> "Incompatible Erlang bytecode found on nodes"}} end. +%% The delegate module sends functions across the cluster; if it is +%% out of sync (say due to mixed compilers), we will get badfun +%% exceptions when trying to do so. Let's detect that at startup. delegate_beam_hash() -> DelegateBeamLocation = code:which(delegate), {ok, {delegate, Hash}} = beam_lib:md5(DelegateBeamLocation), -- cgit v1.2.1 From ace238a2125fc42a64cd227eb9833db1a6785361 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 17 Apr 2013 11:16:28 +0100 Subject: Magic call handler to debug the state. --- src/gen_server2.erl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 9109febd..84536cb6 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -883,6 +883,13 @@ common_become(_Name, _Mod, _NState, [] = _Debug) -> common_become(Name, Mod, NState, Debug) -> sys:handle_debug(Debug, fun print_event/3, Name, {become, Mod, NState}). +handle_msg({'$gen_call', From, {debug_state, Fun}}, + GS2State = #gs2_state{state = State, + name = Name, + debug = Debug}) -> + Debug1 = common_reply(Name, From, catch Fun(State), State, Debug), + loop(GS2State #gs2_state { state = State, + debug = Debug1 }); handle_msg({'$gen_call', From, Msg}, GS2State = #gs2_state { mod = Mod, state = State, name = Name, -- cgit v1.2.1 From 4f58d9a56ff2f72f5524b898a079cc6ee8041acf Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 17 Apr 2013 12:08:05 +0100 Subject: Remove exchange_not_durable as, err, it would never be returned. And strip leading exchange_ since this is rabbit_exchange. --- src/rabbit_exchange.erl | 7 +++---- src/rabbit_policy.erl | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl index 1bed9344..b4bdd348 100644 --- a/src/rabbit_exchange.erl +++ b/src/rabbit_exchange.erl @@ -69,8 +69,7 @@ -spec(update/2 :: (name(), fun((rabbit_types:exchange()) -> rabbit_types:exchange())) - -> exchange_not_found | exchange_not_durable | - rabbit_types:exchange()). + -> not_found | rabbit_types:exchange()). -spec(info_keys/0 :: () -> rabbit_types:info_keys()). -spec(info/1 :: (rabbit_types:exchange()) -> rabbit_types:infos()). -spec(info/2 :: @@ -290,11 +289,11 @@ update(Name, Fun) -> ok = mnesia:write(rabbit_exchange, X1, write), case Durable of true -> ok = mnesia:write(rabbit_durable_exchange, X1, write); - _ -> exchange_not_durable + _ -> ok end, X1; [] -> - exchange_not_found + not_found end. info_keys() -> ?INFO_KEYS. diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl index ca2837f5..0990c662 100644 --- a/src/rabbit_policy.erl +++ b/src/rabbit_policy.erl @@ -176,9 +176,8 @@ update_exchange(X = #exchange{name = XName, policy = OldPolicy}, Policies) -> rabbit_exchange_decorator:set( X0 #exchange{policy = NewPolicy}) end) of - #exchange{} = X1 -> {X, X1}; - exchange_not_found -> {X, X }; - exchange_not_durable -> {X, X } + #exchange{} = X1 -> {X, X1}; + not_found -> {X, X } end end. -- cgit v1.2.1 From 08477ab54edd3b4652fd4d8abd84a2ac3f0bf3ba Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 17 Apr 2013 12:33:40 +0100 Subject: Cosmetic --- src/rabbit_exchange_decorator.erl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/rabbit_exchange_decorator.erl b/src/rabbit_exchange_decorator.erl index 79ea212f..3abaa48c 100644 --- a/src/rabbit_exchange_decorator.erl +++ b/src/rabbit_exchange_decorator.erl @@ -93,13 +93,12 @@ filter(Modules) -> [M || M <- Modules, code:which(M) =/= non_existing]. set(X) -> - X#exchange{ - decorators = - lists:foldl(fun (D, {Route, NoRoute}) -> - Callbacks = D:active_for(X), - {cons_if_eq(all, Callbacks, D, Route), - cons_if_eq(noroute, Callbacks, D, NoRoute)} - end, {[], []}, list())}. + Decs = lists:foldl(fun (D, {Route, NoRoute}) -> + ActiveFor = D:active_for(X), + {cons_if_eq(all, ActiveFor, D, Route), + cons_if_eq(noroute, ActiveFor, D, NoRoute)} + end, {[], []}, list()), + X#exchange{decorators = Decs}. list() -> [M || {_, M} <- rabbit_registry:lookup_all(exchange_decorator)]. -- cgit v1.2.1 From c3291ec23ade84322093c7f67ca17f14353c9ca6 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 17 Apr 2013 16:26:34 +0100 Subject: First pass at splitting all the autoheal stuff out into a separate module. --- src/rabbit_autoheal.erl | 208 ++++++++++++++++++++++++++++++++++++++++++++ src/rabbit_node_monitor.erl | 203 ++++-------------------------------------- 2 files changed, 223 insertions(+), 188 deletions(-) create mode 100644 src/rabbit_autoheal.erl diff --git a/src/rabbit_autoheal.erl b/src/rabbit_autoheal.erl new file mode 100644 index 00000000..4a6307fc --- /dev/null +++ b/src/rabbit_autoheal.erl @@ -0,0 +1,208 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developer of the Original Code is VMware, Inc. +%% Copyright (c) 2007-2013 VMware, Inc. All rights reserved. +%% + +-module(rabbit_autoheal). + +-export([init/0, maybe_start/1, node_down/2, handle_msg/3]). + +%% The named process we are running in. +-define(SERVER, rabbit_node_monitor). + +%%---------------------------------------------------------------------------- + +%% In order to autoheal we want to: +%% +%% * Find the winning partition +%% * Stop all nodes in other partitions +%% * Wait for them all to be stopped +%% * Start them again +%% +%% To keep things simple, we assume all nodes are up. We don't start +%% unless all nodes are up, and if a node goes down we abandon the +%% whole process. To further keep things simple we also defer the +%% decision as to the winning node to the "leader" - arbitrarily +%% selected as the first node in the cluster. +%% +%% To coordinate the restarting nodes we pick a special node from the +%% winning partition - the "winner". Restarting nodes then stop, tell +%% the winner they have done so, and wait for it to tell them it is +%% safe to start again. +%% +%% The winner and the leader are not necessarily the same node! Since +%% the leader may end up restarting, we also make sure that it does +%% not announce its decision (and thus cue other nodes to restart) +%% until it has seen a request from every node that has experienced a +%% partition. + +%%---------------------------------------------------------------------------- + +init() -> not_healing. + +maybe_start(not_healing) -> + case enabled() andalso rabbit_node_monitor:all_nodes_up() of + true -> [Leader | _] = lists:usort(rabbit_mnesia:cluster_nodes(all)), + rabbit_log:info("Autoheal: leader is ~p~n", [Leader]), + send(Leader, {request_winner, node()}), + case node() of + Leader -> not_healing; + _ -> wait_for_winner + end; + false -> not_healing + end; +maybe_start(State) -> + State. + +enabled() -> + {ok, autoheal} =:= application:get_env(rabbit, cluster_partition_handling). + +node_down(_Node, {wait_for, _Nodes, _Notify} = Autoheal) -> + Autoheal; +node_down(_Node, not_healing) -> + not_healing; +node_down(Node, _State) -> + rabbit_log:info("Autoheal: aborting - ~p went down~n", [Node]), + not_healing. + +handle_msg({request_winner, Node}, + not_healing, Partitions) -> + case rabbit_node_monitor:all_nodes_up() of + false -> not_healing; + true -> Nodes = rabbit_mnesia:cluster_nodes(all), + Partitioned = + [N || N <- Nodes -- [node()], + P <- [begin + {_, R} = rpc:call(N, rabbit_node_monitor, + partitions, []), + R + end], + is_list(P) andalso length(P) > 0], + Partitioned1 = case Partitions of + [] -> Partitioned; + _ -> [node() | Partitioned] + end, + rabbit_log:info( + "Autoheal leader start; partitioned nodes are ~p~n", + [Partitioned1]), + handle_msg({request_winner, Node}, + {wait_for_winner_reqs, Partitioned1, Partitioned1}, + Partitions) + end; + +handle_msg({request_winner, Node}, + {wait_for_winner_reqs, [Node], Notify}, Partitions) -> + AllPartitions = all_partitions(Partitions), + Winner = select_winner(AllPartitions), + rabbit_log:info("Autoheal request winner from ~p~n" + " Partitions were determined to be ~p~n" + " Winner is ~p~n", [Node, AllPartitions, Winner]), + [send(N, {winner, Winner}) || N <- Notify], + wait_for_winner; + +handle_msg({request_winner, Node}, + {wait_for_winner_reqs, Nodes, Notify}, _Partitions) -> + rabbit_log:info("Autoheal request winner from ~p~n", [Node]), + {wait_for_winner_reqs, Nodes -- [Node], Notify}; + +handle_msg({winner, Winner}, + wait_for_winner, Partitions) -> + case lists:member(Winner, Partitions) of + false -> case node() of + Winner -> rabbit_log:info( + "Autoheal: waiting for nodes to stop: ~p~n", + [Partitions]), + {wait_for, Partitions, Partitions}; + _ -> rabbit_log:info( + "Autoheal: nothing to do~n", []), + not_healing + end; + true -> restart_me(Winner), + restarting + end; + +handle_msg({winner, _Winner}, State, _Partitions) -> + %% ignore, we already cancelled the autoheal process + State; + +handle_msg({node_stopped, Node}, + {wait_for, [Node], Notify}, _Partitions) -> + rabbit_log:info("Autoheal: final node has stopped, starting...~n",[]), + [{rabbit_outside_app_process, N} ! autoheal_safe_to_start || N <- Notify], + not_healing; + +handle_msg({node_stopped, Node}, + {wait_for, WaitFor, Notify}, _Partitions) -> + {wait_for, WaitFor -- [Node], Notify}; + +handle_msg({node_stopped, _Node}, State, _Partitions) -> + %% ignore, we already cancelled the autoheal process + State. + +%%---------------------------------------------------------------------------- + +send(Node, Msg) -> {?SERVER, Node} ! {autoheal_msg, Msg}. + +select_winner(AllPartitions) -> + {_, [Winner | _]} = + hd(lists:reverse( + lists:sort([{partition_value(P), P} || P <- AllPartitions]))), + Winner. + +partition_value(Partition) -> + Connections = [Res || Node <- Partition, + Res <- [rpc:call(Node, rabbit_networking, + connections_local, [])], + is_list(Res)], + {length(lists:append(Connections)), length(Partition)}. + +restart_me(Winner) -> + rabbit_log:warning( + "Autoheal: we were selected to restart; winner is ~p~n", [Winner]), + rabbit_node_monitor:run_outside_applications( + fun () -> + MRef = erlang:monitor(process, {?SERVER, Winner}), + rabbit:stop(), + send(Winner, {node_stopped, node()}), + receive + {'DOWN', MRef, process, {?SERVER, Winner}, _Reason} -> ok; + autoheal_safe_to_start -> ok + end, + erlang:demonitor(MRef, [flush]), + rabbit:start() + end). + +%% We have our local understanding of what partitions exist; but we +%% only know which nodes we have been partitioned from, not which +%% nodes are partitioned from each other. +all_partitions(PartitionedWith) -> + Nodes = rabbit_mnesia:cluster_nodes(all), + Partitions = [{node(), PartitionedWith} | + [rpc:call(Node, rabbit_node_monitor, partitions, []) + || Node <- Nodes -- [node()]]], + all_partitions(Partitions, [Nodes]). + +all_partitions([], Partitions) -> + Partitions; +all_partitions([{Node, CantSee} | Rest], Partitions) -> + {[Containing], Others} = + lists:partition(fun (Part) -> lists:member(Node, Part) end, Partitions), + A = Containing -- CantSee, + B = Containing -- A, + Partitions1 = case {A, B} of + {[], _} -> Partitions; + {_, []} -> Partitions; + _ -> [A, B | Others] + end, + all_partitions(Rest, Partitions1). diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 2cdde14e..61df9f66 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -30,6 +30,9 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + %% Utils +-export([all_nodes_up/0, run_outside_applications/1]). + -define(SERVER, ?MODULE). -define(RABBIT_UP_RPC_TIMEOUT, 2000). -define(RABBIT_DOWN_PING_INTERVAL, 1000). @@ -198,7 +201,7 @@ init([]) -> {ok, #state{monitors = pmon:new(), subscribers = pmon:new(), partitions = [], - autoheal = not_healing}}. + autoheal = rabbit_autoheal:init()}}. handle_call(partitions, _From, State = #state{partitions = Partitions}) -> {reply, {node(), Partitions}, State}; @@ -262,7 +265,8 @@ handle_info({'DOWN', _MRef, process, Pid, _Reason}, handle_info({mnesia_system_event, {inconsistent_database, running_partitioned_network, Node}}, State = #state{partitions = Partitions, - monitors = Monitors}) -> + monitors = Monitors, + autoheal = AState}) -> %% We will not get a node_up from this node - yet we should treat it as %% up (mostly). State1 = case pmon:is_monitored({rabbit, Node}, Monitors) of @@ -271,96 +275,15 @@ handle_info({mnesia_system_event, monitors = pmon:monitor({rabbit, Node}, Monitors)} end, ok = handle_live_rabbit(Node), - State2 = case application:get_env(rabbit, cluster_partition_handling) of - {ok, autoheal} -> case all_nodes_up() of - true -> autoheal(State1); - false -> State1 - end; - _ -> State1 - end, Partitions1 = ordsets:to_list( ordsets:add_element(Node, ordsets:from_list(Partitions))), - {noreply, State2#state{partitions = Partitions1}}; - -handle_info({autoheal_request_winner, Node}, - State = #state{autoheal = not_healing, - partitions = Partitions}) -> - case all_nodes_up() of - false -> {noreply, State}; - true -> Nodes = rabbit_mnesia:cluster_nodes(all), - Partitioned = - [N || N <- Nodes -- [node()], - P <- [begin - {_, R} = rpc:call(N, rabbit_node_monitor, - partitions, []), - R - end], - is_list(P) andalso length(P) > 0], - Partitioned1 = case Partitions of - [] -> Partitioned; - _ -> [node() | Partitioned] - end, - rabbit_log:info( - "Autoheal leader start; partitioned nodes are ~p~n", - [Partitioned1]), - Autoheal = {wait_for_winner_reqs, Partitioned1, Partitioned1}, - handle_info({autoheal_request_winner, Node}, - State#state{autoheal = Autoheal}) - end; - -handle_info({autoheal_request_winner, Node}, - State = #state{autoheal = {wait_for_winner_reqs, [Node], Notify}, - partitions = Partitions}) -> - AllPartitions = all_partitions(Partitions), - Winner = autoheal_select_winner(AllPartitions), - rabbit_log:info("Autoheal request winner from ~p~n" - " Partitions were determined to be ~p~n" - " Winner is ~p~n", [Node, AllPartitions, Winner]), - [{?MODULE, N} ! {autoheal_winner, Winner} || N <- Notify], - {noreply, State#state{autoheal = wait_for_winner}}; - -handle_info({autoheal_request_winner, Node}, - State = #state{autoheal = {wait_for_winner_reqs, Nodes, Notify}}) -> - rabbit_log:info("Autoheal request winner from ~p~n", [Node]), - {noreply, State#state{autoheal = {wait_for_winner_reqs, - Nodes -- [Node], Notify}}}; - -handle_info({autoheal_winner, Winner}, - State = #state{autoheal = wait_for_winner, - partitions = Partitions}) -> - case lists:member(Winner, Partitions) of - false -> case node() of - Winner -> rabbit_log:info( - "Autoheal: waiting for nodes to stop: ~p~n", - [Partitions]), - {noreply, - State#state{autoheal = {wait_for, Partitions, - Partitions}}}; - _ -> rabbit_log:info( - "Autoheal: nothing to do~n", []), - {noreply, State#state{autoheal = not_healing}} - end; - true -> autoheal_restart(Winner), - {noreply, State} - end; - -handle_info({autoheal_winner, _Winner}, State) -> - %% ignore, we already cancelled the autoheal process - {noreply, State}; - -handle_info({autoheal_node_stopped, Node}, - State = #state{autoheal = {wait_for, [Node], Notify}}) -> - rabbit_log:info("Autoheal: final node has stopped, starting...~n",[]), - [{rabbit_outside_app_process, N} ! autoheal_safe_to_start || N <- Notify], - {noreply, State#state{autoheal = not_healing}}; - -handle_info({autoheal_node_stopped, Node}, - State = #state{autoheal = {wait_for, WaitFor, Notify}}) -> - {noreply, State#state{autoheal = {wait_for, WaitFor -- [Node], Notify}}}; + {noreply, State1#state{partitions = Partitions1, + autoheal = rabbit_autoheal:maybe_start(AState)}}; -handle_info({autoheal_node_stopped, _Node}, State) -> - %% ignore, we already cancelled the autoheal process - {noreply, State}; +handle_info({autoheal_msg, Msg}, State = #state{autoheal = AState, + partitions = Partitions}) -> + AState1 = rabbit_autoheal:handle_msg(Msg, AState, Partitions), + {noreply, State#state{autoheal = AState1}}; handle_info(ping_nodes, State) -> %% We ping nodes when some are down to ensure that we find out @@ -469,93 +392,6 @@ wait_for_cluster_recovery(Nodes) -> wait_for_cluster_recovery(Nodes) end. -%% In order to autoheal we want to: -%% -%% * Find the winning partition -%% * Stop all nodes in other partitions -%% * Wait for them all to be stopped -%% * Start them again -%% -%% To keep things simple, we assume all nodes are up. We don't start -%% unless all nodes are up, and if a node goes down we abandon the -%% whole process. To further keep things simple we also defer the -%% decision as to the winning node to the "leader" - arbitrarily -%% selected as the first node in the cluster. -%% -%% To coordinate the restarting nodes we pick a special node from the -%% winning partition - the "winner". Restarting nodes then stop, tell -%% the winner they have done so, and wait for it to tell them it is -%% safe to start again. -%% -%% The winner and the leader are not necessarily the same node! Since -%% the leader may end up restarting, we also make sure that it does -%% not announce its decision (and thus cue other nodes to restart) -%% until it has seen a request from every node that has experienced a -%% partition. -autoheal(State = #state{autoheal = not_healing}) -> - [Leader | _] = lists:usort(rabbit_mnesia:cluster_nodes(all)), - rabbit_log:info("Autoheal: leader is ~p~n", [Leader]), - {?MODULE, Leader} ! {autoheal_request_winner, node()}, - State#state{autoheal = case node() of - Leader -> not_healing; - _ -> wait_for_winner - end}; -autoheal(State) -> - State. - -autoheal_select_winner(AllPartitions) -> - {_, [Winner | _]} = - hd(lists:reverse( - lists:sort([{autoheal_value(P), P} || P <- AllPartitions]))), - Winner. - -autoheal_value(Partition) -> - Connections = [Res || Node <- Partition, - Res <- [rpc:call(Node, rabbit_networking, - connections_local, [])], - is_list(Res)], - {length(lists:append(Connections)), length(Partition)}. - -autoheal_restart(Winner) -> - rabbit_log:warning( - "Autoheal: we were selected to restart; winner is ~p~n", [Winner]), - run_outside_applications( - fun () -> - MRef = erlang:monitor(process, {?MODULE, Winner}), - rabbit:stop(), - {?MODULE, Winner} ! {autoheal_node_stopped, node()}, - receive - {'DOWN', MRef, process, {?MODULE, Winner}, _Reason} -> ok; - autoheal_safe_to_start -> ok - end, - erlang:demonitor(MRef, [flush]), - rabbit:start() - end). - -%% We have our local understanding of what partitions exist; but we -%% only know which nodes we have been partitioned from, not which -%% nodes are partitioned from each other. -all_partitions(PartitionedWith) -> - Nodes = rabbit_mnesia:cluster_nodes(all), - Partitions = [{node(), PartitionedWith} | - [rpc:call(Node, rabbit_node_monitor, partitions, []) - || Node <- Nodes -- [node()]]], - all_partitions(Partitions, [Nodes]). - -all_partitions([], Partitions) -> - Partitions; -all_partitions([{Node, CantSee} | Rest], Partitions) -> - {[Containing], Others} = - lists:partition(fun (Part) -> lists:member(Node, Part) end, Partitions), - A = Containing -- CantSee, - B = Containing -- A, - Partitions1 = case {A, B} of - {[], _} -> Partitions; - {_, []} -> Partitions; - _ -> [A, B | Others] - end, - all_partitions(Rest, Partitions1). - handle_dead_rabbit_state(Node, State = #state{partitions = Partitions, autoheal = Autoheal}) -> %% If we have been partitioned, and we are now in the only remaining @@ -567,18 +403,9 @@ handle_dead_rabbit_state(Node, State = #state{partitions = Partitions, [] -> []; _ -> Partitions end, - Autoheal1 = case Autoheal of - {wait_for, _Nodes, _Notify} -> - Autoheal; - not_healing -> - not_healing; - _ -> - rabbit_log:info( - "Autoheal: aborting - ~p went down~n", [Node]), - not_healing - end, - ensure_ping_timer(State#state{partitions = Partitions1, - autoheal = Autoheal1}). + ensure_ping_timer( + State#state{partitions = Partitions1, + autoheal = rabbit_autoheal:node_down(Node, Autoheal)}). ensure_ping_timer(State) -> rabbit_misc:ensure_timer( -- cgit v1.2.1 From adf6677168f7ecdf92020cddb5d702a7b5620bf8 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 17 Apr 2013 16:55:39 +0100 Subject: Rename states to hopefully be clearer; add more comments. --- src/rabbit_autoheal.erl | 54 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/rabbit_autoheal.erl b/src/rabbit_autoheal.erl index 4a6307fc..fc3ca1e8 100644 --- a/src/rabbit_autoheal.erl +++ b/src/rabbit_autoheal.erl @@ -46,6 +46,28 @@ %% not announce its decision (and thus cue other nodes to restart) %% until it has seen a request from every node that has experienced a %% partition. +%% +%% Possible states: +%% +%% not_healing +%% - the default +%% +%% {leader_wait_for_winner_requests, OutstandingRequests, Notify} +%% - we are the leader and are waiting to hear requests from all +%% other partitioned nodes +%% +%% wait_for_winner +%% - we are not the leader and are waiting to see what it has to say +%% +%% {winner_wait_for_stops, OutstandingStops, Notify} +%% - we are the winner and are waiting for all losing nodes to stop +%% before telling them they can restart +%% +%% restarting +%% - we are restarting. Of course the node monitor immediately dies +%% then so this state does not last long. We therefore send the +%% autoheal_safe_to_start message to the rabbit_outside_app_process +%% instead. %%---------------------------------------------------------------------------- @@ -68,7 +90,7 @@ maybe_start(State) -> enabled() -> {ok, autoheal} =:= application:get_env(rabbit, cluster_partition_handling). -node_down(_Node, {wait_for, _Nodes, _Notify} = Autoheal) -> +node_down(_Node, {winner_wait_for_stops, _Nodes, _Notify} = Autoheal) -> Autoheal; node_down(_Node, not_healing) -> not_healing; @@ -76,6 +98,7 @@ node_down(Node, _State) -> rabbit_log:info("Autoheal: aborting - ~p went down~n", [Node]), not_healing. +%% By receiving this message we become the leader handle_msg({request_winner, Node}, not_healing, Partitions) -> case rabbit_node_monitor:all_nodes_up() of @@ -97,33 +120,38 @@ handle_msg({request_winner, Node}, "Autoheal leader start; partitioned nodes are ~p~n", [Partitioned1]), handle_msg({request_winner, Node}, - {wait_for_winner_reqs, Partitioned1, Partitioned1}, + {leader_wait_for_winner_requests, + Partitioned1, Partitioned1}, Partitions) end; +%% This is the leader receiving its last winner request - all +%% partitioned nodes have checked in handle_msg({request_winner, Node}, - {wait_for_winner_reqs, [Node], Notify}, Partitions) -> + {leader_wait_for_winner_requests, [Node], Notify}, Partitions) -> AllPartitions = all_partitions(Partitions), Winner = select_winner(AllPartitions), rabbit_log:info("Autoheal request winner from ~p~n" " Partitions were determined to be ~p~n" " Winner is ~p~n", [Node, AllPartitions, Winner]), - [send(N, {winner, Winner}) || N <- Notify], + [send(N, {winner_is, Winner}) || N <- Notify], wait_for_winner; +%% This is the leader receiving any other winner request handle_msg({request_winner, Node}, - {wait_for_winner_reqs, Nodes, Notify}, _Partitions) -> + {leader_wait_for_winner_requests, Nodes, Notify}, _Partitions) -> rabbit_log:info("Autoheal request winner from ~p~n", [Node]), - {wait_for_winner_reqs, Nodes -- [Node], Notify}; + {leader_wait_for_winner_requests, Nodes -- [Node], Notify}; -handle_msg({winner, Winner}, +handle_msg({winner_is, Winner}, wait_for_winner, Partitions) -> case lists:member(Winner, Partitions) of false -> case node() of Winner -> rabbit_log:info( "Autoheal: waiting for nodes to stop: ~p~n", [Partitions]), - {wait_for, Partitions, Partitions}; + {winner_wait_for_stops, + Partitions, Partitions}; _ -> rabbit_log:info( "Autoheal: nothing to do~n", []), not_healing @@ -132,19 +160,21 @@ handle_msg({winner, Winner}, restarting end; -handle_msg({winner, _Winner}, State, _Partitions) -> +handle_msg({winner_is, _Winner}, State, _Partitions) -> %% ignore, we already cancelled the autoheal process State; +%% This is the winner receiving its last notification that a node has +%% stopped - all nodes can now start again handle_msg({node_stopped, Node}, - {wait_for, [Node], Notify}, _Partitions) -> + {winner_wait_for_stops, [Node], Notify}, _Partitions) -> rabbit_log:info("Autoheal: final node has stopped, starting...~n",[]), [{rabbit_outside_app_process, N} ! autoheal_safe_to_start || N <- Notify], not_healing; handle_msg({node_stopped, Node}, - {wait_for, WaitFor, Notify}, _Partitions) -> - {wait_for, WaitFor -- [Node], Notify}; + {winner_wait_for_stops, WaitFor, Notify}, _Partitions) -> + {winner_wait_for_stops, WaitFor -- [Node], Notify}; handle_msg({node_stopped, _Node}, State, _Partitions) -> %% ignore, we already cancelled the autoheal process -- cgit v1.2.1 From 10557c85d7635e923511f5be47058637a5693a32 Mon Sep 17 00:00:00 2001 From: Emile Joubert Date: Thu, 18 Apr 2013 10:30:54 +0100 Subject: Obtain beam hash reliably --- src/rabbit_mnesia.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 95182a7c..8cd976fa 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -797,8 +797,8 @@ check_beam_compatibility(RemoteHash) -> %% out of sync (say due to mixed compilers), we will get badfun %% exceptions when trying to do so. Let's detect that at startup. delegate_beam_hash() -> - DelegateBeamLocation = code:which(delegate), - {ok, {delegate, Hash}} = beam_lib:md5(DelegateBeamLocation), + {delegate, Obj, _} = code:get_object_code(delegate), + {ok, {delegate, Hash}} = beam_lib:md5(Obj), Hash. %% This is fairly tricky. We want to know if the node is in the state -- cgit v1.2.1 From aea1d035dc499f3f4d4bc13ea53ab23186ef8316 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 18 Apr 2013 15:14:15 +0100 Subject: Have the leader decide what to do and then just tell other nodes (rather than have them request a winner). Substantially more reliable and shorter than previously. --- src/rabbit_autoheal.erl | 155 ++++++++++++++++++------------------------------ 1 file changed, 58 insertions(+), 97 deletions(-) diff --git a/src/rabbit_autoheal.erl b/src/rabbit_autoheal.erl index fc3ca1e8..82f26634 100644 --- a/src/rabbit_autoheal.erl +++ b/src/rabbit_autoheal.erl @@ -52,14 +52,7 @@ %% not_healing %% - the default %% -%% {leader_wait_for_winner_requests, OutstandingRequests, Notify} -%% - we are the leader and are waiting to hear requests from all -%% other partitioned nodes -%% -%% wait_for_winner -%% - we are not the leader and are waiting to see what it has to say -%% -%% {winner_wait_for_stops, OutstandingStops, Notify} +%% {winner_waiting, OutstandingStops, Notify} %% - we are the winner and are waiting for all losing nodes to stop %% before telling them they can restart %% @@ -74,14 +67,11 @@ init() -> not_healing. maybe_start(not_healing) -> - case enabled() andalso rabbit_node_monitor:all_nodes_up() of + case enabled() of true -> [Leader | _] = lists:usort(rabbit_mnesia:cluster_nodes(all)), - rabbit_log:info("Autoheal: leader is ~p~n", [Leader]), - send(Leader, {request_winner, node()}), - case node() of - Leader -> not_healing; - _ -> wait_for_winner - end; + send(Leader, {request_start, node()}), + rabbit_log:info("Autoheal request sent to ~p~n", [Leader]), + not_healing; false -> not_healing end; maybe_start(State) -> @@ -90,7 +80,7 @@ maybe_start(State) -> enabled() -> {ok, autoheal} =:= application:get_env(rabbit, cluster_partition_handling). -node_down(_Node, {winner_wait_for_stops, _Nodes, _Notify} = Autoheal) -> +node_down(_Node, {winner_waiting, _Nodes, _Notify} = Autoheal) -> Autoheal; node_down(_Node, not_healing) -> not_healing; @@ -99,82 +89,70 @@ node_down(Node, _State) -> not_healing. %% By receiving this message we become the leader -handle_msg({request_winner, Node}, +%% TODO should we try to debounce this? +handle_msg({request_start, Node}, not_healing, Partitions) -> + rabbit_log:info("Autoheal request received from ~p~n", [Node]), case rabbit_node_monitor:all_nodes_up() of false -> not_healing; - true -> Nodes = rabbit_mnesia:cluster_nodes(all), - Partitioned = - [N || N <- Nodes -- [node()], - P <- [begin - {_, R} = rpc:call(N, rabbit_node_monitor, - partitions, []), - R - end], - is_list(P) andalso length(P) > 0], - Partitioned1 = case Partitions of - [] -> Partitioned; - _ -> [node() | Partitioned] - end, - rabbit_log:info( - "Autoheal leader start; partitioned nodes are ~p~n", - [Partitioned1]), - handle_msg({request_winner, Node}, - {leader_wait_for_winner_requests, - Partitioned1, Partitioned1}, - Partitions) + true -> AllPartitions = all_partitions(Partitions), + {Winner, Losers} = make_decision(AllPartitions), + rabbit_log:info("Autoheal decision~n" + " * Partitions: ~p~n" + " * Winner: ~p~n" + " * Losers: ~p~n", + [AllPartitions, Winner, Losers]), + send(Winner, {become_winner, Losers}), + [send(L, {winner_is, Winner}) || L <- Losers], + not_healing end; -%% This is the leader receiving its last winner request - all -%% partitioned nodes have checked in -handle_msg({request_winner, Node}, - {leader_wait_for_winner_requests, [Node], Notify}, Partitions) -> - AllPartitions = all_partitions(Partitions), - Winner = select_winner(AllPartitions), - rabbit_log:info("Autoheal request winner from ~p~n" - " Partitions were determined to be ~p~n" - " Winner is ~p~n", [Node, AllPartitions, Winner]), - [send(N, {winner_is, Winner}) || N <- Notify], - wait_for_winner; - -%% This is the leader receiving any other winner request -handle_msg({request_winner, Node}, - {leader_wait_for_winner_requests, Nodes, Notify}, _Partitions) -> - rabbit_log:info("Autoheal request winner from ~p~n", [Node]), - {leader_wait_for_winner_requests, Nodes -- [Node], Notify}; +handle_msg({become_winner, Losers}, + not_healing, _Partitions) -> + rabbit_log:info("Autoheal: I am the winner, waiting for ~p to stop~n", + [Losers]), + {winner_waiting, Losers, Losers}; -handle_msg({winner_is, Winner}, - wait_for_winner, Partitions) -> - case lists:member(Winner, Partitions) of - false -> case node() of - Winner -> rabbit_log:info( - "Autoheal: waiting for nodes to stop: ~p~n", - [Partitions]), - {winner_wait_for_stops, - Partitions, Partitions}; - _ -> rabbit_log:info( - "Autoheal: nothing to do~n", []), - not_healing - end; - true -> restart_me(Winner), - restarting - end; +handle_msg({become_winner, Losers}, + {winner_waiting, WaitFor, Notify}, _Partitions) -> + rabbit_log:info("Autoheal: I am the winner, waiting additionally for " + "~p to stop~n", [Losers]), + {winner_waiting, lists:usort(Losers ++ WaitFor), + lists:usort(Losers ++ Notify)}; -handle_msg({winner_is, _Winner}, State, _Partitions) -> - %% ignore, we already cancelled the autoheal process - State; +handle_msg({winner_is, Winner}, + not_healing, _Partitions) -> + rabbit_log:warning( + "Autoheal: we were selected to restart; winner is ~p~n", [Winner]), + rabbit_node_monitor:run_outside_applications( + fun () -> + MRef = erlang:monitor(process, {?SERVER, Winner}), + rabbit:stop(), + send(Winner, {node_stopped, node()}), + receive + {'DOWN', MRef, process, {?SERVER, Winner}, _Reason} -> ok; + autoheal_safe_to_start -> ok + end, + erlang:demonitor(MRef, [flush]), + rabbit:start() + end), + restarting; %% This is the winner receiving its last notification that a node has %% stopped - all nodes can now start again handle_msg({node_stopped, Node}, - {winner_wait_for_stops, [Node], Notify}, _Partitions) -> + {winner_waiting, [Node], Notify}, _Partitions) -> rabbit_log:info("Autoheal: final node has stopped, starting...~n",[]), [{rabbit_outside_app_process, N} ! autoheal_safe_to_start || N <- Notify], not_healing; handle_msg({node_stopped, Node}, - {winner_wait_for_stops, WaitFor, Notify}, _Partitions) -> - {winner_wait_for_stops, WaitFor -- [Node], Notify}; + {winner_waiting, WaitFor, Notify}, _Partitions) -> + {winner_waiting, WaitFor -- [Node], Notify}; + +handle_msg(_, restarting, _Partitions) -> + %% ignore, we can contribute no further + restarting; handle_msg({node_stopped, _Node}, State, _Partitions) -> %% ignore, we already cancelled the autoheal process @@ -184,11 +162,10 @@ handle_msg({node_stopped, _Node}, State, _Partitions) -> send(Node, Msg) -> {?SERVER, Node} ! {autoheal_msg, Msg}. -select_winner(AllPartitions) -> - {_, [Winner | _]} = - hd(lists:reverse( - lists:sort([{partition_value(P), P} || P <- AllPartitions]))), - Winner. +make_decision(AllPartitions) -> + Sorted = lists:sort([{partition_value(P), P} || P <- AllPartitions]), + [[Winner | _] | Rest] = lists:reverse([P || {_, P} <- Sorted]), + {Winner, lists:append(Rest)}. partition_value(Partition) -> Connections = [Res || Node <- Partition, @@ -197,22 +174,6 @@ partition_value(Partition) -> is_list(Res)], {length(lists:append(Connections)), length(Partition)}. -restart_me(Winner) -> - rabbit_log:warning( - "Autoheal: we were selected to restart; winner is ~p~n", [Winner]), - rabbit_node_monitor:run_outside_applications( - fun () -> - MRef = erlang:monitor(process, {?SERVER, Winner}), - rabbit:stop(), - send(Winner, {node_stopped, node()}), - receive - {'DOWN', MRef, process, {?SERVER, Winner}, _Reason} -> ok; - autoheal_safe_to_start -> ok - end, - erlang:demonitor(MRef, [flush]), - rabbit:start() - end). - %% We have our local understanding of what partitions exist; but we %% only know which nodes we have been partitioned from, not which %% nodes are partitioned from each other. -- cgit v1.2.1 From 6435a6cbcf9ad1782fbef8eec9daab9ecc3d8eef Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 18 Apr 2013 15:18:07 +0100 Subject: I suppose it would be polite to add specs here. --- src/rabbit_node_monitor.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 61df9f66..2d237020 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -60,6 +60,9 @@ -spec(partitions/0 :: () -> {node(), [node()]}). -spec(subscribe/1 :: (pid()) -> 'ok'). +-spec(all_nodes_up/0 :: () -> boolean()). +-spec(run_outside_applications/1 :: (fun (() -> any())) -> pid()). + -endif. %%---------------------------------------------------------------------------- -- cgit v1.2.1 From 30421a90bf7a55e4be3c9cece04da44723fed70f Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Thu, 18 Apr 2013 17:30:44 +0100 Subject: Less magic, more essay. --- src/gen_server2.erl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 84536cb6..ec67c04f 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -75,6 +75,12 @@ %% format_message_queue/2 which is the equivalent of format_status/2 %% but where the second argument is specifically the priority_queue %% which contains the prioritised message_queue. +%% +%% 9) The function with_state/2 can be used to debug a process with +%% heavyweight state (without needing to copy the entire state out of +%% process as sys:get_status/1 would). Pass through a function which +%% can be invoked on the state, get back the result. The state is not +%% modified. %% All modifications are (C) 2009-2013 VMware, Inc. @@ -180,7 +186,7 @@ %% API -export([start/3, start/4, start_link/3, start_link/4, - call/2, call/3, + call/2, call/3, with_state/2, cast/2, reply/2, abcast/2, abcast/3, multi_call/2, multi_call/3, multi_call/4, @@ -319,6 +325,14 @@ call(Name, Request, Timeout) -> exit({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end. +with_state(Name, Fun) -> + case catch gen:call(Name, '$with_state', Fun, infinity) of + {ok,Res} -> + Res; + {'EXIT',Reason} -> + exit({Reason, {?MODULE, with_state, [Name, Fun]}}) + end. + %% ----------------------------------------------------------------- %% Make a cast to a generic server. %% ----------------------------------------------------------------- @@ -645,6 +659,8 @@ in({'$gen_cast', Msg} = Input, in({'$gen_call', From, Msg} = Input, GS2State = #gs2_state { prioritisers = {F, _, _} }) -> in(Input, F(Msg, From, GS2State), GS2State); +in({'$with_state', _From, _Fun} = Input, GS2State) -> + in(Input, 0, GS2State); in({'EXIT', Parent, _R} = Input, GS2State = #gs2_state { parent = Parent }) -> in(Input, infinity, GS2State); in({system, _From, _Req} = Input, GS2State) -> @@ -883,7 +899,7 @@ common_become(_Name, _Mod, _NState, [] = _Debug) -> common_become(Name, Mod, NState, Debug) -> sys:handle_debug(Debug, fun print_event/3, Name, {become, Mod, NState}). -handle_msg({'$gen_call', From, {debug_state, Fun}}, +handle_msg({'$with_state', From, Fun}, GS2State = #gs2_state{state = State, name = Name, debug = Debug}) -> -- cgit v1.2.1 From 3d5cef2e53d73f62a795aca8d43df3654125c58d Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Apr 2013 20:44:53 +0100 Subject: cosmetic --- src/rabbit_vm.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index 3d3ef59b..d09b1c9d 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -140,9 +140,9 @@ is_plugin("rabbitmq_" ++ _) -> true; is_plugin(App) -> lists:member(App, ?MAGIC_PLUGINS). %% TODO support Pids, not just Names - need this for plugins -sum_processes(Names, Acc0) -> +sum_processes(Names, Items) -> sum_processes(Names, fun (_, X, Y) -> X + Y end, - [{Item, 0} || Item <- Acc0]). + [{Item, 0} || Item <- Items]). sum_processes(Names, Fun, Acc0) -> Items = [Item || {Item, _Val0} <- Acc0], -- cgit v1.2.1 From e827185233fc97579f50c917e5d3721b92f7c0a4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Apr 2013 20:45:44 +0100 Subject: include the named processes in the sums --- src/rabbit_vm.erl | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index d09b1c9d..f3508cae 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -149,30 +149,37 @@ sum_processes(Names, Fun, Acc0) -> Acc0Dict = orddict:from_list(Acc0), NameAccs0 = orddict:from_list([{Name, Acc0Dict} || Name <- Names]), {NameAccs, OtherAcc} = - lists:foldl(fun (Pid, Acc) -> - case process_info(Pid, [dictionary | Items]) of - undefined -> - Acc; - [{dictionary, D} | Vals] -> - ValsDict = orddict:from_list(Vals), - accumulate(find_ancestor(D, Names), Fun, - ValsDict, Acc) - end - end, {NameAccs0, Acc0Dict}, processes()), + lists:foldl( + fun (Pid, Acc) -> + InfoItems = [registered_name, dictionary | Items], + case process_info(Pid, InfoItems) of + undefined -> + Acc; + [{registered_name, RegName}, {dictionary, D} | Vals] -> + %% see docs for process_info/2 for the + %% special handling of 'registered_name' + %% info items + Extra = case RegName of + [] -> []; + N -> [N] + end, + accumulate(find_ancestor(Extra, D, Names), Fun, + orddict:from_list(Vals), Acc) + end + end, {NameAccs0, Acc0Dict}, processes()), %% these conversions aren't strictly necessary; we do them simply %% for the sake of encapsulating the representation. {[orddict:to_list(NameAcc) || NameAcc <- orddict:to_list(NameAccs)], orddict:to_list(OtherAcc)}. -find_ancestor(D, Names) -> - case lists:keyfind('$ancestors', 1, D) of - false -> undefined; - {_, Ancestors} -> case lists:splitwith( - fun (A) -> not lists:member(A, Names) end, - Ancestors) of - {_, []} -> undefined; - {_, [Name | _]} -> Name - end +find_ancestor(Extra, D, Names) -> + case lists:splitwith(fun (A) -> not lists:member(A, Names) end, + Extra ++ case lists:keyfind('$ancestors', 1, D) of + false -> []; + {_, Ancestors} -> Ancestors + end) of + {_, []} -> undefined; + {_, [Name | _]} -> Name end. accumulate(undefined, Fun, ValsDict, {NameAccs, OtherAcc}) -> -- cgit v1.2.1 From 7cf2a89beaecb43661247cd62adc5851e4cfda68 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Apr 2013 21:52:02 +0100 Subject: wire in sum_processes for all non-plugin process memory --- src/rabbit_vm.erl | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index f3508cae..16294ccd 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -33,17 +33,22 @@ %% Like erlang:memory(), but with awareness of rabbit-y things memory() -> - Conns = (sup_memory(rabbit_tcp_client_sup) + - sup_memory(ssl_connection_sup) + - sup_memory(amqp_sup)), - Qs = (sup_memory(rabbit_amqqueue_sup) + - sup_memory(rabbit_mirror_queue_slave_sup)), + ConnProcs = [rabbit_tcp_client_sup, ssl_connection_sup, amqp_sup], + QProcs = [rabbit_amqqueue_sup, rabbit_mirror_queue_slave_sup], + MsgIndexProcs = [msg_store_transient, msg_store_persistent], + MgmtDbProcs = [rabbit_mgmt_sup], + %% TODO: plug-ins + + All = [ConnProcs, QProcs, MsgIndexProcs, MgmtDbProcs], + + {Sums, _Other} = sum_processes(lists:append(All), [memory]), + + [Conns, Qs, MsgIndexProc, MgmtDbProc] = + [aggregate_memory(Names, Sums) || Names <- All], + Mnesia = mnesia_memory(), MsgIndexETS = ets_memory(rabbit_msg_store_ets_index), - MsgIndexProc = (pid_memory(msg_store_transient) + - pid_memory(msg_store_persistent)), MgmtDbETS = ets_memory(rabbit_mgmt_db), - MgmtDbProc = sup_memory(rabbit_mgmt_sup), Plugins = plugin_memory() - MgmtDbProc, [{total, Total}, @@ -55,6 +60,8 @@ memory() -> {system, System}] = erlang:memory([total, processes, ets, atom, binary, code, system]), + %% TODO: should we replace this with the value extracted from + %% 'Other'? OtherProc = Processes - Conns - Qs - MsgIndexProc - MgmtDbProc - Plugins, [{total, Total}, @@ -139,7 +146,16 @@ plugin_memory(App) -> is_plugin("rabbitmq_" ++ _) -> true; is_plugin(App) -> lists:member(App, ?MAGIC_PLUGINS). +aggregate_memory(Names, Sums) -> + lists:sum([extract_memory(Name, Sums) || Name <- Names]). + +extract_memory(Name, Sums) -> + {_, Accs} = lists:keyfind(Name, 1, Sums), + {memory, V} = lists:keyfind(memory, 1, Accs), + V. + %% TODO support Pids, not just Names - need this for plugins +%% NB: this code is non-rabbit specific sum_processes(Names, Items) -> sum_processes(Names, fun (_, X, Y) -> X + Y end, [{Item, 0} || Item <- Items]). -- cgit v1.2.1 From b36e166e1ada8c6337a58220afe55a4a47e953d6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Thu, 18 Apr 2013 22:39:53 +0100 Subject: deal with plug-ins ...and rip out the now unused sup traversal code --- src/rabbit_vm.erl | 64 +++++++++++++++++-------------------------------------- 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index 16294ccd..a772d9c4 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -37,19 +37,19 @@ memory() -> QProcs = [rabbit_amqqueue_sup, rabbit_mirror_queue_slave_sup], MsgIndexProcs = [msg_store_transient, msg_store_persistent], MgmtDbProcs = [rabbit_mgmt_sup], - %% TODO: plug-ins + PluginProcs = plugin_sups(), - All = [ConnProcs, QProcs, MsgIndexProcs, MgmtDbProcs], + All = [ConnProcs, QProcs, MsgIndexProcs, MgmtDbProcs, PluginProcs], {Sums, _Other} = sum_processes(lists:append(All), [memory]), - [Conns, Qs, MsgIndexProc, MgmtDbProc] = + [Conns, Qs, MsgIndexProc, MgmtDbProc, AllPlugins] = [aggregate_memory(Names, Sums) || Names <- All], Mnesia = mnesia_memory(), MsgIndexETS = ets_memory(rabbit_msg_store_ets_index), MgmtDbETS = ets_memory(rabbit_mgmt_db), - Plugins = plugin_memory() - MgmtDbProc, + Plugins = AllPlugins - MgmtDbProc, [{total, Total}, {processes, Processes}, @@ -62,7 +62,7 @@ memory() -> %% TODO: should we replace this with the value extracted from %% 'Other'? - OtherProc = Processes - Conns - Qs - MsgIndexProc - MgmtDbProc - Plugins, + OtherProc = Processes - Conns - Qs - MsgIndexProc - AllPlugins, [{total, Total}, {connection_procs, Conns}, @@ -85,35 +85,6 @@ memory() -> %%---------------------------------------------------------------------------- -sup_memory(Sup) -> - lists:sum([child_memory(P, T) || {_, P, T, _} <- sup_children(Sup)]) + - pid_memory(Sup). - -sup_children(Sup) -> - rabbit_misc:with_exit_handler( - rabbit_misc:const([]), - fun () -> - %% Just in case we end up talking to something that is - %% not a supervisor by mistake. - case supervisor:which_children(Sup) of - L when is_list(L) -> L; - _ -> [] - end - end). - -pid_memory(Pid) when is_pid(Pid) -> case process_info(Pid, memory) of - {memory, M} -> M; - _ -> 0 - end; -pid_memory(Name) when is_atom(Name) -> case whereis(Name) of - P when is_pid(P) -> pid_memory(P); - _ -> 0 - end. - -child_memory(Pid, worker) when is_pid (Pid) -> pid_memory(Pid); -child_memory(Pid, supervisor) when is_pid (Pid) -> sup_memory(Pid); -child_memory(_, _) -> 0. - mnesia_memory() -> case mnesia:system_info(is_running) of yes -> lists:sum([bytes(mnesia:table_info(Tab, memory)) || @@ -128,21 +99,27 @@ ets_memory(Name) -> bytes(Words) -> Words * erlang:system_info(wordsize). -plugin_memory() -> - lists:sum([plugin_memory(App) || - {App, _, _} <- application:which_applications(), - is_plugin(atom_to_list(App))]). +plugin_sups() -> + lists:append([plugin_sup(App) || + {App, _, _} <- application:which_applications(), + is_plugin(atom_to_list(App))]). -plugin_memory(App) -> +plugin_sup(App) -> case application_controller:get_master(App) of - undefined -> 0; + undefined -> []; Master -> case application_master:get_child(Master) of - {Pid, _} when is_pid(Pid) -> sup_memory(Pid); - Pid when is_pid(Pid) -> sup_memory(Pid); - _ -> 0 + {Pid, _} when is_pid(Pid) -> [process_name(Pid)]; + Pid when is_pid(Pid) -> [process_name(Pid)]; + _ -> [] end end. +process_name(Pid) -> + case process_info(Pid, registered_name) of + {registered_name, Name} -> Name; + _ -> Pid + end. + is_plugin("rabbitmq_" ++ _) -> true; is_plugin(App) -> lists:member(App, ?MAGIC_PLUGINS). @@ -154,7 +131,6 @@ extract_memory(Name, Sums) -> {memory, V} = lists:keyfind(memory, 1, Accs), V. -%% TODO support Pids, not just Names - need this for plugins %% NB: this code is non-rabbit specific sum_processes(Names, Items) -> sum_processes(Names, fun (_, X, Y) -> X + Y end, -- cgit v1.2.1 From 018d8b70943ce8455d96b716c2352e651562d252 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 19 Apr 2013 06:31:51 +0100 Subject: lists:keyfind/3 is not in R12B-3. --- src/rabbit_vm.erl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index a772d9c4..afd8921b 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -127,8 +127,8 @@ aggregate_memory(Names, Sums) -> lists:sum([extract_memory(Name, Sums) || Name <- Names]). extract_memory(Name, Sums) -> - {_, Accs} = lists:keyfind(Name, 1, Sums), - {memory, V} = lists:keyfind(memory, 1, Accs), + {value, {_, Accs}} = lists:keysearch(Name, 1, Sums), + {value, {memory, V}} = lists:keysearch(memory, 1, Accs), V. %% NB: this code is non-rabbit specific @@ -165,11 +165,12 @@ sum_processes(Names, Fun, Acc0) -> orddict:to_list(OtherAcc)}. find_ancestor(Extra, D, Names) -> + Ancestors = case lists:keysearch('$ancestors', 1, D) of + {value, {_, Ancs}} -> Ancs; + false -> [] + end, case lists:splitwith(fun (A) -> not lists:member(A, Names) end, - Extra ++ case lists:keyfind('$ancestors', 1, D) of - false -> []; - {_, Ancestors} -> Ancestors - end) of + Extra ++ Ancestors) of {_, []} -> undefined; {_, [Name | _]} -> Name end. -- cgit v1.2.1 From ec0cf0fc5c621500854125fd41631414192d4d53 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 19 Apr 2013 11:44:03 +0100 Subject: Move to near the other system message and simplify. --- src/gen_server2.erl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index ec67c04f..99fa272a 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -679,6 +679,10 @@ process_msg({system, From, Req}, %% gen_server puts Hib on the end as the 7th arg, but that version %% of the fun seems not to be documented so leaving out for now. sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, GS2State); +process_msg({'$with_state', From, Fun}, + GS2State = #gs2_state{state = State}) -> + reply(From, catch Fun(State)), + loop(GS2State); process_msg({'EXIT', Parent, Reason} = Msg, GS2State = #gs2_state { parent = Parent }) -> terminate(Reason, Msg, GS2State); @@ -899,13 +903,6 @@ common_become(_Name, _Mod, _NState, [] = _Debug) -> common_become(Name, Mod, NState, Debug) -> sys:handle_debug(Debug, fun print_event/3, Name, {become, Mod, NState}). -handle_msg({'$with_state', From, Fun}, - GS2State = #gs2_state{state = State, - name = Name, - debug = Debug}) -> - Debug1 = common_reply(Name, From, catch Fun(State), State, Debug), - loop(GS2State #gs2_state { state = State, - debug = Debug1 }); handle_msg({'$gen_call', From, Msg}, GS2State = #gs2_state { mod = Mod, state = State, name = Name, -- cgit v1.2.1 From 0cc0fccd7797ec1fa269b485527ea923fbe14fc2 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 19 Apr 2013 12:03:45 +0100 Subject: Tiny test --- src/rabbit_tests.erl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index e7b69879..bcb3bf19 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -63,6 +63,7 @@ all_tests() -> passed = test_server_status(), passed = test_amqp_connection_refusal(), passed = test_confirms(), + passed = test_with_state(), passed = do_if_secondary_node( fun run_cluster_dependent_tests/1, @@ -1298,6 +1299,11 @@ test_confirms() -> passed. +test_with_state() -> + fhc_state = gen_server2:with_state(file_handle_cache, + fun (S) -> element(1, S) end), + passed. + test_statistics_event_receiver(Pid) -> receive Foo -> Pid ! Foo, test_statistics_event_receiver(Pid) -- cgit v1.2.1 From 92b4d9f240b4eef2502537f08251409fd4d168d0 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Fri, 19 Apr 2013 12:05:21 +0100 Subject: cosmetic --- src/gen_server2.erl | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/gen_server2.erl b/src/gen_server2.erl index 99fa272a..507d1cda 100644 --- a/src/gen_server2.erl +++ b/src/gen_server2.erl @@ -186,10 +186,11 @@ %% API -export([start/3, start/4, start_link/3, start_link/4, - call/2, call/3, with_state/2, + call/2, call/3, cast/2, reply/2, abcast/2, abcast/3, multi_call/2, multi_call/3, multi_call/4, + with_state/2, enter_loop/3, enter_loop/4, enter_loop/5, enter_loop/6, wake_hib/1]). %% System exports @@ -325,14 +326,6 @@ call(Name, Request, Timeout) -> exit({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end. -with_state(Name, Fun) -> - case catch gen:call(Name, '$with_state', Fun, infinity) of - {ok,Res} -> - Res; - {'EXIT',Reason} -> - exit({Reason, {?MODULE, with_state, [Name, Fun]}}) - end. - %% ----------------------------------------------------------------- %% Make a cast to a generic server. %% ----------------------------------------------------------------- @@ -396,6 +389,16 @@ 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). +%% ----------------------------------------------------------------- +%% Apply a function to a generic server's state. +%% ----------------------------------------------------------------- +with_state(Name, Fun) -> + case catch gen:call(Name, '$with_state', Fun, infinity) of + {ok,Res} -> + Res; + {'EXIT',Reason} -> + exit({Reason, {?MODULE, with_state, [Name, Fun]}}) + end. %%----------------------------------------------------------------- %% enter_loop(Mod, Options, State, , , ) ->_ -- cgit v1.2.1 From 6dff79458ef7b54fe9c253414676a44cd323802e Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 19 Apr 2013 12:36:55 +0100 Subject: Refactor restart handling Avoid logging restart (failures) twice. Do not reset {restarting, Pid} to 'undefined' as this deadlocks the supervisor. --- src/supervisor2.erl | 5 +++- src/supervisor2_tests.erl | 58 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 533ec997..ca219990 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -837,7 +837,7 @@ restart_child(Pid, Reason, State) -> end. try_restart(RestartType, Reason, Child, State) -> - case do_restart(RestartType, Reason, Child, State) of + case handle_restart(RestartType, Reason, Child, State) of {ok, NState} -> {noreply, NState}; {shutdown, State2} -> {stop, shutdown, State2} end. @@ -1260,6 +1260,9 @@ state_del_child(Child, State) -> del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name, Ch#child.restart_type =:= temporary -> Chs; +del_child(NameOrPid, [Ch=#child{pid = ?restarting(_)}|_]=Chs) + when Ch#child.name =:= NameOrPid -> + Chs; del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name -> [Ch#child{pid = undefined} | Chs]; del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid, Ch#child.restart_type =:= temporary -> diff --git a/src/supervisor2_tests.erl b/src/supervisor2_tests.erl index 518f11b7..c53b613c 100644 --- a/src/supervisor2_tests.erl +++ b/src/supervisor2_tests.erl @@ -17,12 +17,22 @@ -module(supervisor2_tests). -behaviour(supervisor2). +-include_lib("eunit/include/eunit.hrl"). + +-define(ASSERT, true). +-define(EUNIT_NOAUTO, true). + -export([test_all/0, start_link/0]). +-export([start_link_bad/0]). -export([init/1]). test_all() -> - ok = check_shutdown(stop, 200, 200, 2000), - ok = check_shutdown(ignored, 1, 2, 2000). + catch ets:new(?MODULE, [named_table, public]), + %% ok = check_shutdown(stop, 200, 200, 2000), + %% ok = check_shutdown(ignored, 1, 2, 2000), + %% ok = check_logging(transient), + ets:delete(?MODULE, bang), + ok = check_logging({permanent, 1}). check_shutdown(SigStop, Iterations, ChildCount, SupTimeout) -> {ok, Sup} = supervisor2:start_link(?MODULE, [SupTimeout]), @@ -52,6 +62,20 @@ check_shutdown(SigStop, Iterations, ChildCount, SupTimeout) -> exit(Sup, shutdown), Res. +check_logging(How) -> + process_flag(trap_exit, true), + {ok, Sup} = supervisor2:start_link(?MODULE, [bang, How]), + io:format("super pid = ~p~n", [Sup]), + MRef = erlang:monitor(process, Sup), + [Pid] = supervisor2:find_child(Sup, test_try_again_sup), + io:format("Pid == ~p~nChildren == ~p~n", [Pid, supervisor2:which_children(Sup)]), + Pid ! {shutdown, bang}, + io:format("restart issued - awaiting sup death~n"), + receive + {'DOWN', MRef, process, Sup, Reason} -> + io:format("stopped Sup == ~p~n", [Reason]) + end. + start_link() -> Pid = spawn_link(fun () -> process_flag(trap_exit, true), @@ -59,6 +83,35 @@ start_link() -> end), {ok, Pid}. +start_link_bad() -> + Boom = ets:lookup(?MODULE, bang), + case Boom of + [{bang, true}] -> io:format("BOOM!~n"), exit(bang); + _ -> ok + end, + io:format("no Boom - starting server~n"), + Pid = spawn_link(fun () -> + process_flag(trap_exit, true), + receive + {shutdown, Bang} -> + ets:insert(?MODULE, [{bang, true}]), + io:format("exiting...~n"), + exit(Bang); + shutdown -> + io:format("exiting (shutdown)...~n"), + exit(shutdown); + Other -> + io:format("odd signal: ~p~n", [Other]), + exit(Other) + end + end), + {ok, Pid}. + +init([bang, How]) -> + {ok, {{one_for_one, 3, 10}, + [{test_try_again_sup, {?MODULE, start_link_bad, []}, + How, 5000, worker, [?MODULE]}]}}; + init([Timeout]) -> {ok, {{one_for_one, 0, 1}, [{test_sup, {supervisor2, start_link, @@ -68,3 +121,4 @@ init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{test_worker, {?MODULE, start_link, []}, temporary, 1000, worker, [?MODULE]}]}}. + -- cgit v1.2.1 From 1b52a408c67351ceb4ae7b6bb3890885520681d9 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 19 Apr 2013 13:46:38 +0100 Subject: back out of f608b9df9a14 changes supervisor2_tests --- src/supervisor2_tests.erl | 58 ++--------------------------------------------- 1 file changed, 2 insertions(+), 56 deletions(-) diff --git a/src/supervisor2_tests.erl b/src/supervisor2_tests.erl index c53b613c..518f11b7 100644 --- a/src/supervisor2_tests.erl +++ b/src/supervisor2_tests.erl @@ -17,22 +17,12 @@ -module(supervisor2_tests). -behaviour(supervisor2). --include_lib("eunit/include/eunit.hrl"). - --define(ASSERT, true). --define(EUNIT_NOAUTO, true). - -export([test_all/0, start_link/0]). --export([start_link_bad/0]). -export([init/1]). test_all() -> - catch ets:new(?MODULE, [named_table, public]), - %% ok = check_shutdown(stop, 200, 200, 2000), - %% ok = check_shutdown(ignored, 1, 2, 2000), - %% ok = check_logging(transient), - ets:delete(?MODULE, bang), - ok = check_logging({permanent, 1}). + ok = check_shutdown(stop, 200, 200, 2000), + ok = check_shutdown(ignored, 1, 2, 2000). check_shutdown(SigStop, Iterations, ChildCount, SupTimeout) -> {ok, Sup} = supervisor2:start_link(?MODULE, [SupTimeout]), @@ -62,20 +52,6 @@ check_shutdown(SigStop, Iterations, ChildCount, SupTimeout) -> exit(Sup, shutdown), Res. -check_logging(How) -> - process_flag(trap_exit, true), - {ok, Sup} = supervisor2:start_link(?MODULE, [bang, How]), - io:format("super pid = ~p~n", [Sup]), - MRef = erlang:monitor(process, Sup), - [Pid] = supervisor2:find_child(Sup, test_try_again_sup), - io:format("Pid == ~p~nChildren == ~p~n", [Pid, supervisor2:which_children(Sup)]), - Pid ! {shutdown, bang}, - io:format("restart issued - awaiting sup death~n"), - receive - {'DOWN', MRef, process, Sup, Reason} -> - io:format("stopped Sup == ~p~n", [Reason]) - end. - start_link() -> Pid = spawn_link(fun () -> process_flag(trap_exit, true), @@ -83,35 +59,6 @@ start_link() -> end), {ok, Pid}. -start_link_bad() -> - Boom = ets:lookup(?MODULE, bang), - case Boom of - [{bang, true}] -> io:format("BOOM!~n"), exit(bang); - _ -> ok - end, - io:format("no Boom - starting server~n"), - Pid = spawn_link(fun () -> - process_flag(trap_exit, true), - receive - {shutdown, Bang} -> - ets:insert(?MODULE, [{bang, true}]), - io:format("exiting...~n"), - exit(Bang); - shutdown -> - io:format("exiting (shutdown)...~n"), - exit(shutdown); - Other -> - io:format("odd signal: ~p~n", [Other]), - exit(Other) - end - end), - {ok, Pid}. - -init([bang, How]) -> - {ok, {{one_for_one, 3, 10}, - [{test_try_again_sup, {?MODULE, start_link_bad, []}, - How, 5000, worker, [?MODULE]}]}}; - init([Timeout]) -> {ok, {{one_for_one, 0, 1}, [{test_sup, {supervisor2, start_link, @@ -121,4 +68,3 @@ init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{test_worker, {?MODULE, start_link, []}, temporary, 1000, worker, [?MODULE]}]}}. - -- cgit v1.2.1 From bb7cf97480b0f524e9660e53ea0b6b6e6aced461 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 22 Apr 2013 09:45:09 +0100 Subject: leave OtherProc info as is --- src/rabbit_vm.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index afd8921b..49172adb 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -60,8 +60,6 @@ memory() -> {system, System}] = erlang:memory([total, processes, ets, atom, binary, code, system]), - %% TODO: should we replace this with the value extracted from - %% 'Other'? OtherProc = Processes - Conns - Qs - MsgIndexProc - AllPlugins, [{total, Total}, -- cgit v1.2.1 From a566245a15d4551c6df60debb2892c2a8b6f3ebe Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 22 Apr 2013 09:47:09 +0100 Subject: fix a (harmless) bug spotted by dialyzer the original code only worked since orddict:to_list is implemented as the identity function. --- src/rabbit_vm.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index 49172adb..1e7bd383 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -159,7 +159,8 @@ sum_processes(Names, Fun, Acc0) -> end, {NameAccs0, Acc0Dict}, processes()), %% these conversions aren't strictly necessary; we do them simply %% for the sake of encapsulating the representation. - {[orddict:to_list(NameAcc) || NameAcc <- orddict:to_list(NameAccs)], + {[{Name, orddict:to_list(Accs)} || + {Name, Accs} <- orddict:to_list(NameAccs)], orddict:to_list(OtherAcc)}. find_ancestor(Extra, D, Names) -> -- cgit v1.2.1 From e870642153f73f5cdafb9188f4f7a1cad9b0db52 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 22 Apr 2013 09:47:26 +0100 Subject: add some specs and docs --- src/rabbit_vm.erl | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl index 1e7bd383..c28b0cd5 100644 --- a/src/rabbit_vm.erl +++ b/src/rabbit_vm.erl @@ -129,11 +129,58 @@ extract_memory(Name, Sums) -> {value, {memory, V}} = lists:keysearch(memory, 1, Accs), V. -%% NB: this code is non-rabbit specific +%%---------------------------------------------------------------------------- + +%% NB: this code is non-rabbit specific. + +-ifdef(use_specs). +-type(process() :: pid() | atom()). +-type(info_key() :: atom()). +-type(info_value() :: any()). +-type(info_item() :: {info_key(), info_value()}). +-type(accumulate() :: fun ((info_key(), info_value(), info_value()) -> + info_value())). +-spec(sum_processes/2 :: ([process()], [info_key()]) -> + {[{process(), [info_item()]}], [info_item()]}). +-spec(sum_processes/3 :: ([process()], accumulate(), [info_item()]) -> + {[{process(), [info_item()]}], [info_item()]}). +-endif. + sum_processes(Names, Items) -> sum_processes(Names, fun (_, X, Y) -> X + Y end, [{Item, 0} || Item <- Items]). +%% summarize the process_info of all processes based on their +%% '$ancestor' hierarchy, recorded in their process dictionary. +%% +%% The function takes +%% +%% 1) a list of names/pids of processes that are accumulation points +%% in the hierarchy. +%% +%% 2) a function that aggregates individual info items -taking the +%% info item key, value and accumulated value as the input and +%% producing a new accumulated value. +%% +%% 3) a list of info item key / initial accumulator value pairs. +%% +%% The process_info of a process is accumulated at the nearest of its +%% ancestors that is mentioned in the first argument, or, if no such +%% ancestor exists or the ancestor information is absent, in a special +%% 'other' bucket. +%% +%% The result is a pair consisting of +%% +%% 1) a k/v list, containing for each of the accumulation names/pids a +%% list of info items, containing the accumulated data, and +%% +%% 2) the 'other' bucket - a list of info items containing the +%% accumulated data of all processes with no matching ancestors +%% +%% Note that this function operates on names as well as pids, but +%% these must match whatever is contained in the '$ancestor' process +%% dictionary entry. Generally that means for all registered processes +%% the name should be used. sum_processes(Names, Fun, Acc0) -> Items = [Item || {Item, _Val0} <- Acc0], Acc0Dict = orddict:from_list(Acc0), -- cgit v1.2.1 From 8b7df16ae4b8014181c32b29db2cba53c35b2c24 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 22 Apr 2013 16:10:51 +0100 Subject: Move those functions to their own place, and replace the autoheal all_nodes_up check with all_rabbit_nodes_up since it will depend on the rabbit application running to DTRT. --- src/rabbit_autoheal.erl | 2 +- src/rabbit_node_monitor.erl | 49 +++++++++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/rabbit_autoheal.erl b/src/rabbit_autoheal.erl index 82f26634..c00c2dd6 100644 --- a/src/rabbit_autoheal.erl +++ b/src/rabbit_autoheal.erl @@ -93,7 +93,7 @@ node_down(Node, _State) -> handle_msg({request_start, Node}, not_healing, Partitions) -> rabbit_log:info("Autoheal request received from ~p~n", [Node]), - case rabbit_node_monitor:all_nodes_up() of + case rabbit_node_monitor:all_rabbit_nodes_up() of false -> not_healing; true -> AllPartitions = all_partitions(Partitions), {Winner, Losers} = make_decision(AllPartitions), diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index 2d237020..ca8e6dbd 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -31,7 +31,7 @@ code_change/3]). %% Utils --export([all_nodes_up/0, run_outside_applications/1]). +-export([all_rabbit_nodes_up/0, run_outside_applications/1]). -define(SERVER, ?MODULE). -define(RABBIT_UP_RPC_TIMEOUT, 2000). @@ -60,7 +60,7 @@ -spec(partitions/0 :: () -> {node(), [node()]}). -spec(subscribe/1 :: (pid()) -> 'ok'). --spec(all_nodes_up/0 :: () -> boolean()). +-spec(all_rabbit_nodes_up/0 :: () -> boolean()). -spec(run_outside_applications/1 :: (fun (() -> any())) -> pid()). -endif. @@ -350,24 +350,6 @@ handle_dead_rabbit(Node) -> end, ok. -majority() -> - Nodes = rabbit_mnesia:cluster_nodes(all), - length(alive_nodes(Nodes)) / length(Nodes) > 0.5. - -all_nodes_up() -> - Nodes = rabbit_mnesia:cluster_nodes(all), - length(alive_nodes(Nodes)) =:= length(Nodes). - -%% mnesia:system_info(db_nodes) (and hence -%% rabbit_mnesia:cluster_nodes(running)) does not give reliable results -%% when partitioned. -alive_nodes() -> alive_nodes(rabbit_mnesia:cluster_nodes(all)). - -alive_nodes(Nodes) -> [N || N <- Nodes, pong =:= net_adm:ping(N)]. - -alive_rabbit_nodes() -> - [N || N <- alive_nodes(), rabbit_nodes:is_process_running(N, rabbit)]. - await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", []), @@ -441,3 +423,30 @@ legacy_should_be_disc_node(DiscNodes) -> add_node(Node, Nodes) -> lists:usort([Node | Nodes]). del_node(Node, Nodes) -> Nodes -- [Node]. + +%%-------------------------------------------------------------------- + +%% mnesia:system_info(db_nodes) (and hence +%% rabbit_mnesia:cluster_nodes(running)) does not give reliable +%% results when partitioned. So we have a small set of replacement +%% functions here. "rabbit" in a function's name implies we test if +%% the rabbit application is up, not just the node. + +majority() -> + Nodes = rabbit_mnesia:cluster_nodes(all), + length(alive_nodes(Nodes)) / length(Nodes) > 0.5. + +all_nodes_up() -> + Nodes = rabbit_mnesia:cluster_nodes(all), + length(alive_nodes(Nodes)) =:= length(Nodes). + +all_rabbit_nodes_up() -> + Nodes = rabbit_mnesia:cluster_nodes(all), + length(alive_rabbit_nodes(Nodes)) =:= length(Nodes). + +alive_nodes(Nodes) -> [N || N <- Nodes, pong =:= net_adm:ping(N)]. + +alive_rabbit_nodes() -> alive_rabbit_nodes(rabbit_mnesia:cluster_nodes(all)). + +alive_rabbit_nodes(Nodes) -> + [N || N <- alive_nodes(Nodes), rabbit_nodes:is_process_running(N, rabbit)]. -- cgit v1.2.1 From d35534e6a7a0a04fc2ed68061d9c2508864a0865 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 22 Apr 2013 16:23:54 +0100 Subject: Switch pause_minority mode to making its decisions entirely based on node-upness, not rabbit-application-upness and explain why. --- src/rabbit_node_monitor.erl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl index ca8e6dbd..7d844c72 100644 --- a/src/rabbit_node_monitor.erl +++ b/src/rabbit_node_monitor.erl @@ -200,6 +200,7 @@ init([]) -> %% writing out the cluster status files - bad things can then %% happen. process_flag(trap_exit, true), + net_kernel:monitor_nodes(true), {ok, _} = mnesia:subscribe(system), {ok, #state{monitors = pmon:new(), subscribers = pmon:new(), @@ -265,6 +266,10 @@ handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #state{subscribers = Subscribers}) -> {noreply, State#state{subscribers = pmon:erase(Pid, Subscribers)}}; +handle_info({nodedown, Node}, State) -> + ok = handle_dead_node(Node), + {noreply, State}; + handle_info({mnesia_system_event, {inconsistent_database, running_partitioned_network, Node}}, State = #state{partitions = Partitions, @@ -333,6 +338,18 @@ handle_dead_rabbit(Node) -> ok = rabbit_amqqueue:on_node_down(Node), ok = rabbit_alarm:on_node_down(Node), ok = rabbit_mnesia:on_node_down(Node), + ok. + +handle_dead_node(_Node) -> + %% In general in rabbit_node_monitor we care about whether the + %% rabbit application is up rather than the node; we do this so + %% that we can respond in the same way to "rabbitmqctl stop_app" + %% and "rabbitmqctl stop" as much as possible. + %% + %% However, for pause_minority mode we can't do this, since we + %% depend on looking at whether other nodes are up to decide + %% whether to come back up ourselves - if we decide that based on + %% the rabbit application we would go down and never come back. case application:get_env(rabbit, cluster_partition_handling) of {ok, pause_minority} -> case majority() of @@ -347,8 +364,7 @@ handle_dead_rabbit(Node) -> rabbit_log:warning("cluster_partition_handling ~p unrecognised, " "assuming 'ignore'~n", [Term]), ok - end, - ok. + end. await_cluster_recovery() -> rabbit_log:warning("Cluster minority status detected - awaiting recovery~n", -- cgit v1.2.1 From 7f4b87411bc8e5d83a4a17740db76fbcb52eec54 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 24 Apr 2013 12:15:43 +0100 Subject: Add explanations to pmon:is_monitored/2 invocations. --- src/rabbit_mirror_queue_slave.erl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl index 22edfcb6..63a53e2b 100644 --- a/src/rabbit_mirror_queue_slave.erl +++ b/src/rabbit_mirror_queue_slave.erl @@ -605,6 +605,10 @@ ensure_monitoring(ChPid, State = #state { known_senders = KS }) -> State #state { known_senders = pmon:monitor(ChPid, KS) }. local_sender_death(ChPid, State = #state { known_senders = KS }) -> + %% The channel will be monitored iff we have received a delivery + %% from it but not heard about its death from the master. So if it + %% is monitored we need to point the death out to the master (see + %% essay). ok = case pmon:is_monitored(ChPid, KS) of false -> ok; true -> credit_flow:peer_down(ChPid), @@ -621,6 +625,10 @@ confirm_sender_death(Pid) -> fun (?MODULE, State = #state { known_senders = KS, gm = GM }) -> %% We're running still as a slave + %% + %% See comment in local_sender_death/2; we might have + %% received a sender_death in the meanwhile so check + %% again. ok = case pmon:is_monitored(Pid, KS) of false -> ok; true -> gm:broadcast(GM, {ensure_monitoring, [Pid]}), @@ -766,6 +774,9 @@ process_instruction({sender_death, ChPid}, State = #state { sender_queues = SQ, msg_id_status = MS, known_senders = KS }) -> + %% The channel will be monitored iff we have received a message + %% from it. In this case we just want to avoid doing work if we + %% never got any messages. {ok, case pmon:is_monitored(ChPid, KS) of false -> State; true -> MS1 = case dict:find(ChPid, SQ) of -- cgit v1.2.1 From 7475a7a9c18fe421c4c69f80903b5d8986c02741 Mon Sep 17 00:00:00 2001 From: Tim Watson Date: Fri, 26 Apr 2013 12:39:40 +0100 Subject: Minor variable renaming, document subtle one_for_all child deletion clauses --- src/supervisor2.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index ca219990..3b971739 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -1260,11 +1260,12 @@ state_del_child(Child, State) -> del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name, Ch#child.restart_type =:= temporary -> Chs; -del_child(NameOrPid, [Ch=#child{pid = ?restarting(_)}|_]=Chs) - when Ch#child.name =:= NameOrPid -> +del_child(Name, [Ch=#child{pid = ?restarting(_)}|_]=Chs) + when Ch#child.name =:= Name -> Chs; del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name -> [Ch#child{pid = undefined} | Chs]; +%% the next two clauses only handle deletions during a one_for_all restart del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid, Ch#child.restart_type =:= temporary -> Chs; del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid -> -- cgit v1.2.1 From cfdc7a1968e4933afea26c9a9332442090dcef27 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Fri, 26 Apr 2013 13:38:25 +0100 Subject: Use the credit extension. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bf33b931..449d1edd 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ TARGET_SRC_DIR=dist/$(TARBALL_NAME) SIBLING_CODEGEN_DIR=../rabbitmq-codegen/ AMQP_CODEGEN_DIR=$(shell [ -d $(SIBLING_CODEGEN_DIR) ] && echo $(SIBLING_CODEGEN_DIR) || echo codegen) -AMQP_SPEC_JSON_FILES_0_9_1=$(AMQP_CODEGEN_DIR)/amqp-rabbitmq-0.9.1.json +AMQP_SPEC_JSON_FILES_0_9_1=$(AMQP_CODEGEN_DIR)/amqp-rabbitmq-0.9.1.json $(AMQP_CODEGEN_DIR)/credit_extension.json AMQP_SPEC_JSON_FILES_0_8=$(AMQP_CODEGEN_DIR)/amqp-rabbitmq-0.8.json ERL_CALL=erl_call -sname $(RABBITMQ_NODENAME) -e -- cgit v1.2.1 From a103c073e56a87b0b2cc86eb1f1cc0ce68c9b91f Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Mon, 29 Apr 2013 17:13:04 +0100 Subject: Comment --- src/rabbit_mirror_queue_mode.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rabbit_mirror_queue_mode.erl b/src/rabbit_mirror_queue_mode.erl index de1c35e2..da4c9b36 100644 --- a/src/rabbit_mirror_queue_mode.erl +++ b/src/rabbit_mirror_queue_mode.erl @@ -25,7 +25,9 @@ -callback description() -> [proplists:property()]. %% Called whenever we think we might need to change nodes for a -%% mirrored queue. +%% mirrored queue. Note that this is called from a variety of +%% contexts, both inside and outside Mnesia transactions. Ideally it +%% will be pure-functional. %% %% Takes: parameters set in the policy, %% current master, -- cgit v1.2.1 From 3429727fd33af852bd1a5450d006e6f3878289f6 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Tue, 30 Apr 2013 12:40:32 +0100 Subject: Typo; fix compilation on - [{description, 0}, {suggested_queue_nodes, 5}, {validate_policy, 1}]. + [{description, 0}, {suggested_queue_nodes, 5}, {validate_policy, 1}]; behaviour_info(_Other) -> undefined. -- cgit v1.2.1 From c44afbbda908628527caf1dba399b49bf0653b43 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Wed, 1 May 2013 10:59:16 +0100 Subject: ever so slightly clearer --- src/supervisor2.erl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 3b971739..8b0899a6 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -1258,14 +1258,12 @@ state_del_child(Child, State) -> NChildren = del_child(Child#child.name, State#state.children), State#state{children = NChildren}. -del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name, Ch#child.restart_type =:= temporary -> +del_child(Name, [Ch=#child{pid = ?restarting(_)}|_]=Chs) when Ch#child.name =:= Name -> Chs; -del_child(Name, [Ch=#child{pid = ?restarting(_)}|_]=Chs) - when Ch#child.name =:= Name -> +del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name, Ch#child.restart_type =:= temporary -> Chs; del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name -> [Ch#child{pid = undefined} | Chs]; -%% the next two clauses only handle deletions during a one_for_all restart del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid, Ch#child.restart_type =:= temporary -> Chs; del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid -> -- cgit v1.2.1 From c95ce9e1c33c58e436f7a3d6365fdcf0407db9e7 Mon Sep 17 00:00:00 2001 From: Simon MacMullen Date: Wed, 1 May 2013 15:06:30 +0100 Subject: Changelogs for 3.1.0. --- packaging/RPMS/Fedora/rabbitmq-server.spec | 3 +++ packaging/debs/Debian/debian/changelog | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/packaging/RPMS/Fedora/rabbitmq-server.spec b/packaging/RPMS/Fedora/rabbitmq-server.spec index 6e02c7a4..7b57cb2f 100644 --- a/packaging/RPMS/Fedora/rabbitmq-server.spec +++ b/packaging/RPMS/Fedora/rabbitmq-server.spec @@ -123,6 +123,9 @@ done rm -rf %{buildroot} %changelog +* Wed May 1 2013 simon@rabbitmq.com 3.1.0-1 +- New Upstream Release + * Tue Dec 11 2012 simon@rabbitmq.com 3.0.1-1 - New Upstream Release diff --git a/packaging/debs/Debian/debian/changelog b/packaging/debs/Debian/debian/changelog index aed68b96..fe5310c8 100644 --- a/packaging/debs/Debian/debian/changelog +++ b/packaging/debs/Debian/debian/changelog @@ -1,3 +1,9 @@ +rabbitmq-server (3.1.0-1) unstable; urgency=low + + * New Upstream Release + + -- Simon MacMullen Wed, 01 May 2013 11:57:58 +0100 + rabbitmq-server (3.0.1-1) unstable; urgency=low * New Upstream Release -- cgit v1.2.1 -- cgit v1.2.1 From 5b3fccb6dcec837fc7e00a0fbee0a9f72f07eaa4 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Tue, 7 May 2013 07:52:19 +0100 Subject: remove R13-ism imported from upstream --- src/supervisor2.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/supervisor2.erl b/src/supervisor2.erl index 8b0899a6..a51bc15c 100644 --- a/src/supervisor2.erl +++ b/src/supervisor2.erl @@ -655,8 +655,9 @@ handle_cast({try_again_restart,Pid,Reason}, #state{children=[Child]}=State) end; handle_cast({try_again_restart,Name,Reason}, State) -> - case lists:keyfind(Name,#child.name,State#state.children) of - Child = #child{pid=?restarting(_), restart_type=RestartType} -> + %% we still support >= R12-B3 in which lists:keyfind/3 doesn't exist + case lists:keysearch(Name,#child.name,State#state.children) of + {value, Child = #child{pid=?restarting(_), restart_type=RestartType}} -> try_restart(RestartType, Reason, Child, State); _ -> {noreply,State} -- cgit v1.2.1