|
@@ -117,7 +117,7 @@
|
|
|
id := subscription_id(),
|
|
id := subscription_id(),
|
|
|
start_time := emqx_ds:time(),
|
|
start_time := emqx_ds:time(),
|
|
|
props := map(),
|
|
props := map(),
|
|
|
- extra := map()
|
|
|
|
|
|
|
+ deleted := boolean()
|
|
|
}.
|
|
}.
|
|
|
|
|
|
|
|
-define(TIMER_PULL, timer_pull).
|
|
-define(TIMER_PULL, timer_pull).
|
|
@@ -313,7 +313,8 @@ subscribe(
|
|
|
Subscription = #{
|
|
Subscription = #{
|
|
|
start_time => now_ms(),
|
|
start_time => now_ms(),
|
|
|
props => SubOpts,
|
|
props => SubOpts,
|
|
|
- id => SubId
|
|
|
|
|
|
|
+ id => SubId,
|
|
|
|
|
+ deleted => false
|
|
|
},
|
|
},
|
|
|
IsNew = true;
|
|
IsNew = true;
|
|
|
Subscription0 = #{} ->
|
|
Subscription0 = #{} ->
|
|
@@ -343,12 +344,17 @@ unsubscribe(
|
|
|
|
|
|
|
|
-spec do_unsubscribe(id(), topic_filter(), subscription(), emqx_persistent_session_ds_state:t()) ->
|
|
-spec do_unsubscribe(id(), topic_filter(), subscription(), emqx_persistent_session_ds_state:t()) ->
|
|
|
emqx_persistent_session_ds_state:t().
|
|
emqx_persistent_session_ds_state:t().
|
|
|
-do_unsubscribe(SessionId, TopicFilter, #{id := SubId}, S0) ->
|
|
|
|
|
- S1 = emqx_persistent_session_ds_state:del_subscription(TopicFilter, [], S0),
|
|
|
|
|
|
|
+do_unsubscribe(SessionId, TopicFilter, SubMeta0 = #{id := SubId}, S0) ->
|
|
|
|
|
+ %% Note: we cannot delete the subscription immediately, since its
|
|
|
|
|
+ %% metadata can be used during replay (see `process_batch'). We
|
|
|
|
|
+ %% instead mark it as deleted, and let `subscription_gc' function
|
|
|
|
|
+ %% dispatch it later:
|
|
|
|
|
+ SubMeta = SubMeta0#{deleted => true},
|
|
|
|
|
+ S1 = emqx_persistent_session_ds_state:put_subscription(TopicFilter, [], SubMeta, S0),
|
|
|
?tp(persistent_session_ds_subscription_delete, #{
|
|
?tp(persistent_session_ds_subscription_delete, #{
|
|
|
session_id => SessionId, topic_filter => TopicFilter
|
|
session_id => SessionId, topic_filter => TopicFilter
|
|
|
}),
|
|
}),
|
|
|
- S = emqx_persistent_session_ds_stream_scheduler:del_subscription(SubId, S1),
|
|
|
|
|
|
|
+ S = emqx_persistent_session_ds_stream_scheduler:on_unsubscribe(SubId, S1),
|
|
|
?tp_span(
|
|
?tp_span(
|
|
|
persistent_session_ds_subscription_route_delete,
|
|
persistent_session_ds_subscription_route_delete,
|
|
|
#{session_id => SessionId, topic_filter => TopicFilter},
|
|
#{session_id => SessionId, topic_filter => TopicFilter},
|
|
@@ -459,7 +465,8 @@ handle_timeout(
|
|
|
Session = emqx_session:ensure_timer(?TIMER_PULL, Timeout, Session1),
|
|
Session = emqx_session:ensure_timer(?TIMER_PULL, Timeout, Session1),
|
|
|
{ok, Publishes, Session};
|
|
{ok, Publishes, Session};
|
|
|
handle_timeout(_ClientInfo, ?TIMER_GET_STREAMS, Session0 = #{s := S0}) ->
|
|
handle_timeout(_ClientInfo, ?TIMER_GET_STREAMS, Session0 = #{s := S0}) ->
|
|
|
- S = emqx_persistent_session_ds_stream_scheduler:renew_streams(S0),
|
|
|
|
|
|
|
+ S1 = subscription_gc(S0),
|
|
|
|
|
+ S = emqx_persistent_session_ds_stream_scheduler:renew_streams(S1),
|
|
|
Interval = emqx_config:get([session_persistence, renew_streams_interval]),
|
|
Interval = emqx_config:get([session_persistence, renew_streams_interval]),
|
|
|
Session = emqx_session:ensure_timer(
|
|
Session = emqx_session:ensure_timer(
|
|
|
?TIMER_GET_STREAMS,
|
|
?TIMER_GET_STREAMS,
|
|
@@ -502,6 +509,7 @@ replay(ClientInfo, [], Session0 = #{s := S0}) ->
|
|
|
Session0,
|
|
Session0,
|
|
|
Streams
|
|
Streams
|
|
|
),
|
|
),
|
|
|
|
|
+ logger:error("Replay streams: ~p~n~p", [Streams, Session]),
|
|
|
%% Note: we filled the buffer with the historical messages, and
|
|
%% Note: we filled the buffer with the historical messages, and
|
|
|
%% from now on we'll rely on the normal inflight/flow control
|
|
%% from now on we'll rely on the normal inflight/flow control
|
|
|
%% mechanisms to replay them:
|
|
%% mechanisms to replay them:
|
|
@@ -897,9 +905,43 @@ do_drain_buffer(Inflight0, S0, Acc) ->
|
|
|
|
|
|
|
|
%%--------------------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
+%% @doc Remove subscriptions that have been marked for deletion, and
|
|
|
|
|
+%% that don't have any unacked messages:
|
|
|
|
|
+subscription_gc(S0) ->
|
|
|
|
|
+ subs_fold_all(
|
|
|
|
|
+ fun(TopicFilter, #{id := SubId, deleted := Deleted}, Acc) ->
|
|
|
|
|
+ case Deleted andalso has_no_unacked_streams(SubId, S0) of
|
|
|
|
|
+ true ->
|
|
|
|
|
+ emqx_persistent_session_ds_state:del_subscription(TopicFilter, [], Acc);
|
|
|
|
|
+ false ->
|
|
|
|
|
+ Acc
|
|
|
|
|
+ end
|
|
|
|
|
+ end,
|
|
|
|
|
+ S0,
|
|
|
|
|
+ S0
|
|
|
|
|
+ ).
|
|
|
|
|
+
|
|
|
|
|
+has_no_unacked_streams(SubId, S) ->
|
|
|
|
|
+ emqx_persistent_session_ds_state:fold_streams(
|
|
|
|
|
+ fun
|
|
|
|
|
+ ({SID, _Stream}, Srs, Acc) when SID =:= SubId ->
|
|
|
|
|
+ emqx_persistent_session_ds_stream_scheduler:is_fully_acked(Srs, S) andalso Acc;
|
|
|
|
|
+ (_StreamKey, _Srs, Acc) ->
|
|
|
|
|
+ Acc
|
|
|
|
|
+ end,
|
|
|
|
|
+ true,
|
|
|
|
|
+ S
|
|
|
|
|
+ ).
|
|
|
|
|
+
|
|
|
|
|
+%% @doc It only returns subscriptions that haven't been marked for deletion:
|
|
|
subs_lookup(TopicFilter, S) ->
|
|
subs_lookup(TopicFilter, S) ->
|
|
|
Subs = emqx_persistent_session_ds_state:get_subscriptions(S),
|
|
Subs = emqx_persistent_session_ds_state:get_subscriptions(S),
|
|
|
- emqx_topic_gbt:lookup(TopicFilter, [], Subs, undefined).
|
|
|
|
|
|
|
+ case emqx_topic_gbt:lookup(TopicFilter, [], Subs, undefined) of
|
|
|
|
|
+ #{deleted := true} ->
|
|
|
|
|
+ undefined;
|
|
|
|
|
+ Sub ->
|
|
|
|
|
+ Sub
|
|
|
|
|
+ end.
|
|
|
|
|
|
|
|
subs_to_map(S) ->
|
|
subs_to_map(S) ->
|
|
|
subs_fold(
|
|
subs_fold(
|
|
@@ -909,6 +951,19 @@ subs_to_map(S) ->
|
|
|
).
|
|
).
|
|
|
|
|
|
|
|
subs_fold(Fun, AccIn, S) ->
|
|
subs_fold(Fun, AccIn, S) ->
|
|
|
|
|
+ subs_fold_all(
|
|
|
|
|
+ fun(TopicFilter, Sub = #{deleted := Deleted}, Acc) ->
|
|
|
|
|
+ case Deleted of
|
|
|
|
|
+ true -> Acc;
|
|
|
|
|
+ false -> Fun(TopicFilter, Sub, Acc)
|
|
|
|
|
+ end
|
|
|
|
|
+ end,
|
|
|
|
|
+ AccIn,
|
|
|
|
|
+ S
|
|
|
|
|
+ ).
|
|
|
|
|
+
|
|
|
|
|
+%% @doc Iterate over all subscriptions, including the deleted ones:
|
|
|
|
|
+subs_fold_all(Fun, AccIn, S) ->
|
|
|
Subs = emqx_persistent_session_ds_state:get_subscriptions(S),
|
|
Subs = emqx_persistent_session_ds_state:get_subscriptions(S),
|
|
|
emqx_topic_gbt:fold(
|
|
emqx_topic_gbt:fold(
|
|
|
fun(Key, Sub, Acc) -> Fun(emqx_topic_gbt:get_topic(Key), Sub, Acc) end,
|
|
fun(Key, Sub, Acc) -> Fun(emqx_topic_gbt:get_topic(Key), Sub, Acc) end,
|