Skip to content

Commit 92ad833

Browse files
authored
Merge pull request #16976 from pterygota/stream-sac-stuck-partition
Fix stream SAC coordinator ignoring deactivating consumer status
2 parents cd0f4c8 + 16fd6bd commit 92ad833

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

deps/rabbit/src/rabbit_stream_sac_coordinator.erl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,12 +386,16 @@ apply(#command_activate_consumer{vhost = VH, stream = S, consumer_name = Name},
386386
%% do we need effects or not?
387387
Effects =
388388
case Csr of
389-
Csr when ?SAME_CSR(Csr, ActCsr) ->
389+
Csr when ?SAME_CSR(Csr, ActCsr) andalso
390+
ActCsr#consumer.status =:= ?CONN_ACT ->
390391
%% it is the same active consumer as before
391392
%% no need to notify it
392393
[];
393394
_ ->
394395
%% new active consumer, need to notify it
396+
%% (also fires when the previous "active" was
397+
%% deactivating -- its client has been told to
398+
%% step down and must be re-notified)
395399
[notify_csr_effect(Csr, S, Name, true)]
396400
end,
397401
{G2, Effects}

deps/rabbit/test/rabbit_stream_sac_coordinator_SUITE.erl

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,61 @@ super_stream_partition_sac_test(_) ->
226226

227227
ok.
228228

229+
%% Regression test for a stuck-group state. When the arrival of the stepping-down
230+
%% consumer's consumer_update response triggers command_activate_consumer and
231+
%% evaluate_active_consumer picks the same consumer that just stepped down,
232+
%% the coordinator must still notify that consumer so its client learns it is
233+
%% active again. Without the notification, the coordinator believes the group
234+
%% has an active member while the client has been told active=false, and the
235+
%% group is silently stranded.
236+
super_stream_partition_sac_re_notifies_stepping_down_consumer_re_selected_as_active_test(_) ->
237+
Stream = <<"stream">>,
238+
ConsumerName = <<"app">>,
239+
ConnectionPid = self(),
240+
GroupId = {<<"/">>, Stream, ConsumerName},
241+
%% Partition index chosen so that evaluate_active_consumer picks the first
242+
%% consumer once three have joined (3 rem 3 = 0), which is precisely the
243+
%% one deactivated by the two-consumer rebalance below.
244+
PartitionIndex = 3,
245+
246+
Command0 = register_consumer_command(Stream, PartitionIndex, ConsumerName, ConnectionPid, 0),
247+
State0 = state(),
248+
{State1, {ok, true}, Effects1} = ?MOD:apply(Command0, State0),
249+
assertSendMessageActivateEffect(ConnectionPid, 0, Stream, ConsumerName, true, Effects1),
250+
251+
Command1 = register_consumer_command(Stream, PartitionIndex, ConsumerName, ConnectionPid, 1),
252+
{State2, {ok, false}, Effects2} = ?MOD:apply(Command1, State1),
253+
%% 3 rem 2 = 1 selects subscription 1, so subscription 0 is asked to step down.
254+
assertSendMessageSteppingDownEffect(ConnectionPid, 0, Stream, ConsumerName, Effects2),
255+
256+
Command2 = register_consumer_command(Stream, PartitionIndex, ConsumerName, ConnectionPid, 2),
257+
{#?STATE{groups = #{GroupId := #group{consumers = Consumers3}}} = State3,
258+
{ok, false}, Effects3} = ?MOD:apply(Command2, State2),
259+
%% 3 rem 3 = 0 selects subscription 0, which is still lookup_active_consumer
260+
%% because is_active({_, deactivating}) is true, so no rebalance happens.
261+
assertCsrsEqual([csr(ConnectionPid, 0, deactivating),
262+
csr(ConnectionPid, 1, waiting),
263+
csr(ConnectionPid, 2, waiting)],
264+
Consumers3),
265+
assertEmpty(Effects3),
266+
267+
%% Simulate the arrival of subscription 0's consumer_update response, which
268+
%% causes the stream reader to fire command_activate_consumer.
269+
Command3 = activate_consumer_command(Stream, ConsumerName),
270+
{#?STATE{groups = #{GroupId := #group{consumers = Consumers4}}},
271+
ok, Effects4} = ?MOD:apply(Command3, State3),
272+
273+
%% Subscription 0 is selected as active again by 3 rem 3 = 0.
274+
assertCsrsEqual([csr(ConnectionPid, 0, active),
275+
csr(ConnectionPid, 1, waiting),
276+
csr(ConnectionPid, 2, waiting)],
277+
Consumers4),
278+
%% Subscription 0's client was previously told active=false and must be
279+
%% notified it is active again.
280+
assertSendMessageActivateEffect(ConnectionPid, 0, Stream, ConsumerName, true, Effects4),
281+
282+
ok.
283+
229284
ensure_monitors_test(_) ->
230285
GroupId = {<<"/">>, <<"stream">>, <<"app">>},
231286
Group = grp([csr(self(), 0, true), csr(self(), 1, false)]),

0 commit comments

Comments
 (0)