fix(persistent_session): Make sure to discard expired sessions on reconnect
This commit is contained in:
parent
f2d26f5e03
commit
fd71bc50ab
|
@ -242,7 +242,13 @@ open_session(false, ClientInfo = #{clientid := ClientId}, ConnInfo) ->
|
||||||
{ok, #{session => Session1,
|
{ok, #{session => Session1,
|
||||||
present => true,
|
present => true,
|
||||||
pendings => Pendings}};
|
pendings => Pendings}};
|
||||||
{error, not_found} ->
|
{expired, OldSession} ->
|
||||||
|
_ = emqx_persistent_session:discard(ClientId, OldSession),
|
||||||
|
Session = create_session(ClientInfo, ConnInfo),
|
||||||
|
Session1 = emqx_persistent_session:persist(ClientInfo, ConnInfo, Session),
|
||||||
|
register_channel(ClientId, Self, ConnInfo),
|
||||||
|
{ok, #{session => Session1, present => false}};
|
||||||
|
none ->
|
||||||
Session = create_session(ClientInfo, ConnInfo),
|
Session = create_session(ClientInfo, ConnInfo),
|
||||||
Session1 = emqx_persistent_session:persist(ClientInfo, ConnInfo, Session),
|
Session1 = emqx_persistent_session:persist(ClientInfo, ConnInfo, Session),
|
||||||
register_channel(ClientId, Self, ConnInfo),
|
register_channel(ClientId, Self, ConnInfo),
|
||||||
|
@ -282,17 +288,15 @@ get_mqtt_conf(Zone, Key) ->
|
||||||
emqx_config:get_zone_conf(Zone, [mqtt, Key]).
|
emqx_config:get_zone_conf(Zone, [mqtt, Key]).
|
||||||
|
|
||||||
%% @doc Try to takeover a session.
|
%% @doc Try to takeover a session.
|
||||||
-spec(takeover_session(emqx_types:clientid())
|
-spec takeover_session(emqx_types:clientid()) ->
|
||||||
-> {error, term()}
|
none
|
||||||
| {living, atom(), pid(), emqx_session:session()}
|
| {living, atom(), pid(), emqx_session:session()}
|
||||||
| {persistent, emqx_session:session()}).
|
| {persistent, emqx_session:session()}
|
||||||
|
| {expired, emqx_session:session()}.
|
||||||
takeover_session(ClientId) ->
|
takeover_session(ClientId) ->
|
||||||
case lookup_channels(ClientId) of
|
case lookup_channels(ClientId) of
|
||||||
[] ->
|
[] ->
|
||||||
case emqx_persistent_session:lookup(ClientId) of
|
emqx_persistent_session:lookup(ClientId);
|
||||||
[] -> {error, not_found};
|
|
||||||
[Session] -> {persistent, Session}
|
|
||||||
end;
|
|
||||||
[ChanPid] ->
|
[ChanPid] ->
|
||||||
takeover_session(ClientId, ChanPid);
|
takeover_session(ClientId, ChanPid);
|
||||||
ChanPids ->
|
ChanPids ->
|
||||||
|
@ -307,10 +311,7 @@ takeover_session(ClientId) ->
|
||||||
takeover_session(ClientId, ChanPid) when node(ChanPid) == node() ->
|
takeover_session(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
case get_chann_conn_mod(ClientId, ChanPid) of
|
case get_chann_conn_mod(ClientId, ChanPid) of
|
||||||
undefined ->
|
undefined ->
|
||||||
case emqx_persistent_session:lookup(ClientId) of
|
emqx_persistent_session:lookup(ClientId);
|
||||||
[] -> {error, not_found};
|
|
||||||
[Session] -> {persistent, Session}
|
|
||||||
end;
|
|
||||||
ConnMod when is_atom(ConnMod) ->
|
ConnMod when is_atom(ConnMod) ->
|
||||||
Session = ConnMod:call(ChanPid, {takeover, 'begin'}, ?T_TAKEOVER),
|
Session = ConnMod:call(ChanPid, {takeover, 'begin'}, ?T_TAKEOVER),
|
||||||
{living, ConnMod, ChanPid, Session}
|
{living, ConnMod, ChanPid, Session}
|
||||||
|
|
|
@ -181,20 +181,19 @@ timestamp_from_conninfo(ConnInfo) ->
|
||||||
|
|
||||||
lookup(ClientID) when is_binary(ClientID) ->
|
lookup(ClientID) when is_binary(ClientID) ->
|
||||||
case lookup_session_store(ClientID) of
|
case lookup_session_store(ClientID) of
|
||||||
none -> [];
|
none -> none;
|
||||||
{value, #session_store{session = S} = SS} ->
|
{value, #session_store{session = S} = SS} ->
|
||||||
case persistent_session_status(SS) of
|
case persistent_session_status(SS) of
|
||||||
not_persistent -> []; %% For completeness. Should not happen
|
expired -> {expired, S};
|
||||||
expired -> [];
|
persistent -> {persistent, S}
|
||||||
persistent -> [S]
|
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec discard_if_present(binary()) -> 'ok'.
|
-spec discard_if_present(binary()) -> 'ok'.
|
||||||
discard_if_present(ClientID) ->
|
discard_if_present(ClientID) ->
|
||||||
case lookup(ClientID) of
|
case lookup(ClientID) of
|
||||||
[] -> ok;
|
none -> ok;
|
||||||
[Session] ->
|
{Tag, Session} when Tag =:= persistent; Tag =:= expired ->
|
||||||
_ = discard(ClientID, Session),
|
_ = discard(ClientID, Session),
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
@ -354,7 +353,7 @@ do_mark_as_delivered(_SessionID, []) ->
|
||||||
-spec pending(emqx_session:sessionID()) ->
|
-spec pending(emqx_session:sessionID()) ->
|
||||||
[{emqx_types:message(), STopic :: binary()}].
|
[{emqx_types:message(), STopic :: binary()}].
|
||||||
pending(SessionID) ->
|
pending(SessionID) ->
|
||||||
pending(SessionID, []).
|
pending_messages_in_db(SessionID, []).
|
||||||
|
|
||||||
-spec pending(emqx_session:sessionID(), MarkerIDs :: [emqx_guid:guid()]) ->
|
-spec pending(emqx_session:sessionID(), MarkerIDs :: [emqx_guid:guid()]) ->
|
||||||
[{emqx_types:message(), STopic :: binary()}].
|
[{emqx_types:message(), STopic :: binary()}].
|
||||||
|
|
|
@ -221,7 +221,7 @@ t_discard_session_race(_) ->
|
||||||
|
|
||||||
t_takeover_session(_) ->
|
t_takeover_session(_) ->
|
||||||
#{conninfo := ConnInfo} = ?ChanInfo,
|
#{conninfo := ConnInfo} = ?ChanInfo,
|
||||||
{error, not_found} = emqx_cm:takeover_session(<<"clientid">>),
|
none = emqx_cm:takeover_session(<<"clientid">>),
|
||||||
erlang:spawn_link(fun() ->
|
erlang:spawn_link(fun() ->
|
||||||
ok = emqx_cm:register_channel(<<"clientid">>, self(), ConnInfo),
|
ok = emqx_cm:register_channel(<<"clientid">>, self(), ConnInfo),
|
||||||
receive
|
receive
|
||||||
|
|
|
@ -81,7 +81,8 @@ init_per_group(persistent_store_enabled, Config) ->
|
||||||
(Other) -> meck:passthrough([Other])
|
(Other) -> meck:passthrough([Other])
|
||||||
end),
|
end),
|
||||||
emqx_common_test_helpers:start_apps([], fun set_special_confs/1),
|
emqx_common_test_helpers:start_apps([], fun set_special_confs/1),
|
||||||
Config;
|
?assertEqual(true, emqx_persistent_session:is_store_enabled()),
|
||||||
|
[{persistent_store_enabled, true}|Config];
|
||||||
init_per_group(persistent_store_disabled, Config) ->
|
init_per_group(persistent_store_disabled, Config) ->
|
||||||
%% Start Apps
|
%% Start Apps
|
||||||
emqx_common_test_helpers:boot_modules(all),
|
emqx_common_test_helpers:boot_modules(all),
|
||||||
|
@ -90,7 +91,8 @@ init_per_group(persistent_store_disabled, Config) ->
|
||||||
(Other) -> meck:passthrough([Other])
|
(Other) -> meck:passthrough([Other])
|
||||||
end),
|
end),
|
||||||
emqx_common_test_helpers:start_apps([], fun set_special_confs/1),
|
emqx_common_test_helpers:start_apps([], fun set_special_confs/1),
|
||||||
Config;
|
?assertEqual(false, emqx_persistent_session:is_store_enabled()),
|
||||||
|
[{persistent_store_enabled, false}|Config];
|
||||||
init_per_group(Group, Config) when Group == tcp; Group == tcp_snabbkaffe ->
|
init_per_group(Group, Config) when Group == tcp; Group == tcp_snabbkaffe ->
|
||||||
[ {port, 1883}, {conn_fun, connect}| Config];
|
[ {port, 1883}, {conn_fun, connect}| Config];
|
||||||
init_per_group(Group, Config) when Group == quic; Group == quic_snabbkaffe ->
|
init_per_group(Group, Config) when Group == quic; Group == quic_snabbkaffe ->
|
||||||
|
@ -382,30 +384,89 @@ t_persist_on_disconnect(Config) ->
|
||||||
?assertEqual(0, client_info(session_present, Client2)),
|
?assertEqual(0, client_info(session_present, Client2)),
|
||||||
ok = emqtt:disconnect(Client2).
|
ok = emqtt:disconnect(Client2).
|
||||||
|
|
||||||
|
wait_for_pending(SId) ->
|
||||||
|
wait_for_pending(SId, 100).
|
||||||
|
|
||||||
|
wait_for_pending(_SId, 0) ->
|
||||||
|
error(exhausted_wait_for_pending);
|
||||||
|
wait_for_pending(SId, N) ->
|
||||||
|
case emqx_persistent_session:pending(SId) of
|
||||||
|
[] -> timer:sleep(1), wait_for_pending(SId, N - 1);
|
||||||
|
[_|_] = Pending -> Pending
|
||||||
|
end.
|
||||||
|
|
||||||
t_process_dies_session_expires(Config) ->
|
t_process_dies_session_expires(Config) ->
|
||||||
%% Emulate an error in the connect process,
|
%% Emulate an error in the connect process,
|
||||||
%% or that the node of the process goes down.
|
%% or that the node of the process goes down.
|
||||||
%% A persistent session should eventually expire.
|
%% A persistent session should eventually expire.
|
||||||
ConnFun = ?config(conn_fun, Config),
|
ConnFun = ?config(conn_fun, Config),
|
||||||
ClientId = ?config(client_id, Config),
|
ClientId = ?config(client_id, Config),
|
||||||
|
Topic = ?config(topic, Config),
|
||||||
|
STopic = ?config(stopic, Config),
|
||||||
|
Payload = <<"test">>,
|
||||||
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
|
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{clientid, ClientId},
|
{clientid, ClientId},
|
||||||
{properties, #{'Session-Expiry-Interval' => 1}},
|
{properties, #{'Session-Expiry-Interval' => 1}},
|
||||||
{clean_start, true}
|
{clean_start, true}
|
||||||
| Config]),
|
| Config]),
|
||||||
{ok, _} = emqtt:ConnFun(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
|
{ok, _, [2]} = emqtt:subscribe(Client1, STopic, qos2),
|
||||||
ok = emqtt:disconnect(Client1),
|
ok = emqtt:disconnect(Client1),
|
||||||
|
|
||||||
maybe_kill_connection_process(ClientId, Config),
|
maybe_kill_connection_process(ClientId, Config),
|
||||||
|
|
||||||
|
ok = publish(Topic, [Payload], Config),
|
||||||
|
|
||||||
|
SessionId =
|
||||||
|
case ?config(persistent_store_enabled, Config) of
|
||||||
|
false -> undefined;
|
||||||
|
true ->
|
||||||
|
%% The session should not be marked as expired.
|
||||||
|
{Tag, Session} = emqx_persistent_session:lookup(ClientId),
|
||||||
|
?assertEqual(persistent, Tag),
|
||||||
|
SId = emqx_session:info(id, Session),
|
||||||
|
case ?config(kill_connection_process, Config) of
|
||||||
|
true ->
|
||||||
|
%% The session should have a pending message
|
||||||
|
?assertMatch([_], wait_for_pending(SId));
|
||||||
|
false ->
|
||||||
|
skip
|
||||||
|
end,
|
||||||
|
SId
|
||||||
|
end,
|
||||||
|
|
||||||
timer:sleep(1100),
|
timer:sleep(1100),
|
||||||
|
|
||||||
|
%% The session should now be marked as expired.
|
||||||
|
case (?config(kill_connection_process, Config) andalso
|
||||||
|
?config(persistent_store_enabled, Config)) of
|
||||||
|
true -> ?assertMatch({expired, _}, emqx_persistent_session:lookup(ClientId));
|
||||||
|
false -> skip
|
||||||
|
end,
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([ {proto_ver, v5},
|
{ok, Client2} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{clientid, ClientId},
|
{clientid, ClientId},
|
||||||
|
{properties, #{'Session-Expiry-Interval' => 30}},
|
||||||
{clean_start, false}
|
{clean_start, false}
|
||||||
| Config]),
|
| Config]),
|
||||||
{ok, _} = emqtt:ConnFun(Client2),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
?assertEqual(0, client_info(session_present, Client2)),
|
?assertEqual(0, client_info(session_present, Client2)),
|
||||||
|
|
||||||
|
case (?config(kill_connection_process, Config) andalso
|
||||||
|
?config(persistent_store_enabled, Config)) of
|
||||||
|
true ->
|
||||||
|
%% The session should be a fresh one
|
||||||
|
{persistent, NewSession} = emqx_persistent_session:lookup(ClientId),
|
||||||
|
?assertNotEqual(SessionId, emqx_session:info(id, NewSession)),
|
||||||
|
%% The old session should now either be marked as abandoned or already be garbage collected.
|
||||||
|
?assertMatch([], emqx_persistent_session:pending(SessionId));
|
||||||
|
false ->
|
||||||
|
skip
|
||||||
|
end,
|
||||||
|
|
||||||
|
%% We should not receive the pending message
|
||||||
|
?assertEqual([], receive_messages(1)),
|
||||||
|
|
||||||
emqtt:disconnect(Client2).
|
emqtt:disconnect(Client2).
|
||||||
|
|
||||||
t_publish_while_client_is_gone(Config) ->
|
t_publish_while_client_is_gone(Config) ->
|
||||||
|
@ -520,7 +581,7 @@ t_unsubscribe(Config) ->
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client, STopic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client, STopic, qos2),
|
||||||
case emqx_persistent_session:is_store_enabled() of
|
case emqx_persistent_session:is_store_enabled() of
|
||||||
true ->
|
true ->
|
||||||
[Session] = emqx_persistent_session:lookup(ClientId),
|
{persistent, Session} = emqx_persistent_session:lookup(ClientId),
|
||||||
SessionID = emqx_session:info(id, Session),
|
SessionID = emqx_session:info(id, Session),
|
||||||
SessionIDs = [SId || #route{dest = SId} <- emqx_session_router:match_routes(Topic)],
|
SessionIDs = [SId || #route{dest = SId} <- emqx_session_router:match_routes(Topic)],
|
||||||
?assert(lists:member(SessionID, SessionIDs)),
|
?assert(lists:member(SessionID, SessionIDs)),
|
||||||
|
@ -582,7 +643,7 @@ t_lost_messages_because_of_gc(init, Config) ->
|
||||||
OldRetain = emqx_config:get(?msg_retain, Retain),
|
OldRetain = emqx_config:get(?msg_retain, Retain),
|
||||||
emqx_config:put(?msg_retain, Retain),
|
emqx_config:put(?msg_retain, Retain),
|
||||||
[{retain, Retain}, {old_retain, OldRetain}|Config];
|
[{retain, Retain}, {old_retain, OldRetain}|Config];
|
||||||
false -> {skip, only_relevant_with_store}
|
false -> {skip, only_relevant_with_store_and_kill_process}
|
||||||
end;
|
end;
|
||||||
t_lost_messages_because_of_gc('end', Config) ->
|
t_lost_messages_because_of_gc('end', Config) ->
|
||||||
OldRetain = ?config(old_retain, Config),
|
OldRetain = ?config(old_retain, Config),
|
||||||
|
|
Loading…
Reference in New Issue