Merge pull request #6036 from emqx/persistent-session-fixes

Persistent session fixes
This commit is contained in:
Tobias Lindahl 2021-11-02 08:44:33 +01:00 committed by GitHub
commit 62f9f6fd91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 94 additions and 39 deletions

View File

@ -116,3 +116,7 @@
## patches dir ## patches dir
-pa {{ platform_data_dir }}/patches -pa {{ platform_data_dir }}/patches
## Mnesia thresholds
-mnesia dump_log_write_threshold 5000
-mnesia dump_log_time_threshold 60000

View File

@ -114,3 +114,7 @@
## patches dir ## patches dir
-pa {{ platform_data_dir }}/patches -pa {{ platform_data_dir }}/patches
## Mnesia thresholds
-mnesia dump_log_write_threshold 5000
-mnesia dump_log_time_threshold 60000

View File

@ -1179,20 +1179,27 @@ terminate(_, #channel{conn_state = idle}) -> ok;
terminate(normal, Channel) -> terminate(normal, Channel) ->
run_terminate_hook(normal, Channel); run_terminate_hook(normal, Channel);
terminate({shutdown, kicked}, Channel) -> terminate({shutdown, kicked}, Channel) ->
_ = emqx_persistent_session:persist(Channel#channel.clientinfo, persist_if_session(Channel),
Channel#channel.conninfo,
Channel#channel.session),
run_terminate_hook(kicked, Channel); run_terminate_hook(kicked, Channel);
terminate({shutdown, Reason}, Channel) when Reason =:= discarded; terminate({shutdown, Reason}, Channel) when Reason =:= discarded;
Reason =:= takeovered -> Reason =:= takeovered ->
run_terminate_hook(Reason, Channel); run_terminate_hook(Reason, Channel);
terminate(Reason, Channel = #channel{will_msg = WillMsg}) -> terminate(Reason, Channel = #channel{will_msg = WillMsg}) ->
(WillMsg =/= undefined) andalso publish_will_msg(WillMsg), (WillMsg =/= undefined) andalso publish_will_msg(WillMsg),
_ = emqx_persistent_session:persist(Channel#channel.clientinfo, persist_if_session(Channel),
Channel#channel.conninfo,
Channel#channel.session),
run_terminate_hook(Reason, Channel). run_terminate_hook(Reason, Channel).
persist_if_session(#channel{session = Session} = Channel) ->
case emqx_session:is_session(Session) of
true ->
_ = emqx_persistent_session:persist(Channel#channel.clientinfo,
Channel#channel.conninfo,
Channel#channel.session),
ok;
false ->
ok
end.
run_terminate_hook(_Reason, #channel{session = undefined}) -> ok; run_terminate_hook(_Reason, #channel{session = undefined}) -> ok;
run_terminate_hook(Reason, #channel{clientinfo = ClientInfo, session = Session}) -> run_terminate_hook(Reason, #channel{clientinfo = ClientInfo, session = Session}) ->
emqx_session:terminate(ClientInfo, Reason, Session). emqx_session:terminate(ClientInfo, Reason, Session).

View File

@ -58,7 +58,9 @@
, lookup_channels/2 , lookup_channels/2
]). ]).
-export([all_channels/0]). -export([ all_channels/0
, all_client_ids/0
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([ init/1
@ -400,6 +402,11 @@ all_channels() ->
Pat = [{{'_', '$1'}, [], ['$1']}], Pat = [{{'_', '$1'}, [], ['$1']}],
ets:select(?CHAN_TAB, Pat). ets:select(?CHAN_TAB, Pat).
all_client_ids() ->
Pat = [{{'$1', '_'}, [], ['$1']}],
ets:select(?CHAN_TAB, Pat).
%% @doc Lookup channels. %% @doc Lookup channels.
-spec(lookup_channels(emqx_types:clientid()) -> list(chan_pid())). -spec(lookup_channels(emqx_types:clientid()) -> list(chan_pid())).
lookup_channels(ClientId) -> lookup_channels(ClientId) ->

View File

@ -179,12 +179,17 @@ timestamp_from_conninfo(ConnInfo) ->
end. end.
lookup(ClientID) when is_binary(ClientID) -> lookup(ClientID) when is_binary(ClientID) ->
case lookup_session_store(ClientID) of case is_store_enabled() of
none -> none; false ->
{value, #session_store{session = S} = SS} -> none;
case persistent_session_status(SS) of true ->
expired -> {expired, S}; case lookup_session_store(ClientID) of
persistent -> {persistent, S} none -> none;
{value, #session_store{session = S} = SS} ->
case persistent_session_status(SS) of
expired -> {expired, S};
persistent -> {persistent, S}
end
end end
end. end.

View File

@ -58,6 +58,7 @@
-export([ info/1 -export([ info/1
, info/2 , info/2
, is_session/1
, stats/1 , stats/1
]). ]).
@ -202,6 +203,9 @@ init(Opts) ->
%% Info, Stats %% Info, Stats
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
is_session(#session{}) -> true;
is_session(_) -> false.
%% @doc Get infos of the session. %% @doc Get infos of the session.
-spec(info(session()) -> emqx_types:infos()). -spec(info(session()) -> emqx_types:infos()).
info(Session) -> info(Session) ->

View File

@ -113,6 +113,8 @@ init_per_group(snabbkaffe, Config) ->
[ {kill_connection_process, true} | Config]; [ {kill_connection_process, true} | Config];
init_per_group(gc_tests, Config) -> init_per_group(gc_tests, Config) ->
%% We need to make sure the system does not interfere with this test group. %% We need to make sure the system does not interfere with this test group.
[maybe_kill_connection_process(ClientId, [{kill_connection_process, true}])
|| ClientId <- emqx_cm:all_client_ids()],
emqx_common_test_helpers:stop_apps([]), emqx_common_test_helpers:stop_apps([]),
SessionMsgEts = gc_tests_session_store, SessionMsgEts = gc_tests_session_store,
MsgEts = gc_tests_msg_store, MsgEts = gc_tests_msg_store,
@ -230,50 +232,70 @@ receive_messages(Count, Msgs) ->
maybe_kill_connection_process(ClientId, Config) -> maybe_kill_connection_process(ClientId, Config) ->
case ?config(kill_connection_process, Config) of case ?config(kill_connection_process, Config) of
true -> true ->
[ConnectionPid] = emqx_cm:lookup_channels(ClientId), case emqx_cm:lookup_channels(ClientId) of
?assert(is_pid(ConnectionPid)), [] ->
Ref = monitor(process, ConnectionPid), ok;
ConnectionPid ! die_if_test, [ConnectionPid] ->
receive {'DOWN', Ref, process, ConnectionPid, normal} -> ok ?assert(is_pid(ConnectionPid)),
after 3000 -> error(process_did_not_die) Ref = monitor(process, ConnectionPid),
ConnectionPid ! die_if_test,
receive {'DOWN', Ref, process, ConnectionPid, normal} -> ok
after 3000 -> error(process_did_not_die)
end,
wait_for_cm_unregister(ClientId)
end; end;
false -> false ->
ok ok
end. end.
snabbkaffe_sync_publish(Topic, Payloads, Config) -> wait_for_cm_unregister(ClientId) ->
wait_for_cm_unregister(ClientId, 10).
wait_for_cm_unregister(_ClientId, 0) ->
error(cm_did_not_unregister);
wait_for_cm_unregister(ClientId, N) ->
case emqx_cm:lookup_channels(ClientId) of
[] -> ok;
[_] -> timer:sleep(100), wait_for_cm_unregister(ClientId, N - 1)
end.
snabbkaffe_sync_publish(Topic, Payloads) ->
Fun = fun(Client, Payload) -> Fun = fun(Client, Payload) ->
?wait_async_action( {ok, _} = emqtt:publish(Client, Topic, Payload, 2) ?wait_async_action( {ok, _} = emqtt:publish(Client, Topic, Payload, 2)
, #{?snk_kind := ps_persist_msg, payload := Payload} , #{?snk_kind := ps_persist_msg, payload := Payload}
) )
end, end,
do_publish(Payloads, Fun, Config). do_publish(Payloads, Fun, true).
publish(Topic, Payloads, Config) -> publish(Topic, Payloads) ->
Fun = fun(Client, Payload) -> Fun = fun(Client, Payload) ->
{ok, _} = emqtt:publish(Client, Topic, Payload, 2) {ok, _} = emqtt:publish(Client, Topic, Payload, 2)
end, end,
do_publish(Payloads, Fun, Config). do_publish(Payloads, Fun, false).
do_publish(Payloads = [_|_], PublishFun, Config) -> do_publish(Payloads = [_|_], PublishFun, WaitForUnregister) ->
%% Publish from another process to avoid connection confusion. %% Publish from another process to avoid connection confusion.
{Pid, Ref} = {Pid, Ref} =
spawn_monitor( spawn_monitor(
fun() -> fun() ->
%% For convenience, always publish using tcp. %% For convenience, always publish using tcp.
%% The publish path is not what we are testing. %% The publish path is not what we are testing.
ClientID = <<"ps_SUITE_publisher">>,
{ok, Client} = emqtt:start_link([ {proto_ver, v5} {ok, Client} = emqtt:start_link([ {proto_ver, v5}
, {clientid, ClientID}
, {port, 1883} ]), , {port, 1883} ]),
{ok, _} = emqtt:connect(Client), {ok, _} = emqtt:connect(Client),
lists:foreach(fun(Payload) -> PublishFun(Client, Payload) end, Payloads), lists:foreach(fun(Payload) -> PublishFun(Client, Payload) end, Payloads),
ok = emqtt:disconnect(Client) ok = emqtt:disconnect(Client),
%% Snabbkaffe sometimes fails unless all processes are gone.
[wait_for_cm_unregister(ClientID) || WaitForUnregister]
end), end),
receive receive
{'DOWN', Ref, process, Pid, normal} -> ok; {'DOWN', Ref, process, Pid, normal} -> ok;
{'DOWN', Ref, process, Pid, What} -> error({failed_publish, What}) {'DOWN', Ref, process, Pid, What} -> error({failed_publish, What})
end; end;
do_publish(Payload, PublishFun, Config) -> do_publish(Payload, PublishFun, WaitForUnregister) ->
do_publish([Payload], PublishFun, Config). do_publish([Payload], PublishFun, WaitForUnregister).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Test Cases %% Test Cases
@ -297,7 +319,7 @@ t_connect_session_expiry_interval(Config) ->
maybe_kill_connection_process(ClientId, Config), maybe_kill_connection_process(ClientId, Config),
publish(Topic, Payload, Config), publish(Topic, Payload),
{ok, Client2} = emqtt:start_link([ {clientid, ClientId}, {ok, Client2} = emqtt:start_link([ {clientid, ClientId},
{proto_ver, v5}, {proto_ver, v5},
@ -356,6 +378,8 @@ t_cancel_on_disconnect(Config) ->
{ok, _} = emqtt:ConnFun(Client1), {ok, _} = emqtt:ConnFun(Client1),
ok = emqtt:disconnect(Client1, 0, #{'Session-Expiry-Interval' => 0}), ok = emqtt:disconnect(Client1, 0, #{'Session-Expiry-Interval' => 0}),
wait_for_cm_unregister(ClientId),
{ok, Client2} = emqtt:start_link([ {clientid, ClientId}, {ok, Client2} = emqtt:start_link([ {clientid, ClientId},
{proto_ver, v5}, {proto_ver, v5},
{clean_start, false}, {clean_start, false},
@ -424,7 +448,7 @@ t_process_dies_session_expires(Config) ->
maybe_kill_connection_process(ClientId, Config), maybe_kill_connection_process(ClientId, Config),
ok = publish(Topic, [Payload], Config), ok = publish(Topic, [Payload]),
SessionId = SessionId =
case ?config(persistent_store_enabled, Config) of case ?config(persistent_store_enabled, Config) of
@ -498,7 +522,7 @@ t_publish_while_client_is_gone(Config) ->
ok = emqtt:disconnect(Client1), ok = emqtt:disconnect(Client1),
maybe_kill_connection_process(ClientId, Config), maybe_kill_connection_process(ClientId, Config),
ok = publish(Topic, [Payload1, Payload2], Config), ok = publish(Topic, [Payload1, Payload2]),
{ok, Client2} = emqtt:start_link([ {proto_ver, v5}, {ok, Client2} = emqtt:start_link([ {proto_ver, v5},
{clientid, ClientId}, {clientid, ClientId},
@ -544,7 +568,7 @@ t_clean_start_drops_subscriptions(Config) ->
maybe_kill_connection_process(ClientId, Config), maybe_kill_connection_process(ClientId, Config),
%% 2. %% 2.
ok = publish(Topic, Payload1, Config), ok = publish(Topic, Payload1),
%% 3. %% 3.
{ok, Client2} = emqtt:start_link([ {proto_ver, v5}, {ok, Client2} = emqtt:start_link([ {proto_ver, v5},
@ -556,7 +580,7 @@ t_clean_start_drops_subscriptions(Config) ->
?assertEqual(0, client_info(session_present, Client2)), ?assertEqual(0, client_info(session_present, Client2)),
{ok, _, [2]} = emqtt:subscribe(Client2, STopic, qos2), {ok, _, [2]} = emqtt:subscribe(Client2, STopic, qos2),
ok = publish(Topic, Payload2, Config), ok = publish(Topic, Payload2),
[Msg1] = receive_messages(1), [Msg1] = receive_messages(1),
?assertEqual({ok, iolist_to_binary(Payload2)}, maps:find(payload, Msg1)), ?assertEqual({ok, iolist_to_binary(Payload2)}, maps:find(payload, Msg1)),
@ -571,7 +595,7 @@ t_clean_start_drops_subscriptions(Config) ->
| Config]), | Config]),
{ok, _} = emqtt:ConnFun(Client3), {ok, _} = emqtt:ConnFun(Client3),
ok = publish(Topic, Payload3, Config), ok = publish(Topic, Payload3),
[Msg2] = receive_messages(1), [Msg2] = receive_messages(1),
?assertEqual({ok, iolist_to_binary(Payload3)}, maps:find(payload, Msg2)), ?assertEqual({ok, iolist_to_binary(Payload3)}, maps:find(payload, Msg2)),
@ -625,7 +649,7 @@ t_multiple_subscription_matches(Config) ->
maybe_kill_connection_process(ClientId, Config), maybe_kill_connection_process(ClientId, Config),
publish(Topic, Payload, Config), publish(Topic, Payload),
{ok, Client2} = emqtt:start_link([ {clientid, ClientId}, {ok, Client2} = emqtt:start_link([ {clientid, ClientId},
{proto_ver, v5}, {proto_ver, v5},
@ -675,9 +699,9 @@ t_lost_messages_because_of_gc(Config) ->
{ok, _, [2]} = emqtt:subscribe(Client1, STopic, qos2), {ok, _, [2]} = emqtt:subscribe(Client1, STopic, qos2),
emqtt:disconnect(Client1), emqtt:disconnect(Client1),
maybe_kill_connection_process(ClientId, Config), maybe_kill_connection_process(ClientId, Config),
publish(Topic, Payload1, Config), publish(Topic, Payload1),
timer:sleep(2 * Retain), timer:sleep(2 * Retain),
publish(Topic, Payload2, Config), publish(Topic, Payload2),
emqx_persistent_session_gc:message_gc_worker(), emqx_persistent_session_gc:message_gc_worker(),
{ok, Client2} = emqtt:start_link([ {clientid, ClientId}, {ok, Client2} = emqtt:start_link([ {clientid, ClientId},
{clean_start, false}, {clean_start, false},
@ -790,7 +814,7 @@ t_snabbkaffe_pending_messages(Config) ->
?check_trace( ?check_trace(
begin begin
snabbkaffe_sync_publish(Topic, Payloads, Config), snabbkaffe_sync_publish(Topic, Payloads),
{ok, Client2} = emqtt:start_link([{clean_start, false} | EmqttOpts]), {ok, Client2} = emqtt:start_link([{clean_start, false} | EmqttOpts]),
{ok, _} = emqtt:ConnFun(Client2), {ok, _} = emqtt:ConnFun(Client2),
Msgs = receive_messages(length(Payloads)), Msgs = receive_messages(length(Payloads)),
@ -829,7 +853,7 @@ t_snabbkaffe_buffered_messages(Config) ->
ok = emqtt:disconnect(Client1), ok = emqtt:disconnect(Client1),
maybe_kill_connection_process(ClientId, Config), maybe_kill_connection_process(ClientId, Config),
publish(Topic, Payloads1, Config), publish(Topic, Payloads1),
?check_trace( ?check_trace(
begin begin
@ -838,7 +862,7 @@ t_snabbkaffe_buffered_messages(Config) ->
#{ ?snk_kind := ps_resume_end }), #{ ?snk_kind := ps_resume_end }),
spawn_link(fun() -> spawn_link(fun() ->
?block_until(#{ ?snk_kind := ps_marker_pendings_msgs }, infinity, 5000), ?block_until(#{ ?snk_kind := ps_marker_pendings_msgs }, infinity, 5000),
publish(Topic, Payloads2, Config) publish(Topic, Payloads2)
end), end),
{ok, Client2} = emqtt:start_link([{clean_start, false} | EmqttOpts]), {ok, Client2} = emqtt:start_link([{clean_start, false} | EmqttOpts]),
{ok, _} = emqtt:ConnFun(Client2), {ok, _} = emqtt:ConnFun(Client2),