184
184
% % command: `{consumer_credit, ReceiverDeliveryCount, Credit}'
185
185
credit_mode = simple_prefetch :: credit_mode (), % part of snapshot data
186
186
lifetime = once :: once | auto ,
187
- suspected_down = false :: boolean ()
187
+ suspected_down = false :: 'cancel' | boolean ()
188
188
}).
189
189
190
190
- type consumer () :: # consumer {}.
@@ -426,10 +426,7 @@ apply(Meta, #checkout{spec = {dequeue, Settlement},
426
426
end
427
427
end ;
428
428
apply (Meta , # checkout {spec = cancel , consumer_id = ConsumerId }, State0 ) ->
429
- {State , Effects } = cancel_consumer (ConsumerId , State0 , []),
430
- % TODO: here we should really demonitor the pid but _only_ if it has no
431
- % other consumers or enqueuers. leaving a monitor in place isn't harmful
432
- % however
429
+ {State , Effects } = cancel_consumer (ConsumerId , State0 , [], consumer_cancel ),
433
430
checkout (Meta , State , Effects );
434
431
apply (Meta , # checkout {spec = Spec , meta = ConsumerMeta ,
435
432
consumer_id = {_ , Pid } = ConsumerId },
@@ -469,7 +466,8 @@ apply(_, {down, ConsumerPid, noconnection},
469
466
{Cons , State , Effects1 } = maps :fold (
470
467
fun ({_ , P } = K ,
471
468
# consumer {checked_out = Checked0 } = C ,
472
- {Co , St0 , Eff }) when node (P ) =:= Node ->
469
+ {Co , St0 , Eff }) when (node (P ) =:= Node ) and
470
+ (C # consumer .suspected_down =/= cancel )->
473
471
St = return_all (St0 , Checked0 ),
474
472
Credit = increase_credit (C , maps :size (Checked0 )),
475
473
Eff1 = ConsumerUpdateActiveFun (St , K , C , false , suspected_down , Eff ),
@@ -514,7 +512,7 @@ apply(Meta, {down, Pid, _Info}, #state{consumers = Cons0,
514
512
DownConsumers = maps :keys (
515
513
maps :filter (fun ({_ , P }, _ ) -> P =:= Pid end , Cons0 )),
516
514
{State , Effects } = lists :foldl (fun (ConsumerId , {S , E }) ->
517
- cancel_consumer (ConsumerId , S , E )
515
+ cancel_consumer (ConsumerId , S , E , down )
518
516
end , {State2 , Effects1 }, DownConsumers ),
519
517
checkout (Meta , State , Effects );
520
518
apply (Meta , {nodeup , Node }, # state {consumers = Cons0 ,
@@ -538,7 +536,7 @@ apply(Meta, {nodeup, Node}, #state{consumers = Cons0,
538
536
ConsumerUpdateActiveFun = consumer_active_flag_update_function (State0 ),
539
537
{Cons1 , SQ , Effects } =
540
538
maps :fold (fun ({_ , P } = ConsumerId , C , {CAcc , SQAcc , EAcc })
541
- when node (P ) =:= Node ->
539
+ when ( node (P ) =:= Node ) and ( C # consumer . suspected_down =/= cancel ) ->
542
540
EAcc1 = ConsumerUpdateActiveFun (State0 , ConsumerId , C , true , up , EAcc ),
543
541
update_or_remove_sub (
544
542
ConsumerId , C # consumer {suspected_down = false },
@@ -606,7 +604,8 @@ maybe_mark_suspect_waiting_consumers(Node,
606
604
_ ->
607
605
{ConsumerId , Consumer }
608
606
end
609
- end || {{_ , P } = ConsumerId , Consumer } <- WaitingConsumers ].
607
+ end || {{_ , P } = ConsumerId , Consumer } <- WaitingConsumers ,
608
+ Consumer # consumer .suspected_down =/= cancel ].
610
609
611
610
- spec state_enter (ra_server :ra_state (), state ()) -> ra_machine :effects ().
612
611
state_enter (leader , # state {consumers = Cons ,
@@ -758,18 +757,24 @@ query_consumers(#state{consumers = Consumers,
758
757
end
759
758
end
760
759
end ,
761
- FromConsumers = maps :map (fun ({Tag , Pid }, # consumer {meta = Meta } = Consumer ) ->
762
- {Active , ActivityStatus } = ActiveActivityStatusFun ({Tag , Pid }, Consumer ),
763
- {Pid , Tag ,
764
- maps :get (ack , Meta , undefined ),
765
- maps :get (prefetch , Meta , undefined ),
766
- Active ,
767
- ActivityStatus ,
768
- maps :get (args , Meta , []),
769
- maps :get (username , Meta , undefined )}
770
- end , Consumers ),
760
+ FromConsumers = maps :fold (fun (_ , # consumer {suspected_down = cancel }, Acc ) ->
761
+ Acc ;
762
+ ({Tag , Pid }, # consumer {meta = Meta } = Consumer , Acc ) ->
763
+ {Active , ActivityStatus } = ActiveActivityStatusFun ({Tag , Pid }, Consumer ),
764
+ maps :put ({Tag , Pid },
765
+ {Pid , Tag ,
766
+ maps :get (ack , Meta , undefined ),
767
+ maps :get (prefetch , Meta , undefined ),
768
+ Active ,
769
+ ActivityStatus ,
770
+ maps :get (args , Meta , []),
771
+ maps :get (username , Meta , undefined )},
772
+ Acc )
773
+ end , #{}, Consumers ),
771
774
FromWaitingConsumers =
772
- lists :foldl (fun ({{Tag , Pid }, # consumer {meta = Meta } = Consumer }, Acc ) ->
775
+ lists :foldl (fun ({_ , # consumer {suspected_down = cancel }}, Acc ) ->
776
+ Acc ;
777
+ ({{Tag , Pid }, # consumer {meta = Meta } = Consumer }, Acc ) ->
773
778
{Active , ActivityStatus } = ActiveActivityStatusFun ({Tag , Pid }, Consumer ),
774
779
maps :put ({Tag , Pid },
775
780
{Pid , Tag ,
@@ -854,42 +859,42 @@ num_checked_out(#state{consumers = Cons}) ->
854
859
end , 0 , maps :values (Cons )).
855
860
856
861
cancel_consumer (ConsumerId ,
857
- # state {consumer_strategy = default } = State , Effects ) ->
862
+ # state {consumer_strategy = default } = State , Effects , Reason ) ->
858
863
% % general case, single active consumer off
859
- cancel_consumer0 (ConsumerId , State , Effects );
864
+ cancel_consumer0 (ConsumerId , State , Effects , Reason );
860
865
cancel_consumer (ConsumerId ,
861
866
# state {consumer_strategy = single_active ,
862
867
waiting_consumers = []} = State ,
863
- Effects ) ->
868
+ Effects , Reason ) ->
864
869
% % single active consumer on, no consumers are waiting
865
- cancel_consumer0 (ConsumerId , State , Effects );
870
+ cancel_consumer0 (ConsumerId , State , Effects , Reason );
866
871
cancel_consumer (ConsumerId ,
867
872
# state {consumers = Cons0 ,
868
873
consumer_strategy = single_active ,
869
874
waiting_consumers = WaitingConsumers0 } = State0 ,
870
- Effects0 ) ->
875
+ Effects0 , Reason ) ->
871
876
% % single active consumer on, consumers are waiting
872
877
case maps :take (ConsumerId , Cons0 ) of
873
- {# consumer { checked_out = Checked0 }, _ } ->
878
+ {Consumer , Cons1 } ->
874
879
% The active consumer is to be removed
875
880
% Cancel it
876
- State1 = return_all ( State0 , Checked0 ),
877
- Effects1 = cancel_consumer_effects (ConsumerId , State1 , Effects0 ),
881
+ { State1 , Effects1 } = maybe_return_all ( ConsumerId , Consumer , Cons1 , State0 , Effects0 , Reason ),
882
+ Effects2 = cancel_consumer_effects (ConsumerId , State1 , Effects1 ),
878
883
% Take another one from the waiting consumers and put it in consumers
879
884
[{NewActiveConsumerId , NewActiveConsumer }
880
885
| RemainingWaitingConsumers ] = WaitingConsumers0 ,
881
- # state {service_queue = ServiceQueue } = State0 ,
886
+ # state {service_queue = ServiceQueue } = State1 ,
882
887
ServiceQueue1 = maybe_queue_consumer (NewActiveConsumerId ,
883
888
NewActiveConsumer ,
884
889
ServiceQueue ),
885
- State = State1 # state {consumers = #{ NewActiveConsumerId =>
886
- NewActiveConsumer } ,
890
+ State = State1 # state {consumers = maps : put ( NewActiveConsumerId ,
891
+ NewActiveConsumer , State1 # state . consumers ) ,
887
892
service_queue = ServiceQueue1 ,
888
893
waiting_consumers = RemainingWaitingConsumers },
889
- Effects2 = consumer_update_active_effects (State , NewActiveConsumerId ,
894
+ Effects = consumer_update_active_effects (State , NewActiveConsumerId ,
890
895
NewActiveConsumer , true ,
891
- single_active , Effects1 ),
892
- {State , Effects2 };
896
+ single_active , Effects2 ),
897
+ {State , Effects };
893
898
error ->
894
899
% The cancelled consumer is not the active one
895
900
% Just remove it from idle_consumers
@@ -914,23 +919,39 @@ consumer_update_active_effects(#state{queue_resource = QName },
914
919
[QName , ConsumerId , false , Ack , Prefetch , Active , ActivityStatus , Args ]}
915
920
| Effects ].
916
921
917
- cancel_consumer0 (ConsumerId ,
918
- # state {consumers = C0 } = S0 , Effects0 ) ->
922
+ cancel_consumer0 (ConsumerId , # state {consumers = C0 } = S0 , Effects0 , Reason ) ->
919
923
case maps :take (ConsumerId , C0 ) of
920
- {# consumer { checked_out = Checked0 }, Cons } ->
921
- S = return_all ( S0 , Checked0 ),
922
- Effects = cancel_consumer_effects (ConsumerId , S , Effects0 ),
923
- case maps :size (Cons ) of
924
+ {Consumer , Cons1 } ->
925
+ { S , Effects2 } = maybe_return_all ( ConsumerId , Consumer , Cons1 , S0 , Effects0 , Reason ),
926
+ Effects = cancel_consumer_effects (ConsumerId , S , Effects2 ),
927
+ case maps :size (S # state . consumers ) of
924
928
0 ->
925
- {S # state { consumers = Cons } , [{aux , inactive } | Effects ]};
929
+ {S , [{aux , inactive } | Effects ]};
926
930
_ ->
927
- {S # state { consumers = Cons } , Effects }
931
+ {S , Effects }
928
932
end ;
929
933
error ->
930
934
% % already removed: do nothing
931
935
{S0 , Effects0 }
932
936
end .
933
937
938
+ maybe_return_all (ConsumerId , # consumer {checked_out = Checked0 } = Consumer , Cons1 ,
939
+ # state {consumers = C0 ,
940
+ service_queue = SQ0 } = S0 , Effects0 , Reason ) ->
941
+ case Reason of
942
+ consumer_cancel ->
943
+ {Cons , SQ , Effects1 } =
944
+ update_or_remove_sub (ConsumerId ,
945
+ Consumer # consumer {lifetime = once ,
946
+ credit = 0 ,
947
+ suspected_down = cancel },
948
+ C0 , SQ0 , Effects0 ),
949
+ {S0 # state {consumers = Cons , service_queue = SQ }, Effects1 };
950
+ down ->
951
+ S1 = return_all (S0 , Checked0 ),
952
+ {S1 # state {consumers = Cons1 }, Effects0 }
953
+ end .
954
+
934
955
apply_enqueue (#{index := RaftIdx } = Meta , From , Seq , RawMsg , State0 ) ->
935
956
Bytes = message_size (RawMsg ),
936
957
case maybe_enqueue (RaftIdx , From , Seq , RawMsg , [], State0 ) of
@@ -1303,6 +1324,8 @@ checkout_one(#state{service_queue = SQ0,
1303
1324
% % can happen when draining
1304
1325
% % recurse without consumer on queue
1305
1326
checkout_one (InitState # state {service_queue = SQ1 });
1327
+ {ok , # consumer {suspected_down = cancel }} ->
1328
+ checkout_one (InitState # state {service_queue = SQ1 });
1306
1329
{ok , # consumer {suspected_down = true }} ->
1307
1330
checkout_one (InitState # state {service_queue = SQ1 });
1308
1331
{ok , # consumer {checked_out = Checked0 ,
0 commit comments