Skip to content

Commit 9594857

Browse files
committed
Replace rabbit_fifo_dlx_sup with a two-level supervisor hierarchy
The flat simple_one_for_one rabbit_fifo_dlx_sup allowed a burst of DLX worker crashes to trip the supervisor's restart intensity and render it permanently unavailable, stranding all at_least_once DLX quorum queues cluster-wide (GitHub #16652). Replace it with: - rabbit_fifo_dlx_sup_sup: simple_one_for_one top-level supervisor whose children are per-queue worker supervisors (temporary). - rabbit_fifo_dlx_worker_sup: one_for_one per-queue supervisor with intensity 0. A worker crash terminates only its own supervisor, isolating failures to a single queue. Additional fixes: - ensure_worker_started now checks live supervisor state via find_worker_sup/1 before starting a new worker, preventing duplicate worker_sups from concurrent state_enter and {dlx, setup} aux calls. - state_enter(eol, ...) now terminates the DLX worker on queue deletion, preventing orphaned worker_sups after queue cleanup.
1 parent 0e47691 commit 9594857

8 files changed

Lines changed: 112 additions & 30 deletions

deps/rabbit/src/rabbit_fifo.erl

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,13 @@ state_enter(leader,
11341134
Effects;
11351135
state_enter(eol, #?STATE{enqueuers = Enqs,
11361136
consumers = Cons0,
1137-
waiting_consumers = WaitingConsumers0}) ->
1137+
waiting_consumers = WaitingConsumers0,
1138+
cfg = #cfg{dead_letter_handler = DLH},
1139+
dlx = DlxState}) ->
1140+
case DLH of
1141+
at_least_once -> ensure_worker_terminated(DlxState);
1142+
_ -> ok
1143+
end,
11381144
Custs = maps:fold(fun(_K, ?CONSUMER_PID(P) = V, S) ->
11391145
S#{P => V}
11401146
end, #{}, Cons0),
@@ -4025,22 +4031,39 @@ dlx_apply(_, Cmd, DLH, State) ->
40254031
%% enqueue overhead 210
40264032

40274033
ensure_worker_started(QRef, #?DLX{consumer = undefined}) ->
4028-
start_worker(QRef);
4034+
case find_worker_sup(QRef) of
4035+
undefined -> start_worker(QRef);
4036+
_SupPid -> ok
4037+
end;
40294038
ensure_worker_started(QRef, #?DLX{consumer = #dlx_consumer{pid = Pid}}) ->
40304039
case is_local_and_alive(Pid) of
40314040
true ->
40324041
?LOG_DEBUG("rabbit_fifo_dlx_worker ~tp already started for ~ts",
40334042
[Pid, rabbit_misc:rs(QRef)]);
40344043
false ->
4035-
start_worker(QRef)
4044+
case find_worker_sup(QRef) of
4045+
undefined -> start_worker(QRef);
4046+
_SupPid -> ok
4047+
end
40364048
end.
40374049

4050+
find_worker_sup(QRef) ->
4051+
lists:foldl(
4052+
fun({_, SupPid, _, _}, undefined) ->
4053+
case rabbit_fifo_dlx_worker_sup:get_qref(SupPid) of
4054+
QRef -> SupPid;
4055+
_ -> undefined
4056+
end;
4057+
(_, Found) -> Found
4058+
end, undefined, supervisor:which_children(rabbit_fifo_dlx_sup_sup)).
4059+
40384060
%% Ensure that starting the rabbit_fifo_dlx_worker succeeds.
40394061
%% Therefore, do not use an effect.
40404062
%% Also therefore, if starting the rabbit_fifo_dlx_worker fails, let the
40414063
%% Ra server process crash in which case another Ra node will become leader.
40424064
start_worker(QRef) ->
4043-
{ok, Pid} = supervisor:start_child(rabbit_fifo_dlx_sup, [QRef]),
4065+
{ok, SupPid} = supervisor:start_child(rabbit_fifo_dlx_sup_sup, [QRef]),
4066+
[{_, Pid, worker, _}] = supervisor:which_children(SupPid),
40444067
?LOG_DEBUG("started rabbit_fifo_dlx_worker ~tp for ~ts",
40454068
[Pid, rabbit_misc:rs(QRef)]).
40464069

@@ -4054,7 +4077,10 @@ terminate_dlx_worker(Pid) ->
40544077
true ->
40554078
%% Note that we can't return a mod_call effect here
40564079
%% because mod_call is executed on the leader only.
4057-
ok = supervisor:terminate_child(rabbit_fifo_dlx_sup, Pid),
4080+
%% Terminate the per-worker supervisor (which also stops the worker).
4081+
{dictionary, Dict} = erlang:process_info(Pid, dictionary),
4082+
{_, [SupPid | _]} = lists:keyfind('$ancestors', 1, Dict),
4083+
ok = supervisor:terminate_child(rabbit_fifo_dlx_sup_sup, SupPid),
40584084
?LOG_DEBUG("terminated rabbit_fifo_dlx_worker ~tp", [Pid]);
40594085
false ->
40604086
ok

deps/rabbit/src/rabbit_fifo_dlx.erl

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,22 +255,39 @@ state_enter(_, _, _, _) ->
255255
[].
256256

257257
ensure_worker_started(QRef, #?MODULE{consumer = undefined}) ->
258-
start_worker(QRef);
258+
case find_worker_sup(QRef) of
259+
undefined -> start_worker(QRef);
260+
_SupPid -> ok
261+
end;
259262
ensure_worker_started(QRef, #?MODULE{consumer = #dlx_consumer{pid = Pid}}) ->
260263
case is_local_and_alive(Pid) of
261264
true ->
262265
?LOG_DEBUG("rabbit_fifo_dlx_worker ~tp already started for ~ts",
263266
[Pid, rabbit_misc:rs(QRef)]);
264267
false ->
265-
start_worker(QRef)
268+
case find_worker_sup(QRef) of
269+
undefined -> start_worker(QRef);
270+
_SupPid -> ok
271+
end
266272
end.
267273

274+
find_worker_sup(QRef) ->
275+
lists:foldl(
276+
fun({_, SupPid, _, _}, undefined) ->
277+
case rabbit_fifo_dlx_worker_sup:get_qref(SupPid) of
278+
QRef -> SupPid;
279+
_ -> undefined
280+
end;
281+
(_, Found) -> Found
282+
end, undefined, supervisor:which_children(rabbit_fifo_dlx_sup_sup)).
283+
268284
%% Ensure that starting the rabbit_fifo_dlx_worker succeeds.
269285
%% Therefore, do not use an effect.
270286
%% Also therefore, if starting the rabbit_fifo_dlx_worker fails, let the
271287
%% Ra server process crash in which case another Ra node will become leader.
272288
start_worker(QRef) ->
273-
{ok, Pid} = supervisor:start_child(rabbit_fifo_dlx_sup, [QRef]),
289+
{ok, SupPid} = supervisor:start_child(rabbit_fifo_dlx_sup_sup, [QRef]),
290+
[{_, Pid, worker, _}] = supervisor:which_children(SupPid),
274291
?LOG_DEBUG("started rabbit_fifo_dlx_worker ~tp for ~ts",
275292
[Pid, rabbit_misc:rs(QRef)]).
276293

@@ -281,7 +298,10 @@ ensure_worker_terminated(#?MODULE{consumer = #dlx_consumer{pid = Pid}}) ->
281298
true ->
282299
%% Note that we can't return a mod_call effect here
283300
%% because mod_call is executed on the leader only.
284-
ok = supervisor:terminate_child(rabbit_fifo_dlx_sup, Pid),
301+
%% Terminate the per-worker supervisor (which also stops the worker).
302+
{dictionary, Dict} = erlang:process_info(Pid, dictionary),
303+
{_, [SupPid | _]} = lists:keyfind('$ancestors', 1, Dict),
304+
ok = supervisor:terminate_child(rabbit_fifo_dlx_sup_sup, SupPid),
285305
?LOG_DEBUG("terminated rabbit_fifo_dlx_worker ~tp", [Pid]);
286306
false ->
287307
ok

deps/rabbit/src/rabbit_fifo_dlx_sup.erl renamed to deps/rabbit/src/rabbit_fifo_dlx_sup_sup.erl

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
%% License, v. 2.0. If a copy of the MPL was not distributed with this
33
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
44
%%
5-
%% Copyright (c) 2007-2026 Broadcom. All Rights Reserved. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
5+
%% Copyright (c) 2007-2026 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
66

7-
-module(rabbit_fifo_dlx_sup).
7+
-module(rabbit_fifo_dlx_sup_sup).
88

99
-behaviour(supervisor).
1010

@@ -24,13 +24,9 @@ start_link() ->
2424
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
2525

2626
init([]) ->
27-
SupFlags = #{strategy => simple_one_for_one,
28-
intensity => 100,
29-
period => 1},
30-
Worker = rabbit_fifo_dlx_worker,
31-
ChildSpec = #{id => Worker,
32-
start => {Worker, start_link, []},
33-
type => worker,
34-
restart => transient,
35-
modules => [Worker]},
27+
SupFlags = #{strategy => simple_one_for_one},
28+
ChildSpec = #{id => rabbit_fifo_dlx_worker_sup,
29+
start => {rabbit_fifo_dlx_worker_sup, start_link, []},
30+
type => supervisor,
31+
restart => temporary},
3632
{ok, {SupFlags, [ChildSpec]}}.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
%% This Source Code Form is subject to the terms of the Mozilla Public
2+
%% License, v. 2.0. If a copy of the MPL was not distributed with this
3+
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
%%
5+
%% Copyright (c) 2007-2026 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
6+
7+
%% Per-queue supervisor for a single rabbit_fifo_dlx_worker process.
8+
%% Started as a temporary child of rabbit_fifo_dlx_sup_sup (simple_one_for_one).
9+
%% This isolation prevents a burst of DLX worker crashes from taking down
10+
%% the top-level supervisor and stranding unrelated queues.
11+
-module(rabbit_fifo_dlx_worker_sup).
12+
13+
-behaviour(supervisor).
14+
15+
-export([start_link/1, init/1, get_qref/1]).
16+
17+
start_link(QRef) ->
18+
supervisor:start_link(?MODULE, [QRef]).
19+
20+
init([QRef]) ->
21+
erlang:put(rabbit_fifo_dlx_qref, QRef),
22+
SupFlags = #{strategy => one_for_one,
23+
intensity => 0,
24+
period => 1},
25+
ChildSpec = #{id => rabbit_fifo_dlx_worker,
26+
start => {rabbit_fifo_dlx_worker, start_link, [QRef]},
27+
type => worker,
28+
restart => transient,
29+
shutdown => 5000},
30+
{ok, {SupFlags, [ChildSpec]}}.
31+
32+
-spec get_qref(pid()) -> rabbit_fifo:rabbit_fifo_queue_ref() | undefined.
33+
get_qref(SupPid) ->
34+
case erlang:process_info(SupPid, dictionary) of
35+
{dictionary, Dict} ->
36+
proplists:get_value(rabbit_fifo_dlx_qref, Dict);
37+
undefined ->
38+
undefined
39+
end.

deps/rabbit/src/rabbit_quorum_queue.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2842,7 +2842,7 @@ queue_vm_stats_sups() ->
28422842
{[quorum_queue_procs,
28432843
quorum_queue_dlx_procs],
28442844
[[ra_server_sup_sup],
2845-
[rabbit_fifo_dlx_sup]]}.
2845+
[rabbit_fifo_dlx_sup_sup]]}.
28462846

28472847
queue_vm_ets() ->
28482848
{[quorum_ets],

deps/rabbit/src/rabbit_vm.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ memory() ->
2222
%% example for existing info keys:
2323
%% [{queue_procs, queue_sups()},
2424
%% {quorum_queue_procs, [ra_server_sup_sup]},
25-
%% {quorum_queue_dlx_procs, [rabbit_fifo_dlx_sup]},
25+
%% {quorum_queue_dlx_procs, [rabbit_fifo_dlx_sup_sup]},
2626
%% {stream_queue_procs, [osiris_server_sup]},
2727
%% {stream_queue_replica_reader_procs, [osiris_replica_reader_sup]},
2828
%% {stream_queue_coordinator_procs, [rabbit_stream_coordinator]}]

deps/rabbit/test/rabbit_fifo_dlx_SUITE.erl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ switch_strategies(_Config) ->
128128
name = <<"blah">>},
129129
application:set_env(rabbit, dead_letter_worker_consumer_prefetch, 1),
130130
application:set_env(rabbit, dead_letter_worker_publisher_confirm_timeout, 1000),
131-
{ok, _} = rabbit_fifo_dlx_sup:start_link(),
131+
{ok, _} = rabbit_fifo_dlx_sup_sup:start_link(),
132132
S0 = rabbit_fifo_dlx:init(),
133133

134134
Handler0 = undefined,
@@ -141,7 +141,8 @@ switch_strategies(_Config) ->
141141
{aux, {dlx, setup}}],
142142
Effects0),
143143
rabbit_fifo_dlx:handle_aux(leader, {dlx, setup}, fake_aux, QRes, Handler1, S1),
144-
[{_, WorkerPid, worker, _}] = supervisor:which_children(rabbit_fifo_dlx_sup),
144+
[{_, WorkerSupPid, supervisor, _}] = supervisor:which_children(rabbit_fifo_dlx_sup_sup),
145+
[{_, WorkerPid, worker, _}] = supervisor:which_children(WorkerSupPid),
145146
{S2, _} = rabbit_fifo_dlx:discard([make_msg(1)], because, Handler1, S1),
146147
Checkout = rabbit_fifo_dlx:make_checkout(WorkerPid, 1),
147148
{S3, _} = rabbit_fifo_dlx:apply(meta(2), Checkout, Handler1, S2),
@@ -158,7 +159,7 @@ switch_strategies(_Config) ->
158159
[1, 1, "queue 'blah' in vhost '/'"]]}],
159160
Effects),
160161
?assertMatch([_, {active, 0}, _, _],
161-
supervisor:count_children(rabbit_fifo_dlx_sup)),
162+
supervisor:count_children(rabbit_fifo_dlx_sup_sup)),
162163
?assertMatch(#{num_discarded := 0}, rabbit_fifo_dlx:overview(S5)),
163164
ok.
164165

deps/rabbit/test/rabbit_fifo_dlx_integration_SUITE.erl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ end_per_testcase(Testcase, Config) ->
154154
?awaitMatch(
155155
true,
156156
begin
157-
DlxWorkers = rabbit_ct_broker_helpers:rpc_all(Config, supervisor, which_children, [rabbit_fifo_dlx_sup]),
157+
DlxWorkers = rabbit_ct_broker_helpers:rpc_all(Config, supervisor, which_children, [rabbit_fifo_dlx_sup_sup]),
158158
lists:all(fun(L) -> L =:= [] end, DlxWorkers)
159159
end, 60000),
160160

@@ -513,7 +513,7 @@ drop_head_falls_back_to_at_most_once(Config) ->
513513
consistently(
514514
?_assertMatch(
515515
[_, {active, 0}, _, _],
516-
rpc(Config, Server, supervisor, count_children, [rabbit_fifo_dlx_sup]))).
516+
rpc(Config, Server, supervisor, count_children, [rabbit_fifo_dlx_sup_sup]))).
517517

518518
%% Test that dynamically switching dead-letter-strategy works.
519519
switch_strategy(Config) ->
@@ -935,7 +935,7 @@ single_dlx_worker(Config) ->
935935
[[_, {active, 1}, _, _],
936936
[_, {active, 0}, _, _],
937937
[_, {active, 0}, _, _]],
938-
rabbit_ct_broker_helpers:rpc_all(Config, supervisor, count_children, [rabbit_fifo_dlx_sup])),
938+
rabbit_ct_broker_helpers:rpc_all(Config, supervisor, count_children, [rabbit_fifo_dlx_sup_sup])),
939939

940940
ok = rabbit_ct_broker_helpers:stop_broker(Config, Server1),
941941
RaName = ra_name(SourceQ),
@@ -948,7 +948,7 @@ single_dlx_worker(Config) ->
948948
consistently(
949949
?_assertEqual(
950950
0,
951-
length(rpc(Config, Server1, supervisor, which_children, [rabbit_fifo_dlx_sup], 1000)))),
951+
length(rpc(Config, Server1, supervisor, which_children, [rabbit_fifo_dlx_sup_sup], 1000)))),
952952

953953
%% Kill the leader node to verify the DLX worker follows leadership.
954954
%% We kill the node rather than just the Ra process: killing only the process
@@ -965,7 +965,7 @@ single_dlx_worker(Config) ->
965965

966966
assert_active_dlx_workers(N, Config, Server) ->
967967
?awaitMatch(N,
968-
length(rpc(Config, Server, supervisor, which_children, [rabbit_fifo_dlx_sup], 5000)),
968+
length(rpc(Config, Server, supervisor, which_children, [rabbit_fifo_dlx_sup_sup], 5000)),
969969
60_000).
970970

971971
declare_queue(Channel, Queue, Args) ->

0 commit comments

Comments
 (0)