Miscellaneous minor improvements in stream SAC coordinator
This commit handles edge cases in the stream SAC coordinator to make sure it does not crash during execution. Most of these edge cases consist in an inconsistent state, so there are very unlikely to happen. This commit also makes sure there is no duplicate in the consumer list of a group. Consumers are also now identified only by their connection PID and their subscription ID, as now the timestamp they contain in their state does not allow a field-by-field comparison.
This commit is contained in:
parent
4bca14a4bb
commit
b4f7d46842
|
@ -710,8 +710,7 @@ apply(#{machine_version := Vsn} = Meta,
|
||||||
_ ->
|
_ ->
|
||||||
return(Meta, State0, stream_not_found, [])
|
return(Meta, State0, stream_not_found, [])
|
||||||
end;
|
end;
|
||||||
apply(#{machine_version := Vsn} = Meta,
|
apply(Meta, {nodeup, Node} = Cmd,
|
||||||
{nodeup, Node} = Cmd,
|
|
||||||
#?MODULE{monitors = Monitors0,
|
#?MODULE{monitors = Monitors0,
|
||||||
streams = Streams0,
|
streams = Streams0,
|
||||||
single_active_consumer = Sac0} = State) ->
|
single_active_consumer = Sac0} = State) ->
|
||||||
|
@ -735,14 +734,8 @@ apply(#{machine_version := Vsn} = Meta,
|
||||||
{Ss#{Id => S}, E}
|
{Ss#{Id => S}, E}
|
||||||
end, {Streams0, Effects0}, Streams0),
|
end, {Streams0, Effects0}, Streams0),
|
||||||
|
|
||||||
{Sac1, Effects2} = case ?V5_OR_MORE(Vsn) of
|
|
||||||
true ->
|
{Sac1, Effects2} = sac_handle_node_reconnected(Meta, Node, Sac0, Effects1),
|
||||||
SacMod = sac_module(Meta),
|
|
||||||
SacMod:handle_node_reconnected(Node,
|
|
||||||
Sac0, Effects1);
|
|
||||||
false ->
|
|
||||||
{Sac0, Effects1}
|
|
||||||
end,
|
|
||||||
return(Meta, State#?MODULE{monitors = Monitors,
|
return(Meta, State#?MODULE{monitors = Monitors,
|
||||||
streams = Streams,
|
streams = Streams,
|
||||||
single_active_consumer = Sac1}, ok, Effects2);
|
single_active_consumer = Sac1}, ok, Effects2);
|
||||||
|
@ -2444,6 +2437,17 @@ sac_handle_connection_down(SacState, Pid, Reason, Vsn) when ?V5_OR_MORE(Vsn) ->
|
||||||
sac_handle_connection_down(SacState, Pid, _Reason, _Vsn) ->
|
sac_handle_connection_down(SacState, Pid, _Reason, _Vsn) ->
|
||||||
?SAC_V4:handle_connection_down(Pid, SacState).
|
?SAC_V4:handle_connection_down(Pid, SacState).
|
||||||
|
|
||||||
|
sac_handle_node_reconnected(#{machine_version := Vsn} = Meta, Node,
|
||||||
|
Sac, Effects) ->
|
||||||
|
case ?V5_OR_MORE(Vsn) of
|
||||||
|
true ->
|
||||||
|
SacMod = sac_module(Meta),
|
||||||
|
SacMod:handle_node_reconnected(Node,
|
||||||
|
Sac, Effects);
|
||||||
|
false ->
|
||||||
|
{Sac, Effects}
|
||||||
|
end.
|
||||||
|
|
||||||
sac_make_purge_nodes(Nodes) ->
|
sac_make_purge_nodes(Nodes) ->
|
||||||
rabbit_stream_sac_coordinator:make_purge_nodes(Nodes).
|
rabbit_stream_sac_coordinator:make_purge_nodes(Nodes).
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,11 @@
|
||||||
-define(DISCONNECTED_TIMEOUT_MS, 60_000).
|
-define(DISCONNECTED_TIMEOUT_MS, 60_000).
|
||||||
-define(SAC_ERRORS, [partition_index_conflict, not_found]).
|
-define(SAC_ERRORS, [partition_index_conflict, not_found]).
|
||||||
-define(IS_STATE_REC(T), is_record(T, ?MODULE)).
|
-define(IS_STATE_REC(T), is_record(T, ?MODULE)).
|
||||||
|
-define(IS_GROUP_REC(T), is_record(T, group)).
|
||||||
|
-define(SAME_CSR(C1, C2),
|
||||||
|
(is_record(C1, consumer) andalso is_record(C2, consumer) andalso
|
||||||
|
C1#consumer.pid =:= C2#consumer.pid andalso
|
||||||
|
C1#consumer.subscription_id =:= C2#consumer.subscription_id)).
|
||||||
|
|
||||||
%% Single Active Consumer API
|
%% Single Active Consumer API
|
||||||
-spec register_consumer(binary(),
|
-spec register_consumer(binary(),
|
||||||
|
@ -132,6 +137,7 @@ activate_consumer(VH, Stream, Name) ->
|
||||||
stream = Stream,
|
stream = Stream,
|
||||||
consumer_name= Name}).
|
consumer_name= Name}).
|
||||||
|
|
||||||
|
%% called by a stream connection to inform it is still alive
|
||||||
-spec connection_reconnected(connection_pid()) ->
|
-spec connection_reconnected(connection_pid()) ->
|
||||||
ok | {error, sac_error() | term()}.
|
ok | {error, sac_error() | term()}.
|
||||||
connection_reconnected(Pid) ->
|
connection_reconnected(Pid) ->
|
||||||
|
@ -228,10 +234,10 @@ apply(#command_register_consumer{vhost = VirtualHost,
|
||||||
subscription_id = SubscriptionId},
|
subscription_id = SubscriptionId},
|
||||||
#?MODULE{groups = StreamGroups0} = State) ->
|
#?MODULE{groups = StreamGroups0} = State) ->
|
||||||
case maybe_create_group(VirtualHost,
|
case maybe_create_group(VirtualHost,
|
||||||
Stream,
|
Stream,
|
||||||
PartitionIndex,
|
PartitionIndex,
|
||||||
ConsumerName,
|
ConsumerName,
|
||||||
StreamGroups0) of
|
StreamGroups0) of
|
||||||
{ok, StreamGroups1} ->
|
{ok, StreamGroups1} ->
|
||||||
do_register_consumer(VirtualHost,
|
do_register_consumer(VirtualHost,
|
||||||
Stream,
|
Stream,
|
||||||
|
@ -256,8 +262,7 @@ apply(#command_unregister_consumer{vhost = VirtualHost,
|
||||||
{State0, []};
|
{State0, []};
|
||||||
Group0 ->
|
Group0 ->
|
||||||
{Group1, Effects} =
|
{Group1, Effects} =
|
||||||
case lookup_consumer(ConnectionPid, SubscriptionId, Group0)
|
case lookup_consumer(ConnectionPid, SubscriptionId, Group0) of
|
||||||
of
|
|
||||||
{value, Consumer} ->
|
{value, Consumer} ->
|
||||||
G1 = remove_from_group(Consumer, Group0),
|
G1 = remove_from_group(Consumer, Group0),
|
||||||
handle_consumer_removal(
|
handle_consumer_removal(
|
||||||
|
@ -274,27 +279,24 @@ apply(#command_unregister_consumer{vhost = VirtualHost,
|
||||||
{State0#?MODULE{groups = SGS}, Effects}
|
{State0#?MODULE{groups = SGS}, Effects}
|
||||||
end,
|
end,
|
||||||
{State1, ok, Effects1};
|
{State1, ok, Effects1};
|
||||||
apply(#command_activate_consumer{vhost = VirtualHost,
|
apply(#command_activate_consumer{vhost = VH, stream = S, consumer_name = Name},
|
||||||
stream = Stream,
|
|
||||||
consumer_name = ConsumerName},
|
|
||||||
#?MODULE{groups = StreamGroups0} = State0) ->
|
#?MODULE{groups = StreamGroups0} = State0) ->
|
||||||
{G, Eff} =
|
{G, Eff} =
|
||||||
case lookup_group(VirtualHost, Stream, ConsumerName, StreamGroups0) of
|
case lookup_group(VH, S, Name, StreamGroups0) of
|
||||||
undefined ->
|
undefined ->
|
||||||
rabbit_log:warning("Trying to activate consumer in group ~tp, but "
|
rabbit_log:warning("Trying to activate consumer in group ~tp, but "
|
||||||
"the group does not longer exist",
|
"the group does not longer exist",
|
||||||
[{VirtualHost, Stream, ConsumerName}]),
|
[{VH, S, Name}]),
|
||||||
{undefined, []};
|
{undefined, []};
|
||||||
G0 ->
|
G0 ->
|
||||||
%% keep track of the former active, if any
|
%% keep track of the former active, if any
|
||||||
{ActPid, ActSubId} =
|
ActCsr = case lookup_active_consumer(G0) of
|
||||||
case lookup_active_consumer(G0) of
|
{value, Consumer} ->
|
||||||
{value, #consumer{pid = ActivePid,
|
Consumer;
|
||||||
subscription_id = ActiveSubId}} ->
|
_ ->
|
||||||
{ActivePid, ActiveSubId};
|
undefined
|
||||||
_ ->
|
end,
|
||||||
{-1, -1}
|
%% connected consumers are set to waiting status
|
||||||
end,
|
|
||||||
G1 = update_connected_consumers(G0, ?CONN_WAIT),
|
G1 = update_connected_consumers(G0, ?CONN_WAIT),
|
||||||
case evaluate_active_consumer(G1) of
|
case evaluate_active_consumer(G1) of
|
||||||
undefined ->
|
undefined ->
|
||||||
|
@ -302,26 +304,23 @@ apply(#command_activate_consumer{vhost = VirtualHost,
|
||||||
#consumer{status = {?DISCONNECTED, _}} ->
|
#consumer{status = {?DISCONNECTED, _}} ->
|
||||||
%% we keep it this way, the consumer may come back
|
%% we keep it this way, the consumer may come back
|
||||||
{G1, []};
|
{G1, []};
|
||||||
#consumer{pid = Pid, subscription_id = SubId} ->
|
Csr ->
|
||||||
G2 = update_consumer_state_in_group(G1, Pid,
|
G2 = update_consumer_state_in_group(G1, Csr, ?CONN_ACT),
|
||||||
SubId,
|
|
||||||
?CONN_ACT),
|
|
||||||
%% do we need effects or not?
|
%% do we need effects or not?
|
||||||
Effects =
|
Effects =
|
||||||
case {Pid, SubId} of
|
case Csr of
|
||||||
{ActPid, ActSubId} ->
|
Csr when ?SAME_CSR(Csr, ActCsr) ->
|
||||||
%% it is the same active consumer as before
|
%% it is the same active consumer as before
|
||||||
%% no need to notify it
|
%% no need to notify it
|
||||||
[];
|
[];
|
||||||
_ ->
|
_ ->
|
||||||
%% new active consumer, need to notify it
|
%% new active consumer, need to notify it
|
||||||
[notify_consumer_effect(Pid, SubId, Stream,
|
[notify_csr_effect(Csr, S, Name, true)]
|
||||||
ConsumerName, true)]
|
end,
|
||||||
end,
|
|
||||||
{G2, Effects}
|
{G2, Effects}
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
StreamGroups1 = update_groups(VirtualHost, Stream, ConsumerName,
|
StreamGroups1 = update_groups(VH, S, Name,
|
||||||
G, StreamGroups0),
|
G, StreamGroups0),
|
||||||
R = case G of
|
R = case G of
|
||||||
undefined ->
|
undefined ->
|
||||||
|
@ -363,28 +362,30 @@ handle_group_connection_reconnected(Pid, #?MODULE{groups = Groups0} = S0,
|
||||||
undefined ->
|
undefined ->
|
||||||
{S0, Eff0};
|
{S0, Eff0};
|
||||||
Group ->
|
Group ->
|
||||||
case has_forgotten_active(Group, Pid) of
|
case has_pdown_active(Group, Pid) of
|
||||||
true ->
|
true ->
|
||||||
%% a forgotten active is coming in the connection
|
%% a presumed-down active is coming back in the connection
|
||||||
%% we need to reconcile the group,
|
%% we need to reconcile the group,
|
||||||
%% as there may have been 2 active consumers at a time
|
%% as there may have been 2 active consumers at a time
|
||||||
handle_forgotten_active_reconnected(Pid, S0, Eff0, K);
|
handle_pdown_active_reconnected(Pid, S0, Eff0, K);
|
||||||
false ->
|
false ->
|
||||||
do_handle_group_connection_reconnected(Pid, S0, Eff0, K)
|
do_handle_group_connection_reconnected(Pid, S0, Eff0, K)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_handle_group_connection_reconnected(Pid, #?MODULE{groups = Groups0} = S0,
|
do_handle_group_connection_reconnected(Pid, #?MODULE{groups = Groups0} = S0,
|
||||||
Eff0, {VH, S, Name} = K) ->
|
Eff0, {VH, S, Name} = K)
|
||||||
|
when is_map_key(K, Groups0) ->
|
||||||
G0 = #group{consumers = Consumers0} = lookup_group(VH, S, Name, Groups0),
|
G0 = #group{consumers = Consumers0} = lookup_group(VH, S, Name, Groups0),
|
||||||
|
%% update the status of the consumers from the connection
|
||||||
{Consumers1, Updated} =
|
{Consumers1, Updated} =
|
||||||
lists:foldr(
|
lists:foldr(
|
||||||
fun(#consumer{pid = P, status = {_, St}} = C, {L, _})
|
fun(#consumer{pid = P, status = {_, St}} = C, {L, _})
|
||||||
when P == Pid ->
|
when P == Pid ->
|
||||||
{[csr_status(C, {?CONNECTED, St}) | L], true};
|
{[csr_status(C, {?CONNECTED, St}) | L], true};
|
||||||
(C, {L, UpdatedFlag}) ->
|
(C, {L, UpdatedFlag}) ->
|
||||||
{[C | L], UpdatedFlag or false}
|
{[C | L], UpdatedFlag or false}
|
||||||
end, {[], false}, Consumers0),
|
end, {[], false}, Consumers0),
|
||||||
|
|
||||||
case Updated of
|
case Updated of
|
||||||
true ->
|
true ->
|
||||||
|
@ -394,60 +395,59 @@ do_handle_group_connection_reconnected(Pid, #?MODULE{groups = Groups0} = S0,
|
||||||
{S0#?MODULE{groups = Groups1}, Eff ++ Eff0};
|
{S0#?MODULE{groups = Groups1}, Eff ++ Eff0};
|
||||||
false ->
|
false ->
|
||||||
{S0, Eff0}
|
{S0, Eff0}
|
||||||
end.
|
end;
|
||||||
|
do_handle_group_connection_reconnected(_, S0, Eff0, _) ->
|
||||||
|
{S0, Eff0}.
|
||||||
|
|
||||||
handle_forgotten_active_reconnected(Pid,
|
handle_pdown_active_reconnected(Pid,
|
||||||
#?MODULE{groups = Groups0} = S0,
|
#?MODULE{groups = Groups0} = S0,
|
||||||
Eff0, {VH, S, Name}) ->
|
Eff0, {VH, S, Name} = K)
|
||||||
|
when is_map_key(K, Groups0) ->
|
||||||
G0 = #group{consumers = Consumers0} = lookup_group(VH, S, Name, Groups0),
|
G0 = #group{consumers = Consumers0} = lookup_group(VH, S, Name, Groups0),
|
||||||
{Consumers1, Eff1} =
|
{Consumers1, Eff1} =
|
||||||
case has_disconnected_active(G0) of
|
case has_disconnected_active(G0) of
|
||||||
true ->
|
true ->
|
||||||
%% disconnected active consumer in the group, no rebalancing possible
|
%% disconnected active consumer in the group, no rebalancing possible
|
||||||
%% we update the disconnected active consumers
|
%% we update the presumed-down active consumers
|
||||||
%% and tell them to step down
|
%% and tell them to step down
|
||||||
lists:foldr(fun(#consumer{status = St,
|
lists:foldr(fun(#consumer{status = St, pid = P} = C, {Cs, Eff})
|
||||||
pid = P,
|
|
||||||
subscription_id = SID} = C, {Cs, Eff})
|
|
||||||
when P =:= Pid andalso St =:= ?PDOWN_ACT ->
|
when P =:= Pid andalso St =:= ?PDOWN_ACT ->
|
||||||
{[csr_status(C, ?CONN_WAIT) | Cs],
|
{[csr_status(C, ?CONN_WAIT) | Cs],
|
||||||
[notify_consumer_effect(Pid, SID, S,
|
[notify_csr_effect(C, S,
|
||||||
Name, false, true) | Eff]};
|
Name, false, true) | Eff]};
|
||||||
(C, {Cs, Eff}) ->
|
(C, {Cs, Eff}) ->
|
||||||
{[C | Cs], Eff}
|
{[C | Cs], Eff}
|
||||||
end, {[], Eff0}, Consumers0);
|
end, {[], Eff0}, Consumers0);
|
||||||
false ->
|
false ->
|
||||||
lists:foldr(fun(#consumer{status = St,
|
lists:foldr(fun(#consumer{status = St, pid = P} = C, {Cs, Eff})
|
||||||
pid = P,
|
|
||||||
subscription_id = SID} = C, {Cs, Eff})
|
|
||||||
when P =:= Pid andalso St =:= ?PDOWN_ACT ->
|
when P =:= Pid andalso St =:= ?PDOWN_ACT ->
|
||||||
%% update forgotten active
|
%% update presumed-down active
|
||||||
%% tell it to step down
|
%% tell it to step down
|
||||||
{[csr_status(C, ?CONN_WAIT) | Cs],
|
{[csr_status(C, ?CONN_WAIT) | Cs],
|
||||||
[notify_consumer_effect(P, SID, S,
|
[notify_csr_effect(C, S,
|
||||||
Name, false, true) | Eff]};
|
Name, false, true) | Eff]};
|
||||||
(#consumer{status = {?PDOWN, _},
|
(#consumer{status = {?PDOWN, _},
|
||||||
pid = P} = C, {Cs, Eff})
|
pid = P} = C, {Cs, Eff})
|
||||||
when P =:= Pid ->
|
when P =:= Pid ->
|
||||||
%% update forgotten
|
%% update presumed-down
|
||||||
{[csr_status(C, ?CONN_WAIT) | Cs], Eff};
|
{[csr_status(C, ?CONN_WAIT) | Cs], Eff};
|
||||||
(#consumer{status = ?CONN_ACT,
|
(#consumer{status = ?CONN_ACT} = C, {Cs, Eff}) ->
|
||||||
pid = P,
|
|
||||||
subscription_id = SID} = C, {Cs, Eff}) ->
|
|
||||||
%% update connected active
|
%% update connected active
|
||||||
%% tell it to step down
|
%% tell it to step down
|
||||||
{[csr_status(C, ?CONN_WAIT) | Cs],
|
{[csr_status(C, ?CONN_WAIT) | Cs],
|
||||||
[notify_consumer_effect(P, SID, S,
|
[notify_csr_effect(C, S,
|
||||||
Name, false, true) | Eff]};
|
Name, false, true) | Eff]};
|
||||||
(C, {Cs, Eff}) ->
|
(C, {Cs, Eff}) ->
|
||||||
{[C | Cs], Eff}
|
{[C | Cs], Eff}
|
||||||
end, {[], Eff0}, Consumers0)
|
end, {[], Eff0}, Consumers0)
|
||||||
end,
|
end,
|
||||||
G1 = G0#group{consumers = Consumers1},
|
G1 = G0#group{consumers = Consumers1},
|
||||||
Groups1 = update_groups(VH, S, Name, G1, Groups0),
|
Groups1 = update_groups(VH, S, Name, G1, Groups0),
|
||||||
{S0#?MODULE{groups = Groups1}, Eff1}.
|
{S0#?MODULE{groups = Groups1}, Eff1};
|
||||||
|
handle_pdown_active_reconnected(_, S0, Eff0, _) ->
|
||||||
|
{S0, Eff0}.
|
||||||
|
|
||||||
has_forgotten_active(#group{consumers = Consumers}, Pid) ->
|
has_pdown_active(#group{consumers = Consumers}, Pid) ->
|
||||||
case lists:search(fun(#consumer{status = ?PDOWN_ACT,
|
case lists:search(fun(#consumer{status = ?PDOWN_ACT,
|
||||||
pid = P}) when P =:= Pid ->
|
pid = P}) when P =:= Pid ->
|
||||||
true;
|
true;
|
||||||
|
@ -473,24 +473,33 @@ has_consumer_with_status(#group{consumers = Consumers}, Status) ->
|
||||||
true
|
true
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
maybe_rebalance_group(#group{partition_index = PI} = G0, _) when PI < -1 ->
|
||||||
|
%% should not happen
|
||||||
|
{G0, []};
|
||||||
|
maybe_rebalance_group(#group{consumers = CS} = G0, _) when length(CS) == 0 ->
|
||||||
|
{G0, []};
|
||||||
maybe_rebalance_group(#group{partition_index = -1, consumers = Consumers0} = G0,
|
maybe_rebalance_group(#group{partition_index = -1, consumers = Consumers0} = G0,
|
||||||
{_VH, S, Name}) ->
|
{_VH, S, Name}) ->
|
||||||
case lookup_active_consumer(G0) of
|
case lookup_active_consumer(G0) of
|
||||||
{value, ActiveConsumer} ->
|
{value, ActiveCsr} ->
|
||||||
%% there is already an active consumer, we just re-arrange
|
%% there is already an active consumer, we just re-arrange
|
||||||
%% the group to make sure the active consumer is the first in the array
|
%% the group to make sure the active consumer is the first in the array
|
||||||
Consumers1 = lists:filter(fun(C) ->
|
%% remove the active consumer from the list
|
||||||
not same_consumer(C, ActiveConsumer)
|
Consumers1 = lists:filter(fun(C) when ?SAME_CSR(C, ActiveCsr) ->
|
||||||
|
false;
|
||||||
|
(_) ->
|
||||||
|
true
|
||||||
end, Consumers0),
|
end, Consumers0),
|
||||||
G1 = G0#group{consumers = [ActiveConsumer | Consumers1]},
|
%% add it back to the front
|
||||||
|
G1 = G0#group{consumers = [ActiveCsr | Consumers1]},
|
||||||
{G1, []};
|
{G1, []};
|
||||||
_ ->
|
_ ->
|
||||||
%% no active consumer
|
%% no active consumer
|
||||||
G1 = compute_active_consumer(G0),
|
G1 = compute_active_consumer(G0),
|
||||||
case lookup_active_consumer(G1) of
|
case lookup_active_consumer(G1) of
|
||||||
{value, #consumer{pid = Pid, subscription_id = SubId}} ->
|
{value, Csr} ->
|
||||||
%% creating the side effect to notify the new active consumer
|
%% creating the side effect to notify the new active consumer
|
||||||
{G1, [notify_consumer_effect(Pid, SubId, S, Name, true)]};
|
{G1, [notify_csr_effect(Csr, S, Name, true)]};
|
||||||
_ ->
|
_ ->
|
||||||
%% no active consumer found in the group, nothing to do
|
%% no active consumer found in the group, nothing to do
|
||||||
{G1, []}
|
{G1, []}
|
||||||
|
@ -499,8 +508,7 @@ maybe_rebalance_group(#group{partition_index = -1, consumers = Consumers0} = G0,
|
||||||
maybe_rebalance_group(#group{partition_index = _, consumers = Consumers} = G,
|
maybe_rebalance_group(#group{partition_index = _, consumers = Consumers} = G,
|
||||||
{_VH, S, Name}) ->
|
{_VH, S, Name}) ->
|
||||||
case lookup_active_consumer(G) of
|
case lookup_active_consumer(G) of
|
||||||
{value, #consumer{pid = ActPid,
|
{value, CurrentActive} ->
|
||||||
subscription_id = ActSubId} = CurrentActive} ->
|
|
||||||
case evaluate_active_consumer(G) of
|
case evaluate_active_consumer(G) of
|
||||||
undefined ->
|
undefined ->
|
||||||
%% no-one to select
|
%% no-one to select
|
||||||
|
@ -510,19 +518,12 @@ maybe_rebalance_group(#group{partition_index = _, consumers = Consumers} = G,
|
||||||
{G, []};
|
{G, []};
|
||||||
_ ->
|
_ ->
|
||||||
%% there's a change, telling the active it's not longer active
|
%% there's a change, telling the active it's not longer active
|
||||||
{update_consumer_state_in_group(G,
|
{update_consumer_state_in_group(G, CurrentActive,
|
||||||
ActPid,
|
|
||||||
ActSubId,
|
|
||||||
{?CONNECTED, ?DEACTIVATING}),
|
{?CONNECTED, ?DEACTIVATING}),
|
||||||
[notify_consumer_effect(ActPid,
|
[notify_csr_effect(CurrentActive, S, Name, false, true)]}
|
||||||
ActSubId,
|
|
||||||
S,
|
|
||||||
Name,
|
|
||||||
false,
|
|
||||||
true)]}
|
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
%% no active consumer in the (non-empty) group,
|
%% no active consumer in the group,
|
||||||
case lists:search(fun(#consumer{status = Status}) ->
|
case lists:search(fun(#consumer{status = Status}) ->
|
||||||
Status =:= {?CONNECTED, ?DEACTIVATING}
|
Status =:= {?CONNECTED, ?DEACTIVATING}
|
||||||
end, Consumers) of
|
end, Consumers) of
|
||||||
|
@ -532,22 +533,16 @@ maybe_rebalance_group(#group{partition_index = _, consumers = Consumers} = G,
|
||||||
{G, []};
|
{G, []};
|
||||||
_ ->
|
_ ->
|
||||||
%% nothing going on in the group
|
%% nothing going on in the group
|
||||||
%% a {disconnected, active} may have become {forgotten, active}
|
%% a {disconnected, active} may have become {pdown, active}
|
||||||
%% we must select a new active
|
%% we must select a new active
|
||||||
case evaluate_active_consumer(G) of
|
case evaluate_active_consumer(G) of
|
||||||
undefined ->
|
undefined ->
|
||||||
%% no-one to select
|
%% no-one to select
|
||||||
{G, []};
|
{G, []};
|
||||||
#consumer{pid = ActPid, subscription_id = ActSubId} ->
|
Csr ->
|
||||||
{update_consumer_state_in_group(G,
|
{update_consumer_state_in_group(G, Csr,
|
||||||
ActPid,
|
|
||||||
ActSubId,
|
|
||||||
{?CONNECTED, ?ACTIVE}),
|
{?CONNECTED, ?ACTIVE}),
|
||||||
[notify_consumer_effect(ActPid,
|
[notify_csr_effect(Csr, S, Name, true)]}
|
||||||
ActSubId,
|
|
||||||
S,
|
|
||||||
Name,
|
|
||||||
true)]}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
@ -640,14 +635,14 @@ connectivity_label(Cnty) ->
|
||||||
map(),
|
map(),
|
||||||
ra_machine:effects()) ->
|
ra_machine:effects()) ->
|
||||||
{state(), map(), ra_machine:effects()}.
|
{state(), map(), ra_machine:effects()}.
|
||||||
ensure_monitors(#command_register_consumer{vhost = VirtualHost,
|
ensure_monitors(#command_register_consumer{vhost = VH,
|
||||||
stream = Stream,
|
stream = S,
|
||||||
consumer_name = ConsumerName,
|
consumer_name = Name,
|
||||||
connection_pid = Pid},
|
connection_pid = Pid},
|
||||||
#?MODULE{pids_groups = PidsGroups0} = State0,
|
#?MODULE{pids_groups = PidsGroups0} = State0,
|
||||||
Monitors0,
|
Monitors0,
|
||||||
Effects) ->
|
Effects) ->
|
||||||
GroupId = {VirtualHost, Stream, ConsumerName},
|
GroupId = {VH, S, Name},
|
||||||
%% get the group IDs that depend on the PID
|
%% get the group IDs that depend on the PID
|
||||||
Groups0 = maps:get(Pid, PidsGroups0, #{}),
|
Groups0 = maps:get(Pid, PidsGroups0, #{}),
|
||||||
%% add the group ID
|
%% add the group ID
|
||||||
|
@ -656,7 +651,7 @@ ensure_monitors(#command_register_consumer{vhost = VirtualHost,
|
||||||
PidsGroups1 = PidsGroups0#{Pid => Groups1},
|
PidsGroups1 = PidsGroups0#{Pid => Groups1},
|
||||||
{State0#?MODULE{pids_groups = PidsGroups1}, Monitors0#{Pid => sac},
|
{State0#?MODULE{pids_groups = PidsGroups1}, Monitors0#{Pid => sac},
|
||||||
[{monitor, process, Pid}, {monitor, node, node(Pid)} | Effects]};
|
[{monitor, process, Pid}, {monitor, node, node(Pid)} | Effects]};
|
||||||
ensure_monitors(#command_unregister_consumer{vhost = VirtualHost,
|
ensure_monitors(#command_unregister_consumer{vhost = VH,
|
||||||
stream = Stream,
|
stream = Stream,
|
||||||
consumer_name = ConsumerName,
|
consumer_name = ConsumerName,
|
||||||
connection_pid = Pid},
|
connection_pid = Pid},
|
||||||
|
@ -664,11 +659,11 @@ ensure_monitors(#command_unregister_consumer{vhost = VirtualHost,
|
||||||
pids_groups = PidsGroups0} = State0,
|
pids_groups = PidsGroups0} = State0,
|
||||||
Monitors,
|
Monitors,
|
||||||
Effects)
|
Effects)
|
||||||
when is_map_key(Pid, PidsGroups0) ->
|
when is_map_key(Pid, PidsGroups0) ->
|
||||||
GroupId = {VirtualHost, Stream, ConsumerName},
|
GroupId = {VH, Stream, ConsumerName},
|
||||||
#{Pid := PidGroup0} = PidsGroups0,
|
#{Pid := PidGroup0} = PidsGroups0,
|
||||||
PidGroup1 =
|
PidGroup1 =
|
||||||
case lookup_group(VirtualHost, Stream, ConsumerName, StreamGroups0) of
|
case lookup_group(VH, Stream, ConsumerName, StreamGroups0) of
|
||||||
undefined ->
|
undefined ->
|
||||||
%% group is gone, can be removed from the PID map
|
%% group is gone, can be removed from the PID map
|
||||||
maps:remove(GroupId, PidGroup0);
|
maps:remove(GroupId, PidGroup0);
|
||||||
|
@ -785,95 +780,78 @@ presume_connection_down(Pid, #?MODULE{groups = Groups} = State0) ->
|
||||||
{State1, Eff}.
|
{State1, Eff}.
|
||||||
|
|
||||||
handle_group_connection_presumed_down(Pid, #?MODULE{groups = Groups0} = S0,
|
handle_group_connection_presumed_down(Pid, #?MODULE{groups = Groups0} = S0,
|
||||||
Eff0, {VH, S, Name} = K) ->
|
Eff0, {VH, S, Name} = K)
|
||||||
case lookup_group(VH, S, Name, Groups0) of
|
when is_map_key(K, Groups0) ->
|
||||||
undefined ->
|
#group{consumers = Consumers0} = G0 = lookup_group(VH, S, Name, Groups0),
|
||||||
{S0, Eff0};
|
{Consumers1, Updated} =
|
||||||
#group{consumers = Consumers0} = G0 ->
|
lists:foldr(
|
||||||
{Consumers1, Updated} =
|
fun(#consumer{pid = P, status = {?DISCONNECTED, St}} = C, {L, _})
|
||||||
lists:foldr(
|
when P == Pid ->
|
||||||
fun(#consumer{pid = P, status = {?DISCONNECTED, St}} = C, {L, _})
|
{[csr_status(C, {?PDOWN, St}) | L], true};
|
||||||
when P == Pid ->
|
(C, {L, UpdatedFlag}) ->
|
||||||
{[csr_status(C, {?PDOWN, St}) | L], true};
|
{[C | L], UpdatedFlag or false}
|
||||||
(C, {L, UpdatedFlag}) ->
|
end, {[], false}, Consumers0),
|
||||||
{[C | L], UpdatedFlag or false}
|
|
||||||
end, {[], false}, Consumers0),
|
|
||||||
|
|
||||||
case Updated of
|
case Updated of
|
||||||
true ->
|
true ->
|
||||||
G1 = G0#group{consumers = Consumers1},
|
G1 = G0#group{consumers = Consumers1},
|
||||||
{G2, Eff} = maybe_rebalance_group(G1, K),
|
{G2, Eff} = maybe_rebalance_group(G1, K),
|
||||||
Groups1 = update_groups(VH, S, Name, G2, Groups0),
|
Groups1 = update_groups(VH, S, Name, G2, Groups0),
|
||||||
{S0#?MODULE{groups = Groups1}, Eff ++ Eff0};
|
{S0#?MODULE{groups = Groups1}, Eff ++ Eff0};
|
||||||
false ->
|
false ->
|
||||||
{S0, Eff0}
|
{S0, Eff0}
|
||||||
end
|
end;
|
||||||
end.
|
handle_group_connection_presumed_down(_, S0, Eff0, _) ->
|
||||||
|
{S0, Eff0}.
|
||||||
|
|
||||||
handle_group_after_connection_down(Pid,
|
handle_group_after_connection_down(Pid,
|
||||||
{#?MODULE{groups = Groups0} = S0, Eff0},
|
{#?MODULE{groups = Groups0} = S0, Eff0},
|
||||||
{VirtualHost, Stream, ConsumerName}) ->
|
{VH, St, Name} = K)
|
||||||
case lookup_group(VirtualHost,
|
when is_map_key(K, Groups0) ->
|
||||||
Stream,
|
#group{consumers = Consumers0} = G0 = lookup_group(VH, St, Name, Groups0),
|
||||||
ConsumerName,
|
%% remove the connection consumers from the group state
|
||||||
Groups0) of
|
%% keep flags to know what happened
|
||||||
undefined ->
|
{Consumers1, ActiveRemoved, AnyRemoved} =
|
||||||
{S0, Eff0};
|
lists:foldl(
|
||||||
#group{consumers = Consumers0} = G0 ->
|
fun(#consumer{pid = P, status = S}, {L, ActiveFlag, _})
|
||||||
%% remove the connection consumers from the group state
|
when P == Pid ->
|
||||||
%% keep flags to know what happened
|
{L, is_active(S) or ActiveFlag, true};
|
||||||
{Consumers1, ActiveRemoved, AnyRemoved} =
|
(C, {L, ActiveFlag, AnyFlag}) ->
|
||||||
lists:foldl(
|
{L ++ [C], ActiveFlag, AnyFlag}
|
||||||
fun(#consumer{pid = P, status = S}, {L, ActiveFlag, _})
|
end, {[], false, false}, Consumers0),
|
||||||
when P == Pid ->
|
|
||||||
{L, is_active(S) or ActiveFlag, true};
|
|
||||||
(C, {L, ActiveFlag, AnyFlag}) ->
|
|
||||||
{L ++ [C], ActiveFlag, AnyFlag}
|
|
||||||
end, {[], false, false}, Consumers0),
|
|
||||||
|
|
||||||
case AnyRemoved of
|
case AnyRemoved of
|
||||||
true ->
|
true ->
|
||||||
G1 = G0#group{consumers = Consumers1},
|
G1 = G0#group{consumers = Consumers1},
|
||||||
{G2, Effects} = handle_consumer_removal(G1, Stream,
|
{G2, Effects} = handle_consumer_removal(G1, St,
|
||||||
ConsumerName,
|
Name,
|
||||||
ActiveRemoved),
|
ActiveRemoved),
|
||||||
Groups1 = update_groups(VirtualHost,
|
Groups1 = update_groups(VH, St, Name, G2, Groups0),
|
||||||
Stream,
|
{S0#?MODULE{groups = Groups1}, Effects ++ Eff0};
|
||||||
ConsumerName,
|
false ->
|
||||||
G2,
|
{S0, Eff0}
|
||||||
Groups0),
|
end;
|
||||||
{S0#?MODULE{groups = Groups1}, Effects ++ Eff0};
|
handle_group_after_connection_down(_, {S0, Eff0}, _) ->
|
||||||
false ->
|
{S0, Eff0}.
|
||||||
{S0, Eff0}
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
handle_group_after_connection_node_disconnected(ConnPid,
|
handle_group_after_connection_node_disconnected(ConnPid,
|
||||||
#?MODULE{groups = Groups0} = S0,
|
#?MODULE{groups = Groups0} = S0,
|
||||||
{VirtualHost, Stream, ConsumerName}) ->
|
{VH, S, Name} = K)
|
||||||
case lookup_group(VirtualHost,
|
when is_map_key(K, Groups0) ->
|
||||||
Stream,
|
#group{consumers = Cs0} = G0 = lookup_group(VH, S, Name, Groups0),
|
||||||
ConsumerName,
|
Cs1 = lists:foldr(fun(#consumer{status = {_, St},
|
||||||
Groups0) of
|
pid = Pid} = C0,
|
||||||
undefined ->
|
Acc) when Pid =:= ConnPid ->
|
||||||
S0;
|
C1 = csr_status(C0, {?DISCONNECTED, St}),
|
||||||
#group{consumers = Cs0} = G0 ->
|
[C1 | Acc];
|
||||||
Cs1 = lists:foldr(fun(#consumer{status = {_, St},
|
(C, Acc) ->
|
||||||
pid = Pid} = C0,
|
[C | Acc]
|
||||||
Acc) when Pid =:= ConnPid ->
|
end, [], Cs0),
|
||||||
C1 = csr_status(C0, {?DISCONNECTED, St}),
|
G1 = G0#group{consumers = Cs1},
|
||||||
[C1 | Acc];
|
Groups1 = update_groups(VH, S, Name, G1, Groups0),
|
||||||
(C, Acc) ->
|
S0#?MODULE{groups = Groups1};
|
||||||
[C | Acc]
|
handle_group_after_connection_node_disconnected(_, S0, _) ->
|
||||||
end, [], Cs0),
|
S0.
|
||||||
G1 = G0#group{consumers = Cs1},
|
|
||||||
Groups1 = update_groups(VirtualHost,
|
|
||||||
Stream,
|
|
||||||
ConsumerName,
|
|
||||||
G1,
|
|
||||||
Groups0),
|
|
||||||
S0#?MODULE{groups = Groups1}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec import_state(ra_machine:version(), map()) -> state().
|
-spec import_state(ra_machine:version(), map()) -> state().
|
||||||
import_state(4, #{<<"groups">> := Groups, <<"pids_groups">> := PidsGroups}) ->
|
import_state(4, #{<<"groups">> := Groups, <<"pids_groups">> := PidsGroups}) ->
|
||||||
|
@ -909,10 +887,13 @@ list_nodes(#?MODULE{groups = Groups}) ->
|
||||||
ra_machine:effects().
|
ra_machine:effects().
|
||||||
state_enter(leader, #?MODULE{groups = Groups} = State)
|
state_enter(leader, #?MODULE{groups = Groups} = State)
|
||||||
when ?IS_STATE_REC(State) ->
|
when ?IS_STATE_REC(State) ->
|
||||||
|
%% becoming leader, we re-issue monitors and timers for connections with
|
||||||
|
%% disconnected consumers
|
||||||
|
|
||||||
%% iterate over groups
|
%% iterate over groups
|
||||||
{Nodes, DisConns} =
|
{Nodes, DisConns} =
|
||||||
maps:fold(fun(_, #group{consumers = Cs}, Acc) ->
|
maps:fold(fun(_, #group{consumers = Cs}, Acc) ->
|
||||||
%% iterage over group consumers
|
%% iterate over group consumers
|
||||||
lists:foldl(fun(#consumer{pid = P,
|
lists:foldl(fun(#consumer{pid = P,
|
||||||
status = {?DISCONNECTED, _},
|
status = {?DISCONNECTED, _},
|
||||||
ts = Ts},
|
ts = Ts},
|
||||||
|
@ -922,7 +903,7 @@ state_enter(leader, #?MODULE{groups = Groups} = State)
|
||||||
{Nodes#{node(P) => true},
|
{Nodes#{node(P) => true},
|
||||||
DisConns#{P => Ts}};
|
DisConns#{P => Ts}};
|
||||||
(#consumer{pid = P}, {Nodes, DisConns}) ->
|
(#consumer{pid = P}, {Nodes, DisConns}) ->
|
||||||
%% store connection node
|
%% store connection node only
|
||||||
{Nodes#{node(P) => true}, DisConns}
|
{Nodes#{node(P) => true}, DisConns}
|
||||||
end, Acc, Cs)
|
end, Acc, Cs)
|
||||||
end, {#{}, #{}}, Groups),
|
end, {#{}, #{}}, Groups),
|
||||||
|
@ -973,7 +954,12 @@ disconnected_timeout(_) ->
|
||||||
|
|
||||||
map_to_groups(Groups) when is_map(Groups) ->
|
map_to_groups(Groups) when is_map(Groups) ->
|
||||||
maps:fold(fun(K, V, Acc) ->
|
maps:fold(fun(K, V, Acc) ->
|
||||||
Acc#{K => map_to_group(V)}
|
case map_to_group(V) of
|
||||||
|
G when ?IS_GROUP_REC(G) ->
|
||||||
|
Acc#{K => map_to_group(V)};
|
||||||
|
_ ->
|
||||||
|
Acc
|
||||||
|
end
|
||||||
end, #{}, Groups);
|
end, #{}, Groups);
|
||||||
map_to_groups(_) ->
|
map_to_groups(_) ->
|
||||||
#{}.
|
#{}.
|
||||||
|
@ -984,15 +970,26 @@ map_to_pids_groups(_) ->
|
||||||
#{}.
|
#{}.
|
||||||
|
|
||||||
map_to_group(#{<<"consumers">> := Consumers, <<"partition_index">> := Index}) ->
|
map_to_group(#{<<"consumers">> := Consumers, <<"partition_index">> := Index}) ->
|
||||||
C = lists:foldl(fun(V, Acc) ->
|
{C, _} =
|
||||||
Acc ++ [map_to_consumer(V)]
|
lists:foldl(fun(V, {Cs, Dedup}) ->
|
||||||
end, [], Consumers),
|
case map_to_consumer(V) of
|
||||||
#group{consumers = C,
|
#consumer{pid = P, subscription_id = SubId} = C
|
||||||
partition_index = Index}.
|
when not is_map_key({P, SubId}, Dedup) ->
|
||||||
|
{[C | Cs], Dedup#{{P, SubId} => true}};
|
||||||
|
_ ->
|
||||||
|
{Cs, Dedup}
|
||||||
|
end
|
||||||
|
end, {[], #{}}, Consumers),
|
||||||
|
#group{consumers = lists:reverse(C),
|
||||||
|
partition_index = Index};
|
||||||
|
map_to_group(_) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
map_to_consumer(#{<<"pid">> := Pid, <<"subscription_id">> := SubId,
|
map_to_consumer(#{<<"pid">> := Pid, <<"subscription_id">> := SubId,
|
||||||
<<"owner">> := Owner, <<"active">> := Active}) ->
|
<<"owner">> := Owner, <<"active">> := Active}) ->
|
||||||
csr(Pid, SubId, Owner, active_to_status(Active)).
|
csr(Pid, SubId, Owner, active_to_status(Active));
|
||||||
|
map_to_consumer(_) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
active_to_status(true) ->
|
active_to_status(true) ->
|
||||||
{?CONNECTED, ?ACTIVE};
|
{?CONNECTED, ?ACTIVE};
|
||||||
|
@ -1008,82 +1005,69 @@ is_active({_, ?DEACTIVATING}) ->
|
||||||
is_active(_) ->
|
is_active(_) ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
do_register_consumer(VirtualHost,
|
do_register_consumer(VH, S, -1 = _PI, Name, Pid, Owner, SubId,
|
||||||
Stream,
|
#?MODULE{groups = StreamGroups0} = State)
|
||||||
-1 = _PartitionIndex,
|
when is_map_key({VH, S, Name}, StreamGroups0) ->
|
||||||
ConsumerName,
|
Group0 = lookup_group(VH, S, Name, StreamGroups0),
|
||||||
ConnectionPid,
|
|
||||||
Owner,
|
|
||||||
SubscriptionId,
|
|
||||||
#?MODULE{groups = StreamGroups0} = State) ->
|
|
||||||
Group0 = lookup_group(VirtualHost, Stream, ConsumerName, StreamGroups0),
|
|
||||||
|
|
||||||
Consumer =
|
Consumer = case lookup_active_consumer(Group0) of
|
||||||
case lookup_active_consumer(Group0) of
|
{value, _} ->
|
||||||
{value, _} ->
|
csr(Pid, SubId, Owner, ?CONN_WAIT);
|
||||||
csr(ConnectionPid, SubscriptionId, Owner, ?CONN_WAIT);
|
false ->
|
||||||
false ->
|
csr(Pid, SubId, Owner, ?CONN_ACT)
|
||||||
csr(ConnectionPid, SubscriptionId, Owner, ?CONN_ACT)
|
end,
|
||||||
end,
|
|
||||||
Group1 = add_to_group(Consumer, Group0),
|
Group1 = add_to_group(Consumer, Group0),
|
||||||
StreamGroups1 = update_groups(VirtualHost, Stream, ConsumerName,
|
StreamGroups1 = update_groups(VH, S, Name,
|
||||||
Group1,
|
Group1,
|
||||||
StreamGroups0),
|
StreamGroups0),
|
||||||
|
|
||||||
#consumer{status = Status} = Consumer,
|
#consumer{status = Status} = Consumer,
|
||||||
Effects =
|
Effects = case Status of
|
||||||
case Status of
|
{_, ?ACTIVE} ->
|
||||||
{_, ?ACTIVE} ->
|
[notify_csr_effect(Consumer, S, Name, is_active(Status))];
|
||||||
[notify_consumer_effect(ConnectionPid, SubscriptionId,
|
_ ->
|
||||||
Stream, ConsumerName, is_active(Status))];
|
[]
|
||||||
_ ->
|
end,
|
||||||
[]
|
|
||||||
end,
|
|
||||||
|
|
||||||
{State#?MODULE{groups = StreamGroups1}, {ok, is_active(Status)}, Effects};
|
{State#?MODULE{groups = StreamGroups1}, {ok, is_active(Status)}, Effects};
|
||||||
do_register_consumer(VirtualHost,
|
do_register_consumer(VH, S, _PI, Name, Pid, Owner, SubId,
|
||||||
Stream,
|
#?MODULE{groups = StreamGroups0} = State)
|
||||||
_PartitionIndex,
|
when is_map_key({VH, S, Name}, StreamGroups0) ->
|
||||||
ConsumerName,
|
Group0 = lookup_group(VH, S, Name, StreamGroups0),
|
||||||
ConnectionPid,
|
|
||||||
Owner,
|
|
||||||
SubscriptionId,
|
|
||||||
#?MODULE{groups = StreamGroups0} = State) ->
|
|
||||||
Group0 = lookup_group(VirtualHost, Stream, ConsumerName, StreamGroups0),
|
|
||||||
|
|
||||||
{Group1, Effects} =
|
{Group1, Effects} =
|
||||||
case Group0 of
|
case Group0 of
|
||||||
#group{consumers = []} ->
|
#group{consumers = []} ->
|
||||||
%% first consumer in the group, it's the active one
|
%% first consumer in the group, it's the active one
|
||||||
Consumer0 = csr(ConnectionPid, SubscriptionId, Owner, ?CONN_ACT),
|
Consumer0 = csr(Pid, SubId, Owner, ?CONN_ACT),
|
||||||
G1 = add_to_group(Consumer0, Group0),
|
G1 = add_to_group(Consumer0, Group0),
|
||||||
{G1,
|
{G1,
|
||||||
[notify_consumer_effect(ConnectionPid, SubscriptionId,
|
[notify_csr_effect(Consumer0, S, Name, true)]};
|
||||||
Stream, ConsumerName, true)]};
|
|
||||||
_G ->
|
_G ->
|
||||||
Consumer0 = csr(ConnectionPid, SubscriptionId, Owner, ?CONN_WAIT),
|
Consumer0 = csr(Pid, SubId, Owner, ?CONN_WAIT),
|
||||||
G1 = add_to_group(Consumer0, Group0),
|
G1 = add_to_group(Consumer0, Group0),
|
||||||
maybe_rebalance_group(G1, {VirtualHost, Stream, ConsumerName})
|
maybe_rebalance_group(G1, {VH, S, Name})
|
||||||
end,
|
end,
|
||||||
StreamGroups1 = update_groups(VirtualHost, Stream, ConsumerName,
|
StreamGroups1 = update_groups(VH, S, Name,
|
||||||
Group1,
|
Group1,
|
||||||
StreamGroups0),
|
StreamGroups0),
|
||||||
{value, #consumer{status = Status}} =
|
{value, #consumer{status = Status}} = lookup_consumer(Pid, SubId, Group1),
|
||||||
lookup_consumer(ConnectionPid, SubscriptionId, Group1),
|
{State#?MODULE{groups = StreamGroups1}, {ok, is_active(Status)}, Effects};
|
||||||
{State#?MODULE{groups = StreamGroups1}, {ok, is_active(Status)}, Effects}.
|
do_register_consumer(_, _, _, _, _, _, _, State) ->
|
||||||
|
{State, {ok, false}, []}.
|
||||||
|
|
||||||
handle_consumer_removal(#group{consumers = []} = G, _, _, _) ->
|
handle_consumer_removal(#group{consumers = []} = G, _, _, _) ->
|
||||||
{G, []};
|
{G, []};
|
||||||
handle_consumer_removal(#group{partition_index = -1} = Group0,
|
handle_consumer_removal(#group{partition_index = -1} = Group0,
|
||||||
Stream, ConsumerName, ActiveRemoved) ->
|
S, Name, ActiveRemoved) ->
|
||||||
case ActiveRemoved of
|
case ActiveRemoved of
|
||||||
true ->
|
true ->
|
||||||
%% this is the active consumer we remove, computing the new one
|
%% this is the active consumer we remove, computing the new one
|
||||||
Group1 = compute_active_consumer(Group0),
|
Group1 = compute_active_consumer(Group0),
|
||||||
case lookup_active_consumer(Group1) of
|
case lookup_active_consumer(Group1) of
|
||||||
{value, #consumer{pid = Pid, subscription_id = SubId}} ->
|
{value, Csr} ->
|
||||||
%% creating the side effect to notify the new active consumer
|
%% creating the side effect to notify the new active consumer
|
||||||
{Group1, [notify_consumer_effect(Pid, SubId, Stream, ConsumerName, true)]};
|
{Group1, [notify_csr_effect(Csr, S, Name, true)]};
|
||||||
_ ->
|
_ ->
|
||||||
%% no active consumer found in the group, nothing to do
|
%% no active consumer found in the group, nothing to do
|
||||||
{Group1, []}
|
{Group1, []}
|
||||||
|
@ -1094,8 +1078,7 @@ handle_consumer_removal(#group{partition_index = -1} = Group0,
|
||||||
end;
|
end;
|
||||||
handle_consumer_removal(Group0, Stream, ConsumerName, ActiveRemoved) ->
|
handle_consumer_removal(Group0, Stream, ConsumerName, ActiveRemoved) ->
|
||||||
case lookup_active_consumer(Group0) of
|
case lookup_active_consumer(Group0) of
|
||||||
{value, #consumer{pid = ActPid,
|
{value, CurrentActive} ->
|
||||||
subscription_id = ActSubId} = CurrentActive} ->
|
|
||||||
case evaluate_active_consumer(Group0) of
|
case evaluate_active_consumer(Group0) of
|
||||||
undefined ->
|
undefined ->
|
||||||
{Group0, []};
|
{Group0, []};
|
||||||
|
@ -1104,12 +1087,10 @@ handle_consumer_removal(Group0, Stream, ConsumerName, ActiveRemoved) ->
|
||||||
{Group0, []};
|
{Group0, []};
|
||||||
_ ->
|
_ ->
|
||||||
%% there's a change, telling the active it's not longer active
|
%% there's a change, telling the active it's not longer active
|
||||||
{update_consumer_state_in_group(Group0,
|
{update_consumer_state_in_group(Group0, CurrentActive,
|
||||||
ActPid,
|
|
||||||
ActSubId,
|
|
||||||
{?CONNECTED, ?DEACTIVATING}),
|
{?CONNECTED, ?DEACTIVATING}),
|
||||||
[notify_consumer_effect(ActPid, ActSubId,
|
[notify_csr_effect(CurrentActive,
|
||||||
Stream, ConsumerName, false, true)]}
|
Stream, ConsumerName, false, true)]}
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
case ActiveRemoved of
|
case ActiveRemoved of
|
||||||
|
@ -1118,11 +1099,10 @@ handle_consumer_removal(Group0, Stream, ConsumerName, ActiveRemoved) ->
|
||||||
case evaluate_active_consumer(Group0) of
|
case evaluate_active_consumer(Group0) of
|
||||||
undefined ->
|
undefined ->
|
||||||
{Group0, []};
|
{Group0, []};
|
||||||
#consumer{pid = P, subscription_id = SID} ->
|
Csr ->
|
||||||
{update_consumer_state_in_group(Group0, P, SID,
|
{update_consumer_state_in_group(Group0, Csr,
|
||||||
{?CONNECTED, ?ACTIVE}),
|
{?CONNECTED, ?ACTIVE}),
|
||||||
[notify_consumer_effect(P, SID,
|
[notify_csr_effect(Csr, Stream, ConsumerName, true)]}
|
||||||
Stream, ConsumerName, true)]}
|
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
%% no active consumer in the (non-empty) group,
|
%% no active consumer in the (non-empty) group,
|
||||||
|
@ -1134,17 +1114,19 @@ handle_consumer_removal(Group0, Stream, ConsumerName, ActiveRemoved) ->
|
||||||
notify_connection_effect(Pid) ->
|
notify_connection_effect(Pid) ->
|
||||||
mod_call_effect(Pid, {sac, check_connection, #{}}).
|
mod_call_effect(Pid, {sac, check_connection, #{}}).
|
||||||
|
|
||||||
notify_consumer_effect(Pid, SubId, Stream, Name, Active) ->
|
notify_csr_effect(Csr, S, Name, Active) ->
|
||||||
notify_consumer_effect(Pid, SubId, Stream, Name, Active, false).
|
notify_csr_effect(Csr, S, Name, Active, false).
|
||||||
|
|
||||||
notify_consumer_effect(Pid, SubId, Stream, Name, Active, false = _SteppingDown) ->
|
notify_csr_effect(#consumer{pid = P, subscription_id = SubId},
|
||||||
mod_call_effect(Pid,
|
Stream, Name, Active, false = _SteppingDown) ->
|
||||||
|
mod_call_effect(P,
|
||||||
{sac, #{subscription_id => SubId,
|
{sac, #{subscription_id => SubId,
|
||||||
stream => Stream,
|
stream => Stream,
|
||||||
consumer_name => Name,
|
consumer_name => Name,
|
||||||
active => Active}});
|
active => Active}});
|
||||||
notify_consumer_effect(Pid, SubId, Stream, Name, Active, true = SteppingDown) ->
|
notify_csr_effect(#consumer{pid = P, subscription_id = SubId},
|
||||||
mod_call_effect(Pid,
|
Stream, Name, Active, true = SteppingDown) ->
|
||||||
|
mod_call_effect(P,
|
||||||
{sac, #{subscription_id => SubId,
|
{sac, #{subscription_id => SubId,
|
||||||
stream => Stream,
|
stream => Stream,
|
||||||
consumer_name => Name,
|
consumer_name => Name,
|
||||||
|
@ -1171,11 +1153,23 @@ lookup_group(VirtualHost, Stream, ConsumerName, StreamGroups) ->
|
||||||
maps:get({VirtualHost, Stream, ConsumerName}, StreamGroups,
|
maps:get({VirtualHost, Stream, ConsumerName}, StreamGroups,
|
||||||
undefined).
|
undefined).
|
||||||
|
|
||||||
add_to_group(Consumer, #group{consumers = Consumers} = Group) ->
|
add_to_group(#consumer{pid = Pid, subscription_id = SubId} = Consumer,
|
||||||
Group#group{consumers = Consumers ++ [Consumer]}.
|
#group{consumers = Consumers} = Group) ->
|
||||||
|
case lookup_consumer(Pid, SubId, Group) of
|
||||||
|
{value, _} ->
|
||||||
|
%% the consumer is already in the group, nothing to do
|
||||||
|
Group;
|
||||||
|
false ->
|
||||||
|
Group#group{consumers = Consumers ++ [Consumer]}
|
||||||
|
end.
|
||||||
|
|
||||||
remove_from_group(Consumer, #group{consumers = Consumers} = Group) ->
|
remove_from_group(Csr, #group{consumers = Consumers} = Group) ->
|
||||||
Group#group{consumers = lists:delete(Consumer, Consumers)}.
|
CS = lists:filter(fun(C) when ?SAME_CSR(C, Csr) ->
|
||||||
|
false;
|
||||||
|
(_) ->
|
||||||
|
true
|
||||||
|
end, Consumers),
|
||||||
|
Group#group{consumers = CS}.
|
||||||
|
|
||||||
has_consumers_from_pid(#group{consumers = Consumers}, Pid) ->
|
has_consumers_from_pid(#group{consumers = Consumers}, Pid) ->
|
||||||
lists:any(fun (#consumer{pid = P}) when P == Pid ->
|
lists:any(fun (#consumer{pid = P}) when P == Pid ->
|
||||||
|
@ -1192,19 +1186,19 @@ compute_active_consumer(#group{partition_index = -1,
|
||||||
compute_active_consumer(#group{partition_index = -1,
|
compute_active_consumer(#group{partition_index = -1,
|
||||||
consumers = Consumers} = G) ->
|
consumers = Consumers} = G) ->
|
||||||
case lists:search(fun(#consumer{status = S}) ->
|
case lists:search(fun(#consumer{status = S}) ->
|
||||||
S =:= {?DISCONNECTED, ?ACTIVE}
|
S =:= ?DISCONN_ACT
|
||||||
end, Consumers) of
|
end, Consumers) of
|
||||||
{value, _DisconnectedActive} ->
|
{value, _DisconnectedActive} ->
|
||||||
|
%% no rebalancing if there is a disconnected active
|
||||||
G;
|
G;
|
||||||
false ->
|
false ->
|
||||||
case evaluate_active_consumer(G) of
|
case evaluate_active_consumer(G) of
|
||||||
undefined ->
|
undefined ->
|
||||||
G;
|
G;
|
||||||
#consumer{pid = Pid, subscription_id = SubId} ->
|
AC ->
|
||||||
Consumers1 =
|
Consumers1 =
|
||||||
lists:foldr(
|
lists:foldr(
|
||||||
fun(#consumer{pid = P, subscription_id = SID} = C, L)
|
fun(C, L) when ?SAME_CSR(AC, C) ->
|
||||||
when P =:= Pid andalso SID =:= SubId ->
|
|
||||||
%% change status of new active
|
%% change status of new active
|
||||||
[csr_status(C, ?CONN_ACT) | L];
|
[csr_status(C, ?CONN_ACT) | L];
|
||||||
(#consumer{status = {?CONNECTED, _}} = C, L) ->
|
(#consumer{status = {?CONNECTED, _}} = C, L) ->
|
||||||
|
@ -1226,11 +1220,15 @@ evaluate_active_consumer(#group{consumers = Consumers} = G) ->
|
||||||
S =:= ?DISCONN_ACT
|
S =:= ?DISCONN_ACT
|
||||||
end, Consumers) of
|
end, Consumers) of
|
||||||
{value, C} ->
|
{value, C} ->
|
||||||
|
%% no rebalancing if there is a disconnected active
|
||||||
C;
|
C;
|
||||||
_ ->
|
_ ->
|
||||||
do_evaluate_active_consumer(G#group{consumers = eligible(Consumers)})
|
do_evaluate_active_consumer(G#group{consumers = eligible(Consumers)})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
do_evaluate_active_consumer(#group{partition_index = PI}) when PI < -1 ->
|
||||||
|
%% should not happen
|
||||||
|
undefined;
|
||||||
do_evaluate_active_consumer(#group{consumers = Consumers})
|
do_evaluate_active_consumer(#group{consumers = Consumers})
|
||||||
when length(Consumers) == 0 ->
|
when length(Consumers) == 0 ->
|
||||||
undefined;
|
undefined;
|
||||||
|
@ -1264,36 +1262,25 @@ lookup_active_consumer(#group{consumers = Consumers}) ->
|
||||||
lists:search(fun(#consumer{status = Status}) -> is_active(Status) end,
|
lists:search(fun(#consumer{status = Status}) -> is_active(Status) end,
|
||||||
Consumers).
|
Consumers).
|
||||||
|
|
||||||
update_groups(_VirtualHost,
|
update_groups(_VH, _S, _Name, undefined, Groups) ->
|
||||||
_Stream,
|
Groups;
|
||||||
_ConsumerName,
|
update_groups(VH, S, Name, #group{consumers = []}, Groups)
|
||||||
undefined,
|
when is_map_key({VH, S, Name}, Groups) ->
|
||||||
StreamGroups) ->
|
|
||||||
StreamGroups;
|
|
||||||
update_groups(VirtualHost,
|
|
||||||
Stream,
|
|
||||||
ConsumerName,
|
|
||||||
#group{consumers = []},
|
|
||||||
StreamGroups) ->
|
|
||||||
%% the group is now empty, removing the key
|
%% the group is now empty, removing the key
|
||||||
maps:remove({VirtualHost, Stream, ConsumerName}, StreamGroups);
|
maps:remove({VH, S, Name}, Groups);
|
||||||
update_groups(VirtualHost,
|
update_groups(_VH, _S, _Name, #group{consumers = []}, Groups) ->
|
||||||
Stream,
|
%% the group is now empty, but not in the group map
|
||||||
ConsumerName,
|
%% just returning the map
|
||||||
Group,
|
Groups;
|
||||||
StreamGroups) ->
|
update_groups(VH, S, Name, G, Groups) ->
|
||||||
StreamGroups#{{VirtualHost, Stream, ConsumerName} => Group}.
|
Groups#{{VH, S, Name} => G}.
|
||||||
|
|
||||||
update_consumer_state_in_group(#group{consumers = Consumers0} = G,
|
update_consumer_state_in_group(#group{consumers = Consumers0} = G, Csr,
|
||||||
Pid,
|
|
||||||
SubId,
|
|
||||||
NewStatus) ->
|
NewStatus) ->
|
||||||
CS1 = lists:map(fun(C0) ->
|
CS1 = lists:map(fun(C0) when ?SAME_CSR(C0, Csr) ->
|
||||||
case C0 of
|
|
||||||
#consumer{pid = Pid, subscription_id = SubId} ->
|
|
||||||
csr_status(C0, NewStatus);
|
csr_status(C0, NewStatus);
|
||||||
C -> C
|
(C) ->
|
||||||
end
|
C
|
||||||
end,
|
end,
|
||||||
Consumers0),
|
Consumers0),
|
||||||
G#group{consumers = CS1}.
|
G#group{consumers = CS1}.
|
||||||
|
@ -1314,12 +1301,6 @@ send_message(ConnectionPid, Msg) ->
|
||||||
ConnectionPid ! Msg,
|
ConnectionPid ! Msg,
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
same_consumer(#consumer{pid = Pid, subscription_id = SubId},
|
|
||||||
#consumer{pid = Pid, subscription_id = SubId}) ->
|
|
||||||
true;
|
|
||||||
same_consumer(_, _) ->
|
|
||||||
false.
|
|
||||||
|
|
||||||
-spec compute_pid_group_dependencies(groups()) -> pids_groups().
|
-spec compute_pid_group_dependencies(groups()) -> pids_groups().
|
||||||
compute_pid_group_dependencies(Groups) ->
|
compute_pid_group_dependencies(Groups) ->
|
||||||
maps:fold(fun(K, #group{consumers = Cs}, Acc) ->
|
maps:fold(fun(K, #group{consumers = Cs}, Acc) ->
|
||||||
|
|
|
@ -562,10 +562,15 @@ import_state_v4_test(_) ->
|
||||||
OldState5 = apply_ensure_monitors(OldMod, Cmd4, OldState4),
|
OldState5 = apply_ensure_monitors(OldMod, Cmd4, OldState4),
|
||||||
Cmd5 = register_consumer_command(P, 1, App1, Pid2, 2),
|
Cmd5 = register_consumer_command(P, 1, App1, Pid2, 2),
|
||||||
OldState6 = apply_ensure_monitors(OldMod, Cmd5, OldState5),
|
OldState6 = apply_ensure_monitors(OldMod, Cmd5, OldState5),
|
||||||
Cmd6 = activate_consumer_command(P, App1),
|
%% a duplicate consumer sneaks in
|
||||||
|
%% this should not happen in real life, but it tests the dedup
|
||||||
|
%% logic in the import function
|
||||||
|
Cmd6 = register_consumer_command(P, 1, App1, Pid0, 0),
|
||||||
OldState7 = apply_ensure_monitors(OldMod, Cmd6, OldState6),
|
OldState7 = apply_ensure_monitors(OldMod, Cmd6, OldState6),
|
||||||
|
Cmd7 = activate_consumer_command(P, App1),
|
||||||
|
OldState8 = apply_ensure_monitors(OldMod, Cmd7, OldState7),
|
||||||
|
|
||||||
Export = OldMod:state_to_map(OldState7),
|
Export = OldMod:state_to_map(OldState8),
|
||||||
#?STATE{groups = Groups, pids_groups = PidsGroups} = ?MOD:import_state(4, Export),
|
#?STATE{groups = Groups, pids_groups = PidsGroups} = ?MOD:import_state(4, Export),
|
||||||
assertHasGroup({<<"/">>, S, App0},
|
assertHasGroup({<<"/">>, S, App0},
|
||||||
grp(-1, [csr(Pid0, 0, active),
|
grp(-1, [csr(Pid0, 0, active),
|
||||||
|
|
Loading…
Reference in New Issue