|
@@ -25,6 +25,7 @@
|
|
|
|
|
|
|
|
-include("emqx_mqtt.hrl").
|
|
-include("emqx_mqtt.hrl").
|
|
|
|
|
|
|
|
|
|
+-include("emqx_session.hrl").
|
|
|
-include("emqx_persistent_session_ds/session_internals.hrl").
|
|
-include("emqx_persistent_session_ds/session_internals.hrl").
|
|
|
|
|
|
|
|
-ifdef(TEST).
|
|
-ifdef(TEST).
|
|
@@ -63,6 +64,7 @@
|
|
|
deliver/3,
|
|
deliver/3,
|
|
|
replay/3,
|
|
replay/3,
|
|
|
handle_timeout/3,
|
|
handle_timeout/3,
|
|
|
|
|
+ handle_info/2,
|
|
|
disconnect/2,
|
|
disconnect/2,
|
|
|
terminate/2
|
|
terminate/2
|
|
|
]).
|
|
]).
|
|
@@ -106,6 +108,7 @@
|
|
|
seqno/0,
|
|
seqno/0,
|
|
|
timestamp/0,
|
|
timestamp/0,
|
|
|
topic_filter/0,
|
|
topic_filter/0,
|
|
|
|
|
+ share_topic_filter/0,
|
|
|
subscription_id/0,
|
|
subscription_id/0,
|
|
|
subscription/0,
|
|
subscription/0,
|
|
|
session/0,
|
|
session/0,
|
|
@@ -117,7 +120,8 @@
|
|
|
%% Currently, this is the clientid. We avoid `emqx_types:clientid()' because that can be
|
|
%% Currently, this is the clientid. We avoid `emqx_types:clientid()' because that can be
|
|
|
%% an atom, in theory (?).
|
|
%% an atom, in theory (?).
|
|
|
-type id() :: binary().
|
|
-type id() :: binary().
|
|
|
--type topic_filter() :: emqx_types:topic() | #share{}.
|
|
|
|
|
|
|
+-type share_topic_filter() :: #share{}.
|
|
|
|
|
+-type topic_filter() :: emqx_types:topic() | share_topic_filter().
|
|
|
|
|
|
|
|
%% Subscription and subscription states:
|
|
%% Subscription and subscription states:
|
|
|
%%
|
|
%%
|
|
@@ -155,6 +159,8 @@
|
|
|
subopts := map()
|
|
subopts := map()
|
|
|
}.
|
|
}.
|
|
|
|
|
|
|
|
|
|
+-type shared_sub_state() :: term().
|
|
|
|
|
+
|
|
|
-define(TIMER_PULL, timer_pull).
|
|
-define(TIMER_PULL, timer_pull).
|
|
|
-define(TIMER_GET_STREAMS, timer_get_streams).
|
|
-define(TIMER_GET_STREAMS, timer_get_streams).
|
|
|
-define(TIMER_BUMP_LAST_ALIVE_AT, timer_bump_last_alive_at).
|
|
-define(TIMER_BUMP_LAST_ALIVE_AT, timer_bump_last_alive_at).
|
|
@@ -172,6 +178,8 @@
|
|
|
props := map(),
|
|
props := map(),
|
|
|
%% Persistent state:
|
|
%% Persistent state:
|
|
|
s := emqx_persistent_session_ds_state:t(),
|
|
s := emqx_persistent_session_ds_state:t(),
|
|
|
|
|
+ %% Shared subscription state:
|
|
|
|
|
+ shared_sub_s := shared_sub_state(),
|
|
|
%% Buffer:
|
|
%% Buffer:
|
|
|
inflight := emqx_persistent_session_ds_inflight:t(),
|
|
inflight := emqx_persistent_session_ds_inflight:t(),
|
|
|
%% In-progress replay:
|
|
%% In-progress replay:
|
|
@@ -277,8 +285,11 @@ info(created_at, #{s := S}) ->
|
|
|
emqx_persistent_session_ds_state:get_created_at(S);
|
|
emqx_persistent_session_ds_state:get_created_at(S);
|
|
|
info(is_persistent, #{}) ->
|
|
info(is_persistent, #{}) ->
|
|
|
true;
|
|
true;
|
|
|
-info(subscriptions, #{s := S}) ->
|
|
|
|
|
- emqx_persistent_session_ds_subs:to_map(S);
|
|
|
|
|
|
|
+info(subscriptions, #{s := S, shared_sub_s := SharedSubS}) ->
|
|
|
|
|
+ maps:merge(
|
|
|
|
|
+ emqx_persistent_session_ds_subs:to_map(S),
|
|
|
|
|
+ emqx_persistent_session_ds_shared_subs:to_map(S, SharedSubS)
|
|
|
|
|
+ );
|
|
|
info(subscriptions_cnt, #{s := S}) ->
|
|
info(subscriptions_cnt, #{s := S}) ->
|
|
|
emqx_persistent_session_ds_state:n_subscriptions(S);
|
|
emqx_persistent_session_ds_state:n_subscriptions(S);
|
|
|
info(subscriptions_max, #{props := Conf}) ->
|
|
info(subscriptions_max, #{props := Conf}) ->
|
|
@@ -356,15 +367,23 @@ print_session(ClientId) ->
|
|
|
%% Client -> Broker: SUBSCRIBE / UNSUBSCRIBE
|
|
%% Client -> Broker: SUBSCRIBE / UNSUBSCRIBE
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
+%% Suppress warnings about clauses handling unimplemented reuslts
|
|
|
|
|
+%% of `emqx_persistent_session_ds_shared_subs:on_subscribe/3`
|
|
|
|
|
+-dialyzer({nowarn_function, subscribe/3}).
|
|
|
-spec subscribe(topic_filter(), emqx_types:subopts(), session()) ->
|
|
-spec subscribe(topic_filter(), emqx_types:subopts(), session()) ->
|
|
|
{ok, session()} | {error, emqx_types:reason_code()}.
|
|
{ok, session()} | {error, emqx_types:reason_code()}.
|
|
|
subscribe(
|
|
subscribe(
|
|
|
- #share{},
|
|
|
|
|
- _SubOpts,
|
|
|
|
|
- _Session
|
|
|
|
|
|
|
+ #share{} = TopicFilter,
|
|
|
|
|
+ SubOpts,
|
|
|
|
|
+ Session
|
|
|
) ->
|
|
) ->
|
|
|
- %% TODO: Shared subscriptions are not supported yet:
|
|
|
|
|
- {error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED};
|
|
|
|
|
|
|
+ case emqx_persistent_session_ds_shared_subs:on_subscribe(TopicFilter, SubOpts, Session) of
|
|
|
|
|
+ {ok, S1} ->
|
|
|
|
|
+ S = emqx_persistent_session_ds_state:commit(S1),
|
|
|
|
|
+ {ok, Session#{s => S}};
|
|
|
|
|
+ Error = {error, _} ->
|
|
|
|
|
+ Error
|
|
|
|
|
+ end;
|
|
|
subscribe(
|
|
subscribe(
|
|
|
TopicFilter,
|
|
TopicFilter,
|
|
|
SubOpts,
|
|
SubOpts,
|
|
@@ -378,8 +397,23 @@ subscribe(
|
|
|
Error
|
|
Error
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
|
|
+%% Suppress warnings about clauses handling unimplemented reuslts
|
|
|
|
|
+%% of `emqx_persistent_session_ds_shared_subs:on_subscribe/4`
|
|
|
|
|
+-dialyzer({nowarn_function, unsubscribe/2}).
|
|
|
-spec unsubscribe(topic_filter(), session()) ->
|
|
-spec unsubscribe(topic_filter(), session()) ->
|
|
|
{ok, session(), emqx_types:subopts()} | {error, emqx_types:reason_code()}.
|
|
{ok, session(), emqx_types:subopts()} | {error, emqx_types:reason_code()}.
|
|
|
|
|
+unsubscribe(
|
|
|
|
|
+ #share{} = TopicFilter,
|
|
|
|
|
+ Session = #{id := SessionId, s := S0}
|
|
|
|
|
+) ->
|
|
|
|
|
+ case emqx_persistent_session_ds_shared_subs:on_unsubscribe(SessionId, TopicFilter, S0) of
|
|
|
|
|
+ {ok, S1, #{id := SubId, subopts := SubOpts}} ->
|
|
|
|
|
+ S2 = emqx_persistent_session_ds_stream_scheduler:on_unsubscribe(SubId, S1),
|
|
|
|
|
+ S = emqx_persistent_session_ds_state:commit(S2),
|
|
|
|
|
+ {ok, Session#{s => S}, SubOpts};
|
|
|
|
|
+ Error = {error, _} ->
|
|
|
|
|
+ Error
|
|
|
|
|
+ end;
|
|
|
unsubscribe(
|
|
unsubscribe(
|
|
|
TopicFilter,
|
|
TopicFilter,
|
|
|
Session = #{id := SessionId, s := S0}
|
|
Session = #{id := SessionId, s := S0}
|
|
@@ -540,6 +574,8 @@ pubcomp(_ClientInfo, PacketId, Session0) ->
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
+%% Delivers
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
|
|
|
-spec deliver(clientinfo(), [emqx_types:deliver()], session()) ->
|
|
-spec deliver(clientinfo(), [emqx_types:deliver()], session()) ->
|
|
|
{ok, replies(), session()}.
|
|
{ok, replies(), session()}.
|
|
@@ -551,6 +587,10 @@ deliver(ClientInfo, Delivers, Session0) ->
|
|
|
),
|
|
),
|
|
|
{ok, [], pull_now(Session)}.
|
|
{ok, [], pull_now(Session)}.
|
|
|
|
|
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
+%% Timeouts
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
-spec handle_timeout(clientinfo(), _Timeout, session()) ->
|
|
-spec handle_timeout(clientinfo(), _Timeout, session()) ->
|
|
|
{ok, replies(), session()} | {ok, replies(), timeout(), session()}.
|
|
{ok, replies(), session()} | {ok, replies(), timeout(), session()}.
|
|
|
handle_timeout(ClientInfo, ?TIMER_PULL, Session0) ->
|
|
handle_timeout(ClientInfo, ?TIMER_PULL, Session0) ->
|
|
@@ -573,14 +613,15 @@ handle_timeout(ClientInfo, ?TIMER_PULL, Session0) ->
|
|
|
handle_timeout(ClientInfo, ?TIMER_RETRY_REPLAY, Session0) ->
|
|
handle_timeout(ClientInfo, ?TIMER_RETRY_REPLAY, Session0) ->
|
|
|
Session = replay_streams(Session0, ClientInfo),
|
|
Session = replay_streams(Session0, ClientInfo),
|
|
|
{ok, [], Session};
|
|
{ok, [], Session};
|
|
|
-handle_timeout(ClientInfo, ?TIMER_GET_STREAMS, Session0 = #{s := S0}) ->
|
|
|
|
|
|
|
+handle_timeout(ClientInfo, ?TIMER_GET_STREAMS, Session0 = #{s := S0, shared_sub_s := SharedSubS0}) ->
|
|
|
S1 = emqx_persistent_session_ds_subs:gc(S0),
|
|
S1 = emqx_persistent_session_ds_subs:gc(S0),
|
|
|
- S = emqx_persistent_session_ds_stream_scheduler:renew_streams(S1),
|
|
|
|
|
|
|
+ S2 = emqx_persistent_session_ds_stream_scheduler:renew_streams(S1),
|
|
|
|
|
+ {S, SharedSubS} = emqx_persistent_session_ds_shared_subs:renew_streams(S2, SharedSubS0),
|
|
|
Interval = get_config(ClientInfo, [renew_streams_interval]),
|
|
Interval = get_config(ClientInfo, [renew_streams_interval]),
|
|
|
Session = emqx_session:ensure_timer(
|
|
Session = emqx_session:ensure_timer(
|
|
|
?TIMER_GET_STREAMS,
|
|
?TIMER_GET_STREAMS,
|
|
|
Interval,
|
|
Interval,
|
|
|
- Session0#{s => S}
|
|
|
|
|
|
|
+ Session0#{s => S, shared_sub_s => SharedSubS}
|
|
|
),
|
|
),
|
|
|
{ok, [], Session};
|
|
{ok, [], Session};
|
|
|
handle_timeout(_ClientInfo, ?TIMER_BUMP_LAST_ALIVE_AT, Session0 = #{s := S0}) ->
|
|
handle_timeout(_ClientInfo, ?TIMER_BUMP_LAST_ALIVE_AT, Session0 = #{s := S0}) ->
|
|
@@ -601,6 +642,45 @@ handle_timeout(_ClientInfo, Timeout, Session) ->
|
|
|
?SLOG(warning, #{msg => "unknown_ds_timeout", timeout => Timeout}),
|
|
?SLOG(warning, #{msg => "unknown_ds_timeout", timeout => Timeout}),
|
|
|
{ok, [], Session}.
|
|
{ok, [], Session}.
|
|
|
|
|
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
+%% Generic messages
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+-spec handle_info(term(), session()) -> session().
|
|
|
|
|
+handle_info(?shared_sub_message(Msg), Session = #{s := S0, shared_sub_s := SharedSubS0}) ->
|
|
|
|
|
+ {S, SharedSubS} = emqx_persistent_session_ds_shared_subs:on_info(S0, SharedSubS0, Msg),
|
|
|
|
|
+ Session#{s => S, shared_sub_s => SharedSubS}.
|
|
|
|
|
+
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
+%% Shared subscription outgoing messages
|
|
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+shared_sub_opts(SessionId) ->
|
|
|
|
|
+ #{
|
|
|
|
|
+ session_id => SessionId,
|
|
|
|
|
+ send_funs => #{
|
|
|
|
|
+ send => fun send_message/2,
|
|
|
|
|
+ send_after => fun send_message_after/3
|
|
|
|
|
+ }
|
|
|
|
|
+ }.
|
|
|
|
|
+
|
|
|
|
|
+send_message(Dest, Msg) ->
|
|
|
|
|
+ case Dest =:= self() of
|
|
|
|
|
+ true ->
|
|
|
|
|
+ erlang:send(Dest, ?session_message(?shared_sub_message(Msg))),
|
|
|
|
|
+ Msg;
|
|
|
|
|
+ false ->
|
|
|
|
|
+ erlang:send(Dest, Msg)
|
|
|
|
|
+ end.
|
|
|
|
|
+
|
|
|
|
|
+send_message_after(Time, Dest, Msg) ->
|
|
|
|
|
+ case Dest =:= self() of
|
|
|
|
|
+ true ->
|
|
|
|
|
+ erlang:send_after(Time, Dest, ?session_message(?shared_sub_message(Msg)));
|
|
|
|
|
+ false ->
|
|
|
|
|
+ erlang:send_after(Time, Dest, Msg)
|
|
|
|
|
+ end.
|
|
|
|
|
+
|
|
|
bump_last_alive(S0) ->
|
|
bump_last_alive(S0) ->
|
|
|
%% Note: we take a pessimistic approach here and assume that the client will be alive
|
|
%% Note: we take a pessimistic approach here and assume that the client will be alive
|
|
|
%% until the next bump timeout. With this, we avoid garbage collecting this session
|
|
%% until the next bump timeout. With this, we avoid garbage collecting this session
|
|
@@ -814,13 +894,17 @@ session_open(
|
|
|
S4 = emqx_persistent_session_ds_state:set_will_message(MaybeWillMsg, S3),
|
|
S4 = emqx_persistent_session_ds_state:set_will_message(MaybeWillMsg, S3),
|
|
|
S5 = set_clientinfo(ClientInfo, S4),
|
|
S5 = set_clientinfo(ClientInfo, S4),
|
|
|
S6 = emqx_persistent_session_ds_state:set_protocol({ProtoName, ProtoVer}, S5),
|
|
S6 = emqx_persistent_session_ds_state:set_protocol({ProtoName, ProtoVer}, S5),
|
|
|
- S = emqx_persistent_session_ds_state:commit(S6),
|
|
|
|
|
|
|
+ {ok, S7, SharedSubS} = emqx_persistent_session_ds_shared_subs:open(
|
|
|
|
|
+ S6, shared_sub_opts(SessionId)
|
|
|
|
|
+ ),
|
|
|
|
|
+ S = emqx_persistent_session_ds_state:commit(S7),
|
|
|
Inflight = emqx_persistent_session_ds_inflight:new(
|
|
Inflight = emqx_persistent_session_ds_inflight:new(
|
|
|
receive_maximum(NewConnInfo)
|
|
receive_maximum(NewConnInfo)
|
|
|
),
|
|
),
|
|
|
#{
|
|
#{
|
|
|
id => SessionId,
|
|
id => SessionId,
|
|
|
s => S,
|
|
s => S,
|
|
|
|
|
+ shared_sub_s => SharedSubS,
|
|
|
inflight => Inflight,
|
|
inflight => Inflight,
|
|
|
props => #{}
|
|
props => #{}
|
|
|
}
|
|
}
|
|
@@ -869,6 +953,7 @@ session_ensure_new(
|
|
|
id => Id,
|
|
id => Id,
|
|
|
props => Conf,
|
|
props => Conf,
|
|
|
s => S,
|
|
s => S,
|
|
|
|
|
+ shared_sub_s => emqx_persistent_session_ds_shared_subs:new(shared_sub_opts(Id)),
|
|
|
inflight => emqx_persistent_session_ds_inflight:new(receive_maximum(ConnInfo))
|
|
inflight => emqx_persistent_session_ds_inflight:new(receive_maximum(ConnInfo))
|
|
|
}.
|
|
}.
|
|
|
|
|
|
|
@@ -879,8 +964,8 @@ session_drop(SessionId, Reason) ->
|
|
|
case emqx_persistent_session_ds_state:open(SessionId) of
|
|
case emqx_persistent_session_ds_state:open(SessionId) of
|
|
|
{ok, S0} ->
|
|
{ok, S0} ->
|
|
|
?tp(debug, drop_persistent_session, #{client_id => SessionId, reason => Reason}),
|
|
?tp(debug, drop_persistent_session, #{client_id => SessionId, reason => Reason}),
|
|
|
- emqx_persistent_session_ds_subs:on_session_drop(SessionId, S0),
|
|
|
|
|
- emqx_persistent_session_ds_state:delete(SessionId);
|
|
|
|
|
|
|
+ ok = emqx_persistent_session_ds_subs:on_session_drop(SessionId, S0),
|
|
|
|
|
+ ok = emqx_persistent_session_ds_state:delete(SessionId);
|
|
|
undefined ->
|
|
undefined ->
|
|
|
ok
|
|
ok
|
|
|
end.
|
|
end.
|
|
@@ -917,9 +1002,12 @@ do_ensure_all_iterators_closed(_DSSessionID) ->
|
|
|
%% Normal replay:
|
|
%% Normal replay:
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
|
|
|
-fetch_new_messages(Session = #{s := S}, ClientInfo) ->
|
|
|
|
|
- Streams = emqx_persistent_session_ds_stream_scheduler:find_new_streams(S),
|
|
|
|
|
- fetch_new_messages(Streams, Session, ClientInfo).
|
|
|
|
|
|
|
+fetch_new_messages(Session0 = #{s := S0}, ClientInfo) ->
|
|
|
|
|
+ Streams = emqx_persistent_session_ds_stream_scheduler:find_new_streams(S0),
|
|
|
|
|
+ Session1 = fetch_new_messages(Streams, Session0, ClientInfo),
|
|
|
|
|
+ #{s := S1, shared_sub_s := SharedSubS0} = Session1,
|
|
|
|
|
+ {S2, SharedSubS1} = emqx_persistent_session_ds_shared_subs:on_streams_replayed(S1, SharedSubS0),
|
|
|
|
|
+ Session1#{s => S2, shared_sub_s => SharedSubS1}.
|
|
|
|
|
|
|
|
fetch_new_messages([], Session, _ClientInfo) ->
|
|
fetch_new_messages([], Session, _ClientInfo) ->
|
|
|
Session;
|
|
Session;
|