8080 max_length ,
8181 % % max length in bytes, if configured
8282 max_bytes ,
83+ % % an action to perform if queue is to be over a limit,
84+ % % can be either drop-head (default) or reject-publish
85+ overflow ,
8386 % % when policies change, this version helps queue
8487 % % determine what previously scheduled/set up state to ignore,
8588 % % e.g. message expiration messages from previously set up timers
@@ -159,7 +162,8 @@ init_state(Q) ->
159162 senders = pmon :new (delegate ),
160163 msg_id_to_channel = gb_trees :empty (),
161164 status = running ,
162- args_policy_version = 0 },
165+ args_policy_version = 0 ,
166+ overflow = 'drop-head' },
163167 rabbit_event :init_stats_timer (State , # q .stats_timer ).
164168
165169init_it (Recover , From , State = # q {q = # amqqueue {exclusive_owner = none }}) ->
@@ -260,7 +264,7 @@ init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS,
260264 msg_id_to_channel = MTC },
261265 State2 = process_args_policy (State1 ),
262266 State3 = lists :foldl (fun (Delivery , StateN ) ->
263- deliver_or_enqueue (Delivery , true , StateN )
267+ maybe_deliver_or_enqueue (Delivery , true , StateN )
264268 end , State2 , Deliveries ),
265269 notify_decorators (startup , State3 ),
266270 State3 .
@@ -378,6 +382,7 @@ process_args_policy(State = #q{q = Q,
378382 {<<" message-ttl" >>, fun res_min /2 , fun init_ttl /2 },
379383 {<<" max-length" >>, fun res_min /2 , fun init_max_length /2 },
380384 {<<" max-length-bytes" >>, fun res_min /2 , fun init_max_bytes /2 },
385+ {<<" overflow" >>, fun res_arg /2 , fun init_overflow /2 },
381386 {<<" queue-mode" >>, fun res_arg /2 , fun init_queue_mode /2 }],
382387 drop_expired_msgs (
383388 lists :foldl (fun ({Name , Resolve , Fun }, StateN ) ->
@@ -421,6 +426,18 @@ init_max_bytes(MaxBytes, State) ->
421426 {_Dropped , State1 } = maybe_drop_head (State # q {max_bytes = MaxBytes }),
422427 State1 .
423428
429+ init_overflow (undefined , State ) ->
430+ State ;
431+ init_overflow (Overflow , State ) ->
432+ OverflowVal = binary_to_existing_atom (Overflow , utf8 ),
433+ case OverflowVal of
434+ 'drop-head' ->
435+ {_Dropped , State1 } = maybe_drop_head (State # q {overflow = OverflowVal }),
436+ State1 ;
437+ _ ->
438+ State # q {overflow = OverflowVal }
439+ end .
440+
424441init_queue_mode (undefined , State ) ->
425442 State ;
426443init_queue_mode (Mode , State = # q {backing_queue = BQ ,
@@ -621,12 +638,22 @@ attempt_delivery(Delivery = #delivery{sender = SenderPid,
621638 State # q {consumers = Consumers })}
622639 end .
623640
641+ maybe_deliver_or_enqueue (Delivery , Delivered , State = # q {overflow = Overflow }) ->
642+ send_mandatory (Delivery ), % % must do this before confirms
643+ case {will_overflow (Delivery , State ), Overflow } of
644+ {true , 'reject-publish' } ->
645+ % % Drop publish and nack to publisher
646+ send_reject_publish (Delivery , Delivered , State );
647+ _ ->
648+ % % Enqueue and maybe drop head later
649+ deliver_or_enqueue (Delivery , Delivered , State )
650+ end .
651+
624652deliver_or_enqueue (Delivery = # delivery {message = Message ,
625653 sender = SenderPid ,
626654 flow = Flow },
627655 Delivered , State = # q {backing_queue = BQ ,
628656 backing_queue_state = BQS }) ->
629- send_mandatory (Delivery ), % % must do this before confirms
630657 {Confirm , State1 } = send_or_record_confirm (Delivery , State ),
631658 Props = message_properties (Message , Confirm , State1 ),
632659 {IsDuplicate , BQS1 } = BQ :is_duplicate (Message , BQS ),
@@ -644,6 +671,7 @@ deliver_or_enqueue(Delivery = #delivery{message = Message,
644671 {BQS3 , MTC1 } = discard (Delivery , BQ , BQS2 , MTC ),
645672 State3 # q {backing_queue_state = BQS3 , msg_id_to_channel = MTC1 };
646673 {undelivered , State3 = # q {backing_queue_state = BQS2 }} ->
674+
647675 BQS3 = BQ :publish (Message , Props , Delivered , SenderPid , Flow , BQS2 ),
648676 {Dropped , State4 = # q {backing_queue_state = BQS4 }} =
649677 maybe_drop_head (State3 # q {backing_queue_state = BQS3 }),
@@ -665,7 +693,9 @@ deliver_or_enqueue(Delivery = #delivery{message = Message,
665693maybe_drop_head (State = # q {max_length = undefined ,
666694 max_bytes = undefined }) ->
667695 {false , State };
668- maybe_drop_head (State ) ->
696+ maybe_drop_head (State = # q {overflow = 'reject-publish' }) ->
697+ {false , State };
698+ maybe_drop_head (State = # q {overflow = 'drop-head' }) ->
669699 maybe_drop_head (false , State ).
670700
671701maybe_drop_head (AlreadyDropped , State = # q {backing_queue = BQ ,
@@ -684,6 +714,35 @@ maybe_drop_head(AlreadyDropped, State = #q{backing_queue = BQ,
684714 {AlreadyDropped , State }
685715 end .
686716
717+ send_reject_publish (# delivery {confirm = true ,
718+ sender = SenderPid ,
719+ msg_seq_no = MsgSeqNo } = Delivery ,
720+ _Delivered ,
721+ State = # q { backing_queue = BQ ,
722+ backing_queue_state = BQS ,
723+ msg_id_to_channel = MTC }) ->
724+ {BQS1 , MTC1 } = discard (Delivery , BQ , BQS , MTC ),
725+ gen_server2 :cast (SenderPid , {reject_publish , MsgSeqNo , self ()}),
726+ State # q { backing_queue_state = BQS1 , msg_id_to_channel = MTC1 };
727+ send_reject_publish (# delivery {confirm = false },
728+ _Delivered , State ) ->
729+ State .
730+
731+ will_overflow (_ , # q {max_length = undefined ,
732+ max_bytes = undefined }) -> false ;
733+ will_overflow (# delivery {message = Message },
734+ # q {max_length = MaxLen ,
735+ max_bytes = MaxBytes ,
736+ backing_queue = BQ ,
737+ backing_queue_state = BQS }) ->
738+ ExpectedQueueLength = BQ :len (BQS ) + 1 ,
739+
740+ # basic_message {content = # content {payload_fragments_rev = PFR }} = Message ,
741+ MessageSize = iolist_size (PFR ),
742+ ExpectedQueueSizeBytes = BQ :info (message_bytes_ready , BQS ) + MessageSize ,
743+
744+ ExpectedQueueLength > MaxLen orelse ExpectedQueueSizeBytes > MaxBytes .
745+
687746over_max_length (# q {max_length = MaxLen ,
688747 max_bytes = MaxBytes ,
689748 backing_queue = BQ ,
@@ -1254,8 +1313,10 @@ handle_cast({run_backing_queue, Mod, Fun},
12541313 State = # q {backing_queue = BQ , backing_queue_state = BQS }) ->
12551314 noreply (State # q {backing_queue_state = BQ :invoke (Mod , Fun , BQS )});
12561315
1257- handle_cast ({deliver , Delivery = # delivery {sender = Sender ,
1258- flow = Flow }, SlaveWhenPublished },
1316+ handle_cast ({deliver ,
1317+ Delivery = # delivery {sender = Sender ,
1318+ flow = Flow },
1319+ SlaveWhenPublished },
12591320 State = # q {senders = Senders }) ->
12601321 Senders1 = case Flow of
12611322 % % In both credit_flow:ack/1 we are acking messages to the channel
@@ -1270,7 +1331,7 @@ handle_cast({deliver, Delivery = #delivery{sender = Sender,
12701331 noflow -> Senders
12711332 end ,
12721333 State1 = State # q {senders = Senders1 },
1273- noreply (deliver_or_enqueue (Delivery , SlaveWhenPublished , State1 ));
1334+ noreply (maybe_deliver_or_enqueue (Delivery , SlaveWhenPublished , State1 ));
12741335% % [0] The second ack is since the channel thought we were a slave at
12751336% % the time it published this message, so it used two credits (see
12761337% % rabbit_amqqueue:deliver/2).
0 commit comments