refactor(session): pass config to `SessionImpl:open/3` as well

* Anticipate that connection info may change during session takeover.
* Avoid persisting session conf as part of persistent session state.
This commit is contained in:
Andrew Mayorov 2023-11-28 19:13:14 +03:00
parent 6eba082992
commit 88103c5f0e
No known key found for this signature in database
GPG Key ID: 2837C62ACFBFED5D
6 changed files with 65 additions and 57 deletions

View File

@ -26,9 +26,9 @@
%% Clients Subscriptions. %% Clients Subscriptions.
subscriptions :: map(), subscriptions :: map(),
%% Max subscriptions allowed %% Max subscriptions allowed
max_subscriptions :: non_neg_integer() | infinity, max_subscriptions = infinity :: non_neg_integer() | infinity,
%% Upgrade QoS? %% Upgrade QoS?
upgrade_qos :: boolean(), upgrade_qos = false :: boolean(),
%% Client <- Broker: QoS1/2 messages sent to the client but %% Client <- Broker: QoS1/2 messages sent to the client but
%% have not been unacked. %% have not been unacked.
inflight :: emqx_inflight:inflight(), inflight :: emqx_inflight:inflight(),
@ -40,14 +40,14 @@
%% Next packet id of the session %% Next packet id of the session
next_pkt_id = 1 :: emqx_types:packet_id(), next_pkt_id = 1 :: emqx_types:packet_id(),
%% Retry interval for redelivering QoS1/2 messages (Unit: millisecond) %% Retry interval for redelivering QoS1/2 messages (Unit: millisecond)
retry_interval :: timeout(), retry_interval = 0 :: timeout(),
%% Client -> Broker: QoS2 messages received from the client, but %% Client -> Broker: QoS2 messages received from the client, but
%% have not been completely acknowledged %% have not been completely acknowledged
awaiting_rel :: map(), awaiting_rel :: map(),
%% Maximum number of awaiting QoS2 messages allowed %% Maximum number of awaiting QoS2 messages allowed
max_awaiting_rel :: non_neg_integer() | infinity, max_awaiting_rel = infinity :: non_neg_integer() | infinity,
%% Awaiting PUBREL Timeout (Unit: millisecond) %% Awaiting PUBREL Timeout (Unit: millisecond)
await_rel_timeout :: timeout(), await_rel_timeout = 0 :: timeout(),
%% Created at %% Created at
created_at :: pos_integer() created_at :: pos_integer()
}). }).

View File

@ -29,7 +29,7 @@
%% Session API %% Session API
-export([ -export([
create/3, create/3,
open/2, open/3,
destroy/1 destroy/1
]). ]).
@ -119,6 +119,8 @@
conninfo := emqx_types:conninfo(), conninfo := emqx_types:conninfo(),
%% Timers %% Timers
timer() => reference(), timer() => reference(),
%% Upgrade QoS?
upgrade_qos := boolean(),
%% %%
props := map() props := map()
}. }.
@ -151,11 +153,12 @@
session(). session().
create(#{clientid := ClientID}, ConnInfo, Conf) -> create(#{clientid := ClientID}, ConnInfo, Conf) ->
% TODO: expiration % TODO: expiration
ensure_timers(ensure_session(ClientID, ConnInfo, Conf)). Session = ensure_timers(session_ensure_new(ClientID, ConnInfo)),
preserve_conf(ConnInfo, Conf, Session).
-spec open(clientinfo(), conninfo()) -> -spec open(clientinfo(), conninfo(), emqx_session:conf()) ->
{_IsPresent :: true, session(), []} | false. {_IsPresent :: true, session(), []} | false.
open(#{clientid := ClientID} = _ClientInfo, ConnInfo) -> open(#{clientid := ClientID} = _ClientInfo, ConnInfo, Conf) ->
%% NOTE %% NOTE
%% The fact that we need to concern about discarding all live channels here %% The fact that we need to concern about discarding all live channels here
%% is essentially a consequence of the in-memory session design, where we %% is essentially a consequence of the in-memory session design, where we
@ -165,20 +168,16 @@ open(#{clientid := ClientID} = _ClientInfo, ConnInfo) ->
ok = emqx_cm:discard_session(ClientID), ok = emqx_cm:discard_session(ClientID),
case session_open(ClientID, ConnInfo) of case session_open(ClientID, ConnInfo) of
Session0 = #{} -> Session0 = #{} ->
ReceiveMaximum = receive_maximum(ConnInfo), Session = preserve_conf(ConnInfo, Conf, Session0),
Session = Session0#{receive_maximum => ReceiveMaximum},
{true, ensure_timers(Session), []}; {true, ensure_timers(Session), []};
false -> false ->
false false
end. end.
ensure_session(ClientID, ConnInfo, Conf) -> preserve_conf(ConnInfo, Conf, Session) ->
Session = session_ensure_new(ClientID, ConnInfo, Conf),
ReceiveMaximum = receive_maximum(ConnInfo),
Session#{ Session#{
conninfo => ConnInfo, receive_maximum => receive_maximum(ConnInfo),
receive_maximum => ReceiveMaximum, upgrade_qos => maps:get(upgrade_qos, Conf)
subscriptions => #{}
}. }.
-spec destroy(session() | clientinfo()) -> ok. -spec destroy(session() | clientinfo()) -> ok.
@ -644,25 +643,24 @@ session_open(SessionId, NewConnInfo) ->
end end
end). end).
-spec session_ensure_new(id(), emqx_types:conninfo(), _Props :: map()) -> -spec session_ensure_new(id(), emqx_types:conninfo()) ->
session(). session().
session_ensure_new(SessionId, ConnInfo, Props) -> session_ensure_new(SessionId, ConnInfo) ->
transaction(fun() -> transaction(fun() ->
ok = session_drop_subscriptions(SessionId), ok = session_drop_subscriptions(SessionId),
Session = export_session(session_create(SessionId, ConnInfo, Props)), Session = export_session(session_create(SessionId, ConnInfo)),
Session#{ Session#{
subscriptions => #{}, subscriptions => #{},
inflight => emqx_persistent_message_ds_replayer:new() inflight => emqx_persistent_message_ds_replayer:new()
} }
end). end).
session_create(SessionId, ConnInfo, Props) -> session_create(SessionId, ConnInfo) ->
Session = #session{ Session = #session{
id = SessionId, id = SessionId,
created_at = now_ms(), created_at = now_ms(),
last_alive_at = now_ms(), last_alive_at = now_ms(),
conninfo = ConnInfo, conninfo = ConnInfo
props = Props
}, },
ok = mnesia:write(?SESSION_TAB, Session, write), ok = mnesia:write(?SESSION_TAB, Session, write),
Session. Session.

View File

@ -102,7 +102,7 @@
-export([should_keep/1]). -export([should_keep/1]).
% Tests only % Tests only
-export([get_session_conf/2]). -export([get_session_conf/1]).
-export_type([ -export_type([
t/0, t/0,
@ -137,8 +137,6 @@
-type conf() :: #{ -type conf() :: #{
%% Max subscriptions allowed %% Max subscriptions allowed
max_subscriptions := non_neg_integer() | infinity, max_subscriptions := non_neg_integer() | infinity,
%% Max inflight messages allowed
max_inflight := non_neg_integer(),
%% Maximum number of awaiting QoS2 messages allowed %% Maximum number of awaiting QoS2 messages allowed
max_awaiting_rel := non_neg_integer() | infinity, max_awaiting_rel := non_neg_integer() | infinity,
%% Upgrade QoS? %% Upgrade QoS?
@ -171,7 +169,7 @@
-callback create(clientinfo(), conninfo(), conf()) -> -callback create(clientinfo(), conninfo(), conf()) ->
t(). t().
-callback open(clientinfo(), conninfo()) -> -callback open(clientinfo(), conninfo(), conf()) ->
{_IsPresent :: true, t(), _ReplayContext} | false. {_IsPresent :: true, t(), _ReplayContext} | false.
-callback destroy(t() | clientinfo()) -> ok. -callback destroy(t() | clientinfo()) -> ok.
@ -181,7 +179,7 @@
-spec create(clientinfo(), conninfo()) -> t(). -spec create(clientinfo(), conninfo()) -> t().
create(ClientInfo, ConnInfo) -> create(ClientInfo, ConnInfo) ->
Conf = get_session_conf(ClientInfo, ConnInfo), Conf = get_session_conf(ClientInfo),
create(ClientInfo, ConnInfo, Conf). create(ClientInfo, ConnInfo, Conf).
create(ClientInfo, ConnInfo, Conf) -> create(ClientInfo, ConnInfo, Conf) ->
@ -198,12 +196,12 @@ create(Mod, ClientInfo, ConnInfo, Conf) ->
-spec open(clientinfo(), conninfo()) -> -spec open(clientinfo(), conninfo()) ->
{_IsPresent :: true, t(), _ReplayContext} | {_IsPresent :: false, t()}. {_IsPresent :: true, t(), _ReplayContext} | {_IsPresent :: false, t()}.
open(ClientInfo, ConnInfo) -> open(ClientInfo, ConnInfo) ->
Conf = get_session_conf(ClientInfo, ConnInfo), Conf = get_session_conf(ClientInfo),
Mods = [Default | _] = choose_impl_candidates(ConnInfo), Mods = [Default | _] = choose_impl_candidates(ConnInfo),
%% NOTE %% NOTE
%% Try to look the existing session up in session stores corresponding to the given %% Try to look the existing session up in session stores corresponding to the given
%% `Mods` in order, starting from the last one. %% `Mods` in order, starting from the last one.
case try_open(Mods, ClientInfo, ConnInfo) of case try_open(Mods, ClientInfo, ConnInfo, Conf) of
{_IsPresent = true, _, _} = Present -> {_IsPresent = true, _, _} = Present ->
Present; Present;
false -> false ->
@ -212,24 +210,20 @@ open(ClientInfo, ConnInfo) ->
{false, create(Default, ClientInfo, ConnInfo, Conf)} {false, create(Default, ClientInfo, ConnInfo, Conf)}
end. end.
try_open([Mod | Rest], ClientInfo, ConnInfo) -> try_open([Mod | Rest], ClientInfo, ConnInfo, Conf) ->
case try_open(Rest, ClientInfo, ConnInfo) of case try_open(Rest, ClientInfo, ConnInfo, Conf) of
{_IsPresent = true, _, _} = Present -> {_IsPresent = true, _, _} = Present ->
Present; Present;
false -> false ->
Mod:open(ClientInfo, ConnInfo) Mod:open(ClientInfo, ConnInfo, Conf)
end; end;
try_open([], _ClientInfo, _ConnInfo) -> try_open([], _ClientInfo, _ConnInfo, _Conf) ->
false. false.
-spec get_session_conf(clientinfo(), conninfo()) -> conf(). -spec get_session_conf(clientinfo()) -> conf().
get_session_conf( get_session_conf(_ClientInfo = #{zone := Zone}) ->
#{zone := Zone},
#{receive_maximum := MaxInflight}
) ->
#{ #{
max_subscriptions => get_mqtt_conf(Zone, max_subscriptions), max_subscriptions => get_mqtt_conf(Zone, max_subscriptions),
max_inflight => MaxInflight,
max_awaiting_rel => get_mqtt_conf(Zone, max_awaiting_rel), max_awaiting_rel => get_mqtt_conf(Zone, max_awaiting_rel),
upgrade_qos => get_mqtt_conf(Zone, upgrade_qos), upgrade_qos => get_mqtt_conf(Zone, upgrade_qos),
retry_interval => get_mqtt_conf(Zone, retry_interval), retry_interval => get_mqtt_conf(Zone, retry_interval),

View File

@ -59,7 +59,7 @@
-export([ -export([
create/3, create/3,
open/2, open/3,
destroy/1 destroy/1
]). ]).
@ -152,24 +152,24 @@
-spec create(clientinfo(), conninfo(), emqx_session:conf()) -> -spec create(clientinfo(), conninfo(), emqx_session:conf()) ->
session(). session().
create(#{zone := Zone, clientid := ClientId}, #{expiry_interval := EI}, Conf) -> create(
#{zone := Zone, clientid := ClientId},
#{expiry_interval := EI, receive_maximum := ReceiveMax},
Conf
) ->
QueueOpts = get_mqueue_conf(Zone), QueueOpts = get_mqueue_conf(Zone),
#session{ Session = #session{
id = emqx_guid:gen(), id = emqx_guid:gen(),
clientid = ClientId, clientid = ClientId,
created_at = erlang:system_time(millisecond), created_at = erlang:system_time(millisecond),
is_persistent = EI > 0, is_persistent = EI > 0,
subscriptions = #{}, subscriptions = #{},
inflight = emqx_inflight:new(maps:get(max_inflight, Conf)), inflight = emqx_inflight:new(ReceiveMax),
mqueue = emqx_mqueue:init(QueueOpts), mqueue = emqx_mqueue:init(QueueOpts),
next_pkt_id = 1, next_pkt_id = 1,
awaiting_rel = #{}, awaiting_rel = #{}
max_subscriptions = maps:get(max_subscriptions, Conf), },
max_awaiting_rel = maps:get(max_awaiting_rel, Conf), preserve_conf(Conf, Session).
upgrade_qos = maps:get(upgrade_qos, Conf),
retry_interval = maps:get(retry_interval, Conf),
await_rel_timeout = maps:get(await_rel_timeout, Conf)
}.
get_mqueue_conf(Zone) -> get_mqueue_conf(Zone) ->
#{ #{
@ -195,14 +195,16 @@ destroy(_Session) ->
%% Open a (possibly existing) Session %% Open a (possibly existing) Session
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec open(clientinfo(), conninfo()) -> -spec open(clientinfo(), conninfo(), emqx_session:conf()) ->
{_IsPresent :: true, session(), replayctx()} | _IsPresent :: false. {_IsPresent :: true, session(), replayctx()} | _IsPresent :: false.
open(ClientInfo = #{clientid := ClientId}, _ConnInfo) -> open(ClientInfo = #{clientid := ClientId}, ConnInfo, Conf) ->
case emqx_cm:takeover_session_begin(ClientId) of case emqx_cm:takeover_session_begin(ClientId) of
{ok, SessionRemote, TakeoverState} -> {ok, SessionRemote, TakeoverState} ->
Session = resume(ClientInfo, SessionRemote), Session0 = resume(ClientInfo, SessionRemote),
case emqx_cm:takeover_session_end(TakeoverState) of case emqx_cm:takeover_session_end(TakeoverState) of
{ok, Pendings} -> {ok, Pendings} ->
Session1 = resize_inflight(ConnInfo, Session0),
Session = preserve_conf(Conf, Session1),
clean_session(ClientInfo, Session, Pendings); clean_session(ClientInfo, Session, Pendings);
{error, _} -> {error, _} ->
% TODO log error? % TODO log error?
@ -212,6 +214,20 @@ open(ClientInfo = #{clientid := ClientId}, _ConnInfo) ->
false false
end. end.
resize_inflight(#{receive_maximum := ReceiveMax}, Session = #session{inflight = Inflight}) ->
Session#session{
inflight = emqx_inflight:resize(ReceiveMax, Inflight)
}.
preserve_conf(Conf, Session = #session{}) ->
Session#session{
max_subscriptions = maps:get(max_subscriptions, Conf),
max_awaiting_rel = maps:get(max_awaiting_rel, Conf),
upgrade_qos = maps:get(upgrade_qos, Conf),
retry_interval = maps:get(retry_interval, Conf),
await_rel_timeout = maps:get(await_rel_timeout, Conf)
}.
clean_session(ClientInfo, Session = #session{mqueue = Q}, Pendings) -> clean_session(ClientInfo, Session = #session{mqueue = Q}, Pendings) ->
Q1 = emqx_mqueue:filter(fun emqx_session:should_keep/1, Q), Q1 = emqx_mqueue:filter(fun emqx_session:should_keep/1, Q),
Session1 = Session#session{mqueue = Q1}, Session1 = Session#session{mqueue = Q1},

View File

@ -67,7 +67,7 @@ t_session_init(_) ->
Session = emqx_session_mem:create( Session = emqx_session_mem:create(
ClientInfo, ClientInfo,
ConnInfo, ConnInfo,
emqx_session:get_session_conf(ClientInfo, ConnInfo) emqx_session:get_session_conf(ClientInfo)
), ),
?assertEqual(#{}, emqx_session_mem:info(subscriptions, Session)), ?assertEqual(#{}, emqx_session_mem:info(subscriptions, Session)),
?assertEqual(0, emqx_session_mem:info(subscriptions_cnt, Session)), ?assertEqual(0, emqx_session_mem:info(subscriptions_cnt, Session)),
@ -531,7 +531,7 @@ session(InitFields) when is_map(InitFields) ->
Session = emqx_session_mem:create( Session = emqx_session_mem:create(
ClientInfo, ClientInfo,
ConnInfo, ConnInfo,
emqx_session:get_session_conf(ClientInfo, ConnInfo) emqx_session:get_session_conf(ClientInfo)
), ),
maps:fold( maps:fold(
fun(Field, Value, SessionAcc) -> fun(Field, Value, SessionAcc) ->

View File

@ -54,7 +54,7 @@
init(ClientInfo) -> init(ClientInfo) ->
ConnInfo = #{receive_maximum => 1, expiry_interval => 0}, ConnInfo = #{receive_maximum => 1, expiry_interval => 0},
SessionConf = emqx_session:get_session_conf(ClientInfo, ConnInfo), SessionConf = emqx_session:get_session_conf(ClientInfo),
#{ #{
registry => emqx_mqttsn_registry:init(), registry => emqx_mqttsn_registry:init(),
session => emqx_session_mem:create(ClientInfo, ConnInfo, SessionConf) session => emqx_session_mem:create(ClientInfo, ConnInfo, SessionConf)