Skip to content

Commit 3a26277

Browse files
Merge pull request #11990 from rabbitmq/mergify/bp/v4.0.x/pr-11979
2 parents 246f031 + 006f517 commit 3a26277

File tree

5 files changed

+136
-37
lines changed

5 files changed

+136
-37
lines changed

deps/rabbit/src/rabbit_amqqueue.erl

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
-export([queue/1, queue_names/1]).
7171

7272
-export([kill_queue/2, kill_queue/3, kill_queue_hard/2, kill_queue_hard/3]).
73+
-export([delete_transient_queues_on_node/1]).
7374

7475
%% internal
7576
-export([internal_declare/2, internal_delete/2, run_backing_queue/3,
@@ -1839,13 +1840,39 @@ on_node_up(_Node) ->
18391840
-spec on_node_down(node()) -> 'ok'.
18401841

18411842
on_node_down(Node) ->
1843+
case delete_transient_queues_on_node(Node) of
1844+
ok ->
1845+
ok;
1846+
{error, timeout} ->
1847+
%% This case is possible when running Khepri. The node going down
1848+
%% could leave the cluster in a minority so the command to delete
1849+
%% the transient queue records would fail. Also see
1850+
%% `rabbit_khepri:init/0': we also try this deletion when the node
1851+
%% restarts - a time that the cluster is very likely to have a
1852+
%% majority - to ensure these records are deleted.
1853+
rabbit_log:warning("transient queues for node '~ts' could not be "
1854+
"deleted because of a timeout. These queues "
1855+
"will be removed when node '~ts' restarts or "
1856+
"is removed from the cluster.", [Node, Node]),
1857+
ok
1858+
end.
1859+
1860+
-spec delete_transient_queues_on_node(Node) -> Ret when
1861+
Node :: node(),
1862+
Ret :: ok | rabbit_khepri:timeout_error().
1863+
1864+
delete_transient_queues_on_node(Node) ->
18421865
{Time, Ret} = timer:tc(fun() -> rabbit_db_queue:delete_transient(filter_transient_queues_to_delete(Node)) end),
18431866
case Ret of
1844-
ok -> ok;
1845-
{QueueNames, Deletions} ->
1867+
ok ->
1868+
ok;
1869+
{error, timeout} = Err ->
1870+
Err;
1871+
{QueueNames, Deletions} when is_list(QueueNames) ->
18461872
case length(QueueNames) of
18471873
0 -> ok;
1848-
N -> rabbit_log:info("~b transient queues from an old incarnation of node ~tp deleted in ~fs",
1874+
N -> rabbit_log:info("~b transient queues from node '~ts' "
1875+
"deleted in ~fs",
18491876
[N, Node, Time / 1_000_000])
18501877
end,
18511878
notify_queue_binding_deletions(Deletions),

deps/rabbit/src/rabbit_db.erl

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,10 @@ init_using_mnesia() ->
100100
rabbit_sup:start_child(mnesia_sync).
101101

102102
init_using_khepri() ->
103-
case rabbit_khepri:members() of
104-
[] ->
105-
timer:sleep(1000),
106-
init_using_khepri();
107-
Members ->
108-
?LOG_WARNING(
109-
"Found the following metadata store members: ~p", [Members],
110-
#{domain => ?RMQLOG_DOMAIN_DB})
111-
end.
103+
?LOG_DEBUG(
104+
"DB: initialize Khepri",
105+
#{domain => ?RMQLOG_DOMAIN_DB}),
106+
rabbit_khepri:init().
112107

113108
init_finished() ->
114109
%% Used during initialisation by rabbit_logger_exchange_h.erl

deps/rabbit/src/rabbit_db_queue.erl

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,8 @@ set_many_in_khepri(Qs) ->
10121012
Queue :: amqqueue:amqqueue(),
10131013
FilterFun :: fun((Queue) -> boolean()),
10141014
QName :: rabbit_amqqueue:name(),
1015-
Ret :: {[QName], [Deletions :: rabbit_binding:deletions()]}.
1015+
Ret :: {[QName], [Deletions :: rabbit_binding:deletions()]}
1016+
| rabbit_khepri:timeout_error().
10161017
%% @doc Deletes all transient queues that match `FilterFun'.
10171018
%%
10181019
%% @private
@@ -1073,26 +1074,59 @@ delete_transient_in_khepri(FilterFun) ->
10731074
%% process might call itself. Instead we can fetch all of the transient
10741075
%% queues with `get_many' and then filter and fold the results outside of
10751076
%% Khepri's Ra server process.
1076-
case rabbit_khepri:get_many(PathPattern) of
1077-
{ok, Qs} ->
1078-
Items = maps:fold(
1079-
fun(Path, Queue, Acc) when ?is_amqqueue(Queue) ->
1080-
case FilterFun(Queue) of
1081-
true ->
1082-
QueueName = khepri_queue_path_to_name(
1083-
Path),
1084-
case delete_in_khepri(QueueName, false) of
1085-
ok ->
1086-
Acc;
1087-
Deletions ->
1088-
[{QueueName, Deletions} | Acc]
1089-
end;
1090-
false ->
1091-
Acc
1092-
end
1093-
end, [], Qs),
1094-
{QueueNames, Deletions} = lists:unzip(Items),
1095-
{QueueNames, lists:flatten(Deletions)};
1077+
case rabbit_khepri:adv_get_many(PathPattern) of
1078+
{ok, Props} ->
1079+
Qs = maps:fold(
1080+
fun(Path0, #{data := Q, payload_version := Vsn}, Acc)
1081+
when ?is_amqqueue(Q) ->
1082+
case FilterFun(Q) of
1083+
true ->
1084+
Path = khepri_path:combine_with_conditions(
1085+
Path0,
1086+
[#if_payload_version{version = Vsn}]),
1087+
QName = amqqueue:get_name(Q),
1088+
[{Path, QName} | Acc];
1089+
false ->
1090+
Acc
1091+
end
1092+
end, [], Props),
1093+
do_delete_transient_queues_in_khepri(Qs, FilterFun);
1094+
{error, _} = Error ->
1095+
Error
1096+
end.
1097+
1098+
do_delete_transient_queues_in_khepri([], _FilterFun) ->
1099+
%% If there are no changes to make, avoid performing a transaction. When
1100+
%% Khepri is in a minority this avoids a long timeout waiting for the
1101+
%% transaction command to be processed. Otherwise it avoids appending a
1102+
%% somewhat large transaction command to Khepri's log.
1103+
{[], []};
1104+
do_delete_transient_queues_in_khepri(Qs, FilterFun) ->
1105+
Res = rabbit_khepri:transaction(
1106+
fun() ->
1107+
rabbit_misc:fold_while_ok(
1108+
fun({Path, QName}, Acc) ->
1109+
%% Also see `delete_in_khepri/2'.
1110+
case khepri_tx_adv:delete(Path) of
1111+
{ok, #{data := _}} ->
1112+
Deletions = rabbit_db_binding:delete_for_destination_in_khepri(
1113+
QName, false),
1114+
{ok, [{QName, Deletions} | Acc]};
1115+
{ok, _} ->
1116+
{ok, Acc};
1117+
{error, _} = Error ->
1118+
Error
1119+
end
1120+
end, [], Qs)
1121+
end),
1122+
case Res of
1123+
{ok, Items} ->
1124+
{QNames, Deletions} = lists:unzip(Items),
1125+
{QNames, lists:flatten(Deletions)};
1126+
{error, {khepri, mismatching_node, _}} ->
1127+
%% One of the queues changed while attempting to update all
1128+
%% queues. Retry the operation.
1129+
delete_transient_in_khepri(FilterFun);
10961130
{error, _} = Error ->
10971131
Error
10981132
end.
@@ -1366,6 +1400,3 @@ khepri_queues_path() ->
13661400

13671401
khepri_queue_path(#resource{virtual_host = VHost, name = Name}) ->
13681402
[?MODULE, queues, VHost, Name].
1369-
1370-
khepri_queue_path_to_name([?MODULE, queues, VHost, Name]) ->
1371-
rabbit_misc:r(VHost, queue, Name).

deps/rabbit/src/rabbit_khepri.erl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696

9797
-export([setup/0,
9898
setup/1,
99+
init/0,
99100
can_join_cluster/1,
100101
add_member/2,
101102
remove_member/1,
@@ -323,6 +324,30 @@ wait_for_register_projections(Timeout, Retries) ->
323324

324325
%% @private
325326

327+
-spec init() -> Ret when
328+
Ret :: ok | timeout_error().
329+
330+
init() ->
331+
case members() of
332+
[] ->
333+
timer:sleep(1000),
334+
init();
335+
Members ->
336+
?LOG_NOTICE(
337+
"Found the following metadata store members: ~p", [Members],
338+
#{domain => ?RMQLOG_DOMAIN_DB}),
339+
%% Delete transient queues on init.
340+
%% Note that we also do this in the
341+
%% `rabbit_amqqueue:on_node_down/1' callback. We must try this
342+
%% deletion during init because the cluster may have been in a
343+
%% minority when this node went down. We wait for a majority while
344+
%% booting (via `rabbit_khepri:setup/0') though so this deletion is
345+
%% likely to succeed.
346+
rabbit_amqqueue:delete_transient_queues_on_node(node())
347+
end.
348+
349+
%% @private
350+
326351
can_join_cluster(DiscoveryNode) when is_atom(DiscoveryNode) ->
327352
ThisNode = node(),
328353
try

deps/rabbit_common/src/rabbit_misc.erl

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
maps_put_falsy/3
9090
]).
9191
-export([remote_sup_child/2]).
92-
-export([for_each_while_ok/2]).
92+
-export([for_each_while_ok/2, fold_while_ok/3]).
9393

9494
%% Horrible macro to use in guards
9595
-define(IS_BENIGN_EXIT(R),
@@ -1655,3 +1655,24 @@ for_each_while_ok(Fun, [Elem | Rest]) ->
16551655
end;
16561656
for_each_while_ok(_, []) ->
16571657
ok.
1658+
1659+
-spec fold_while_ok(FoldFun, Acc, List) -> Ret when
1660+
FoldFun :: fun((Element, Acc) -> {ok, Acc} | {error, ErrReason}),
1661+
Element :: any(),
1662+
List :: Element,
1663+
Ret :: {ok, Acc} | {error, ErrReason}.
1664+
%% @doc Calls the given `FoldFun' on each element of the given `List' and the
1665+
%% accumulator value, short-circuiting if the function returns `{error,_}'.
1666+
%%
1667+
%% @returns the first `{error,_}' returned by `FoldFun' or `{ok,Acc}' if
1668+
%% `FoldFun' never returns an error tuple.
1669+
1670+
fold_while_ok(Fun, Acc0, [Elem | Rest]) ->
1671+
case Fun(Elem, Acc0) of
1672+
{ok, Acc} ->
1673+
fold_while_ok(Fun, Acc, Rest);
1674+
{error, _} = Error ->
1675+
Error
1676+
end;
1677+
fold_while_ok(_Fun, Acc, []) ->
1678+
{ok, Acc}.

0 commit comments

Comments
 (0)